[OPEN SOURCE] Hexagonal Based Tile System With Terrain

In short, i started making a CIV6 like game but couldn’t really find a team, so i am going to release the code for the Map Generation here, Like i am pretty sure there are other better ways to do this but this is what i made (after torturing ChatGpt for many hours and losing my mental sanity)
Basically it uses perlin noise to make a map, the current default one is pretty small so it may not seem that detailed, so increase size of the board if you want more beautiful landscapes.

This is also my first post so i might have done somethings wrong, i apologise if i did.

Link to my badly made game :skull:

local replicatedStorage = game:GetService("ReplicatedStorage")
local tile = replicatedStorage.Assets:WaitForChild("HexagonTile")
local trees = replicatedStorage.Assets:WaitForChild("Trees")
local mountains = replicatedStorage.Assets:WaitForChild("Mountains")

local boardSize = 20
local pos = Vector3.new(0, 0, 0) -- Starting point, will adjust later

-- Define tile types and their properties including material and reflectance
local TILE_TYPES = {
	{name = "WATER", color = BrickColor.new("Bright blue"), threshold = 0.075, material = Enum.Material.Plastic, reflectance = 0.25},
	{name = "LAND", color = BrickColor.new("Bright green"), threshold = 0.45, material = Enum.Material.Fabric, reflectance = 0.1},
	{name = "MOUNTAIN", color = BrickColor.new("Reddish brown"), threshold = 1.0, material = Enum.Material.Fabric, reflectance = 0.05},
}

-- Function to get tile type based on Perlin noise value
local function getTileType(noiseValue)
	for _, typeInfo in ipairs(TILE_TYPES) do
		if noiseValue < typeInfo.threshold then
			return typeInfo
		end
	end
	return TILE_TYPES[#TILE_TYPES]
end

-- Generate a random seed
math.randomseed(tick())

-- Create a unique seed offset
local seedOffsetX = math.random() * 1000
local seedOffsetY = math.random() * 1000

local function generatePerlinNoise(x, y, baseFrequency, baseAmplitude, octaves, persistence, lacunarity, offsetX, offsetY)
	local noise = 0
	local amplitude = baseAmplitude
	local frequency = baseFrequency

	for i = 1, octaves do
		noise = noise + math.noise((x + offsetX) * frequency, (y + offsetY) * frequency) * amplitude
		amplitude = amplitude * persistence
		frequency = frequency * lacunarity
	end

	return noise
end

-- Array to store the created tiles
local board = {}

local baseFrequency = 0.1
local baseAmplitude = 1
local octaves = 5
local persistence = 0.7
local lacunarity = 1.9

local hexagonWidth = tile.Size.X
local hexagonHeight = tile.Size.Z * 0.75

local maxBoardWidth = (boardSize + math.ceil(boardSize / 2) - 1) * hexagonWidth
pos = Vector3.new(-maxBoardWidth / 2, 0, -(boardSize * hexagonHeight / 2))

for x = 1, boardSize do
	board[x] = {}
	local start, finish = nil, nil
	if x < math.ceil(boardSize / 2) then
		start, finish = math.ceil(boardSize / 2) - x + 1, boardSize
	else
		start, finish = 1, boardSize - math.floor(x - boardSize / 2)
	end

	for y = start, finish do
		local newTile = tile:Clone()
		newTile.Parent = workspace
		local offset = 0
		if y % 2 == 0 then
			offset = newTile.Size.X * 0.5
		end
		newTile.Position = pos + Vector3.new((x + math.floor(y / 2)) * newTile.Size.X - offset, 0, y * hexagonHeight)

		-- Get Perlin noise value and determine tile type based on thresholds
		local noiseValue = generatePerlinNoise(x, y, baseFrequency, baseAmplitude, octaves, persistence, lacunarity, seedOffsetX, seedOffsetY)

		-- Determine the tile type and set properties
		local tileTypeInfo = getTileType(noiseValue)
		newTile.BrickColor = tileTypeInfo.color
		newTile.Material = tileTypeInfo.material
		newTile.Reflectance = tileTypeInfo.reflectance

		-- Create and set the StringValue for tile type
		local tileTypeValue = Instance.new("StringValue")
		tileTypeValue.Name = "Type"
		tileTypeValue.Value = tileTypeInfo.name
		tileTypeValue.Parent = newTile

		-- Store the type in the board
		board[x][y] = tileTypeInfo.name

		-- Spawn trees on "LAND" and mountains on "MOUNTAIN" with random Y-rotation, parented to the tile
		if tileTypeInfo.name == "LAND" then
			local treeInstance = trees:Clone()
			treeInstance.Position = newTile.Position + Vector3.new(0, 2.25, 0) 
			treeInstance.Orientation = Vector3.new(0, math.random(0, 360), 0)
			treeInstance.Parent = newTile -- Parent to the tile
		elseif tileTypeInfo.name == "MOUNTAIN" then
			local mountainInstance = mountains:Clone()
			mountainInstance.Position = newTile.Position + Vector3.new(0, 2.25, 0) 
			mountainInstance.Orientation = Vector3.new(0, math.random(0, 360), 0)
			mountainInstance.Parent = newTile -- Parent to the tile
		end
	end
end
12 Likes