Terrain is not generated where the player is standing

Hi all! Sorry for the fact that this is the second post in the last two days.


The problem is that if I start walking along one axis, then the generation goes well, but if I decide to go in the opposite direction or change the direction axis, then the generation will have to wait, and provided that generation occurs only when the player moves , then just standing around and waiting won’t work.
Please help me find the problem in my script that causes this error


Code:

local Players = game:GetService("Players")
local RS = game:GetService("ReplicatedStorage")
local assetLibrary = RS:WaitForChild("Assets")
local trees = assetLibrary:WaitForChild("Trees")
local rocks = assetLibrary:WaitForChild("Rocks")
local perlinFolder = workspace:WaitForChild("PerlinTest")
local terrain = workspace:WaitForChild("Terrain")
local periodicity = 60
local size = 100
local chunck = {}
local seed = math.random()*30
local rnd = Random.new(os.clock())
print(seed)
local amp = 80
local materials = {
	[1] = Enum.Material.Ground,
	[2] = Enum.Material.Grass,
	[3] = Enum.Material.Granite,
	[4] = Enum.Material.Sand,
	[5] = Enum.Material.Snow,
	[6] = Enum.Material.Mud,
}
local lastWait = tick()
local EXEC_TIME_LIMIT = 1/90
local smartWait = function()
	if tick() - lastWait > EXEC_TIME_LIMIT then
		task.wait()
		lastWait = tick()
	end
end

local Tree_Scale = 0.6


function SelectMaterial(x,z)
	local xNoise = x/50
	local zNoise = z/50
	local noise = math.noise(xNoise,zNoise,seed)+1
	local delta = #materials/2
	local num = math.floor(noise*delta)
	if num < 1 then
		num = 1
	elseif num > #materials then
		num = #materials
	end


	return materials[num]
end



function NoizeCount(x,z)
	local xNoise = x/50
	local zNoise = z/50
	local noise = math.noise(xNoise,zNoise,seed)*periodicity+amp 
	return noise
end

function CheckChunk(x,z)
	local ret = nil
	if chunck[x] == nil then
		chunck[x] = {}
	end
	ret = chunck[x][z]
	smartWait()
	return ret
end

function EntitySpawn(CFramePos:CFrame)
	if rnd:NextInteger(1,10000) <= Tree_Scale*100 then
		local pos = CFramePos.Position
		local params = RaycastParams.new()
		local dir =-Vector3.yAxis*50
		params.IgnoreWater = true
		local ray = workspace:Raycast(pos+Vector3.new(0,10,0),dir,params)
		warn(ray.Material)
		if ray.Material == Enum.Material.Grass then
			local treeArray = trees:GetChildren()
			local random = math.random(1,#treeArray)
			local newTree:Model = treeArray[random]:Clone()
			local cx=pos.X
			local cz=pos.Z
			local cy=pos.y

			newTree:PivotTo(CFrame.new(cx, cy + newTree.PrimaryPart.Size.Y*0.5, cz)) -- переместить
			newTree.Parent = workspace
		end
		
		if ray.Material == Enum.Material.Ground then
			local rocksArray = rocks:GetChildren()
			local random = math.random(1,#rocksArray)
			local newRock:Model = rocksArray[random]:Clone()
			local cx=pos.X
			local cz=pos.Z
			local cy=pos.y

			newRock:PivotTo(CFrame.new(cx, cy + newRock.PrimaryPart.Size.Y*3.2, cz)) -- переместить
			newRock.Parent = workspace
		end
	end
end

function WorldGenerate(posX,posZ)

	warn(posX,posZ)
	for x=posX-size,posX+size do

		for z=posZ-size, posZ+size do
			if CheckChunk(x,z) == nil then
				--print("Start generation")
				chunck[x][z] = true
				local part = Instance.new("Part",perlinFolder)
				part.Shape = Enum.PartType.Block
				part.Anchored = true
				local y = NoizeCount(x,z)
				local y1 = y - 4^2
				part.Size = Vector3.new(4,(y-y1)/2,4)
				part.Position = Vector3.new(x*4,(y+y1)/2,z*4)

				terrain:FillBlock(part.CFrame,part.Size,SelectMaterial(x,z))
				EntitySpawn(part.CFrame)
				part:Destroy()
			end
		end
		smartWait()
	end
	print("End generation")
	task.wait()
end



function CharacterMoved(character)
	local oldX = nil
	local oldZ = nil
	repeat
		if character and character.Parent then
			local coordinates = character:WaitForChild("HumanoidRootPart"):GetPivot()
			local posX = math.floor(coordinates.Position.X)
			local posZ = math.floor(coordinates.Position.Z)
			if posX ~= oldX or posZ ~= oldZ then
				warn("Генерация началась в ", posX,posZ)
				WorldGenerate(posX,posZ)
				oldX = posX
				oldZ = posZ
				smartWait()
				end
		else
			smartWait()
			return
		end
		smartWait()
	until not (character and character.Parent)
	smartWait()
end

function CharacterAdded(character)
	task.spawn(CharacterMoved,character)
end

function PlayerAdded(player:Player)
	player.CharacterAdded:Connect(CharacterAdded)
end

Players.PlayerAdded:Connect(PlayerAdded)

Video


Thanks for your help! :hearts:

The delay in your world generation system when changing direction or standing idle likely comes from the following issues:

1. Redundant Generation Triggers

The CharacterMoved function checks for changes in player position (posX and posZ) and starts generating terrain. However, without adequate spacing between these checks, you may repeatedly trigger generation for small positional changes, causing inefficiency.

2. Unoptimized Chunk Checking

The CheckChunk function creates new entries in the chunck table during every check, even if the player hasn’t moved far enough to enter a new chunk. This can lead to unnecessary writes and excessive computations.

3. Large Generation Scope

The WorldGenerate function generates a 2D grid of size (2 * size + 1)². This large scope means that changing directions or standing idle results in recalculations over a wide area, even for chunks that are already generated.

Fixes and Optimizations

A. Optimize Chunk Checking

Only generate terrain for entirely new chunks. Modify CheckChunk to avoid writing data unnecessarily:

function CheckChunk(x, z)
    if not chunck[x] then
        chunck[x] = {}
    end
    return chunck[x][z] or false
end

B. Limit Generation Scope

Reduce the area generated per movement to minimize delays. Instead of generating the full area around the player every time, generate only the surrounding chunks when they enter a new chunk.

Modify WorldGenerate to take smaller steps:

function WorldGenerate(centerX, centerZ)
    local chunkSize = size / 10 -- Smaller generation scope
    for x = centerX - chunkSize, centerX + chunkSize do
        for z = centerZ - chunkSize, centerZ + chunkSize do
            if not CheckChunk(x, z) then
                chunck[x][z] = true
                local part = Instance.new("Part", perlinFolder)
                part.Anchored = true
                part.Size = Vector3.new(4, 4, 4)
                local y = NoizeCount(x, z)
                part.Position = Vector3.new(x * 4, y, z * 4)
                terrain:FillBlock(part.CFrame, part.Size, SelectMaterial(x, z))
                EntitySpawn(part.CFrame)
                part:Destroy()
            end
        end
        smartWait()
    end
end

C. Track Current Chunk

Keep track of the player’s current chunk and generate only when they enter a new one:

function CharacterMoved(character)
    local oldChunkX, oldChunkZ = nil, nil
    repeat
        if character and character.Parent then
            local position = character:WaitForChild("HumanoidRootPart").Position
            local currentChunkX = math.floor(position.X / size)
            local currentChunkZ = math.floor(position.Z / size)
            
            if currentChunkX ~= oldChunkX or currentChunkZ ~= oldChunkZ then
                WorldGenerate(currentChunkX, currentChunkZ)
                oldChunkX, oldChunkZ = currentChunkX, currentChunkZ
            end
        end
        task.wait(0.1) -- Adjust to balance generation speed
    until not (character and character. Parent)
end

D. Add Pre-Generated Buffer

Pre-generate chunks near the player’s starting position to avoid noticeable delays when they start moving:

function PreGenerate(startX, startZ, range)
    for x = startX - range, startX + range do
        for z = startZ - range, startZ + range do
            if not CheckChunk(x, z) then
                chunck[x][z] = true
                local part = Instance.new("Part", perlinFolder)
                part.Anchored = true
                part.Size = Vector3.new(4, 4, 4)
                local y = NoizeCount(x, z)
                part.Position = Vector3.new(x * 4, y, z * 4)
                terrain:FillBlock(part.CFrame, part.Size, SelectMaterial(x, z))
                EntitySpawn(part.CFrame)
                part:Destroy()
            end
        end
    end
end

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        local startPosition = character:WaitForChild("HumanoidRootPart").Position
        PreGenerate(math.floor(startPosition.X / size), math.floor(startPosition.Z / size), 2) -- Pre-generate 2 chunks around start
    end)
end)

Summary of Improvements

  • Smaller generation steps reduce lag and improve responsiveness when switching directions.
  • Chunk-based tracking ensures generation happens only when entering new areas.
  • Pre-generation creates a buffer around the starting position to avoid noticeable delays.

Let me know how these changes work or if you’d like to change smth!

  • mostly by Ai, changes by me.

This error is not caused by generation speed.
It is caused by incorrect calculation of player coordinates and/or incorrect calculation of generation

But i can’t understand where calculating is wrong

if it won’t work the way you want, just find a solution with ai - it’s the problems in the script, you’ll do the rest