Making an AI director is something that I've wanted to do for a long time. Having a good AI director is something that makes such a big difference in immersion and replayability in games. Two good example of this is the AI in Left for Dead & Alien Isolation. In both of these games the player’s experience is shaped by the AI director dynamically controlling pacing, tension, and challenge. This makes every playthrough feel different and unpredictable, which is also what I'm trying to replicate and experiment with in this project.
This project I've worked on for approximately two months (October 2024 - December 2024), around 20 % part-time.
Implemented in Unreal Engine 5.4
As the name suggest, the main purpose of the AI Director is to direct/manage the game. Most often, the reason you'd want a AI director in your game is to be able to create a dynamic experience for the player, meaning that each playthrough of the same level should feel different from each other.
How you manage to achieve this can be done in many different ways. For example, you can use emotional cues, such as visual effects, sounds, character feedback, and so on. It could also be more direct gameplay elements, such as controlling the enemies, health, ammo, difficulty, and anything that effects the perceived game intensity for the player.
Example of the AI Director config in my project:
For my AI Director project I took a lot of inspiration of the AI Director in Left for Dead. It use three different stages to control the intensity of the game. To the player a stress score is attached and depending on the stress score a tempo stage is selected. Depending on the tempo stage a different spawn rate of enemies and ammo pickups are set.
Build up stage:
This is the default stage at the start of the game. In the build up stage stress points accumulates as you fight enemies. The more intense the fight is perceived the more points are accumulated. The more stress points you have the faster enemies spawn. Once you finally reach the peak threshold a transition to the Swarm stage is made.
Swarm stage:
This is the stage where the intensity of the game is at its highest. A swarm of enemies is thrown at the player for a time based duration. Once the time is up the a transition to the Relax stage is made.
Relax stage:
This stage is a period of time for the player to collect themself after the swarm stage. It's also time based, some enemies will still spawn but in a very limited amount. After the time is up the a transition to the build up stage is made, the stress points are reset and cycle beings over again.
Map of the playable area:
To calculate the stress score during the build-up stage, I decided to utilize Unreal's Environment Query System (EQS). I found that EQS was an effective way to combine the factors I wanted to influence the score. Every time an enemy dies, the query is run, and a score is generated.
The factors affecting the score are:
Distance to Enemy killed:
This factor is fairly self-explanatory. It refers to how far you are from the enemy at the moment of its death. The closer you are, the more intense the fight is perceived to be, resulting in a higher stress score.
This check is performed using the predefined Distance Test in Unreal Engine.
The line of sight to the enemy:
This factor is used as a weight to determine whether the player is behind cover when killing an enemy. Whenever an enemy dies, a line trace is sent from the enemy’s position toward the player. If the trace hits a blocking object, a lower score is generated.
Since this test is not implemented in Unreal Engine by default, I need to define it myself in C++.
Angle of facing Enemy:
This check determines the angle at which the enemy is facing when it dies. For example, if the enemy is facing away from the player, the stress score will be lower compared to when the enemy is looking directly at them.
This EQS test was also implemented in C++.
Here you can see all these factors reflected in the EQS test pawn:
As you can tell by the image:
In my project, I have two types of enemies:
Spawned in melee:
The logic for this enemy is very simple. When it spawns, it move towards the player and once it's close enough it does a melee attack.
Patrolling ranged:
The patrolling ranged is also simple. When the player is detected it does the attack sequence, else it does the patrol sequence.
Most of the behavior tree tasks are defined in c++ and then set up in the behavior tree unreal editor.
Example: Behavior tree for the patrolling ranged enemy:
To see if the player is detected I made a simple Detection Component in c++.
Example of the Behavior tree task (AimAtPlayer):
- Achieved my goal of creating a dynamic experience for the player.
- The Director is very customizable. It's fun to experiment with, tweaking the stress score generation.
- Learned and became very comfortable working with the AI infrastructure in Unreal. Especially in c++ for behavior tree and EQS task.
- Could not spend as much time as I wanted on the project since I had classes at the same time.
- Became much simpler than I wanted since I didn't have that much time to put into the project. I also prioritized deep diving into the GOAP-system over continuing on this project.
- It was a bit difficult to test in the beginning since you need a game and enemy setup to be able to test it well. For example, adjusting the enemy logic and difficulty may result in the AI Director feeling unbalanced.
- Would be fun to expand the area the AI Director effects and perhaps implement it in an actual game. For example, it could have visual effects, audio effects and perhaps even effect the layout of the map. This would make the AI Director having a much larger impact on the perceived pacing and experience for the player.
- At the start I planned to have a lot more different enemies and have the AI Director also effect the behavior of the enemies. For example, during Swarm stage all the enemies would be aggressive and in the Relax stage they would be more complacent. However, in the end I prioritized to keep this project simple to be able to have time with the GOAP-system.