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

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

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

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

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