How can infinitely generate flat terrain/baseplate underneath the player wherever they walk?

The title explains it all. It would prefer be generating in a grid or based on how large the part will be, and be something similar to the video. Also preferrably client-sided and also removes parts that are out of range.


thanks in advance!

2 Likes

You can make a big part and place it in ReplicatedStorage. Then you can make a serverscript(or a localscript) so it will appear below you. Below script is just an example. You can customize if you’d like.

local instance = game.ReplicatedStorage.Instance

game.Players.PlayerAdded:Connect(function(plr)
local char = plr.CharacterAdded:Wait()
local root = char:WaitForChild("HumanoidRootPart")

-- Add code here that detects if the character is going towards the part's end


local clonedInstance  = instance:Clone()
clonedInstance.Parent = workspace
clonedInstance.Position = Vector3.new(root.Position.X, root.Position.Y - 4, root.Position.Z) -- Note that "- 4" is just an example. You can set it to what you want.end

3 Likes

the thing is i don’t know how to check if the player is near the end of a part. Although this might work, I want it to generate parts on a grid and remove them when the player walks away from them. Putting the part’s position on the player would not work for this.

2 Likes

Hello there!

If you want to create a grid you could do it chunk by chunk.

local GridSize = 4
local ChunkSize = 16

local function GenerateChunk(At)

local ChunkCordinates = At * ChunkSize

local StandingChunk

local OldChunk = Vector3.Zero

for i = 0, ChunkSize^2, 1 do

local Folder = instance.new("Folder")
Folder.Parent = Workspace --Create a Chunks Folder

local Floor = Instance.new("Part")
Floor.Size = Vector3.new(GridSize, GridSize, GridSize)
Floor.Parent = Workspace -- Crate the folder .Folder
Floor.Position = ChunkCordinates + Vector3.new((i % ChunkSize) * GridSize, 0, (math.floor(i / ChunkSize)) * Grid)
Floor.Name = toString(i) --Optional

end

Now to detect the player position to generate it near you could do

local Players = game:GetService("Players")

local player = Players.LocalPlayer

repeat task.Wait(1) until player.Character
local Character = player.Character
local Root = Character.HumanoidRootPart

local RenderDistance

Root:GetPropertyChangesSignal("Position"):Connect(function()

StandingChunk = math.floor(Root.Position / ChunkSize)

if StandingChunk ~= OldChunk then

for i = 0, RenderDistance^2, 1 do

GenerateChunk(StandingChunk + Vector3.new(i % RenderDistance, 0, math.floor(i / RenderDistance))

end

end

OldChunk = StandingChunk

end)

I think this Whould work. To delete the chunks is more complex and i’ll help you in a minute, i need to go. But basically you can add a table with the generated cords and check if those cords are further than the render distance, then delete the chunk

I hope it helps :wink:

4 Likes

is there a way i can generate multiple chunks at a time? if not then this is already pretty good!

2 Likes

Im back!

Just set the render distance to the size you want!

It’ll generate it around you

But there’s a tiny problem, the script right now generates even on top of existing chunks.

Do you want help solving that?

sorry, im not very experienced

Me neither! There are probably bether ways of doing it.
But just learn a bit every day and you’ll get far!

If you want me to explain the script feel free to ask

Let’s try to solve this
Firstly we need to save the loaded chunks informations in a table

local GridSize = 4
local ChunkSize = 16

local Loaded = {} --Table with the chunks

local function GenerateChunk(At)

local ChunkCordinates = At * ChunkSize

local StandingChunk

local OldChunk = Vector3.Zero

for i = 0, ChunkSize^2, 1 do

local Folder = instance.new("Folder")
Folder.Parent = Workspace --Create a Chunks Folder

table.add(Loaded, (toString(At.X) .. toString(At.Z)), Folder) 

-- Here I added on the table an item (The chunck) with the name being the coordinates of the chunck

local Floor = Instance.new("Part")
Floor.Size = Vector3.new(GridSize, GridSize, GridSize)
Floor.Parent = Workspace -- Crate the folder .Folder
Floor.Position = ChunkCordinates + Vector3.new((i % ChunkSize) * GridSize, 0, (math.floor(i / ChunkSize)) * Grid)
Floor.Name = toString(i) --Optional

end

Now we will check if the coordinates that we wamt the chunck to generate are already ocupied

`if table.find(Loaded, (toString(At.X … toString(At.Z)), 1) ~= nil then return end

–if it’s occupied then leave the function`

Im still trying to figure out how to delete the chunks, once i discover it i’ll tell you :wink:

Here’s the entire code, hope it works

local Players = game:GetService("Players")

local player = Players.LocalPlayer

repeat task.Wait(1) until player.Character
local Character = player.Character
local Root = Character.HumanoidRootPart

--You can play with these values as you want!
local RenderDistance = 3
local GridSize = 4
local ChunkSize = 16
-- ;)

local Loaded = {} --Table with the chunks

local function GenerateChunk(At)

local ChunkCordinates = At * ChunkSize

local StandingChunk

local OldChunk = Vector3.Zero

for i = 0, ChunkSize^2, 1 do

if table.find(Loaded, (toString(At.X .. toString(At.Z)), 1) ~= nil then return end

local Folder = instance.new("Folder")
Folder.Parent = Workspace --Create a Chunks Folder

table.add(Loaded, (toString(At.X) .. toString(At.Z)), Folder) 

local Floor = Instance.new("Part")
Floor.Size = Vector3.new(GridSize, GridSize, GridSize)
Floor.Parent = Workspace -- Crate the folder .Folder
Floor.Position = ChunkCordinates + Vector3.new((i % ChunkSize) * GridSize, 0, (math.floor(i / ChunkSize)) * Grid)
Floor.Name = toString(i) --Optional

end


Root:GetPropertyChangesSignal("Position"):Connect(function()

StandingChunk = math.floor(Root.Position / ChunkSize)

if StandingChunk ~= OldChunk then

for i = 0, RenderDistance^2, 1 do

GenerateChunk(StandingChunk + Vector3.new(i % RenderDistance, 0, math.floor(i / RenderDistance))

end

end

OldChunk = StandingChunk

end)

I think I found a way to delete the chunks! (Haven’t tested it yet)

local Distance = math.ceil(RenderDistance / 2)

for i = 0, Distance^2, 1 do

local Target = StandingChunk + Vector3.new(-Distance + i % Distance, 0, -Distance + math.floor(i / Distance))

local X = Target.X - StandingChunk.X
local Z = Target.Z - StandingChunk.Z

if X > -Distance and X < Distance then

if Z > -Distance and Z < Distance then

Table[toString(Target.X) .. toString(Target.Z)]:Destroy

end

end

end

Here is the updated code, tell me if it works

local Players = game:GetService("Players")

local player = Players.LocalPlayer

repeat task.Wait(1) until player.Character
local Character = player.Character
local Root = Character.HumanoidRootPart

--You can play with these values as you want!
local RenderDistance = 3
local GridSize = 4
local ChunkSize = 16
-- ;)

local Loaded = {} --Table with the chunks

local function GenerateChunk(At)

local ChunkCordinates = At * ChunkSize

local StandingChunk

local OldChunk = Vector3.Zero

for i = 0, ChunkSize^2, 1 do

if table.find(Loaded, (toString(At.X .. toString(At.Z)), 1) ~= nil then return end

local Folder = instance.new("Folder")
Folder.Parent = Workspace --Create a Chunks Folder

table.add(Loaded, (toString(At.X) .. toString(At.Z)), Folder) 

local Floor = Instance.new("Part")
Floor.Size = Vector3.new(GridSize, GridSize, GridSize)
Floor.Parent = Workspace -- Crate the folder .Folder
Floor.Position = ChunkCordinates + Vector3.new((i % ChunkSize) * GridSize, 0, (math.floor(i / ChunkSize)) * Grid)
Floor.Name = toString(i) --Optional

end


Root:GetPropertyChangesSignal("Position"):Connect(function()

StandingChunk = math.floor(Root.Position / ChunkSize)

if StandingChunk ~= OldChunk then

for i = 0, RenderDistance^2, 1 do

GenerateChunk(StandingChunk + Vector3.new(i % RenderDistance, 0, math.floor(i / RenderDistance))

local Distance = math.ceil(RenderDistance / 2)

for i = 0, Distance^2, 1 do

local Target = StandingChunk + Vector3.new(-Distance + i % Distance, 0, -Distance + math.floor(i / Distance))

local X = Target.X - StandingChunk.X
local Z = Target.Z - StandingChunk.Z

if X > -Distance and X < Distance then

if Z > -Distance and Z < Distance then

Table[toString(Target.X) .. toString(Target.Z)]:Destroy

end

end

OldChunk = StandingChunk

end)

Hey again!

About the previous script. I was testing it and it didn’t work well, lol. Sry about that

i’m tring to fix it, but I haven’t been able to delete the chunks yet, but here is the updated code.

local Players = game:GetService("Players")

local player = Players.LocalPlayer

repeat task.wait(1) until player.Character
local Character = player.Character
local Humanoid = Character.Humanoid
local Root = Character.HumanoidRootPart

--You can play with these values as you want!
local RenderDistance = 3
local GridSize = 4
local ChunkSize = 16
-- ;)

local Loaded = {} --Table with the chunks

local Moving

local Checking = false

local StandingChunk

local OldChunk = Vector3.Zero

local ChunkSpace = ChunkSize * GridSize

local function GenerateChunk(At)
	
	if Loaded[(tostring(At.X) .. tostring(At.Z))] ~= nil then return end

	local ChunkCordinates = At * ChunkSpace
	
	print(At, Loaded, ChunkCordinates)
	
	local Folder = Instance.new("Folder")
	Folder.Parent = workspace.Chunks

	for i = 0, ChunkSize^2 -1, 1 do 

		Loaded[(tostring(At.X) .. tostring(At.Z))] = Folder

		local Floor = Instance.new("Part")
		Floor.Anchored = true
		Floor.Color = Color3.fromRGB(50,200,50)
		Floor.Size = Vector3.new(GridSize, GridSize, GridSize)
		Floor.Parent = Folder
		Floor.Position = ChunkCordinates + Vector3.new((i % ChunkSize) * GridSize, 0, (math.floor(i / ChunkSize)) * GridSize)
		Floor.Name = tostring(i) --Optional

	end

end

local function CheckCords()

	while Moving do

		Checking = true

		StandingChunk = Vector3.new(math.floor(Root.Position.X / ChunkSpace), 0, math.floor(Root.Position.Z / ChunkSpace))

		print('Check', StandingChunk)

		local Distance = math.floor(RenderDistance/2)

		if StandingChunk ~= OldChunk then

			for i = 0, RenderDistance^2 - 1, 1 do

				GenerateChunk(StandingChunk + Vector3.new(-Distance + (i % RenderDistance), 0, -Distance + math.floor(i / RenderDistance)))

			end

		end

		OldChunk = StandingChunk

		task.wait(0.1)

	end

	Checking = false

end

Humanoid:GetPropertyChangedSignal('MoveDirection'):Connect(function()

	if Humanoid.MoveDirection ~= Vector3.zero then

		Moving = true
		
		if not Checking then CheckCords() end
	
	else 
		
		Moving = false
		
	end
	
end)

can you explain what the “At” part of the function does?

This code actually works really well though! But for now, I’ll mark it as solved when there’s a way toremove blocks.

Thats ok :wink:

The “At” Variable is the coordinates in which the chunk will be generated. It is used to get the world coordinates (marked as ChunkCoordinates) and position the chunk.

In exemple, the chunk with the coordinates 0,0 will generate at the world coordinates of 0,0
The chunk 1,0 will generate on the side of the chunk 0,0 at the world coordinates 64, 0. (64 is the size that the chunk ocupies in the world)

The at is also used to name the chunks in the Loaded table

In exemple, the chunk 0,0 will have the name “00”
The chunk 1,0 will have the name “10”
The chunk -1,-1 will have the name “-1-1”
It is used to check if the chunk is loaded

I’ll try to finish the code as soon as possible :wink:

It’s done! Sorry it took so long

local Players = game:GetService("Players")

local player = Players.LocalPlayer

repeat task.wait(1) until player.Character
local Character = player.Character
local Humanoid = Character.Humanoid
local Root = Character.HumanoidRootPart

--You can play with these values as you want!

local RenderDistance = 5 -- The Area around you that'll be loaded
local GridSize = 4 -- Size of each block that makes the Chunk
local ChunkSize = 16 -- Amount of Blocks that make the Chunk

local UseGrid = false --true = Each chunk is made of blocks, laggier. false = Each chunk is one block, Faster

-- Keep it false for better performance, or lower the chunksize for less blocks

-- ;)

local Loaded = {} --Table with the chunks

local Moving

local Checking = false

local StandingChunk

local OldChunk = Vector3.Zero

local ChunkSpace = ChunkSize * GridSize

local function GenerateChunk(At)

	if Loaded[(tostring(At))] ~= nil then return end

	local ChunkCordinates = At * ChunkSpace

	local Folder = Instance.new("Folder")
	Folder.Parent = workspace.Chunks
	
	if UseGrid then	

	for i = 0, ChunkSize^2 -1, 1 do 

		Loaded[(tostring(At))] = Folder

		local Floor = Instance.new("Part")
		Floor.Anchored = true
		Floor.Color = Color3.fromRGB(50,200,50)
		Floor.Size = Vector3.new(GridSize, GridSize, GridSize)
		Floor.Parent = Folder
		Floor.Position = ChunkCordinates + Vector3.new((i % ChunkSize) * GridSize, 0, (math.floor(i / ChunkSize)) * GridSize)
		Floor.Name = tostring(i) --Optional

	end
	
	else
		
		Loaded[(tostring(At))] = Folder
		
		local Floor = Instance.new("Part")
		Floor.Anchored = true
		Floor.Color = Color3.fromRGB(50,200,50)
		Floor.Size = Vector3.new(ChunkSpace, GridSize, ChunkSpace)
		Floor.Parent = Folder
		Floor.Position = ChunkCordinates + Vector3.new(ChunkSpace / 2, 0, ChunkSpace / 2)
		
	end

end

local function CheckCords()

	while Moving do

		Checking = true

		StandingChunk = Vector3.new(math.floor(Root.Position.X / ChunkSpace), 0, math.floor(Root.Position.Z / ChunkSpace))

		local Distance = math.floor(RenderDistance / 2)

		if StandingChunk ~= OldChunk then

			for i = 0, RenderDistance^2 - 1, 1 do

				GenerateChunk(StandingChunk + Vector3.new(-Distance + (i % RenderDistance), 0, -Distance + math.floor(i / RenderDistance)))

			end

			for i = 0, RenderDistance * 2 + 3, 1 do

				local Target = StandingChunk + Vector3.new(-Distance - 1 + (i % (RenderDistance + 2)), 0, -Distance - 1 + (math.floor(i / (RenderDistance + 2)) * (RenderDistance + 1)))

				local Chunk = Loaded[(tostring(Target))]

				if Chunk ~= nil then 

					Chunk:Destroy() 

					Loaded[(tostring(Target))] = nil

				end

			end

			for i = 0, RenderDistance * 2 - 1 , 1 do

				local Target = StandingChunk + Vector3.new(-Distance - 1 + (math.floor(i / (RenderDistance)) * (RenderDistance + 1)), 0, -Distance + (i % RenderDistance))

				local Chunk = Loaded[(tostring(Target))]

				if Chunk ~= nil then 
					
					Chunk:Destroy() 
					
					Loaded[(tostring(Target))] = nil
					
				end

			end

		end

		OldChunk = StandingChunk

		task.wait(0.1)

	end

	Checking = false

end

Humanoid:GetPropertyChangedSignal('MoveDirection'):Connect(function()

	if Humanoid.MoveDirection ~= Vector3.zero then

		Moving = true

		if not Checking then CheckCords() end

	else 

		Moving = false

	end

end)

Humanoid.WalkSpeed = 100 -- This is just for testing, feel free to delete this

Feel free to ask me any questions and to add things to it :wink:

you should also reuse parts because deleting parts gets laggy after a while

partcache/objectcache already implements this

1 Like