NPC Animation Optimization

I’ve read a lot of articles discussing NPC optimization and one common topic in that discussion is whether to handle the animation on the server or on the client. Among what I have read, the common answer is on the client. However, across the reading I’ve done (and videos I’ve watched) there are a few issues (see below). I’d appreciate some advice/guidance on how others have recently approached this problem while keeping optimization in mind.

Assumptions/Requirements with why I am ultimately trying to achieve:

  1. I’d like to handle playing and transitioning from one animation to within an NPC (not a player) that contains a Humanoid
  2. I plan to have at least 100 NPCs and preferably as many as possible without negatively impacting performance (server or client) in such a way to create lag for a player
  3. The NPCs will running with some level of AI where they will be independently moving (i.e. roaming) and there will be combat against players, which will create several points where animations will need to transition from idle, moving, fighting, skill use in combat, etc
  4. All players will need to visually see the animation (at least when the player is within a certain distance from the NPC)

Issues with the content I’ve come across during my research:

  1. Some articles are multiple years old and I’m unsure the content is still accurate/relevant considering there may have been changes to the Roblox platform
  2. Its often not clear whether suggestions in these articles are only appropriate to specific use cases and not appropriate to others.
    2. It seems that at least for some articles the focus doesnt consider large NPC numbers or only considers minimal NPC activity such as static NPCs that only animate when a player interacts with them, which doesn’t address my goals.
  3. Many offered solutions only provide a high level suggestion and dont provide any details on how to implement the solution or how that solution would work.

After spending considerable time researching this topic I’m left unsure of how to optimally approach managing playing animations client side or what people mean when they suggest running animations client side. Here are some of the concerns I have with the idea, but I am sure I am missing some critical pieces to make this work optimally.

The trigger for an animation to change/begin will be based on actions taken by an NPC (i.e. begin moving, stop moving and begin ideling, begin attacking, use skill, died, etc) or actions taken on an NPC (was hit). My thought is that when actions are taken will be functions executed to take that actions such as begin walking or to hit a target and within those functions that require an animation change a FireAllClients event could be fired. However, my concern with this is that is it optimal to fire an event to ALL clients every time any NPC begins an action and will this risk exhausting the event budget limits say when 100+ enemies need an animation to be started within a very short time period. However, I dont know how else to inform every client to play an animation client side.

With the focus on optimization, is the optimization argument, or the optimization gained, of handling animations client side > the network traffic of fire all clients for every animation change?

Otherwise, would having the server tell each NPC to play a loaded animation directly and have that replicate to each client, thus not requiring firing any events to clients, be more optimal?

Is there some other approach I am missing? Please let me know where I am going wrong and how to better approach this problem. Thanks

For reference, here are a few of the articles I’ve read:

1 Like

Actually, I have had a similar problem to you.

In my first bodge attempt I simply set the animations to be played from the server. I highly do not receive playing animations on the server. I got an insane amount of lag and network traffic when a server got filled with players. Since each player had 8 NPC 30 × 8 = 240 NPCs playing animations. Including the fact that Humanoids in themselves are laggy, from my experience I do not recommend you run animations on the server.

In my case I would say I have an easy fix because of the type system AI NPC I will be using.

I simply used Collection Service on the client and set an event every time the state of the humanoid changed. In other words, I handled the actual event on the client too. That makes it unnecessary to send remote events.

So, what you could do(I am not sure if this will work) is to make an IntValue to store the current action of the NPC then on the client have an event every time the Value is changed and play the corresponding animation.

Hope this helps.

1 Like

Thanks for the reply and interesting suggestion. I hadn’t come across GetPropertyChangedSignal before. I could create create a string or int value on the NPC instance as a child. I was already considering a similar approach to looping through a collection of npcs on the client, and where an NPC is within a predetermined range of the player I could connect to GetPropertyChangedSignal for the NPC child value. For those NPCs out of the range I could simply disconnect to save resources.

So, what you could do(I am not sure if this will work) is to make an IntValue to store the current action of the NPC then on the client have an event every time the Value is changed and play the corresponding animation.

I suppose it wouldn’t really matter what the stored value really is. I will still need to get the proper animation track from a module for the given NPC that I’ve had setup with a table of animation when the NPC spawns into the world. I could either reference the value from the GetPropertyChangedSignal as a key to the animation table or get use the GetPropertyChangedSignal as a trigger where the client will call a get animation track function from the NPC animation module to return the proper animation track to play.

I’ll have to do some experimenting, but this feels promising. Thanks again for the suggestion. I just needed an idea to get me started on a direction.

1 Like

Not sure if this would work in your situation, but with some of the npc’s in my game, I have my map divided into a grid, and only npc’s in grids close the the player display animation (played from the client), and even farther out, the AI is suspended

One more question, if you dont mind.

I simply used Collection Service on the client and set an event every time the state of the humanoid changed. In other words, I handled the actual event on the client too. That makes it unnecessary to send remote events.

Assuming you will have multiple humanoids for which you have a connection for listening for change events, how will you identify for which humanoid the event was fired? The change event only gives the new value set in the ValueBase (IntValue, StringValue, etc), and I’m not sure if there is a way to easily identify its parent.

Ah, well that is where in my case it works.

Since all the NPCs will have the same animation and the same events I can simply loop through the entire thing. There is no need to differentiate between the NPCs so they can all be connected to the same function. Like this

function DoThis(OldState,NewState)
    if NewState == --Yeah you get the idea...
    -- Code that animates accordingly
end
for _,Character in ipairs(CollectionService:GetTagged("NPC")) do
    Character.Humanoid.StateChanged:Connect(DoThis)
end

I simply have to tag each NPC that is in the game and remember to connect any NPCs that are cloned too.

Ahhh, I see. Since you are leveraging the humanoid states you are connecting to StateChanged to know which humanoid you need to animate.

I’m looking into utilizing tag signals as my trigger since I am not relying on StateChanged and trying to implement my own custom state tracking. Adding a tag to signal that a specific instance has reached a corresponding state, thus received the corresponding tag, will inform all clients that the instance should have the matching animation played. I’ll just have to align a tag for each animation and make sure an animation exists that matches the tag.

Since tags signals provide the instance for which the tag was added this should hopefully solve my issue of not knowing which instance the .Changed event was fired.

2 Likes