I’ve recently encountered an issue with my current NPC system. Basically, the server handles the position of NPC’s at any given time and uses :SetPrimaryPartCFrame() to replace them from one node to another. The client receives updates on where the NPC should be moving to and will tween + animate the NPC for the time it takes to reach the node. The idea is that the server synchronizes the position of the NPC between all clients. This works fine except for high volumes of NPC’s (roughly 160-200+). With this many NPC’s the delay becomes noticeably different between the client and server which gives the appearance of teleporting NPC’s.
The main issue I’m identifying is a difference in delay from wait() between the client and server. This is a snippet of relevant code to the problem:
-- SERVER
target.Value = fin
state.Value = "Walk"
currentNode.Value = node
local dist = math.sqrt(math.pow(fin.X - root.Position.X, 2) + math.pow(fin.Z - root.Position.Z, 2))
local waitToStop = dist / SPEED -- distance / walkspeed
wait(waitToStop)
NPC:SetPrimaryPartCFrame(fin)
-- CLIENT
local dist = math.sqrt(math.pow(newDest.X - root.Position.X,2) + math.pow(newDest.Z - root.Position.Z,2))
local waitToStop = dist / SPEED
setVisible(object, 0)
local tween = TweenService:Create(root, TweenInfo.new(waitToStop, Enum.EasingStyle.Linear), {CFrame = newDest})
tween:Play()
I change a few ObjectVals, StringVals, and CFrameVals of the NPC on the server to notify the clients what animations to play and where to move the NPC.
To be clear, the system works for small amounts of NPC’s, but for larger numbers the wait() on the server becomes less accurate which is why the NPC’s look like they backtrack before moving forward.
I’m not sure about performance, but if what if you calculated the pathfinding or whatever’s is determining their position on the client. Instead of waiting for the server to tell the npcs where to go.
I use custom pathfinding with A* which runs fine and is very constrained to optimize on performance. The main issue here is not that the current solution isn’t performant, but that the wait() is not translated very cleanly across the client-server boundary.
The server’s wait() is different than the client’s, so the position of the NPC becomes a bit bugged. This only happens for high volumes of NPC’s though, as I mentioned earlier.
EDIT: Also, the server has to control where the NPC’s go as this is how the client earns cash. I will not trust the client to monitor their own cash inflow.
CFrameValues have been working perfect so far. They’re the approach I’ve been using for a while now for this system and it works fairly well in most cases. They replicate quickly, but I’m still seeing that the NPC’s are not synced.
Like I mentioned, I’m mostly concerned now with my use of wait() and am looking for alternatives that might help cleanly replicate between the server and client.
Yeah, sorry, I misread. But I mentioned on the edit I added that the NPC’s position is what generates income for the player, so the server should be in charge of that. Plus, if each client handles the NPC’s individually, they won’t be replicated between each other that well.
Using wait() will obviously cause a noticeable delay. Using Heartbeat or Heartbeat:Wait() will make replication look cleaner since you are updating more frequently.
You’re misunderstanding. I’m not talking about wait() without any delay inside. I’m talking about the wait() as I posted in my sample code, where the server yields for the expected time it takes for the NPC to reach the node. This is the same time that the client should be tweening for, but they do not replicate cleanly. I’m suspecting that the times are slightly different which results in the appearance of teleporting.
In case anyone stumbles upon this thread, I’d like to share the solution I’ve come up with to resolve this issue. Basically, I’ve completely replaced the physical representation of the NPC on the server - so currently the server only maintains a record of the NPC’s position, rotation, destination, etc by using a bunch of Value objects. The client handles all of the rendering of the NPC’s and dynamically creates/unloads them. This has definitely boosted performance by a bunch for me and I can handle upwards of 200 NPC’s with minimal replication lag since there aren’t any conflicting copies of the NPC from the server (everything is on the client).
TLDR: For anyone trying to accomplish something similar, keep the server representation of the NPC pure data by using Value objects and allow the client to handle rendering, animating, tweening, etc.