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.
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".
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:
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.
-- 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.
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:
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).