Large scale optimization for AI

Hello everyone, I am currently making a PVE game but currently there is a issue, Optimization. Due to the large scale of my game, It has become apperant without severe optimizations the player expierience will suffer.

The AI should have these capabilities:

Locate and pathfind to the Enemy target.
Attack this enemy target.

Now this is the basic AI, and i would be satisfied if the ai could run with 100 smoothly.

These are current optimization Tactics I have implemented, any feedback is appreciated.

Skip pathfinding if target is directly visible and not elavated above a certain degree.

Group up with nearby AI. The current way this is made is:

AI will attempt to join a group, if none in radius are found it will create it’s own.
Groups that collide will immedietly find a better Leader of all members.
This is determind by seeing which member encompesses the most amount of AI, while retaining the most percentage of HP. (recalculating all groups upon death is expensive)

Note:
With the current scale of my game, the server starts to lag at around 75ish, this isn’t good since this doesn’t include any player interaction or other AI with more sophisticated Attack patterns.
This is also with a very large grouping range, which is also quite boring to fight against since it’s just a massive clump of AI.

Also I was wondering what a good server memory range should be at around 100 of these AI and what i should be aiming for.

Thanks for reading.

2 Likes

Reply:

Hello, I will leave this forum open, because someone might have more efficient solutions. I will post mine below, I have managed to reach my goal.

Only do certain calculations every 2-3 or even every 10 frames, to save memory.
Avoid large scale raycasts (they we’re largely the cause of my memory issues).

I will edit this if I find anything else that works, and can be added on.

1 Like

Try limiting the HumanoidStateType. It’s what I’m currently doing with my game!

For example, if you are certain your NPCs will not touch water. You can disable their swimming humanoidstate. Like this!

humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, false)
1 Like

Here’s what I’d do:

  1. Make sure your NPC’s are being animated on the client, not the server. This both makes your NPC animations smoother, and reduces the amount of network traffic being sent to clients.
  2. (if this is a feasible tactic for your game based on how important anti-exploitability is) Run pathfinding on the client
  3. Use multithreading for the pathfinding calculations
  4. Run pathfinding less often. If the last point you were pathfinding to still has a clear path to the player’s current position, you don’t need to pathfind again, etc.

Also do some benchmarking and share the results here (RAM, CPU time, network send/recv, microprofiler) so you/we know what specifically is causing the lag. It might not be pathfinding.

3 Likes

Thanks for the reply!

I did not know that this was possible or a benefit to memory costs.
Thank you very much, this will help significantly!

1 Like

I’ll be honest, I have no idea how to use the micro profiler, so I will use the scrip profiler.

This is me spawning in 100 basic enemies, that fire a bullet every second, for about 10 seconds.

Certain things like .Chatted are due to the spawning commands being controlled by chat.

I haven’t changed the animations to run client sided yet. but that would also improve performance.
Running is connected with the animation controller, to fix this just change the run context on the animate script to client.

Delayedthreads is actually the enemy class handler, which also loads the loop that controls the enemy AI. due the fact that enemy AI skips pathfinding calculations if the target is directly visible, the memory cost was quite low. I managed to lower this even further by only making the targetting loop run every few frames instead of every frame.

Waiting scripts is the bullet handler. Since the bullets have travel time and bullet drop, you could expect it to be quite expensive. I’ve tried to resolve this by only making it update 24 fps and only doing dot product checks every significant change in angle (10 degrees). This could also be mitigated by lowering the amount of functions it has. currently bullets can do a lot of things.

Now we have the big hitters, Stepped and heartbeat.

Stepped is the update loop for the target update, and align orientation changes. It runs significantly more often than the pathfinding loop, because the loop is updates more frequently it has a high memory cost. for the fact AI is able to shoot at a different target, than it is pathfinding to. Funnily enough, it is actually the raycasts that cost the most memory for this function, clearly for checking visibility. A way to reduce memory costs on this is to use the same target and calculation for nearby AI sacrificing accuracy. Which I will skip because of the drawbacks.

Heartbeat is also the bullet scripts, and has the exact same reasons for the high memory cost. there isn’t a way to really fix this, other than using a Pool of bullets instead of instancing every one.

this is a screenshot showing the high memory costs of moving the bullet and Hit detection. This could be reduced by making bullets update even less, or changing to a single raycast. but this would make the game less fun.

for anyone else looking for a optimization for pathfinding, I do not recommend using a grouping system, as merging and other things take significant memory.

Instead opt for a method where if a person requires pathfinding, it will search for other AI within radius a single time to skip pathfinding.

As for the parallel / multithreaded thing, I am too inexperienced to know how to do this. I will inform myself, but currently all update loops are task.spawned.
This was also mainly a memory optimization thing, any network optimizations would also be highly appreciated.
Thank you for reading. any other optimization suggestions are very welcomed.

Another reply, I found changing the animations to be client sided, so beneficial to stop delays and lag, that I was genuinely astonished, thank you!

2 Likes

Are you using physical bullets with BasePart.Touched for hit detection? If so, don’t. It’s slow, memory intensive and the hit detection isn’t great sometimes in my experience. Use something like FastCast if your game needs bullet travel time and just normal raycasting if it’s hitscan. Also, spawn in your bullet tracers on the client, not the server, after doing this (if using FastCast, send the origin and direction for each bullet fired to clients and have the clients fire their own FastCast cast for tracer handling). This will increase the smoothness of your tracers, reduce network traffic and reduce memory/CPU use on the server.

There really isn’t a lot you can do to save on performance for your AlignOrientation and target updating loops. If you’re doing a lot of computation for target detection like checking if a target is in view, in distance, armed, etc… You may want to “smear” the computation over multiple frames by using task.wait

1 Like

Tracers and everything visual is handled on the client. As for using Bullet hit detection. It just shoots a ray from the last position to the new position and checks for collisions.

The only thing the update loop does is change the allign Orientation and check if the target is within range.

Exact way the bullets are handled:

Bullet is created via spawning in a part. (Pretty much required since bullets can be deflected and other things, and such need an instance)

Bullet creates a heartbeat loop that fires continuously every few frames, that moves the bullet forward based off of delta time.

bullet fired a ray between the last and current position to check for collisions.
bullet despawns after 2 seconds.

bullets also calculate bullet drop and others thing in this heartbeat loop, by moving the bullets forward orientation downwards.

bullets also have the ability to pierce through walls, which adds a slight extra memory cost, to calculate thickness and other things.

Thanks for the reply!

I don’t actually think you need an Instance for deflections. Granted, I haven’t tried it, but FastCast has methods to change the velocity of a bullet upon hitting a surface, along with functions for gravity and piercing.

1 Like

Hello, after reading your reply. I changed the bullet instance from a part to a Cframe value. This somehow improved my performance by 50% or around that for bullets.

Thank you so much for the reply. I would’ve never thought of this.

Maybe I could completely remove the Cframe value entirely and change it to be a table of Bullets instead. Possibly improving memory usage significantly again.

Yeah, that would improve it significantly, using Instance values in general uses a lot of memory.