Map Generation Script

I have created some Module Scripts that can generate a map. Can anyone give feedback/suggestions for how to make this better?

Here is the result from my code:

Script Layout in Studio
image

Script 1. Perlin Noise

local PerlinNoise = {}


-- Function to generate a permutation table
local function generatePermutationTable(size)
	local perm = {}
	local factor = 2

	for i = 1, size do
		local factor = math.random(1, 2)
		perm[i] = (i - 1) * factor
	end

	return perm
end

function gaussianBlur(map, radius)
	local size = radius * 2 + 1
	local weight = {}
	local kernel = {}

	-- Calculate the weights for the Gaussian kernel
	for i = 1, size do
		weight[i] = math.exp(-(i - radius - 1) ^ 2 / (2 * radius ^ 2))
	end

	-- Normalize the weights
	local sum = 0
	for i = 1, size do
		sum = sum + weight[i]
	end
	for i = 1, size do
		weight[i] = weight[i] / sum
	end

	-- Apply the Gaussian blur
	local width = #map[1]
	local height = #map

	local blurredMap = {}
	for y = 1, height do
		blurredMap[y] = {}
		for x = 1, width do
			local sum = 0
			for i = 1, size do
				local index = x - radius - 1 + i
				if index >= 1 and index <= width then
					sum = sum + map[y][index] * weight[i]
				end
			end
			blurredMap[y][x] = sum
		end
	end

	return blurredMap
end

function perlinNoiseDoMath(x,y)
	local floor = math.floor
	local perm = {}

	for i = 1,512 do
		perm[i] = math.random(1, 384)
	end
	
	local perm = generatePermutationTable(512)
	
	local function grad( hash, x, y )
		local h = hash%8; -- Convert low 3 bits of hash code
		local u = h<4 and x or y; -- into 8 simple gradient directions,
		local v = h<4 and y or x; -- and compute the dot product with (x,y).
		return ((h%2==1) and -u or u) + ((floor(h/2)%2==1) and -2.0*v or 2.0*v);
	end
	
	local ix0, iy0, ix1, iy1;
	local fx0, fy0, fx1, fy1;
	local s, t, nx0, nx1, n0, n1;
	ix0 = floor(x); -- Integer part of x
	iy0 = floor(y); -- Integer part of y
	fx0 = x - ix0; -- Fractional part of x
	fy0 = y - iy0; -- Fractional part of y
	fx1 = fx0 - 1.0;
	fy1 = fy0 - 1.0;
	ix1 = (ix0 + 1) % 255; -- Wrap to 0..255
	iy1 = (iy0 + 1) % 255;
	ix0 = ix0 % 255;
	iy0 = iy0 % 255;
	t = (fy0*fy0*fy0*(fy0*(fy0*6-15)+10));
	s = (fx0*fx0*fx0*(fx0*(fx0*6-15)+10));
	nx0 = grad(perm[ix0 + perm[iy0+1]+1], fx0, fy0);
	nx1 = grad(perm[ix0 + perm[iy1+1]+1], fx0, fy1);
	n0 = nx0 + t*(nx1-nx0);
	nx0 = grad(perm[ix1 + perm[iy0+1]+1], fx1, fy0);
	nx1 = grad(perm[ix1 + perm[iy1+1]+1], fx1, fy1);
	n1 = nx0 + t*(nx1-nx0);
	return 0.5*(1 + (0.507 * (n0 + s*(n1-n0))))
end


function PerlinNoise.GenerateNoiseMap(mapWidth, mapHeight, scale)
	local noiseMap = {}
	
	if scale ~= nil then
		if scale <= 0 then
			scale = 0.0001
		end
	else
		scale = 0.0001
	end
	
	for y = 0, mapHeight do
		noiseMap[y] = {}
		for x = 0, mapWidth do
			local sampleX = x / scale
			local sampleY = y / scale
			
			local perlinValue = perlinNoiseDoMath(sampleX, sampleY)
			noiseMap[y][x] = perlinValue
		end
	end
	
	local finalNoiseMap = gaussianBlur(noiseMap, 4)
	return finalNoiseMap
end

return PerlinNoise

Script 2.Map

local ServerScriptService = game:GetService("ServerScriptService")
local MapGen = ServerScriptService:WaitForChild("MapGen")
local PerlinNoise = require(MapGen:WaitForChild("PerlinNoise"))
local WorkspaceTerrain = game.Workspace:WaitForChild("TerrainModels")

local Map = {}

function Map.GenerateForestTile(part, partModel, partSize)
	local function makeTree(vector3_position)
		local treeBase = Instance.new("Part")
		treeBase.Name = "TreeBase"
		treeBase.Size = Vector3.new(partSize / 8, partSize / 6, partSize / 8)

		treeBase.Position = vector3_position
		treeBase.Position = (Vector3.new(part.Position.X, part.Position.Y + part.Size.Y / 2 + treeBase.Size.Y / 2, part.Position.Z) + vector3_position)
		treeBase.Anchored = true
		treeBase.BrickColor = BrickColor.new("Brown")
		treeBase.Parent = partModel

		local treeTop = Instance.new("Part")
		treeTop.Name = "TreeTop"
		treeTop.Size = Vector3.new(partSize / 4, partSize / 4, partSize / 4)
		treeTop.Position = (Vector3.new(part.Position.X, treeBase.Position.Y + treeBase.Size.Y / 2 + treeTop.Size.Y / 2, part.Position.Z) + vector3_position)
		treeTop.Anchored = true
		treeTop.BrickColor = BrickColor.new("Slime green")
		treeTop.Parent = partModel
	end
	
	local num_trees = math.random(1, 3)
		
	if num_trees >= 1 then
		makeTree(Vector3.new((math.random(1, 20) / 10), (0 - (math.random(1, 5) / 10)),  (math.random(1, 20) / 10)))
	end
	
	if num_trees >= 2 then
		makeTree(Vector3.new((math.random(21, 35) / 10), (0 - (math.random(1, 5) / 10)),  -(math.random(11, 36) / 10)))
	end
	
	if num_trees >= 3 then
		makeTree(Vector3.new(-(math.random(36, 49) / 10), (0 - (math.random(1, 5) / 10)),  (math.random(21, 35) / 10)))
	end
end

-- Loads map terrain into the game using a noise map.
function Map.LoadTerrain(noiseMap)
	-- Define grid properties
	local gridSize = #noiseMap[1]
	local partSize = 8 -- Adjust as needed-- Create a folder to hold all the models

	-- Create a folder to hold all the models
	local modelsFolder = Instance.new("Folder")
	modelsFolder.Name = "TerrainModels"
	modelsFolder.Parent = workspace

	-- Create grid of parts
	for x = 1, gridSize do
		for z = 1, gridSize do
			local value = noiseMap[x][z]

			-- Define color based on altitude
			local color
			if value < 0.3 then
				color = BrickColor.new("Bright blue") -- Deep water
			elseif value < 0.4 then
				color = BrickColor.new("Medium blue") -- Shallow water
			elseif value < 0.41 then
				color = BrickColor.new("Sand yellow") -- Sand
			elseif value < 0.55 then
				color = BrickColor.new("Forest green") -- Lowland
			elseif value < 0.6 then
				color = BrickColor.new("Bright green") -- Hill
			elseif value < 0.62 then
				color = BrickColor.new("Grey") -- Natural Resource
			else			
				color = BrickColor.new("Dark grey") -- Mountain
			end

			-- Amplify higher values, especially for mountain range
			local scale = 2

			-- Amplify higher values, especially for mountain range
			local amplifiedValue = value
			if value >= 0.45 then
				amplifiedValue = value ^ scale * 1.5
			elseif value >= 0.55 then
				amplifiedValue = value ^ scale * 2
			elseif value >= 0.6 then
				amplifiedValue = value ^ scale * 3
			else
				amplifiedValue = value ^ scale
			end

			-- Apply smoothing to average neighboring cells
			local smoothValue = amplifiedValue
			if smoothValue > 0.45 then
				local neighborCount = 0
				for dx = -1, 1 do
					for dz = -1, 1 do
						if x + dx >= 1 and x + dx <= gridSize and z + dz >= 1 and z + dz <= gridSize then
							local neighborValue = noiseMap[x + dx][z + dz]
							local neighborAmplifiedValue = neighborValue
							if neighborValue >= 0.6 then
								neighborAmplifiedValue = neighborValue ^ scale * 2
							else
								neighborAmplifiedValue = neighborValue ^ scale
							end
							smoothValue = smoothValue + neighborAmplifiedValue
							neighborCount = neighborCount + 1
						end
					end
				end
				smoothValue = smoothValue / (neighborCount + 1)
			end

			local partModel = Instance.new("Model")
			partModel.Name = string.format("[%d][%d]", x, z)
			local part = Instance.new("Part")
			part.Name = string.format("TerrainPart[%d][%d]", x, z)
			part.Size = Vector3.new(partSize, smoothValue * 50, partSize) -- Adjust height based on smoothed value
			local new_position_for_part = Vector3.new(x * partSize, smoothValue * 50 / 2, z * partSize) -- Adjust position based on smoothed height
			local offset_position_for_part = Vector3.new(-8, -8, -8)
			part.Position = (offset_position_for_part + new_position_for_part)
			part.Anchored = true
			part.BrickColor = color
			part.Parent = partModel
			partModel.Parent = modelsFolder
			
			-- Add trees on forest green parts
			if color == BrickColor.new("Forest green") then
				-- generate a forest tile using info about the part that was generated
				Map.GenerateForestTile(part, partModel, partSize)
			end
			
		end
	end
end

-- Creates a new map using the Perlin Noise generator
function Map.NewTerrain()
	local noiseMap = PerlinNoise.GenerateNoiseMap(128, 128, math.random(16, 64))
	Map.LoadTerrain(noiseMap)
end

-- TODO: Load game objects onto the map using some of the player's saved data.


return Map

How to generate the map

local MapLoader = require(game:GetService("ServerScriptService"):WaitForChild("GameLoader"):WaitForChild("Map"))
MapLoader.NewTerrain()
4 Likes