Spawns a bunch of parts on a basepart in an orderly manner based on factors like minimum distance, how many of them and which map to spawn on. has a little bit type and sanity checking i guess
local GemSpawning = {}
local self = GemSpawning
local function isFarEnough(newPos : Vector3, spawnedPositions, MIN_DISTANCE : number)
for _, pos in ipairs(spawnedPositions) do
if (pos - newPos).Magnitude < MIN_DISTANCE then
return false
end
end
return true
end
function self.spawnGem(MIN_DISTANCE : number, gemModel : BasePart, mapArea : BasePart, AMOUNT_TO_SPAWN : number)
local spawnedPositions: { Vector3 } = {}
local pos = mapArea.Position
local px, py, pz = pos.X, pos.Y, pos.Z
local size = mapArea.Size
local sx, sy, sz = size.X, size.Y, size.Z
while #spawnedPositions < AMOUNT_TO_SPAWN do
local spawnX = math.random(px - sx/2, px + sx/2)
local spawnZ = math.random(pz - sz/2, pz + sz/2)
local spawnPos = Vector3.new(spawnX, py + gemModel.Size.Y, spawnZ)
if isFarEnough(spawnPos, spawnedPositions, MIN_DISTANCE ) then
local gemCl = gemModel:Clone()
gemCl.Position = spawnPos
gemCl.Parent = workspace
table.insert(spawnedPositions, spawnPos)
end
end
return #spawnedPositions > 0
end
return GemSpawning
Backtracked, was taking longer than expected. The point is that there are multiple other approaches that produce similar results and are faster in worst-case scenarios. The approach you presented may perform many random calculations before it completes, many times more than the amount input. You can minimize the worst-case scenarios by adding limitations if you want to keep the same approach.
The grid approach splits the area into a 2-dimensional grid, distancing each section by the minimum distance and ensuring that a random point is picked within the section, so randomization is only needed once and no verification is necessary. The math can be quite tricky to get right since the amount could be non-square (4, 9, 16, …), which tripped me up.
You are appending to the table, however each time you append you are also searching through the entire table to ensure you can append. The approach you are using could perform quite bad in cases where the area to spawn the gems and the distance to separate them conflicts with the amount. I suggest adding a way to check to ensure that even spawning the amount is possible, if not then reduce the gem amount until it is. Maybe sx * sz / MIN_DISTANCE > AMOUNT_TO_SPAWN.
A different approach could be taken
The isFarEnough method could be removed in favor of a grid based system that ensures all gems are separated at least some distance and doesn’t require a table. The grid system would split the mapArea into multiple segments. The grid system will require some testing to flesh out and studio isn’t currently letting me play test, so I’ll get back to you in a few days, but it will most likely involve using a loop to position each created part within segment of the mapArea allocated, randomly.
function spawnGem(
MIN_DISTANCE: number,
gemModel: BasePart,
mapArea: BasePart,
AMOUNT_TO_SPAWN: number
)
local pos = mapArea.Position
local px, py, pz = pos.X, pos.Y, pos.Z
local size = mapArea.Size
local sx, sy, sz = size.X, size.Y, size.Z
local gemYSize = gemModel.Size.Y
-- uncertain
if sx * sz // MIN_DISTANCE > AMOUNT_TO_SPAWN then
error(`AMOUNT_TO_SPAWN is too large, cannot be greater than {sx * sz // MIN_DISTANCE}`)
end
-- or i=1?
for i=0, AMOUNT_TO_SPAWN do
local gemCl = gemModel:Clone()
-- some calculations, will get back to you later
local minX, maxX = 0, 1
local minZ, maxZ = 0, 1
gemCl.Position = Vector3.new(
math.random(minX, maxX),
gemYSize,
math.random(minZ, maxZ)
)
gemCl.Parent = workspace
end
end