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