Help with making decision about optimization

I heard that there is a method called stereming, it was a word like that, I don’t know exactly, it is in the humanoid section and you can adjust it.
I heard that enabling it will make the game much faster.
And try to use task.wait in all your loops so that the game doesn’t lag.
Try not to use things that consist of too many parts because they cause a lot of lag. If you can create them in Blender and then add them, I think it can be more helpful.
If you follow these tips, your game will be great.

If your making a zombie game what you can try to learn is server simulation and client replication. Essentially humanoids are VERY expensive because they are constantly replicating to clients meaning sending lots of data such as CFrames, positions, humanoid states, etc.
I recommend simulating “parts” that follow the zombie’s behavior movement ON the server and have that data (like position and orientation) sent to the players (clients) to replicate visually.
I am currently working on a similar system with smart NPCs and have utilized this method.

Additionally:

I recommend to learning bit packing and using network buffers if you do decide to do server simulation as it significantly decreases the amount of data being sent to the client. From me for example before I implemented this 2 NPCs were taking up over 517 bytes sending info like positions constantly, after packing that data into bits, 1 NPC only consumes around 5 bytes for me.

Optimization is key if you want to handle loads of NPC without lag.

1 Like

exclude cframe and position, cause these properties are replicated of every instance from server

Not unless you’re simulating on a server the NPC behavior where CFrame and Positions are not replicated. Other things like animations, orientation, etc. anything to do with the NPC is not replicated while using server simulation.

The main benefit of server simulation is you can control what gets replicated to the client so essential things like positions and CFrame orientations can be bitpacked and sent to the client to be replicated (I’ve done this and it only cost me 5 bytes!), behavior logic and such can be relayed onto the client using behavior trees as well by bitpacking certain numbers:
idle = 1
walking = 2
running = 3
and so on…

The main reason handling a lot of NPC cause lag is the humanoid replicating humanoid states, health, position, cframe, and more constantly each frame. Using server simulation controls the rate and allows you to only send necessary data that the client needs to know, eg. positional data (x,y,z).

I’ve been able to bitpack positional data into 32-bit (4 bytes) and orientation into a 8 bit (1 byte) [1+4 = 5 bytes per npc]. However, OP you might need more or less bytes for position depending on the scale of the game as the way I packed my bytes is the X and Z value can range from 0 to 2054ish smth, and the Y value to 256.

Below is a tutorial going over bitpacking, etc.
Simulating thousands of moving NPCs with humanoids/physics performantly

u didnt get my point obviously, what i meant was Humanoids dont have CFrame NOR position, these are properties of basepart, not humanoid

1 Like

sorry for being late, I’ve found the DOD manager i’ve written long ago, its very simple, includes replication batch, entity ID batching and syncing.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local Remotes = ReplicatedStorage.Remotes
local ReplicateRemote = Remotes.DataReplicate

local IDCache = {}
local Updates, Count = {}, 0

local DataManager = {}

--// Base \\
DataManager.ID = 0
DataManager.Active = {} :: {[number]: boolean}

--// World \\
DataManager.WorldName = nil 
DataManager.WorldWidth = nil
DataManager.WorldHeight = nil

DataManager.Tile = {} :: { number }

DataManager.ForegroundTile = {} :: { number } --// [GlobalIndex]: TileId
DataManager.ForegroundId = {} :: { number }

--// Player \\
DataManager.Username = {} :: { string }
DataManager.UserId = {} :: { number }
DataManager.PlayerPosition = {} :: { vector }

local Enums = {
    GenerateID = 1,
    Track = 2,
    Set = 3,
    Destroy = 4
}

function DataManager.GenerateID(id: number)
    local ID = id
    
    if not ID then
        if #IDCache > 0 then
            ID = table.remove(IDCache)
        else
            DataManager.ID += 1
            ID = DataManager.ID
        end
    end
    
    Count += 1
    Updates[Count] = {Enums.GenerateID, id}
    
    return ID
end

function DataManager.Track(id: number)
    DataManager.Active[id] = true
    
    Count += 1
    Updates[Count] = {Enums.Track, id}
end

function DataManager.Set(...)
    if select("#", ...) == 3 then
        local Id, Field, Value = ...
        
        if not DataManager[Field] then
            return
        end
        
        DataManager[Field][Id] = Value
    else
        local Field, Value = ...
        
        DataManager[Field] = Value
    end
    
    Count += 1
    Updates[Count] = {Enums.Set, ...}
end

function DataManager.Destroy(id: number)
    if not DataManager.Active[id] then
        return  
    end
    
    DataManager.Active[id] = nil    
    
    for Key, Field in next, DataManager do
        if typeof(Field) == "table" and Field[id] then
            Field[id] = nil
        end
    end
    
    table.insert(IDCache, id)
    
    Count += 1
    Updates[Count] = {Enums.Destroy, id}
end

function DataManager.Sync(plr: Player)
    local FreshData = {}
    
    for Key, Field in next, DataManager do
        if typeof(DataManager[Key]) == "function" then
            continue
        end

        FreshData[Key] = Field
    end
    
    ReplicateRemote:FireClient(plr, "Init", FreshData)
end

if RunService:IsServer() then
    local function Heartbeat()
        if #Updates == 0 then
            return
        end
        
        local BatchSize = math.min(#Updates, 5000)
        local Batch = {}
        
        for Index = 1, BatchSize do
            Batch[Index] = Updates[Index]
        end
        
        if #Updates > BatchSize then
            table.move(Updates, BatchSize + 1, #Updates, 1, Updates)
            
            for Index = #Updates, #Updates - BatchSize + 1, -1 do
                Updates[Index] = nil
                Count -= 1
            end
        else
            Updates = {}
            Count = 0
        end
        
        ReplicateRemote:FireAllClients("Update", Batch)
    end
    
    RunService.Heartbeat:Connect(Heartbeat)
else
    local function OnClientEvent(Action: string, ...)
        if Action == "Update" then
            local Batch = ...
            
            for _, Update in next, Batch do
                local MethodEnum = Update[1]
                local Method
                
                for MethodName, Index in next, Enums do
                    if Index == MethodEnum then
                        Method = DataManager[MethodName]
                        break
                    end
                end
                
                if Method then
                    Method(table.unpack(Update, 2))
                end
            end
        elseif Action == "Init" then
            local Data = ...
            
            for Key, Field in next, Data do
                if typeof(DataManager[Key]) == "function" then
                    continue
                end
                
                DataManager[Key] = Field
            end
        end
        
        Updates = {}
        Count = 0
    end
    
    ReplicateRemote.OnClientEvent:Connect(OnClientEvent)
    
    game:GetService("UserInputService").InputBegan:Connect(function(inp, gpe)
        if gpe then return end
        if inp.KeyCode == Enum.KeyCode.F then
            print(DataManager)
        end
    end)
end

return DataManager

and this is minimal example

local function LoadWorld(worldName: string)
    local Result, WorldData = GetWorldData(worldName)
    
    if Result ~= "Success" or not WorldData then
        warn("Failed to load World")
        return
    end
    
    DataManager.Set("WorldName", WorldData.name)
    DataManager.Set("WorldWidth", WorldData.width)
    DataManager.Set("WorldHeight", WorldData.height)
    
    for _, Block: WorldTypes.Block in next, WorldData.blocks do
        local GlobalIndex = Block.y * DataManager.WorldWidth + Block.x
        local BlockId = DataManager.GenerateID()
        DataManager.Track(BlockId)
        DataManager.Set(BlockId, "Tile", true)
        DataManager.Set(BlockId, "ForegroundId", Block.fg)
        DataManager.Set(GlobalIndex, "ForegroundTile", BlockId)
    end

    warn(`Loaded world ({WorldData.name})`)
end

even if there were thousands of WorldData.blocks, it would batch without any lag


these are “components”, you can create seperate module for it and implement it within this manager if you want to