Hello, I have a game where worker npcs move across the map to accomplish tasks, these npcs are stored as folders in replicated storage, and the client then independently renders them based on their PositionXZ attribute, which is just a vector 2 with the npc’s X and Z positions (I already minmaxed pretty hard so the Y value is always the same, therefore I dont need to store or update it)
the position value is calculated and updated in a while wait() do loop, and when hundreds of npcs move at once, the received network jumps up to 100 - 200 kb/s (highest ive seen was 280 I think) .
here is the relevant code:
local pos = npc:GetAttribute("PositionXZ")
if pos then
local currentPos = GetnpcPos.GetPosition(npc)
local effectiveDestination
if CalculatedNPCs[npc]==destinationCFrame.Position then
effectiveDestination = destinationCFrame.Position
else
effectiveDestination = destinationCFrame.Position + offsets[i]
task.synchronize()
npc.DestinationCFrame.Value = CFrame.new(effectiveDestination) * CFrame.Angles(npc.DestinationCFrame.Value.Rotation.X, npc.DestinationCFrame.Value.Rotation.Y, npc.DestinationCFrame.Value.Rotation.Z)
task.desynchronize()
CalculatedNPCs[npc] = effectiveDestination
end
local newPos = moveTowards(npc, currentPos, effectiveDestination, SPEED, deltaTime)
if newPos.Y < 1.67 then
newPos = Vector3.new(newPos.X, 1.67, newPos.Z)
end
if game.ReplicatedStorage.TestValue.Value == true then
--posVectorValue.Value = newPos
local newVect2 =Vector2.new(newPos.X-newPos.X%0.01, newPos.Z-newPos.Z%0.01)
--modulo to truncate at 2 decimal points to send less data
task.synchronize() --this is in an actor script because
--modulo is surprisingly performance heavy, although the spikes
--happen even when this code runs outside of the actor script
npc:SetAttribute("PositionXZ", newVect2)
task.desynchronize()
end
end
end
after a LOT of testing, the only thing that is causing the high network usage is the line npc:SetAttribute("PositionXZ", newVect2)
I’ve also taken a look at this post, which is how I got the idea to truncate the number for example. Still, while it got slightly better, I am still concerned because every other game ive seen has at most 30kb/s network usage, which is why I assume that its problematic, if its not then forgive me for being overly cautious.
Attributes are tricky to optimize, anything you do to them gets replicated essentially and can use quite a bit of data.
If you want to optimize your data flow in your project I recommend using buffers and sending data through remote events instead since buffer objects support on-the-go compression.
I also once figured out that you can actually optimize position data by using 16 bit integers, you’d have 1 decimal of precision but this is actually enough for just visual effects and even can be for player position data.
Do be aware that 16 bit integers have a limit of like 65,536 possible values.
This means that if a player were to move away further than 3000 studs from the 0,0,0 point of the universe, you’d have to move their point of reference.
Also, you use task.desyncronize and task.synchronize wayyyy too often for it to be effective.
You only have to synchronize when you’re modifying object properties, calculations and table manipulation should still be possible to do in parallel.
thanks for the reply! I’ll try the method you suggested tomorrow and let will let you know how it goes, although I will have to rewrite my client replication code for it to work, and just to double check, vector2int16 are signed ints right?
also the 3000 studs shouldn’t be a problem as the map is quite small
and to avoid confusion, the cframe that is being edited is an actual cframe value that exists in the npc folder, and as far as I know an unsynchronized script cannot write things like that (especially if theyre outside of the given LUAU vm)
Don’t use vectors at all, just extract the X, Y and Z components, convert to 16-bit integers and write that to a buffer, that should be enough.
You can practically replicate object positions with only 6 bytes + 3 bytes for rotation if you do it right.
You will lose a bit of precision but it won’t visually be all too noticeable really.
okay well I tried using buffers with a setup like this:
local buf = buffer.create(6)
task.synchronize()
event:FireAllClients(buf) --i am the only player by the way
however after testing that with 120 npcs, the network received remained at ~70 kbps, which I find weird because doing the math this means that every npc requires 4600 bits of information to be sent every second, and since the update loop update loop runs 30 times a second, every npc apparently requires ~155 bits of information to be sent every iteration while the buffer only takes up 6.
never mind im kind of stupid, I forgot that firing the remote event itself also has a cost.
any ideas on what I should do? the only idea I have right now is to reduce the frequency of the updates massively (to like once a second) and make a separate system on the client to smooth out the movements or something along those lines