Welcome to the Mark One Dev Log #2! In this post I’m going to about the logic that drives ranged AI enemies in the game. Before I dive into more details about it in case you want to support my project you can wishlist on Steam using the following link:

Rules of ranged enemies

Each enemy wants to find a suitable location in the level from which he can shoot at the player. This location needs to meet the following criteria:

  • The distance from the player must be <= than the attack range of each enemy (range varies between different types of enemies)
  • The player must be visible from the location that the AI wants to go, otherwise enemies will be shooting at walls and other types of blocking geometries
  • The location shouldn’t block other range enemies from shooting at the player

Considering the above criteria it makes sense that some form of communication between AIs has to be made in order for them to spread around the player and take up different locations in order to attack him. Unreal Engine has a tool called EQS (Enviroment Query System) which I thought it would be ideal for my use case. Unfortunately, while this system is great in the UE4 version that I’m using it’s still experimental and results in a lot of crashes during runtime, which is a deal breaker for my case (by the time of this writing Chris Murphy mentioned on Twitter that EQS isn’t stated as experimental in 4.26). This meant that I had to develop my own system that would allow my enemies to communicate with each other and find the ideal spot.

Behavior of ranged enemies

Every single ranged enemy in the game has the same behavior and the differences come from various parameters such as Damage, Attack Range, Movement Speed etc.. Let’s take a look at the Behavior Tree for their logic

Behavior Tree of Ranged Enemies. Click on image to enlarge in a new tab.

The logic behind the AI is the following:

  • If the pawn is currently patrolling a path, move along the path and when it reaches a patrol node a task just assigns the next patrol node to keep the pawn moving
  • If the AI is alive and has detected the player (in the Behavior Tree context this is the FixatedTarget key) then the enemy is currently in combat.

Combat logic of ranged enemies

When the ranged enemies are in combat first they check if they’re out of their attack range. If that’s the case, then they spring their way through until they reach a certain amount of units far away from the player (this distance varies between enemies that have different attack range).

When enemies are in attack range, their first priority is to “Find a Shooting Spot”, meaning to find a location that meets the criteria I listed two in the Rules of Ranged Enemies section. The “Find Shooting Spot” behavior tree task is a C++ task that created in order to replace the EQS system.

Find Shooting Spot Behavior Tree Task

The logic behind this task is summed up with the following points:

  1. First we check if the current location of the enemy instance meets all the criteria. If that’s the case then we don’t try to find another location since we’re good to go.
  2. If the current location of the enemy isn’t valid for the reasons I listed above then a list of candidate locations is generated around the player. At this step, in each location there’s a visibility trace test going on to verify that the player can be seen from that location. If that test is valid we add the location of our list.
  3. Once the candidate location list is generated, we’re performing an ascending sort of the locations starting from the one which is closest to the AI that is trying to find a new shooting location.
  4. When the list is sorted the AI will select the closest point that doesn’t have a nearby ranged enemy and that is not blocking any other ranged enemy from shooting at the player.
  5. Once a selection has been made, we’re registering the location as “occupied” to a component that has a static list of locations with the assigned AIs. At this point it’s worth mentioning that each time an enemy moves from its location it unregisters that location from the aforementioned list

At this point, once the “Find Shooting Spot” behavior tree finds a valid location in the world, it updates the AI’s blackboard with it and from the logic contained in the Behavior Tree the enemy will start moving to the new location while shooting. If the AI shoots by mistake one of its friendlies then we discard the damage (no friendly fire allowed!).

To sum up the wall of text above, here’s a short video that visualizes the tested locations in a testing map:

Thank you for reading my new dev log! Until next time!