Random Asset (Non-Recurring & Specific)

Creating a function to grab random assets from a storage objects (Accounting for grabbing specific objects & non recurring random objects)

by im_jig

Author Avatar

This tutorial will in-depth describe to you how to make a function that gets a random asset inside of a storage object whilst accounting for grabbing specific assets & stop randomly grabbed assets from recurring.

This can be used for grabbing random maps or any asset in general.


To start off you need to first create a storage object to house all of your assets, in my case I'm going to use folders.

Inside the folder are the models I'm going to be picking randomly from.

I recommend placing the storage object (folder) into ReplicatedStorage, this way it can be accessed from both the Server & Client (If needed). However, if you only plan for these assets to be accessed from the Server, placing the storage object into ServerStorage works great.

image|200x100

Depending on how you're structuring your code, you might find it more convenient to use ModuleScripts to make this system, I prefer this way as it keeps everything organized. However, for this, I'm going to stick to using a single script for simplicity's sake.

Insert a Script into ServerScriptService & name it something that easily explains what it does. I'm going to name mine "Model Randomizer".

image|200x100


Now to the actual scripting part, we begin with defining our storage object as "storageObject" that way we don't have to write out a long line to get the object everytime we want to use it.

local storageObject = game:GetService("ReplicatedStorage"):WaitForChild("MyMaps")

The next step would be to start making a new function.

A, Get function, which will return us with a randomized map.

local function get( specific : string )
	local totalStorageObjects = storageObject:GetChildren()
	specific = (specific) or totalStorageObjects[ Random.new():NextInteger( 1, #totalStorageObjects ) ].Name
	
	local asset = storageObject:FindFirstChild( specific )
	return asset
end

To explain this function line by line:

  1. Defines "totalStorageObjects" as an array of all assets inside of the storage object
  2. Sets "specific" to the name of an asset; if "specific" initially has data, it finds that asset inside of the storage object, otherwise it picks a random asset.
  3. Defines "asset" as the physical object inside of the storage object.
  4. Returns "asset" to where the function was called.

Now, let's set up an example of this function being called both getting a random asset & getting a specific asset.

Inside my storage object, I have 4 models named A to D.

image|100x100

-- Returns a random asset
local myAsset = get()
print(myAsset)

-- Returns asset 'B'
local myAsset = get("B")
print(myAsset)

After setting up any system, it's best to look back & set up fail-safes & checks. You may notice that if you send a parameter for the "specific" argument for the "get" function, it will error if it does not find the asset. You can go about this a few ways, here are two.

  1. Add an assertion (Checks if the asset is not found & errors detailed information setup by you)
  2. Return a random map if no asset is found.

Option One:

local function get( specific : string )
	local totalStorageObjects = storageObject:GetChildren()
	specific = (specific) or totalStorageObjects[ Random.new():NextInteger( 1, #totalStorageObjects ) ].Name
	
	local asset = storageObject:FindFirstChild( specific )
	
	assert(asset, string.format("Asset %s not found inside of storage object %s", specific, storageObject.Name) )
	return asset
end

Option Two:

local function get( specific : string )
	local totalStorageObjects = storageObject:GetChildren()
	specific = (specific) or totalStorageObjects[ Random.new():NextInteger( 1, #totalStorageObjects ) ].Name
	
	local asset = storageObject:FindFirstChild( specific ) or get()
	return asset
end

If you wanted you could even combine both options to have the best of both worlds, the benefit of this would be if the "specific" argument had data & turned out the object was missing, it would get a random object. If this object somehow (not impossible) got destroyed, the assertion would let you know.


A quick example of what this system would look like modularized would look like this:

image|100x100

MyScript:

local AssetManager = require( script.AssetManager )

local myRandomAsset = AssetManager:Get()
local mySpecificAsset = AssetManager:Get( "B" )
local myInvalidAsset = AssetManager:Get( "Z" ) -- Since not found in storage object, returns a random asset

-- Random Asset, Asset 'B', Random Asset
print(myRandomAsset, mySpecificAsset, myInvalidAsset)

AssetManager:

local library = {
	storageObject = game:GetService("ReplicatedStorage"):WaitForChild("MyMaps")
}

function library:Get( specific : string )
	local totalStorageObjects = self.storageObject:GetChildren()
	specific = (specific) or totalStorageObjects[ Random.new():NextInteger( 1, #totalStorageObjects ) ].Name

	local asset = self.storageObject:FindFirstChild( specific ) or self:Get()
	return asset
end

return library

This acts the same as what we did, in the beginning, using only one script, however, this way looks clean & organized. It's also arguably more customizable, using one script you would have to eventually work around existing code while using a modular setup would allow you to just edit that one module.


One might want to stop assets from recurring or being picked randomly in succession. To combat this, you must store the last chosen asset & on the next pick, compare the random asset against the stored asset. If it's the same you call the function again, getting another random asset, otherwise, it continues. To make this work you must always store the last picked asset.

Example: (Modular)

local library = {
	storageObject = game:GetService("ReplicatedStorage"):WaitForChild("MyMaps"),
	recurring = true,
}

function library:Get( specific : string )
	local totalStorageObjects = self.storageObject:GetChildren()
	specific = (specific) or totalStorageObjects[ Random.new():NextInteger( 1, #totalStorageObjects ) ].Name

	local newAsset = self.storageObject:FindFirstChild( specific ) or self:Get()
	local asset = if (self.recurring) or self.lastAsset ~= newAsset then newAsset
		elseif (not self.recurring) and #totalStorageObjects > 1 then self:Get()
		else newAsset
	
	self.lastAsset = asset
	return asset
end

return library

When "recurring" is set to "false", it will never choose the same asset twice in a row. You might also notice a fail-safe when defining "asset". It checks to see if #"totalStorageObjects" is greater than 1, which checks to see if there is more than one asset in the storage object. If there wasn't & "recurring" was set to "false", an infinite loop would occur & subsequentially crash the server.


This covers everything I wanted to explain about getting random assets (Including specific & non-recuring).

View in-game to comment, award, and more!