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.)

3 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.)