Enemy Logic – AI
Enemy Logic – AI

Enemy Logic – AI

Behavior Tree

We opted to utilize behavior trees for our enemy movements for two main reasons:

  1. Behavior trees allow for each enemy to have its personality
  2. It is easier to expand on that personality and create presets for each type of movement

If you are unfamiliar with a behavior tree, it is a node-based system where the entity chooses its actions based on the context around it. I won’t go into much detail, but for our enemies, it went something like this:

  1. Initialization – The enemy was initialized, and its behavior tree starts, following a series of nodes
  1. Spawned? – It cannot do anything else until the enemy has completed its spawn process. For some enemies this means waiting until the player is in range, others may have different conditions or spawn immediately to patrol.
  1. Attack, Pursue, or Patrol
    • The enemy will attack if the player is in range and conditions have been met.
    • Otherwise, if a player is in pursuit range and has been spotted, the enemy will pursue until it reaches the last spotted location.
    • If the player isn’t in sight, then the enemy will idle or patrol, or do a custom behavior until one of the above is fulfilled
  2. Death – Upon death, the enemy will play its death animation and return to the enemy pool.

The behavior tree attempts to do certain behaviors in order, and if the conditions of a certain behavior are not met it switches to a second condition.

Optimization

The behavior tree also allows us to “recycle” states across enemies with ease. Like state machines, we can drop a node containing the patrol logic and modify some parameters to adjust it to the enemy’s settings.

We optimize movement by using simple ray casts to alternate between linear movement and more complex pathfinding. Check out Enemy Logic to learn more.

Behavior trees also allow us to keep enemy logic overhead at a minimum. An enemy that is patrolling will not be running any other code in the tree, so we can focus on making that particular node as light as possible.

Event System

We need to make sure the entire tree is not cycling unnecessarily or doing checks every tick. This usually happens as enemies calculate the player’s distance multiple times a second to determine if they are in range.
A simple, in-built solution is to use colliders and the Unity event system:

Each enemy has a pair of colliders to determine their “detection” and “attack” range.
Once the player enters one or more of these colliders, events are triggered and flags are set:

  • InDetRange -> Checks if in sight (raycast)
    • InSight event, and inSight bool = true
    • Which “stops” any idle or patrol logic and sets the enemy chasing
  • InAttRange event and inAttRange bool = true
    • Which “stops” any current logic and the enemy switches to an “attack” branch.

For example, the Blightwold idles until the player enters the range. It will continue to “idle” until that event is triggered. There are no constant checks for the player’ in the range,’s position.

Once the player is nearby it is alerted and its sight expands.

In the Blightwolf’s case, If the player remains in its sight for too long it will slowly pursue, otherwise, it will patrol. The wolf will creep toward the player for a few seconds before attacking, giving them a chance to flee or prepare.