Procedural part terrain generation script doesnt generate water properly

I am making a part generation script, and when a water tile is generated, each adjacent tile generated to that water tile is supposed to have a 60% chance of generating another water tile, which would make lakes or rivers. However, it just makes straight lines as shown in the picture. I have tried many things to get it to work, but cant figure it out.

Here is the script:


folder = Instance.new("Folder")
folder.Name = "MapTerrain"
folder.Parent =game.Workspace
local TotalRarity = 0
local ChanceTable = {}
local TotalTiles = 0
local debris = game:GetService("Debris")

for i, v in pairs(game.Workspace.Hills:GetDescendants()) do
	local rar = v:GetAttribute("RarityWeight")
	if rar ~= nil then
		TotalRarity += rar
		table.insert(ChanceTable, {Rarity = TotalRarity, Object = v})
	end
end
table.sort(ChanceTable, function(a, b)
	if a.Rarity < b.Rarity then
		return true
	end
	return false
end)
function SelectPart()
	local PartRan = math.random(0, TotalRarity)
	local PartSelected
	local PreviousStep = 0
	for i, v in pairs(ChanceTable) do
		if PartRan >= PreviousStep and PartRan <= v.Rarity then
			PartSelected = v.Object
			return PartSelected
		end
		PreviousStep = v.Rarity
	end
end
function SpawnAdjacentTiles(CenterTile, Water)
	if TotalTiles >= 6500 then return end
	local OriginalPos
	if CenterTile ~= nil then
	if CenterTile:IsA("Model") then
		OriginalPos = CenterTile.PrimaryPart.Position
	else
		OriginalPos = CenterTile.Position
	end
	if math.abs(OriginalPos.X) >= 4000 or math.abs(OriginalPos.Z) >= 4000 then return end
	else
		OriginalPos = Vector3.new(50, 0, 50)
	end
	for i = 1, 4 do
		local x
		local z
		if i == 1 then x = 1 z = 0 elseif i == 2 then x =0 z = 1 elseif i == 3 then x = -1 z = 0 else x = 0 z = -1 end
		local Tile = SelectPart()
		if CenterTile ~= nil then
			if Water and math.random(1, 10) >= 5 then
				Tile = game.Workspace.Hills.BasicTiles.Water
			end
			else
			end
		Tile = Tile:Clone()
		local special = false
		local  params = RaycastParams.new()
		params.FilterDescendantsInstances = {folder, Tile}
		params.FilterType = Enum.RaycastFilterType.Include
		local rayRes = workspace:Raycast(OriginalPos, Vector3.new((x * 100), 0,  (z*100)), params)
		if Tile:GetAttribute("SpecialTile") == true then
			special = true
		end
		Tile.Parent = folder
		local Rotation = math.random(1, 4)
		Rotation*=90
		Rotation *= math.pi/180
		if rayRes == nil then
		if Tile:IsA("Model") then
			Tile:PivotTo(CFrame.new(Vector3.new(OriginalPos.X + (x * 100), 12.5, OriginalPos.Z + (z*100)))*CFrame.Angles(0, Rotation, 0))
		else
			Tile.CFrame = CFrame.new(Vector3.new(OriginalPos.X + (x * 100), 12.5, OriginalPos.Z + (z*100)))*CFrame.Angles(0, Rotation, 0)
		end
		task.defer(function()
			if Tile.Name == "Water" then
				SpawnAdjacentTiles(Tile, true)
			else
				SpawnAdjacentTiles(Tile, false)
			end
		end)
		TotalTiles += 1
		else
			Tile:Destroy()
		end
	end
end
SpawnAdjacentTiles()
1 Like

The problem might be that the water spawning thread only has time to generate water in the first surrounding tile loop (x = 1, z = 0) and the rest of the tiles are overtaken by grass tiles placed shortly before.

You may need to try a different terrain generation method, the one used in the script appears to expand outwards and not allow for tile changes which would could cause terrain formations (especially water) to have directional biases. Personally, I would suggest a generation system where you add procedural features to an initially featureless/flat terrain (I think games like Terraria do it).

1 Like

So, I would start off with a flat terrain, and then add extra tiles to add texture, water, ect from there? I also tried making it so if a tile spawns next to it, the water replaces the tile that spawned next to it with a 50% chance, but it didnt work for some reason. I might try your approach of starting flat, and making terrain features generate one by one. (e.g. rivers first, then mountains, ect.)