Already, this terrain system is very optimized and fast. But when it comes to generating trees and structures, thats when the slowness and lag starts to come.
I am not sure why, but at the start of loops for placing plants and structures, you get a lag spike. And also I want a way to speed up going through all the available structures without waiting like 10 seconds for structures that aren’t even going to be in the map most of the time.
I’ve attempted to do this my self by adding waits or even my “Advanced wait” functions to it. But its still slow and laggy when getting to the models.
I’d also want to overall improve this system as well.
Some help would be greatly appreciated
local TerrainTemplatesModule = require(script:WaitForChild("TerrainTemplates"))
local Terrain = workspace:WaitForChild("Terrain")
local ServerStorage = game:GetService("ServerStorage")
local Structures = ServerStorage:WaitForChild("Structures")
local PlantsWorkspaceFolder = workspace:WaitForChild("Plants")
local StructuresWorkspaceFolder = workspace:WaitForChild("Structures")
local SpawnPointsWorkspaceFolder = workspace:WaitForChild("SpawnPoints")
local ChosenTerrain = TerrainTemplatesModule.Snowy
local NoiseScale = ChosenTerrain.NoiseScale
local NoiseHeightLevel = ChosenTerrain.NoiseHeightLevel
local ExtraMapHeight = ChosenTerrain.ExtraMapHeight
local WaterEnabled = ChosenTerrain.WaterEnabled
local CavesEnabled = ChosenTerrain.CavesEnabled
local PlantsChance = ChosenTerrain.PlantsChance
local Plants = ChosenTerrain.Plants
local PartSize = 10
local MapSize = 100
local MapLengthY = 200
local WaterThickness = 20
local CaveInterval = 4
local CaveSize = 15
local CaveLength = 100
local Caves = 50
local Seed = math.random(-50000, 50000)
--local Seed = 8000
local PerlinNoiseAPI = require(script:WaitForChild("PerlinNoise"))
-- PerlinNoiseAPI.new(coords,amplitude,octaves,persistence)
print("Seed: " .. Seed)
local GenerationTimer = os.time()
local function CreateWaterPart(PosY)
local WaterPart = Instance.new("Part")
WaterPart.Color = Color3.fromRGB(0, 179, 255)
WaterPart.Material = Enum.Material.Granite
WaterPart.Transparency = 0.8
WaterPart.Size = Vector3.new(MapSize * PartSize - 10, MapLengthY/2 + PartSize + WaterThickness, MapSize * PartSize - 10)
WaterPart.Position = Vector3.new((MapSize * PartSize) / 2, (PosY + (MapSize / 2) - (WaterThickness / 2)) / PartSize, (MapSize * PartSize) / 2)
return WaterPart
end
local WaitCounter = 0
local function AdvancedWait(WaitTime)
if WaitCounter >= WaitTime then
WaitCounter = 0
wait()
else
WaitCounter = WaitCounter + 1
end
end
if WaterEnabled then -- Generate water
local WaterPart = CreateWaterPart(-30)
Terrain:FillBlock(WaterPart.CFrame, WaterPart.Size, Enum.Material.Water)
end
for x = 1, MapSize do -- Generate perlin noise terrain
wait()
for z = 1, MapSize do
local Height = PerlinNoiseAPI.new({x / NoiseScale, z / NoiseScale, Seed}, 10, 50, 0.5) * NoiseHeightLevel
local Part = Instance.new("Part")
Part.Size = Vector3.new(PartSize + 3, MapLengthY + PartSize, PartSize + 3)
Part.TopSurface = Enum.SurfaceType.Smooth
Part.BottomSurface = Enum.SurfaceType.Smooth
--Part.Color = Color3.new(Height, Height, Height)
Part.Position = Vector3.new(x, Height + ExtraMapHeight, z) * Vector3.new(PartSize, PartSize, PartSize)
Part.Anchored = true
Part.Parent = workspace
if Part.Position.Y > 150 then -- snow
Terrain:FillBlock(Part.CFrame, Part.Size, Enum.Material.Snow)
elseif Part.Position.Y > 50 then -- stone
Terrain:FillBlock(Part.CFrame, Part.Size, Enum.Material.Rock)
elseif Part.Position.Y > -25 then -- grass
Terrain:FillBlock(Part.CFrame, Part.Size, Enum.Material.Ground)
elseif Part.Position.Y < -25 then -- sand
Terrain:FillBlock(Part.CFrame, Part.Size, Enum.Material.Sand)
end
Part:Destroy()
end
end
local NumWorm = 0
if CavesEnabled then -- Create perlin worm caves
for i = 1, Caves do
wait()
NumWorm = NumWorm + 1
local sX = math.noise(NumWorm / CaveInterval + 0.1, Seed)
local sY = math.noise(NumWorm / CaveInterval + sX + 0.1, Seed)
local sZ = math.noise(NumWorm / CaveInterval + sY + 0.1, Seed)
local WormCF = CFrame.new(math.random(0, MapSize * PartSize), math.random(0, MapSize * PartSize), math.random(0, MapSize * PartSize)) * CFrame.new(sX * CaveLength, (sY * CaveLength), sZ * CaveLength)
print("Worm "..NumWorm.." spawning at "..WormCF.X..", "..WormCF.Y..", "..WormCF.Z)
local Dist = (math.noise(NumWorm / CaveInterval + WormCF.p.magnitude,Seed) + 0.5) * CaveLength
for i = 1, CaveLength do
local X = math.noise(WormCF.X / CaveInterval + 0.1, Seed)
local Y = math.noise(WormCF.Y / CaveInterval + 0.1, Seed)
local Z = math.noise(WormCF.Z / CaveInterval + 0.1, Seed)
WormCF = WormCF * CFrame.Angles(X * 2,Y * 2,Z * 2) * CFrame.new(0, 0, -CaveInterval)
local Part = Instance.new("Part")
Part.Anchored = true
Part.CFrame = CFrame.new(WormCF.p)
Part.Parent = workspace
Terrain:FillBall(Part.CFrame.p, CaveSize, Enum.Material.Air)
Part:Destroy()
end
end
end
local GrassParams = RaycastParams.new()
local StructureParams = RaycastParams.new()
function PaintMaterial(MinPos, MaxPos, MaterialToReplace, ReplacementMaterial)
local Reg = Region3.new(MinPos, MaxPos)
local materialToReplace = Enum.Material.Ground
local replacementMaterial = Enum.Material.Grass
local NewReg = Reg:ExpandToGrid(4)
workspace.Terrain:ReplaceMaterial(NewReg, 4, materialToReplace, replacementMaterial)
end
for x = 1, MapSize do -- Add grass
x = x * PartSize
AdvancedWait(10)
for z = 1, MapSize do
z = z * PartSize
local GrassRay = workspace:Raycast(Vector3.new(x, 1000, z), Vector3.new(x, -1000, z), GrassParams)
if GrassRay then
if GrassRay.Instance == Terrain then
PaintMaterial(GrassRay.Position - Vector3.new(15, 15, 15), GrassRay.Position + Vector3.new(15, 15, 15), Enum.Material.Ground, Enum.Material.Grass)
end
end
end
end
-- V V V Starts to lag and slow down here V V V--
local function PlaceStructure(ModelToClone) -- Structures
print("Adding structures...")
for x = 1, MapSize do
x = x * PartSize
for z = 1, MapSize do
z = z * PartSize
local StructureRay = workspace:Raycast(Vector3.new(x, 1000, z), Vector3.new(x, -2000, z), StructureParams)
if StructureRay then
if StructureRay.Instance == Terrain then
if ModelToClone.Configuration.MaterialsToBePlacedOn:FindFirstChild(StructureRay.Material) then
local AngleOffset = ModelToClone.Configuration.SpawnAngle.Value
local Chance = math.random(0, 1000000)
if Chance <= ModelToClone.Configuration.SpawnChance.Value then
local NewModel = ModelToClone:Clone()
local NewModelCFrame = CFrame.new(StructureRay.Position) * CFrame.Angles(math.rad(math.random(-AngleOffset.X, AngleOffset.X)), math.rad(math.random(-AngleOffset.Y, AngleOffset.Y)), math.rad(math.random(-AngleOffset.Z, AngleOffset.Z)))
NewModel:SetPrimaryPartCFrame(NewModelCFrame)
NewModel.Parent = StructuresWorkspaceFolder
local Parts = NewModel:GetChildren()
for i, PartToConvert in ipairs(Parts) do
if PartToConvert:FindFirstChild("ConvertToMaterial") and PartToConvert:IsA("BasePart") then
if PartToConvert.Shape == Enum.PartType.Ball then
Terrain:FillBall(PartToConvert.Position, PartToConvert.Size.Y / 2, PartToConvert.ConvertToMaterial.Value)
elseif PartToConvert.Shape == Enum.PartType.Block then
Terrain:FillBlock(PartToConvert.CFrame, PartToConvert.Size, PartToConvert.ConvertToMaterial.Value)
end
PartToConvert:Destroy()
end
end
end
AdvancedWait(150)
end
end
end
end
end
end
local function PlacePlant(PlantToClone) -- Plants and trees
print("Adding plants and trees...")
for x = 1, MapSize do
x = x * PartSize
for z = 1, MapSize do
z = z * PartSize
local PlantRay = workspace:Raycast(Vector3.new(x, 500, z), Vector3.new(x, -500, z), GrassParams)
if PlantRay then
if PlantRay.Instance == Terrain then
if PlantToClone.Configuration.MaterialsToBePlacedOn:FindFirstChild(PlantRay.Material) then
local AngleOffset = PlantToClone.Configuration.SpawnAngle.Value
local Chance = math.random(0, 1000000)
if Chance <= ChosenTerrain.PlantsChance then
local NewModel = PlantToClone:Clone()
local NewModelCFrame = CFrame.new(PlantRay.Position) * CFrame.Angles(math.rad(math.random(-AngleOffset.X, AngleOffset.X)), math.rad(math.random(-AngleOffset.Y, AngleOffset.Y)), math.rad(math.random(-AngleOffset.Z, AngleOffset.Z)))
NewModel:SetPrimaryPartCFrame(NewModelCFrame)
NewModel.Parent = PlantsWorkspaceFolder
end
AdvancedWait(150)
end
end
end
end
end
end
for i = 1, 25 do -- Add spawn points
wait()
local RandomX = math.random(0, MapSize) * PartSize
local RandomZ = math.random(0, MapSize) * PartSize
local SpawnRay = workspace:Raycast(Vector3.new(RandomX, 1500, MapSize / 2), Vector3.new(RandomX, -2000, RandomZ), GrassParams)
if SpawnRay then
if SpawnRay.Instance == Terrain then
local SpawnPart = Instance.new("SpawnLocation")
SpawnPart.Position = SpawnRay.Position
SpawnPart.CanCollide = false
SpawnPart.Transparency = 1
SpawnPart.Anchored = true
SpawnPart.Parent = SpawnPointsWorkspaceFolder
end
end
end
for i, Structure in ipairs(Structures:GetChildren()) do -- Get structures to add to the world
local ConfigurationFolder = Structure:FindFirstChild("Configuration")
if ConfigurationFolder then
if ConfigurationFolder.CanBeUsed.Value == true then
PlaceStructure(Structure)
end
end
wait()
end
for i, Object in ipairs(Plants) do -- Get plants to add to the world
local ConfigurationFolder = Object:FindFirstChild("Configuration")
if ConfigurationFolder then
if ConfigurationFolder.CanBeUsed.Value == true then
PlacePlant(Object)
end
end
wait()
end
print("Finished generating! | Time: " .. os.time() - GenerationTimer .. "s")