Skip to content

Training · 07/12/2023 · Ankara, Türkiye

Game AI Training at Başkent University

The Computer Society committee and the Artificial Intelligence Study Group of the IEEE Başkent University Student Branch invited us to their AI Training Series. In a session of about ninety minutes, our founder Can Karabulut, a computer engineer, showed how AI algorithms work in games. Our computer engineer Batuhan Tatlısert gave an introduction to game coding. More than 70 students joined, and we put what we learned into practice by coding a game together.

The AI Training Series poster by the IEEE Başkent University Student Branch shows Can Karabulut as the speaker

Below we share what we covered that day, with examples from more than ten years in the software industry. We hope it is a good starting point for anyone who is new to game AI.

Finite state machines

An enemy that patrols, chases the player once it spots them and falls when its health runs out is moving between states. A finite state machine (FSM) is a model that defines how a character, an enemy or a menu moves between such states. Each state stands for one behaviour. Transitions decide when the entity moves from one state to another, for example after a trigger, when a timer runs out or when the player presses a key.

FSMs help game developers in three ways. Complex behaviour stays tidy because it is managed through clear states. Adding a new behaviour or changing an existing one becomes simple. Debugging gets easier too, since it is clear which transition changes each state.

Three ways to build one in Unity

The quickest start is to define the states in an enum and handle them in the update method of a C# script.

public enum EnemyState
{
    Idle,
    Patrol,
    Chase,
    Attack,
    Dead
}

For a more flexible and reusable structure, each state can be its own class (the State pattern). Every state class has EnterState, UpdateState and ExitState methods, and the character or AI class simply runs the current state.

Unity's Animator (Mecanim) is an FSM as well. Each animation clip is a state, and the transitions between them are set with bool, trigger, float and int parameters. For example, the character switches to the run animation when its speed is above zero. The Animator is great for animations. Keeping game logic such as AI in an FSM written in C# is usually the cleaner choice.

A boss character as an example

Think of a powerful boss character with five states. It waits until the player comes close (Idle). Then it starts its first attack patterns (PhaseOne). When its health drops below half, it moves on to new attacks and traps on the floor (PhaseTwo). When it takes a certain hit, it gets angry and attacks faster for a short time, then returns to the second phase (Rage). When its health reaches zero, the death animation plays (Dead). With an FSM, the attack and movement logic of each phase stays separate and easy to maintain.

Pathfinding algorithms

Finding the way from an enemy to the player is a graph problem. Points on the map are nodes and the routes between them are edges. In the training we compared five approaches.

BFS (breadth first search)

BFS explores a graph layer by layer. It first visits the neighbours of the start node, then their neighbours, until every node is reached, and it uses a queue to do so. When all edges have the same weight, BFS finds the shortest path, and it easily lists the nodes at a given distance. On deep graphs with many branches it can use a lot of memory, and when edge weights differ it cannot find the true shortest path.

DFS (depth first search)

DFS goes down one branch to its end, then backtracks and moves on to the branches it has not visited yet. It is written with a stack or with recursion. DFS does not guarantee the shortest path.

Dijkstra's algorithm

Dijkstra finds the shortest path on graphs whose edges have positive weights. It calculates the shortest distances from the start node to all other nodes with a priority queue. Unlike BFS, it also works when the weights differ. A positive weight means that crossing that edge has a cost or a distance. Dijkstra gives wrong results when there are negative weights, and the priority queue operations take more time as the search grows.

The A* algorithm

A* also looks for the shortest path, but it speeds up the search with a heuristic. For every node it works out f(n) = g(n) + h(n). Here g(n) is the real cost from the start to the node and h(n) is the estimated cost from the node to the goal. On grid maps the Manhattan distance, the sum of the horizontal and vertical differences, is a common choice for h(n). The straight line (Euclidean) distance works too. A good heuristic cuts unnecessary branching, so A* is faster than Dijkstra in most cases.

The greedy approach

A greedy algorithm makes the choice that looks best at each step. It is simple to write and fast to compute, and for some problems such as Huffman coding and the algorithms of Prim and Kruskal it reaches the best solution. It does not always find the best overall result, because the best local choices made step by step may not solve the whole problem in the best way.

Comparing them

AlgorithmShortest pathWeightsHeuristicTime complexity
BFSOnly with equal edge weightsNoNoO(V + E)
DFSNoNoNoO(V + E)
DijkstraYesPositiveNoO((V + E) log V)
A*YesPositiveYesO((V + E) log V), faster in practice with a good heuristic
GreedyDepends on the problemVariesNoVaries

Here V is the number of nodes and E the number of edges.

Coding a game together

After the theory it was time to practise. Batuhan's introduction to game coding and the algorithms above came together in a game we wrote with the students. You can watch short moments from the session below.

Thank you

Many thanks to the IEEE Başkent University Student Branch, its Computer Society committee and its Artificial Intelligence Study Group for hosting us, and to every student who joined. If you have a question about game AI, you can always write to us.

Videos

Photos

On social media

Date
07/12/2023
Time
13:00
Location
Başkent University, Ankara, Türkiye