You can write your topic however you want, but you need to answer these questions:
-
What do you want to achieve? Keep it simple and clear!
Hello, i am following a tutoriial on procedural terrain generation by Sebastian Lague (of course the tutorial is for unity so i modified it to fit my style and work on roblox) -
What is the issue? Include screenshots / videos if possible!
While many chunks connect ideally some have issues, i cannot find the reason why
-
What solutions have you tried so far? Did you look for solutions on the Developer Hub?
Yes i did but couldn’t find any solution
After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!
Code:
local noise = require(script.ModuleScript);
local TweenService = game:GetService("TweenService");
type TerrainType = {
height: number,
color: Color3,
name: string,
material: Enum.Material
}
-- Currently not used but might be in the future, good for debugging If anyone needs it
function generateHeightMap(mapHeight, mapWidth, noiseMap)
local colorMap = {};
for x = 0, mapHeight do
colorMap[x] = {};
for y = 0, mapWidth do
local black = Color3.new(0, 0, 0);
colorMap[x][y] = black:Lerp(Color3.new(1,1,1), noiseMap[x][y]);
end
end
return colorMap;
end
function generateColorMap(mapHeight, mapWidth, noiseMap, regions)
local colorMap = {};
for x = 0, mapHeight do
colorMap[x] = {};
for y = 0, mapWidth do
local currentHeight = noiseMap[x][y];
for i = 1, #regions do
if currentHeight <= regions[i].height then
colorMap[x][y] = regions[i].color;
break;
end
end
end
end
return colorMap;
end
function generateMaterialMap(mapHeight, mapWidth, noiseMap, regions)
local colorMap = {};
for x = 0, mapHeight do
colorMap[x] = {};
for y = 0, mapWidth do
local currentHeight = noiseMap[x][y];
for i = 1, #regions do
if currentHeight <= regions[i].height then
colorMap[x][y] = regions[i].material;
break;
end
end
end
end
return colorMap;
en
function getSurroundingPositions(range, position: Vector3): { [number]: Vector3 }
local inRange = {};
local location = position - Vector3.new(3,0,3);
for x = 0, 6 do
for y = 0, 6 do
local newLocation = location + Vector3.new(x, 0 ,y);
table.insert(inRange, newLocation);
end
end
return inRange;
end
local players = game.Players;
local Chunk = require(script.Chunks);
local chunks = {};
local CHUNK_SIZE = 32;
local seed = 10;
function onPlayerMove(character)
local humanoidRootPart: BasePart = character.HumanoidRootPart;
local xPosition = math.floor(humanoidRootPart.Position.X / 160);
local zPosition = math.floor(humanoidRootPart.Position.Z / 160);
local chunkPosition = Vector3.new(xPosition, 0, zPosition);
if not chunks[chunkPosition] then
local chunk = Chunk.new(seed, chunkPosition, Vector3.new(32,32,32), script:GetAttribute("offset"));
chunk:render();
chunks[chunkPosition] = chunk;
local surroundingPositions = getSurroundingPositions(3, chunkPosition);
for i, position in pairs(surroundingPositions) do
local newChunk = Chunk.new(seed, position);
chunks[position] = newChunk
newChunk:render();
end
end
end
players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function(character)
local humanoid: Humanoid = character:WaitForChild("HumanoidRootPart");
while true do
wait()
onPlayerMove(character);
end
end)
end)
Chunk class
Chunk = {}
Chunk.__index = Chunk
local ChunkGenerator = require(script.Parent.ModuleScript);
local regions = {
{
name = "Water",
material = Enum.Material.Water,
height = 0.3,
color = Color3.new(0.2, 0.384314, 0.752941)
},
{
name = "Sand",
material = Enum.Material.Sand,
height = 0.35,
color = Color3.new(0.823529, 0.823529, 0.490196)
},
{
name = "Grass",
material = Enum.Material.Grass,
height = 0.55,
color = Color3.new(0.341176, 0.592157, 0.0823529)
},
{
name = "Grass2",
height = 0.6,
material = Enum.Material.LeafyGrass,
color = Color3.new(0.243137, 0.419608, 0.0745098)
},
{
name = "Rock",
material = Enum.Material.Asphalt,
height = 0.7,
color = Color3.new(0.356863, 0.262745, 0.227451)
},
{
name = "Rock2",
height = 0.9,
material = Enum.Material.Rock,
color = Color3.new(0.278431, 0.223529, 0.215686)
},
{
name = "Snow",
material = Enum.Material.Snow,
height = 1,
color = Color3.new(1, 1, 1)
}
}
function getHeightMultiplier(heightMultiplier: NumberSequence, _time: number)
local keypoints = heightMultiplier.Keypoints;
if _time == 0 then
return keypoints[1].Value;
end
if _time == 1 then
return keypoints[#keypoints].Value;
end
for i, keypoint in pairs(keypoints) do
local _next = keypoints[i + 1];
if _time >= keypoint.Time and _time < _next.Time then
local alpha = (_time - keypoint.Time) / (_next.Time - keypoint.Time);
return (_next.Value - keypoint.Value) * alpha + keypoint.Value
end
end
end
function generateMaterialMap(mapHeight, mapWidth, noiseMap, regions)
local colorMap = {};
for x = 1, mapHeight do
colorMap[x] = {};
for y = 1, mapWidth do
local currentHeight = noiseMap[x][y];
for i = 1, #regions do
if currentHeight <= regions[i].height then
colorMap[x][y] = regions[i].material;
break;
end
end
end
end
return colorMap;
end
function Chunk.new(seed: number, location: Vector3, size: Vector3?, offset)
local self = {}
local size = size or Vector3.new(32,32,32);
setmetatable(self, Chunk)
self.seed = seed;
self.chunkLocation = location;
self.chunkSize = size;
self.offset = offset;
return self
end
function Chunk:render()
local heightMultiplier = script.Parent:GetAttribute("heightMultiplier");
local noiseMap = ChunkGenerator.GenerateNoiseMap(32, 32, self.seed, 27.6, 4, 0.5, 1.87, Vector2.new(0,0), Vector2.new(self.chunkLocation.X * 32, self.chunkLocation.Z * 32));
local materialMap = generateMaterialMap(32, 32, noiseMap, regions);
for x = 1, 32 do
for y = 1, 32 do
local height = noiseMap[x][y];
local size = Vector3.new(5,height * getHeightMultiplier(heightMultiplier, height) * 100 + 1,5);
local position = CFrame.new((x * 5) + self.chunkLocation.X * 160, 0, (y * 5) + self.chunkLocation.Z * 160);
workspace.Terrain:FillBlock(position, size, materialMap[x][y])
end
end
end
return Chunk
ChunkGenerator
local module = {}
function module.perlinNoise(x, y)
local clampedNoise = math.clamp(math.noise(x, y), -1, 1);
--return (clampedNoise + 1) / 2
return clampedNoise;
end
function InverseLerp(from, to, value)
if from < to then
if value < from then
return 0
end
if value > to then
return 1
end
value = value - from
value = value/(to - from)
return value
end
if from <= to then
return 0
end
if value < to then
return 1
end
if value > from then
return 0
end
return 1.0 - ((value - to) / (from - to))
end
function module.GenerateNoiseMap(mapWidth: number, mapHeight: number, seed: number, scale: number, octaves: number, persistance: number, lacunarity: number, offset: Vector2, realOffset: Vector2)
local prng = Random.new(seed);
local noiseMap = {};
local octaveOffsets = {};
for i = 1, octaves do
local offsetX = prng:NextInteger(-100000, 100000) + offset.X;
local offsetY = prng:NextInteger(-100000, 100000) + offset.Y;
octaveOffsets[i] = Vector2.new(offsetX, offsetY);
end
if scale <= 0 then
scale = 0.0001;
end
local maxNoiseHeight = -3.402823E+38;
local minNoiseHeight = 3.402823E+38;
local halfWidth = mapWidth / 2;
local halfHeight = mapHeight / 2;
for x = 1, mapHeight do
noiseMap[x] = {};
for y = 1, mapWidth do
local amplitude = 1;
local frequency = 1;
local noiseHeight = 0;
for i = 1, octaves do
local realX = x + realOffset.X;
local realY = y + realOffset.Y;
local sampleX = (realX - halfWidth) / scale * frequency + octaveOffsets[i].X;
local sampleY = (realY - halfHeight) / scale * frequency + octaveOffsets[i].Y;
local perlinValue = module.perlinNoise(sampleX, sampleY);
noiseHeight += perlinValue * amplitude;
amplitude *= persistance;
frequency *= lacunarity;
end
if noiseHeight > maxNoiseHeight then
maxNoiseHeight = noiseHeight;
else if noiseHeight < minNoiseHeight then
minNoiseHeight = noiseHeight;
end
end
noiseMap[x][y] = noiseHeight;
end
end
for x = 1, mapHeight do
for y = 1, mapWidth do
noiseMap[x][y] = InverseLerp(minNoiseHeight, maxNoiseHeight, noiseMap[x][y]);
end
end
return noiseMap;
end
return module