How to generate an island map with a river

Hey!
I’m currently making a roblox game based on a specific game which I probably can’t mention here, but it involves animals who like to cross.

Anyways, I’m working on a randomly generated island generator to provide a unique experience per gameplay, but I’m not entirely sure how to make it generate smooth rivers.

Right now, I have this basic layout:

local materialTypes = {
	[1] = Enum.Material.Grass,
	[2] = Enum.Material.Water
}

local mapGrid = {}

-- grid generation
for y = 1,10 do
	mapGrid[y] = {}
	for x = 1, 10 do
		mapGrid[y][x] = math.random(1,2)
	end
end

-- terrain generation
local xIndex, yIndex = 0,0
local blockSize = 10
for _,y in pairs(mapGrid) do
	yIndex += 1
	xIndex = 0
	for _,x in pairs(y) do
		xIndex += 1
		workspace.Terrain:FillBlock(CFrame.new(xIndex * blockSize, 0, yIndex * blockSize),Vector3.new(blockSize,5,blockSize), materialTypes[x])
	end
end

So far, this is rather simple and just does a random scatter of materials like this:

The issue is finding proper resources which can help me with generating something more like this:
(Ignore the beach borders, main focus here is just the rivers right now)

If I could get some guidance on what would work for something like this, it would be greatly appreciated.

Thanks!

2 Likes

this could help Procedural Rivers in Unity - P2 Perlin Worm Script - YouTube

2 Likes

been trying to decode this although i will admit it’s a little tricky trying to grasp what’s fully going on here
what i’m assuming is going on is that it has a set start and end point, where it’ll try to somewhat randomly steer itself towards the end point with some weight added to it?

sorry if my guess is incorrect, i haven’t really spent ages with unity (and adhd absolutely sucks lol)

1 Like

Yeah something like that - ive been tryna research it myself too, so far into this video the speaker is very descriptive, and even gives a different technique for river creation, ive not finished watching yet so ill edit my opinion when the video is finished

edit: finished watching and yes it does give a good method to create the rivers i can send examples if you want

1 Like

sorry for the late response, timezones and stuff

i’m gonna give this a watch in a bit and i’ll see how it goes!!
also if you wish to, feel free to give examples (you don’t have to though)

1 Like

this uses the non perlin “wander towards” technique from the video using a start and endpoint that could easily be randomly generated

image
image
code for 1st and 2nd image (in serverscriptservice or workspace)::



local goal = Vector3.new(math.random(-500,500), 0, math.random(-500,500))
local start = Vector3.new(math.random(-500,500), 0, math.random(-500,500))

local function createPart(pos) 
	local part = Instance.new("Part")
	part.Position = pos
	part.Anchored =true
	part.CanCollide =false
	part.Size = Vector3.new(20,20,20)
	part.Parent = workspace.Garbage
	
	return part
end



local lastPos = start

local posTable = {}
local i=0

local lastUnit = Vector3.new(1,0, 0)

local seed = math.random(1, 279879)
local function getPos()

	
	local dir1 = CFrame.fromAxisAngle(Vector3.new(0,1,0), math.rad( math.random(-90,90) )):VectorToWorldSpace(lastUnit)
	local dir2 = CFrame.fromAxisAngle(Vector3.new(0,1,0), math.rad( math.random(-90,90) )):VectorToWorldSpace(lastUnit)
	
	local towardsGoal = CFrame.lookAt(lastPos, goal).LookVector
	
	
	local directions = {dir1, dir2, towardsGoal, dir1, dir2}
	
	local nextUnit = directions[math.random(1,#directions)]
	local newPos = lastPos +( nextUnit * 15)
	
	lastUnit = nextUnit
	
	if newPos == lastPos then
		return getPos()
	else
		return newPos
	end
end


while true do
	
	task.wait()
	
	
	local newPos =getPos() 
	--workspace.Terrain:FillBlock(CFrame.new(newPos), Vector3.new(20,20,20), Enum.Material.Water)
	
	createPart(newPos)
	
	
	if (newPos - goal).Magnitude < 10 then
		break
		
	end
	
	
	lastPos = newPos
	

	
	
end

this is one using the worm method again from a start point to end point
image

code for the 3rd image (in serverscriptservice or workspace):



local goal = Vector3.new(math.random(-500,500), 0, math.random(-500,500))
local start = Vector3.new(math.random(-500,500), 0, math.random(-500,500))

local function createPart(pos) 
	local part = Instance.new("Part")
	part.Position = pos
	part.Anchored =true
	part.CanCollide =false
	part.Size = Vector3.new(10,10,10)
	part.Parent = workspace
	
	return part
end



local lastPos =start

local posTable = {}
local i=0

local lastUnit = Vector3.new(1,0, 0)

local seed = math.random(1, 279879)
local function getPos()

	
	local towardsGoal = CFrame.lookAt(lastPos, goal).LookVector

	local dir1 = CFrame.fromAxisAngle(Vector3.new(0,1,0), math.rad(math.clamp( (math.noise(lastPos.Magnitude,0,seed) * 800),-45,45 ) ) ):VectorToWorldSpace( (lastUnit+towardsGoal).Unit )

	
	local directions = {dir1}
	
	local nextUnit = directions[math.random(1,#directions)]
	local newPos = lastPos +( nextUnit * 10)
	
	lastUnit = nextUnit
	
	if newPos == lastPos then
		return getPos()
	else
		return newPos
	end
end


while true do
	
	task.wait()
	
	
	local newPos =getPos() 
	--workspace.Terrain:FillBlock(CFrame.new(newPos), Vector3.new(20,20,20), Enum.Material.Water)
	
	createPart(newPos)
	
	
	if (newPos - goal).Magnitude < 10 then
		break
		
	end
	
	
	lastPos = newPos
	

	
	
end

edit:: the first one can be easily locked to a grid like your original map is

1 Like