Help with creating randomly generated islands

I’m working on a system that generates islands that have different sizes. Generating them was easy enough, it’s just the matter of placing objects randomly throughout the island. While working on the script, however, I noticed that sometimes, trees and rocks may generate on top of one-another, even though I have a minimum distance check and I also add the positions of generated objects to tables, etc.

I will provide code and a place file since there’s a decent chunk of code here, some of you may want to look through it and help me find the problem.

Also, if there’s anything I can improve on besides all the bugs then please let me know! Thanks.

DecorationPlacer module

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local treePositions = {}
local rockPositions = {}

local tree = ReplicatedStorage.Tree
local rock = ReplicatedStorage.Rock

local MAX_TREES = 100 -- Maximum number of trees to generate on the island
local MAX_ROCKS = 100 -- Maximum number of rocks to generate on the island
local MIN_DISTANCE = 10 -- Minimum distance apart from each tree/rock. A new one will not generate if there's another within this distance

local module = {}

function module.CountTrees(folder)
	return #folder:GetChildren()
end

function module.CountRocks(folder)
	return #folder:GetChildren()
end

function module.FindClosestObject(newObjectPosition, objectPositions)
	local currentDistance = math.huge
	local currentPosition = nil

	for _, position in ipairs(objectPositions) do
		local distance = (position - newObjectPosition).Magnitude

		if distance < currentDistance then
			currentDistance = distance
			currentPosition = position
		end
	end
	return currentPosition, currentDistance -- Return both the closest position and the distance
end

function module.PlaceDecoration(decorationModel, decorationPositions, folder, part)
	local decorationClone = decorationModel:Clone()
	decorationClone.Parent = folder

	local landSizeX = part.Size.X
	local landSizeZ = part.Size.Z

	local offsetX = landSizeX / 2
	local offsetZ = landSizeZ / 2

	local minX = -offsetX + MIN_DISTANCE
	local maxX = offsetX - MIN_DISTANCE
	local minZ = -offsetZ + MIN_DISTANCE
	local maxZ = offsetZ - MIN_DISTANCE

	local randomPosX = math.random(minX, maxX)
	local randomPosZ = math.random(minZ, maxZ)

	local newPosition = Vector3.new(randomPosX, 0, randomPosZ) -- Set Y position to 0 initially

	-- Perform a raycast to find the correct Y position on the terrain surface
	local raycastResult = workspace:Raycast(newPosition + Vector3.new(0, 100, 0), Vector3.new(0, -100, 0))

	if raycastResult then
		local terrainSurfaceY = raycastResult.Position.Y
		newPosition = Vector3.new(newPosition.X, terrainSurfaceY, newPosition.Z)
	end

	if #decorationPositions > 0 then
		local closestDecorationPosition, closestDistance = module.FindClosestObject(newPosition, decorationPositions)

		if closestDecorationPosition and closestDistance < MIN_DISTANCE then
			decorationClone:Destroy()
		else
			decorationClone.PrimaryPart.CFrame = CFrame.new(newPosition) * CFrame.Angles(0, math.rad(math.random(0, 360)), 0)
			table.insert(decorationPositions, decorationClone.PrimaryPart.Position)
		end
	else
		decorationClone.PrimaryPart.CFrame = CFrame.new(newPosition) * CFrame.Angles(0, math.rad(math.random(0, 360)), 0)
		table.insert(decorationPositions, decorationClone.PrimaryPart.Position)
	end
end

function module.PlaceDecorationTypes(treesFolder, rocksFolder, part)
	if module.CountTrees(treesFolder) >= MAX_TREES and module.CountRocks(rocksFolder) >= MAX_ROCKS then
		return
	end

	local decorationType = math.random(1, 2)

	if decorationType == 1 and module.CountTrees(treesFolder) < MAX_TREES then
		module.PlaceDecoration(tree, treePositions, treesFolder, part)
	elseif decorationType == 2 and module.CountRocks(rocksFolder) < MAX_ROCKS then
		module.PlaceDecoration(rock, rockPositions, rocksFolder, part)
	end
end

return module

IslandGenerator module

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local SandPartTemplate = ReplicatedStorage.Sand
local DirtPartTemplate = ReplicatedStorage.Dirt
local GrassPartTemplate = ReplicatedStorage.Grass

local decorationPlacer = require(script.Parent.DecorationPlacer)

local module = {}

local function CreatePart(template, position, size, parent)
	local part = template:Clone()
	part.Position = position
	part.Size = size
	part.Parent = parent

	return part
end

function module.GenerateIsland(sizeX, sizeZ, sandHeight, dirtHeight, grassHeight, parent)
	local islandTemplate = script.Island:Clone()
	islandTemplate.Parent = workspace.Islands.Generated
	
	local sandPart = CreatePart(SandPartTemplate, Vector3.new(0, 0, 0), Vector3.new(sizeX + 20, sandHeight, sizeZ + 20), islandTemplate)

	local dirtPosition = sandPart.Position + Vector3.new(0, sandPart.Size.Y / 2 + dirtHeight / 2, 0)
	local dirtPart = CreatePart(DirtPartTemplate, dirtPosition, Vector3.new(sizeX, dirtHeight, sizeZ), islandTemplate)

	local grassPosition = dirtPart.Position + Vector3.new(0, dirtPart.Size.Y / 2 + grassHeight / 2, 0)
	local grassPart = CreatePart(GrassPartTemplate, grassPosition, Vector3.new(sizeX + 2, grassHeight, sizeZ + 2), islandTemplate)
	
	for i = 1, 12 do
		decorationPlacer.PlaceDecorationTypes(islandTemplate.Trees, islandTemplate.Rocks, grassPart)
	end
	
	return sandPart, dirtPart, grassPart
end

return module

WIP.rbxl (63.1 KB)

The models did not generated on top of each others I tried many times, the only problem I see in your code that sometimes the small island generated empty and thats normal because of the MIN_DISTANCE if you wanna solve this problem you can lower that variable, make the islands minimum size bigger and use a number more than 12 in this for loop

1 Like