Semih's Portfolio

Voice of the Ocean - NPC AI - Part 1


The last few months, my team and I have been hard at work, creating an underwater world. Touched by the lack of humans. Reclaimed by nature.

We're making a game with a narrative and unique gameplay, using the player's voice to interact with the world. One of the things the player can interact with, are sea animals. Mainly pufferfish and sharks.

I've been given the task of programming the AI of these underwater creatures. I'd like to go over my thought process and how I am implementing these behaviors.

Some context

Before diving into how I'm implementing the AI, I'd like to go over the development environment and some terminology that's linked to the AI systems.

This is the first part of a multi-part blogpost.

The Engine

We're using Unreal Engine 5.4 to develop our game, known for its rendering capabilities and triple-A nature it was a good fit for our project where visuals and narrative are key.

Unreal's AI Tools

Unreal comes pre packaged with tools that make programming and setting up NPC AI easier. I'll be listing the ones I use and add what I have since learned about them.

AI Controller

This is a subclass of the Controller class that enables the AI systems to "Possess" the controlled pawn/character and take control of their movement.

Behavior Tree

Behavior Trees are assets that define the actions an AI takes. The AI's behavior is composed by the developer by placing composite nodes and tasks in the order that they should fire, from top to bottom and from left to right.

Composite Nodes

Composite nodes are nodes that don't execute any logic themselves. They are used to structure the behavior tree and can control the flow of execution depending on the success or failure of the tasks the composite contains.

There are two main composite nodes:

  1. The Selector: This composite executes the nodes below it until one of them returns a success. Think of this like selecting the most suitable task. When the nodes below a selector are arranged correctly (higher priority to the left), the selector will go through all of them until it finds one that returns a success and if it can't find one, will return a failure.
  2. The Sequence: This one executes all the sub nodes, again from left to right, but only returns successful if all the sub nodes returns a success. If even one fails, the whole sequence fails. As the name suggests, this composite is used to define a set of actions that need to happen in a sequence that can't be changed.

Besides returning "Succeeded" or "Failed", task nodes can also return "Aborted" and "InProgress". The last one being useful when a task needs a certain condition to be met before the behavior tree's execution moves along.

Blackboard

The blackboard is a data container that is linked to a Behavior Tree. It allows the developer to set "Keys" at runtime and retrieve their values to make the AI aware of various things in the game world.

Behavior Tree Tasks

The behavior tree that unreal provides does not come with a whole lot of variety in tasks. So it's up to the developer to create custom tasks, either by Blueprints or by native C++. The latter being harder to develop, but more performant.

These tasks derive from a base BTTask class that defines the set of functions the developer is allowed to override. The most basic one being "ExecuteTask".

While tasks are easy to create and use, it's best to think modularly when creating them. Create them with reusability in mind. Leverage the fact that Tasks can access the Blackboard.

Environment Query System

This is an additional system Unreal Engine provides that allows AI agents to query their surroundings for a target location or actor.

The Environment Query

This is the asset that defines what the query actually contains. The query consists of a Generator and Tests, where the Generator generates an item and the tests can filter and/or score them.

Generators

Using Generators we can generate:

If these are not enough, Unreal also provides us with a way to make custom generators using Blueprints or native C++, just like with behavior tree tasks. These then derive from EnvQueryGenerator_BlueprintBase and EnvQueryGenerator respectively.

Query Contexts

Query Contexts define a frame of reference from which a generator or test will operate. A simple example of this is the Querier context that returns the actor that is performing the Environment Query.

Contexts can provide generators and tests with one of the following:

Of course, Unreal has some Contexts ready to use for us:

Contexts are very useful if we have certain logic that needs to be performed around a certain actor or location. Creating our own Contexts is as easy as creating a Blueprint derived from EnvQueryContext_BlueprintBase or if C++ is preferred EnvQueryContext.

Tests

Tests are used to score and/or filter items produced by generators. Filtering outright removes an item that doesn't pass a test, while scoring determines the "best" item out of all of them.

Each node can be filtered based on a min & max value or if a boolean value matches. This depends on the test type its returned value type.

Scoring has a similar setup. Raw test values can:

Test values are scored based on a scoring equation that is also up to the developer:

The equation maps the raw scores to the chosen equation, making the scoring more intuitive and easier to work with.

Unreal Engine comes with a list of tests that can be very useful in a variety of situations and again, if you need more or these test just don't cut it for you, you can create your own through C++ code. Currently, there seems to be no support for creating tests through Blueprints.

The built-in tests are:

End of part 1

Thanks for reading! This was an overview of what I know and have learned thus far, which allowed me to implement the behaviors I'll specify in the next posts.

Hope to see you there!

References:
Unreal Documentation - EQS
Unreal Documentation - EQS - Tests
Unreal Documentation - EQS - Generators
Unreal Documentation - EQS - Query Contexts
The Games Dev - All about BTTasks in C++
Github - Unreal AI Module