Tips on making a procedural part-terrain generator

I am currently making a procedural terrain generator for a game I am work on, and I have made a primitive version of the procedural terrain generator. However, I want the terrain to be (mostly) smooth, with slopes that fit nicely with eachother, along with rivers, and other special terrain. The generator will also have variables such as “smoothness” (how many or little sudden drops and rough terrain there will be), and flatness (how much vertical height there will be.) Also, it can adapt to generate different types of terrain by veiwing attributes of parts and terrain-type folders. Here are some current basic terrain generation, where each part has random orientation (so the smoothness variable is basically non-existant), and water generation (i plan to make random rivers and lakes) hasnt been figrured out, nor has any vertical aspect.

Pictures of current terrain generation:


Current terrain gen code:


folder = Instance.new("Folder")
folder.Name = "MapTerrain"
folder.Parent =game.Workspace
local TotalRarity = 0
local ChanceTable = {}
local TotalTiles = 0
local debris = game:GetService("Debris")
local TerrainType = game.Workspace.Hills --change the folder to change the map type
local FlatTilePreset = TerrainType.BasicTiles.Flat

function SortByRarity(RarityTable, ObjectTable)
	RarityTable = RarityTable or {}
for i, v in pairs(ObjectTable) do
	local rar = v:GetAttribute("RarityWeight")
	if rar ~= nil then
		TotalRarity += rar
		table.insert(RarityTable, {Rarity = TotalRarity, Object = v})
	end
end
table.sort(ChanceTable, function(a, b)
	if a.Rarity < b.Rarity then
		return true
	end
	return false
end)
	return RarityTable
end
SortByRarity(ChanceTable, TerrainType:GetDescendants())
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)
	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():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*5, OriginalPos.Z + (z*100)))*CFrame.Angles(0, Rotation, 0))
		else
			Tile.CFrame = CFrame.new(Vector3.new(OriginalPos.X + (x * 100), 12.5*5, OriginalPos.Z + (z*100)))*CFrame.Angles(0, Rotation, 0)
		end
		task.defer(function()
			SpawnAdjacentTiles(Tile)
		end)
		TotalTiles += 1
		else
			Tile:Destroy()
		end
	end
end
--[[for x = 1, 80 do --Spawns flat preset
	for z = 1, 80 do
		local Tile = FlatTilePreset:Clone()
		local Rotation = math.random(1, 4)
		Rotation*=90
		Rotation *= math.pi/180
		if Tile:IsA("Model") then
			Tile:PivotTo(CFrame.new(Vector3.new((x*100)-4050, 12.5*5, (z*100)-4050))*CFrame.Angles(0, Rotation, 0))
			Tile.Parent = folder
		else
			Tile.CFrame = CFrame.new(Vector3.new((x*100)-4050, 12.5*5, (z*100)-4050))*CFrame.Angles(0, Rotation, 0)
			Tile.Parent = folder
		end
	end
end--]]
function SpawnWater()
	
end
SpawnAdjacentTiles()

Note that the generator uses various rarity and slope attributes to determine frequency and location of parts (though the slope part is unfinished). My current idea is generating a flat baseplate of tiles, then adding features like sloped parts, special tile parts (lake and hill models), rivers and lakes (not special tile models), and verticallity. Also note that the special tiles aren’t completly neccesary, and could be added after basic terrain generation (I am making this to greatly simplify terrain generation for a game, as opposed to manually making tons of planet terrain.)

4 Likes

Bump since I still need help, and I haven’t figured out how to control factors like “smoothness” of the terrain (as in, models will cleanly “combine” with eachother, to make for example, a hill.)

hello! would you like to work with me? i am also trying to make a part terrain generator

What you can do is instead of a random terrain piece for each tile, first determine it’s height by doing a math.noise(x, y). Then, to shape that terrain, all you need is a wedge and a part.

If the gound is even (like the height difference in all four directions is less than 1 stud), spawn in a part. If the height distance exceeds that, spawn in a wedge.

Rotate the wedge depending on which direction is the highest. For example, if the tile north of the part is the highest, don’t rotate. If the tile south is the highest, rotate 180.

If you want a more complex generation, you could use CornerWedgePart to smoothen the corners even more, but that’s going to need 8 total checks instead of 4.

1 Like

Thanks – however, in the meantime, I have already got a different system working pretty well, which I have decided on using, shown in more detail here: Feedback on planet/biome generation (WIP) (you could probably give far more detailed feedback than that which I had received so far, anyways)

Also, would math.Noise work well for terrain with extreme height variation, and how could I control aspects of the terrain with it?

I likely will not – I have got it pretty well working by now (even though it is not finished, I’d say that it’s quite close)

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.