How to optimize multiple pathfinding. Optimizing a huge number of enemies

Godot Version 4.2.1

Question

I’ll spawn a lot of enemies, which in theory could be >500 in the course of the game. However, in tests, at 300 enemies the fps becomes unplayable <15. The main problem lies in the PhysicsProcess functions of the enemies, for it takes the most time.

Photo monitoring (Active objects are all enemies):

(Without pathfinding)

(With pathfinding)

You can see that without miscalculating the path to the player there is a noticeably better drop in fps, that is, the graph is more like a straight line.

I know about ObjectPooling and PhysicsServers, but if I am sure that I don’t need ObjectPooling now, I don’t know how to check collision of enemies with each other via PhysicsServers and it seems too complicated to me.

I look forward to your suggestions

Did all your units needed updated same time?
you can make chunks and update near player.
Make from enemies groups and process them as single unit?
You can extend time between physic process.
You can skip physic process and collect delta time from skipped updates.
start multi threading.

ObjectPooling is not recommended in Godot.

To be more precise, it is not needed for simple objects. It still makes sense to pool complex “objects” that have noticeable setup time. E.g. complex nodes that do a lot of performance expensive stuff on creation.

Objectpooling for simple objects makes no sense in Godot due to its core C++ engine as long as you stay within that core (e.g. GDSCript or C++). The Godot core does not suffer from the same problems as other engines with a garbage collected language (e.g. C#) that adds mandatory game stutters when not pooling. Hence why pooling in certain game engines like Unity is seen mandatory for everything but makes no sense by default in Godot.

That said if you are using C# with Godot you might want to do pooling for certain things as otherwise the C# garbage collector will kill your games performance at some point.

This is too little context because the physics process increase can be a lot of things.

The agent avoidance runs in physics as well as the navigation map sync. Also CharacterBody nodes are notorious physics performance killers when used in large quantities.

If you have a noticeable performance drop from pathsearch when the target is not reachable that is a sign that your navigation mesh is too big and unwieldy and your world needs to be chunked and/or the pathfinding needs a hierarchy layer.

If a position is not reachable the pathfinding searches all available polygons to confirm that the position can not be reached. That does not matter on a “normal” sized map but on a very big map with an excessive amount of polygons that matters a lot for performance. As long as a path can be found quick the pathsearch will do an early exit so this can hide a too large map for a long time but performance hits back hard when the position can not be reached.

Each enemy is a CharacterBody2D. Started trying to use servers for optimization. If you take the task, just to reach the player, then before at 300 enemies was less than 60 fps, and with PhysicsServer2D number of enemies at a stable fps increased to 2000. This pleased me very much, but it forced every second to create a new ShapeParameters to handle collision (perhaps you can do otherwise, but tutorials less than I have fingers on one hand) because of what were jumps on objects 30000-80000-30000, etc. Although it did not eat frame time, but raises doubts.

About pooling, I work in C# and my objects are components of a composition, all entities have plus or minus similar structure:

изображение

I wouldn’t say they are complex objects, they are quite primitive, they just take up a lot of nodes. I plan to start replacing all components to use servers (except visual ones). If you know a good tutorial on how to create top-down entities on servers, I’d love to see it. If you can just show sample code, then all I need is this:

Handling movement (along with the visuals represented by nodes, not RenderingServers),

Creating bodies and areas (I know about Body and Area in PhysicsServers, but I don’t understand how they work).

Receive and send signals (to pass information between Attack/HitboxComponent)

If I take my current code:

using System;
using Godot;

public partial class TestEnemy : Node2D
{
    [Export(PropertyHint.Layers2DPhysics)] private uint collisionLayer = 0; 
    [Export(PropertyHint.Layers2DPhysics)] private uint collisionMask = 0; 
    [Export] private float shapeRadius = 0f;

    private CharacterBody2D player;

    private Rid shapeRID;
    private Rid bodyRID;

    public override void _Ready()
    {
        player = GetNode("/root/Main/Place/Player") as CharacterBody2D;

        shapeRID = PhysicsServer2D.CircleShapeCreate();
        PhysicsServer2D.ShapeSetData(shapeRID, shapeRadius);

        bodyRID = PhysicsServer2D.BodyCreate();
        PhysicsServer2D.BodySetParam(bodyRID, PhysicsServer2D.BodyParameter.Friction, 0);

        PhysicsServer2D.BodySetMode(bodyRID, PhysicsServer2D.BodyMode.RigidLinear);
        PhysicsServer2D.BodySetState(bodyRID, PhysicsServer2D.BodyState.Transform, Transform);
        PhysicsServer2D.BodyAddShape(bodyRID, shapeRID);

        PhysicsServer2D.BodySetCollisionLayer(bodyRID, collisionLayer);
        PhysicsServer2D.BodySetCollisionMask(bodyRID, collisionMask);

        PhysicsServer2D.BodySetSpace(bodyRID, GetWorld2D().Space);
    }

    public override void _PhysicsProcess(double delta)
    {
        Vector2 direction = (player.GlobalPosition - this.GlobalPosition).Normalized();
        Vector2 moveVector = direction * 100 * Convert.ToSingle(delta);

        PhysicsTestMotionParameters2D Parameters = new PhysicsTestMotionParameters2D();
        //Parameters.CollideSeparationRay = true;
        Parameters.From = Transform;
        Parameters.Motion = moveVector; 

        if (!PhysicsServer2D.BodyTestMotion(bodyRID, Parameters))
        {
            PhysicsServer2D.BodySetState(bodyRID, PhysicsServer2D.BodyState.Transform, Transform);
            this.Translate(moveVector);
        }

    }
}

It turns out that when colliding with the player, they push him, which should not be, and also if I disable collision with enemies in the player, but leave collision with the player in the enemies, then the enemies do not see the player and do not collide with him.

There’s a lot of problems, I’m crying

Grouping enemies I think is not my option, also I tried calling my functions in physics not constantly, but every 0.5-1 second, but it did not give a profit from the word at all.