I have this voxel based game with tile like generation sorta like the game 3008. As of right now I make 900 cells per generation. (one whole map). each cell is like 16 by 1 by 16 blocks. Here is the code for the generation:
--============================================================
-- MAP GENERATION SCRIPT
-- Procedurally builds a grid of "cells" (tiles) surrounded by
-- walls and a roof, using parallel Actors to speed up generation.
-- COMMENTS DONE BY CLAUDE SORRY
--============================================================
-- ==== GRID CONFIGURATION ====
local Width = 30 -- number of cells along the X axis
local Length = 30 -- number of cells along the Z axis -- must be equal to width and being greater than 20 causes problemo
local YStart = 1.5 -- base Y position (height) for the floor grid
local CellSize = 16 * 3 -- size of a single cell (48 studs)
local SpacingSize = 3 * 3 -- gap/spacing between cells (9 studs)
local Step = CellSize + SpacingSize -- distance between the centers of two adjacent cells
-- Half of the total grid width/length, used to center the grid on the origin
local HalfW = Width * Step / 2
local HalfL = Length * Step / 2
-- Bounding box (min/max X and Z) of the entire grid, including a little padding
-- for the outer edge cells
local MinX = Step - HalfW - CellSize / 2
local MaxX = Width * Step - HalfW + CellSize / 2
local MinZ = Step - HalfL - CellSize / 2
local MaxZ = Length * Step - HalfL + CellSize / 2
-- Overall grid dimensions and center point (used for walls/roof/border placement)
local GridW = MaxX - MinX
local GridL = MaxZ - MinZ
local GridCX = (MinX + MaxX) / 2
local GridCZ = (MinZ + MaxZ) / 2
-- ==== WALL / ROOF CONFIGURATION ====
local WallHeight = 600
local WallThickness = 6
-- Walls are extended slightly beyond the grid bounds so they meet
-- at the corners and include the outer spacing gap
local ExtendedW = GridW + WallThickness * 2 + SpacingSize * 2
local ExtendedL = GridL + WallThickness * 2 + SpacingSize * 2
-- ==== SERVICES & FOLDER REFERENCES ====
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local MapFolder = workspace.Map -- where floor/wall/roof parts get parented
local BlocksFolder = workspace.Blocks -- where placeable "block" parts end up
local CellPresetsFolder = ServerStorage.CellPresets -- folder containing all possible cell templates
local CellPresets = CellPresetsFolder:GetChildren() -- list of cell preset templates
local Preset1 = CellPresetsFolder.Preset1 -- (unused reference to a specific preset)
local Texture0Template = ServerStorage.Texture0 -- texture template applied to ground/space parts
local TextureTemplate = ServerStorage.Texture -- texture template applied to walls
-- Names of the 6 faces of a part, used when applying textures to all sides
local FACE_NAMES = { "Front", "Back", "Left", "Right", "Top", "Bottom" }
-- ==== HELPER SIZES FOR SPACER/GAP PARTS ====
local CellHalf = CellSize / 2
local StepHalf = CellHalf + SpacingSize / 2
local RowSize = Vector3.new(SpacingSize, 3, CellSize) -- thin gap strip running along a row
local ColSize = Vector3.new(CellSize, 3, SpacingSize) -- thin gap strip running along a column
local CornerSize = Vector3.new(SpacingSize, 3, SpacingSize) -- small square filling a corner gap
-- Rounds each component of a Vector3 up to the nearest multiple of 3
-- (keeps part sizes aligned to a 3-stud grid)
local function RoundTo3(v)
return Vector3.new(
math.ceil(v.X / 3) * 3,
math.ceil(v.Y / 3) * 3,
math.ceil(v.Z / 3) * 3
)
end
-- ==== WEIGHTED RANDOM CELL PRESET SELECTION ====
-- Sum up the "Weight" attribute of every cell preset (defaults to 1 if missing)
-- so we can pick presets with weighted probability
local TotalWeight = 0
for _, preset in pairs(CellPresets) do
TotalWeight += preset:GetAttribute("Weight") or 1
end
-- Picks a random cell preset, favoring presets with higher Weight values
local function getRandomTemplate()
local roll = math.random(1, TotalWeight)
-- Step 3: Walk through the table until we find the winner
local cumulative = 0
for _, item in ipairs(CellPresets) do
cumulative += item:GetAttribute("Weight") or 1
if roll <= cumulative then
return item
end
end
end
-- Folder of all possible "block" types (materials/appearances) that can be placed
local Blocks = game.ReplicatedStorage.Blocks
-- Copies the visual properties (material, color, transparency, textures, name)
-- from a "Type" block template onto an existing "Block" part.
-- Used to reskin a placed block to look like a different block type.
local function LarpBlock(Block: BasePart, Type: BasePart)
if Block == nil or Type == nil then warn("check 4 FAILLLLLLLLLLL") return end
Block.Material = Type.Material
Block.Color = Type.Color
Block.Transparency = Type.Transparency
Block.Name = Type.Name
Blocks:SetAttribute("Strength", Type:GetAttribute("Strength"))
-- Build a lookup of the target type's textures by face
local typeTextures = {}
for _, texture in pairs(Type:GetChildren()) do
if texture:IsA("Texture") then
typeTextures[texture.Face] = texture
end
end
-- Apply the matching texture (same face) from the type onto the block
for _, texture in pairs(Block:GetChildren()) do
if not texture:IsA("Texture") then
continue
end
local matchingTexture = typeTextures[texture.Face]
if matchingTexture then
texture.Texture = matchingTexture.Texture
texture.StudsPerTileU = matchingTexture.StudsPerTileU
texture.StudsPerTileV = matchingTexture.StudsPerTileV
end
end
end
-- Returns a random block template from the Blocks folder.
-- If a Keyword is given, only blocks whose name contains that keyword are considered.
-- Blocks tagged "Exclude" are swapped out for a default "Tiles" block.
local function getRandomBlock(Keyword: string)
local blocks = Blocks:GetChildren()
if Keyword then
local filtered = {}
for _, block in blocks do
if block.Name:find(Keyword) then
table.insert(filtered, block)
end
end
blocks = filtered
end
if #blocks == 0 then
return Blocks.Tiles
end
local block = blocks[math.random(1, #blocks)]
return if block:HasTag("Exclude") then Blocks.Tiles else block
end
-- Creates a simple decorative "ground/gap" part (used for spacing between cells,
-- borders, etc.) at the given position and size, with a grass look and texture.
local function CreateSpacePart(Pos, Size)
local Space = Instance.new("Part")
Space.Size = Size
Space.Anchored = true
Space.Material = Enum.Material.Grass
Space.BrickColor = BrickColor.new("White")
Space.Position = Pos
Space:AddTag("Ground")
Space.Parent = MapFolder
Texture0Template:Clone().Parent = Space
end
-- Builds the four outer walls surrounding the entire grid.
local function CreateWalls()
local WallYCenter = YStart + WallHeight / 2 -- vertical center of the walls
local WallYOffset = Vector3.new(0, 1.5, 0) -- small extra vertical offset
-- Helper to create one wall part with textures applied to all 6 faces
local function MakeWall(size, pos)
local wall = Instance.new("Part")
wall.Size = RoundTo3(size)
wall.Position = pos + WallYOffset
wall.Anchored = true
wall.Material = Enum.Material.Concrete
wall.Color = Color3.new(0.258824, 0.258824, 0.258824)
wall.CastShadow = false
wall.Name = "Wall"
wall.Parent = MapFolder
for i = 1, 6 do
local texture = TextureTemplate:Clone()
texture.Face = Enum.NormalId[FACE_NAMES[i]]
texture.Parent = wall
end
end
-- Far wall (+Z side)
MakeWall(
Vector3.new(ExtendedW, WallHeight, WallThickness),
Vector3.new(GridCX, WallYCenter, MaxZ + SpacingSize + WallThickness / 2)
)
-- Near wall (-Z side)
MakeWall(
Vector3.new(ExtendedW, WallHeight, WallThickness),
Vector3.new(GridCX, WallYCenter, MinZ - SpacingSize - WallThickness / 2)
)
-- Right wall (+X side)
MakeWall(
Vector3.new(WallThickness, WallHeight, ExtendedL),
Vector3.new(MaxX + SpacingSize + WallThickness / 2, WallYCenter, GridCZ)
)
-- Left wall (-X side)
MakeWall(
Vector3.new(WallThickness, WallHeight, ExtendedL),
Vector3.new(MinX - SpacingSize - WallThickness / 2, WallYCenter, GridCZ)
)
end
-- Builds a flat roof/ceiling covering the whole walled area.
local function CreateRoof()
local RoofThickness = 10
local Roof = Instance.new("Part")
Roof.Size = Vector3.new(ExtendedW, RoofThickness, ExtendedL)
Roof.Position = Vector3.new(GridCX, YStart + WallHeight + RoofThickness / 2, GridCZ)
Roof.Anchored = true
Roof.Material = Enum.Material.Concrete
Roof.Color = Color3.new(0.258824, 0.258824, 0.258824)
Roof.CastShadow = false
Roof.Name = "Roof"
Roof.Parent = MapFolder
end
-- Fills in the spacing/gap parts that run around the outer edge of the grid
-- (top/bottom/left/right border strips plus the 4 corner pieces).
local function CreateBorderSpacing()
local HalfSpacing = SpacingSize / 2
CreateSpacePart(Vector3.new(GridCX, YStart, MaxZ + HalfSpacing), Vector3.new(GridW, 3, SpacingSize))
CreateSpacePart(Vector3.new(GridCX, YStart, MinZ - HalfSpacing), Vector3.new(GridW, 3, SpacingSize))
CreateSpacePart(Vector3.new(MaxX + HalfSpacing, YStart, GridCZ), Vector3.new(SpacingSize, 3, GridL))
CreateSpacePart(Vector3.new(MinX - HalfSpacing, YStart, GridCZ), Vector3.new(SpacingSize, 3, GridL))
local cornerSize = Vector3.new(SpacingSize, 3, SpacingSize)
CreateSpacePart(Vector3.new(MaxX + HalfSpacing, YStart, MaxZ + HalfSpacing), cornerSize)
CreateSpacePart(Vector3.new(MinX - HalfSpacing, YStart, MaxZ + HalfSpacing), cornerSize)
CreateSpacePart(Vector3.new(MaxX + HalfSpacing, YStart, MinZ - HalfSpacing), cornerSize)
CreateSpacePart(Vector3.new(MinX - HalfSpacing, YStart, MinZ - HalfSpacing), cornerSize)
end
--============================================================
-- ACTOR EXECUTION SETUP
-- This script runs both as the "main" coordinator script and,
-- when cloned into Actor instances, as a parallel "worker" script.
-- script:GetActor() tells us which mode we're in.
--============================================================
local NUM_WORKERS = 100
local actor = script:GetActor()
if actor == nil then
-- ==== MAIN / COORDINATOR MODE ====
-- Spin up a pool of Actor instances, each running a clone of this script,
-- so cell generation can happen in parallel across multiple threads.
local workers = {}
for i = 1, NUM_WORKERS do
local workerActor = Instance.new("Actor")
script:Clone().Parent = workerActor
table.insert(workers, workerActor)
end
for _, workerActor in workers do
workerActor.Parent = script
end
-- Track how many cells have finished generating so we know when to stop
local totalCells = Width * Length
local completedCells = 0
local doneEvent = Instance.new("BindableEvent")
-- Fired by each worker once it finishes generating a cell
local CountEvent = Instance.new("BindableEvent")
CountEvent.Name = "CellDone"
CountEvent.Parent = script
CountEvent.Event:Connect(function()
completedCells += 1
if completedCells >= totalCells then
doneEvent:Fire()
end
end)
-- Dispatch a "GenerateCell" message to workers (round-robin) for every
-- cell position in the grid
task.defer(function()
local workerIndex = 1
for i = 1, Width do
for v = 1, Length do
task.wait(.001) -- small delay to avoid flooding all workers at once
local X = v * Step - HalfW
local Z = i * Step - HalfL
workers[workerIndex]:SendMessage("GenerateCell", i, v, X, Z)
workerIndex = (workerIndex % NUM_WORKERS) + 1
end
end
end)
-- Wait until every single cell has reported completion
doneEvent.Event:Wait()
-- Clean up the worker actors now that generation is done
for _, workerActor in workers do
workerActor:Destroy()
end
CountEvent:Destroy()
-- Finish the map with borders, walls, and a roof
CreateBorderSpacing()
CreateWalls()
CreateRoof()
return
end
--============================================================
-- WORKER MODE
-- Code below only runs inside the cloned Actor instances.
--============================================================
-- Reference to the shared "CellDone" event on the coordinator script
local CountEvent = script.Parent.Parent:WaitForChild("CellDone")
-- Handles a single "GenerateCell" request: places a random cell preset
-- at the given grid position with a random rotation, skins its blocks,
-- and fills in the small gaps around it.
actor:BindToMessageParallel("GenerateCell", function(i, v, X, Z)
local Pos = Vector3.new(X, YStart, Z)
-- Pick a random cell preset and a random 90-degree rotation
local template = getRandomTemplate()
local Rotation = CFrame.Angles(0, math.random(1, 4) / 2 * math.pi, 0)
-- Sync back to the main thread to safely clone/parent instances
task.synchronize()
local Cell = template:Clone()
Cell.Parent = MapFolder
-- Compute each child part's new world CFrame based on its offset
-- from the cell's pivot, then move them all in one bulk operation
local CFList = {}
local PList = {}
local Pivot = Cell:GetPivot()
for idx, block in pairs(Cell:GetChildren()) do
PList[idx] = block
local RelativeCFrame = Pivot:ToObjectSpace(block.CFrame)
CFList[idx] = CFrame.new(Pos) * Rotation * RelativeCFrame
end
workspace:BulkMoveTo(PList, CFList, Enum.BulkMoveMode.FireAllEvents)
-- Pick a random block type to use for this cell's top texture/appearance
local random = getRandomBlock()
local Texture = (function()
for _, texture in pairs(random:GetChildren()) do
if texture:IsA("Texture") and texture.Face == Enum.NormalId.Top then
return texture:Clone()
end
end
return random.Texture or Blocks.Tiles.Texture
end)()
-- Apply the chosen texture/material/color to the cell's primary part
Texture.Parent = Cell.PrimaryPart
Texture.OffsetStudsU = 0
Texture.OffsetStudsV = 0
Cell.PrimaryPart.Material = random.Material
Cell.PrimaryPart.Color = random.Color
-- If this cell is tagged "Randomize" and contains "Acacia Planks",
-- randomly decide whether to reskin its Log or Planks pieces to a
-- different matching block type
local BlockLarpTarget, Keyword = (function()
if not Cell:HasTag("Randomize") then return nil, nil end
if Cell:FindFirstChild("Acacia Planks") then
local pickedKeyword = (if math.random(1, 2) == 1 then "Log" else "Planks")
return getRandomBlock(pickedKeyword), pickedKeyword
end
return nil, nil
end)()
-- Move all "Block"-tagged parts into the shared BlocksFolder,
-- reskinning matching pieces (Log/Planks) if applicable
for _, block in ipairs(Cell:GetChildren()) do
if block:HasTag("Block") then
block.Parent = BlocksFolder
block:SetAttribute("Owner", nil)
if Keyword and block.Name:lower():find(Keyword:lower()) then
LarpBlock(block, BlockLarpTarget)
end
end
end
-- (Redundant second pass ensuring all "Block"-tagged parts are
-- moved into BlocksFolder with no owner set)
for _, block in ipairs(Cell:GetChildren()) do
if block:HasTag("Block") then
block.Parent = BlocksFolder
block:SetAttribute("Owner", nil)
end
end
-- Fill in the small gap parts between this cell and its neighbors
-- (only added on the "trailing" side so gaps aren't duplicated)
if v < Length then
CreateSpacePart(Vector3.new(X + StepHalf, YStart, Z), RowSize)
end
if i < Width then
CreateSpacePart(Vector3.new(X, YStart, Z + StepHalf), ColSize)
end
if v < Length and i < Width then
CreateSpacePart(Vector3.new(X + StepHalf, YStart, Z + StepHalf), CornerSize)
end
-- Report this cell as finished so the coordinator can track progress
CountEvent:Fire()
end)
workspace settings:
important
useless
important
ISSUE
The issue is rendering all the blocks (note on average block amount can reach 100k or more with each block having a texture). When standing still fps can reach about 170. which is awesome!!! however when moving fps tanks to like 20 and then shoots up. this is probably because of chunks. How can I fix this???
boring side note: personally the game is fun to me atlest however optimiaztion is KILLING me, generating all these parts in the begginning is laggy enough but I can barely even render them, is there like anything i can do??







