UE4 AI Research (pt. 1)

As you all know I’ve been working on End of Days for quite some time and one of the biggest hurdles has been with AI. I recently began diving into AI on a deeper level and there are some concepts I want to look into like Utility AI , UE4’s crowd AI controller, as well as dig into some of my own hunches for optimization. I’ll be writing about my findings to see if I can help the community progress in the same direction as I noticed it’s hard to find good set ups for large quantities of AI.

Originally, I was using AI behavior toolkit for my AI and having even 25 of them on a map can reduce my fps to 40 or less. I just switched over to some very simple c++ AI using pawn sensing, behavior tress, and a custom movement component ( I’ll go over that in a moment) and I’ve found that I can now get over 100 AI running at 120 fps with worst case scenario being about 90 fps. I want to crank those numbers as high as possible.

The custom movement component essentially allows for occlusion and does not render the movement updates when the AI aren’t on screen. I got this nice improvement from a user ColdSteel48  on the UE4 forums back in 2017 and didn’t understand a bit of it, until recently, as I am now more acquainted with the engine and c++.

Here is a sample of this in action. This is not End of Days, but rather a modified version of Tom Looman’s survival game example

100 (105?) AI running at 120 best case and 90 fps worse case i7 3770k and 980ti classified

For those interested, the tweak looks a bit like this :

Header File:

 class YOUR_GAME_NAME_API UCM: public UCharacterMovementComponent
     virtual void InitializeComponent() override;
         //Useful to make sure that your characters are using the correct Component.
     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = StubCheck)
     bool m_dummyToMakeSure; 
     virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
     float m_DeltaTime;
     int m_nTicksCount;
     int    m_nTickGroup;
     bool m_SkipFloorChecks;

.CPP File

void UCM::InitializeComponent()
     m_nTickGroup = FMath::RandRange(3, 4);
     m_DeltaTime = 0.0f;
     m_SkipFloorChecks = FMath::RandBool();
     m_nTicksCount = 0;
 void UCM::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
        m_DeltaTime += DeltaTime;
     if (!CharacterOwner->GetMesh()->bRecentlyRendered)
         if (++ m_nTicksCount % m_nTickGroup == 0)
             m_nTicksCount = 0;
             DeltaTime = m_DeltaTime;
     m_DeltaTime = 0.0f;
     //Call Default CharacterMovementComponent
     Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

All this is well and good, but not good enough for the numbers that I want to push. The next step to is find a way to stagger the tick of AI in groups sorted by distance. This is easy in single player, but how does this work in a multiplayer environment when players can be at 4 corners of a map? I’ll be investigating this in the coming week.

Other things to consider:

  • pathing in such a way that not all the characters run in a straight line after their target. A simple solution to this is to generate offsets to the left and right of the player and set those as the destination until the AI is close enough to the player to run straight at them.
  • These AI are quite simple and do not do that great in an open world environment, let alone a procedural one. This is where I believe the Utility AI concept will come in handy.
  • AI should avoid colliding with each other and also should know how to path with other AI of varying sizes
  • The number of AI controllers becomes a problem at a certain point, so I am considering setting up a fixed number of AI controllers and having the densest cluster near the players so that the AI seem more intelligent and all AI behind those AI fall into some sort of group AI controller ( almost RTS style ) as they won’t need to make any choices other than follow the AI in front of them and avoid colliding with each other. That doesn’t even need to be that precise when the distance and fog end up hiding it naturally. Perhaps this is also how I will control the staggered tick.

7 thoughts on “UE4 AI Research (pt. 1)”

  1. Hey just want to say thanks again for leaving the link to this in unreal slackers! Look forward to reading more about your ventures into this. I have a game idea i want to do some mob AI and learn more about Utility AI. (found out about it by Tom Looman)

  2. Hey Starkium, thanks for mentioning me!
    I have done further research in to movement and rendering since then. Culling ticks of occluded units is fine, but I was in need ti render 100+ moving units.
    I ended up making my own AgentCollision avoidance and even Physics (2D physics based on navmesh edges).

    Here is an early video (the only I have for now)

    Around 200 moving agents please notice that the bottleneck is GPU – since then I also optimized the rendering – and so far I have around +/- 60 fps for 200 moving units (some units takes less some more) my goal was to have 100 at decent FPS since I aiming low spec PCs and macs. I have fulfilled my goal with 100 units at 100 fps.

    Maybe I will make a newer video showing that.

    So yet still the movement bottleneck is a SetActorLocation function – it is heavy even with teleport and no sweeps and even when agents are not generating overlap events (please note overlap events are very very tolling ).

    Further optimization could be done to rendering by instancing skeletal meshes, but I am off that because of my goal is reached.

    I thought of making a plugin with all my research – but that will probably come after the release of my game.

    If you need any help regarding movement you can contact me directly and I can explain in details how to achieve that.

    1. I came to a lot of similar conclusions in my research here. I found that houdini allows for instanced skeletal meshes ( it can instance anything derived from UObject ), but I won’t be paying for houdini as of yet. Weirdly enough gpu is not my bottleneck. My gpu is only at 4 ms with 100 AI all moving. Overlaps do play a part, but really what I’m noticing is a lot of cpu waits so my guess is latent actions from movement. Another huge chunk is skeletal. The last biggest chunk comes from all the ai doing calcs at roughly the same time. I think having a data table of some kind or maybe working in blackboard somehow will allow me to “cache” some data instead of redoing a lot of unnecessary work. Recently watched a GDC talk from rare about sea of thieves and how grouping up the ticks or events of similar actors with similar tasks really brought down their bottleneck. https://youtu.be/CBP5bpwkO54

      1. Yes, as I mentioned – once you will rule out the movement, rendering becomes the next biggest bottleneck.

        Back to movement:
        UE4 CMC (Character Movement Component) uses way too much traces because it tries to simulate collisions and physics, So for each move it traces towards the move direction – then for the floor, however if collision occurred it puts the pawn where the collision was and traces again to effectively simulate the whole movement DeltaTime.

        These traces are very expensive.
        Building my own collision tree and Substepping on it for each move (Similar algorithm) gave huge performance gain… in fact on my best PC 200 moving agents takes less than 8 ms… and it can be optimized further… but as I mentioned before – I am not interested in it right now.

          1. Still very interesting, I’ll try stealing that idea from you as an optional movement component in the package. Not sure where to start on that, but might as well give it a go.

Leave a Reply