Spawning and Despawning Trees

Hi. I currently have a script that spawns trees randomly but it will only work for a short while before spawning the same tree in the same place when a tree is already there. Here is the script:

while true do
	wait(30)
	local randomnumber = math.random(1,3)
	print("Trees spawning")
	if randomnumber == 1 then
		local Tree1 = game.ReplicatedStorage.Trees.Tree:Clone()
		Tree1.Parent = game:GetService("Workspace")
	else if randomnumber == 2 then
			local Tree2 = game.ReplicatedStorage.Trees.Tree2:Clone()
			Tree2.Parent = game:GetService("Workspace")
		else if randomnumber == 3 then
				local Tree3 = game.ReplicatedStorage.Trees.Tree3:Clone()
				Tree3.Parent = game:GetService("Workspace")
			end
				
		end
			
	end
end

How do I make the trees spawn in different locations randomly and make sure a tree at point A will not spawn if there is a tree already there. And, how do I despawn trees after lets say 30 seconds after no one comes to cut them down. (This is to prevent too many trees spawning and overloading the server.)

Your issue is when you :Clone() the tree from ReplicatedStorage you don’t actually set where you want to put it inside of workspace. If your tree model is a model you could use the function SetPrimaryPartCFrame() to set the tree’s primary part to a random position. To clarify,

while true do
    wait(waitTime)
    local tree = game.ReplicatedStorage.Trees.Tree:Clone()
    tree.Parent = workspace
    if tree:IsA("Model") and tree.PrimaryPart then -- checks if the tree is a class model and that the primary part exists
        tree:SetPrimaryPartCFrame(CFrame.new(RANDOMPOSITION))
    end
end

You’d also want to define/create a random position for the tree to spawn using math.random to set a range of x, y, z values to spawn it between. There are other methods of doing so but this is one. Hope this helps.

1 Like

Hmm, how do I use math random to set positions? Also, how do I make sure that 2 trees dont spawn in the same spot and how do I make them despawn after awhile?

You can make sure that 2 trees dont spawn in the same spot by checking if the tree is touching another tree with the Touched event. If it detects another tree, you can delete the tree or reposition it.

2 Likes

Let’s say you want your tree to spawn randomly between two points. Example: Trees x position is in between 100 and 200. Then the trees z position is between 10 and 50. You simply do tree.position = vector3.new(math.random(100,200),y,math.random(10,50)) and to check if it’s in the same position as another you can just set their parent inside a folder and check their positions as @happygeneral2015 said

Positioning:
Use math.random(m, n), for the X and Y coordinates of your trees, keeping m, n within the boundaries of your map. I would recommend keeping all the trees in a folder or table and then looping through that, then checking the magnitude (distance within a 3d space) from the new tree and the pre-existing trees. If it’s say >30 studs away, generate a new position (use a repeat loop).

Destroying trees after a certain time:
Use the Debris service, e.g:
game:GetService("Debris"):AddItem(Tree, *Insert time here*)

For any decent number of trees this will be pretty slow, e.g. for the 100th tree that’s 99 distance checks, PLUS the chance of overlapping rises which means you have to do another 99 distance checks.

Wouldn’t it be better to only spawn new trees once some have been cut down?

You can choose to e.g. spawn trees up to 30, then stop spawning until the number gets below 30, etc.

Summary
local treeTypes = game.ServerStorage.TreeTypes:GetChildren()

local maxTrees = 30
local spawnedTrees = {}

function chooseRandomTree()
	return treeTypes[math.random(1, #treeTypes)]
end

function spawnTree()
	local tree = chooseRandomTree():Clone()
	tree:SetPrimaryPartCFrame(CFrame.new(
		math.random(-100, 100),
		0,
		math.random(-100, 100)
	))
	table.insert(spawnedTrees, tree)
	tree.Parent = game.Workspace

	local treeCutDownEvent = tree:FindFirstChild("CutDown")
	local c
	c = treeCutDownEvent.Event:Connect(function()
		c:Disconnect()
		despawnTree(tree)
	end)
end

function despawnTree( tree )
	table.remove( spawnedTrees, table.find(spawnedTrees, tree) ) --all new trees to be spawned
	Debris:AddItem(tree, 5) --remove the model after a few seconds
end

while wait(1) do
	if #spawnedTrees < maxTrees then
		spawnTree()
	end
end

To prevent trees from overlapping, I would keep track of every possible position a tree can be in and if that position is already taken. To limit the possible positions, I’d place trees on a grid. To prevent it from looking like a grid, I’d place the trees at a grid position but offset a small random amount.

Summary
local treeTypes = game.ServerStorage.TreeTypes:GetChildren()

local GRID_EXTENTS = {Min = Vector2.new(-100, -100), Max = Vector2.new(100, 100)}
local GRID_SPACING = 10
local CHOOSE_RANDOM_MAX_TRIES = 10
local random = Random.new()
local maxTrees = 30
local spawnedTrees = {}
local treeMap = {}

function mapGet( map, x, y )
	if map[x] then
		return map[x][y]
	end
	return nil
end

function mapSet( map, x, y, value )
	map[x] = map[x] or {}
	map[x][y] = value
end

function round( n, to )
	return math.floor(n/to + 0.5) * to
end

function chooseRandomTree()
	return treeTypes[random:NextInteger(1, #treeTypes)]
end

function chooseRandomGridPosition( extents )
	local gridX, gridY =
		round( random:NextInteger(extents.Min.X, extents.Max.X), 1 ),
		round( random:NextInteger(extents.Min.Y, extents.Max.Y), 1 )
end

function chooseRandomEmptyGridPosition( extents )
	local chosenPos
	for _ = 1, CHOOSE_RANDOM_MAX_TRIES do
		local randomPos = chooseRandomGridPosition( extents )
		local treeAtPos = mapGet(treeMap, randomPos.X, randomPos.Y)
		if not treeAtPos then
			chosenPos = randomPos
			break
		end
	end

	return chosenPos
end

function spawnTree()
	local treeGridPos = chooseRandomGridPosition(GRID_EXTENTS)
	if treeGridPos then
		local tree = chooseRandomTree():Clone()
		tree:SetPrimaryPartCFrame(
			CFrame.new() + Vector3.new(
				treeGridPos.X + random:NextNumber(-0.5, 0.5), 
				0, 
				treeGridPos.Y + random:NextNumber(-0.5, 0.5)
			) * GRID_SPACING
		)
		tree.GridPosValue.Value = treeGridPos
		table.insert(spawnedTrees, tree)
		mapSet(treeMap, treeGridPos.X, treeGridPos.Y, tree)
		tree.Parent = game.Workspace

		local treeCutDownEvent = tree:FindFirstChild("CutDown")
		local c
		c = treeCutDownEvent.Event:Connect(function()
			c:Disconnect()
			despawnTree(tree)
		end)

		return tree
	else
		return nil
	end
end

function despawnTree( tree )
	table.remove( spawnedTrees, table.find(spawnedTrees, tree) ) --all new trees to be spawned
	local treeGridPos = tree.GridPosValue.Value
	mapSet(treeMap, treeGridPos.X, treeGridPos.Y, nil)
	Debris:AddItem(tree, 5) --remove the model after a few seconds
end

while wait(1) do
	if #spawnedTrees < maxTrees then
		spawnTree()
	end
end
1 Like

I have published a demo place to help you out, feel free to copy its open to all. Just a quick knock up. Its using a Unioned Part as the tree as per OP requirements.

4 Likes

This is helping me so much! Thank you for the demo.

1 Like