1. Game Development
  2. Artificial Intelligence

Create a Hockey Game AI Using Steering Behaviors: Attack

This post is part of a series called Create AI for a Hockey Game Using Steering Behaviors.
Create a Hockey Game AI Using Steering Behaviors: Foundation
Create a Hockey Game AI Using Steering Behaviors: Defense

In this tutorial, we continue coding artificial intelligence for a hockey game using steering behaviors and finite state machines. In this part of the series, you will learn about the AI required by game entities to coordinate an attack, which involves intercepting and carrying the puck to the opponent's goal.

A Few Words About Attacking

Coordinating and performing an attack in a cooperative sport game is a very complex task. In the real world, when humans play a hockey game, they make several decisions based on many variables.

Those decisions involve calculations and understanding what is going on. A human can tell why an opponent is moving based on the actions of another opponent, for instance, "he is moving to be in a better strategic position." It's not trivial to port that understanding to a computer.

As a consequence, if we try to code the AI to follow all the human nuances and perceptions, the result will be a huge and scary pile of code. Additionally, the result might not be precise or easily modifiable.

That's the reason why our attack AI will try to mimic the result of a group of humans playing, not the human perception itself. That approach will lead to approximations, but the code will be easier to understand and tweak. The outcome is good enough for several use cases.

Organizing the Attack With States

We'll break the attack process down into smaller pieces, with each one performing a very specific action. Those pieces are the states of a stack-based finite state machine. As previously explained, each state will produce a steering force that will make the athlete behave accordingly.

The orchestration of those states and the conditions to switch among them will define the attack. The image below presents the complete FSM used in the process:

A stack-based finite state machine representing the attack process.

As illustrated by the image, the conditions to switch among the states will be solely based on the puck's distance and ownership. For instance, team has the puck or puck is too far away.

The attack process will be composed of four states: idle, attack, stealPuck, and pursuePuck. The idle state was already implemented in the previous tutorial, and it is the starting point of the process. From there, an athlete will switch to attack if the team has the puck, to stealPuck if the opponent's team has the puck, or to pursuePuck if the puck has no owner and it is close enough to be collected.

The attack state represents an offensive movement. While in that state, the athlete carrying the puck (named leader) will try to reach the opponent's goal. Teammates will move along, trying to support the action.

The stealPuck state represents something between a defensive and an offensive movement. While in that state, an athlete will focus on pursuing the opponent carrying the puck. The objective is to recover the puck, so the team can start attacking again.

Finally, the pursuePuck state is not related to attack or defense; it will just guide the athletes when the puck has no owner. While in that state, an athlete will try to get the puck that is freely moving on the rink (for instance, after being hit by someone's stick).

Updating the Idle State

The idle state that was previously implemented had no transitions. Since this state is the starting point for the whole AI, let's update it and make it able to switch to other states.

The idle state has three transitions:

 The idle state and its transitions in the FSM describing the attack process.

If the athlete's team has the puck, idle should be popped from the brain and attack should be pushed. Similarly, if the opponent's team has the puck, idle should be replaced by stealPuck. The remaining transition happens when nobody owns the puck and it is close to the athlete; in that case, pursuePuck should be pushed into the brain.

The updated version of idle is as follows (all other states will be implemented later):

Let's proceed with the implementation of the other states.

Pursuing the Puck

Now that the athlete has gained some perception about the environment and is able to switch from idle to any state, let's focus on pursuing the puck when it has no owner.

An athlete will switch to pursuePuck immediately after the match begins, because the puck will be placed at the center of the rink with no owner. The pursuePuck state has three transitions:

The pursuePuck state and its transitions in the FSM describing the attack process.

The first transition is puck is too far away, and it tries to simulate what happens in a real game regarding chasing the puck. For strategic reasons, usually the athlete closest to the puck is the one that tries to catch it, while the others wait or try to help.

Without switching to idle when the puck is distant, every AI-controlled athlete would pursue the puck at the same time, even if they are away from it. By checking the distance between the athlete and the puck, pursuePuck pops itself from the brain and pushes idle when the puck is too distant, which means the athlete just "gave up" pursuing the puck:

When the puck is close, the athlete must go after it, which can be easily achieved with the seek behavior. Using the puck's position as the seek destination, the athlete will gracefully pursue the puck and adjust his trajectory as the puck moves:

The remaining two transitions in the pursuePuck state, team has the puck and opponent has the puck, are related to the puck being caught during the pursue process. If somebody catches the puck, the athlete must pop the pursuePuck state and push a new one into the brain. 

The state to be pushed depends on the puck's ownership. If the call to doesMyTeamHaveThePuck() returns true, it means that a teammate got the puck, so the athlete must push attack, which means it's time to stop pursuing the puck and start moving towards the opponent's goal. If an opponent got the puck, the athlete must push stealPuck, which will make the team try to recover the puck.

As a small enhancement, athletes should not remain too close from each other during the pursuePuck state, because a "crowded" pursuing movement is unnatural. Adding separation to the state's steering force (line 6 in the code above) ensures athletes will keep a minimum distance among them.

The result is a team that's able to pursue the puck. For the sake of testing, in this demo, the puck is placed at the center of the rink every few seconds, to make the athletes move continually:

Attacking With the Puck

After obtaining the puck, an athlete and his team must move towards the opponent's goal to score. That's the purpose of the attack state:

The attack state and its transitions in the FSM describing the attack process.

The attack state has only two transitions: opponent has the puck and puck has no owner. Since the state is solely designed to make athletes move towards the opponent's goal, there is no point to remain attacking if the puck is not under the team's possession any more.

Regarding the movement towards the opponent's goal: the athlete carrying the puck (leader) and the teammates helping him should behave differently. The leader must reach the opponent's goal, and the teammates should help him along the way.

This can be implemented by checking whether the athlete running the code has the puck:

If amIThePuckOwner() returns true (line 10), the athlete running the code has the puck. In that case, he will just seek the opponent's goal position. That's pretty much the same logic used to pursue the puck in the pursuePuck state.

If amIThePuckOwner() returns false, the athlete doesn't have the puck, so he must help the leader. Helping the leader is a complicated task, so we will simplify it. An athlete will assist the leader just by seeking a position ahead of him:

Teammates assisting the leader.

As the leader moves, he will be surrounded by teammates as they follow the ahead point. This gives the leader some options to pass the puck to if there's any of trouble. As in a real game, the surrounding teammates should also stay out of the leader's way.

This assistance pattern can be achieved by adding a slightly modified version of the leader following behavior (line 18). The only difference is that athletes will follow a point ahead of the leader, instead of one behind him as was originally implemented in that behavior.

Athletes assisting the leader should also keep a minimum distance among each other. That's implemented by adding a separation force (line 19).

The result is a team able to move towards the opponent's goal, without crowding and while simulating an assisted attack movement:

Improving the Attack Support

The current implementation of the attack state is good enough for some situations, but it has a flaw. When someone catches the puck, he becomes the leader and is immediately followed by teammates.

What happens if the leader is moving towards his own goal when he catches the puck? Take a closer look at the demo above and notice the unnatural pattern when teammates start following the leader.

When the leader catches the puck, the seek behavior takes some time to correct the leader's trajectory and effectively make him move towards the opponent's goal. Even when the leader is "maneuvering", teammates will try to seek his ahead point, which means they will move towards their own goal (or the place that the leader is staring at).

When the leader is finally in position and ready to move towards the opponent's goal, teammates will be "maneuvering" to follow the leader. The leader will then move without teammate support for as long as the others are adjusting their trajectories.

This flaw can be fixed by checking whether the teammate is ahead of the leader when the team recovers the puck. Here, the condition "ahead" means "closer to the opponent's goal":

If the leader (who is the puck owner) is ahead of the athlete running the code, then the athlete should follow the leader just like he was doing before (lines 27 and 28). If the leader is behind him, the athlete should hold his current position, keeping a minimum distance between the others (line 33).

The result is a bit more convincing than the initial attack implementation:

Tip: By tweaking the distance calculations and comparisons in the isAheadOfMe() method, it's possible to modify the way athletes hold their current positions.

Stealing the Puck

The final state in the attacking process is stealPuck, which becomes active when the opposing team has the puck. The main purpose of the stealPuck state is to steal the puck from the opponent carrying it, so that the team can start attacking again:

 The stealPuck state and its transitions in the FSM describing the attack process.

Since the idea behind this state is to steal the puck from the opponent, if the puck is recovered by the team or it becomes free (that is, it has no owner), stealPuck will pop itself from the brain and push the right state to deal with the new situation:

If the puck has an owner and he belongs to the opponent's team, the athlete must pursue the opposing leader and try to steal the puck. In order to pursue the opponent's leader, an athlete must predict where he will be in the near future, so he can be intercepted in his trajectory. That's different from just seeking the opposing leader.

Fortunately, this can be easily achieved with the pursue behavior (line 19). By using a pursuit force in the stealPuck state, athletes will try to intercept the opponent's leader, instead of just following him:

Preventing a Crowded Steal Movement

The current implementation of stealPuck works, but in a real game only one or two athletes approach the opponent leader to steal the puck. The rest of the team remains in the surrounding areas trying to help, which prevents a crowded stealing pattern.

It can be fixed by adding a distance check (line 17) before the opponent's leader pursuit:

Instead of blindly pursuing the opponent's leader, an athlete will check whether the distance between him and the opponent leader is less than, say, 150. If that's true, the pursuit happens normally, but if the distance is greater than 150, it means the athlete is too far from the opponent leader.

If that happens, there is no point in continuing trying to steal the puck, since it is too far away and there are probably teammates already in place trying to do the same. The best option is to pop stealPuck from the brain and push the defense state (which will be explained in the next tutorial). For now, an athlete will just hold his current position if the opponent leader is too far away.

The result is a more convincing and natural stealing pattern (no crowding):

Avoiding Opponents While Attacking

There is one last trick that the athletes must learn in order to attack effectively. Right now, they move towards the opponent's goal without considering the opponents along the way. An opponent must be seen as a threat, and should be avoided.

Using the collision avoidance behavior, athletes can dodge opponents while they move:

Collision avoidance behavior used to avoid opponents.

Opponents will be seen as circular obstacles. As a result of the dynamic nature of steering behaviors, which are updated in every game loop, the avoidance pattern will gracefully and smoothly work for moving obstacles (which is the case here).

In order to make athletes avoid opponents (obstacles), a single line must be added to the attack state (line 14):

This line will add a collision avoidance force to the athlete, which will be combined with the forces that already exist. As a result, the athlete will avoid obstacles at the same time as seeking the opponent's goal.

Below is a demonstration of an athlete running the attack state. Opponents are immovable to highlight the collision avoidance behavior:


This tutorial explained the implementation of the attack pattern used by the athletes to steal and carry the puck towards the opponent's goal. Using a combination of steering behaviors, athletes are now able to perform complex movement patterns, such as following a leader or pursuing the opponent with the puck.

As previously discussed, the attack implementation aims to simulate what humans do, so the result is an approximation of a real game. By individually tweaking the states that compose the attack, you can produce a better simulation, or one that fits your needs.

In the next tutorial, you will learn how to make athletes defend. The AI will become feature-complete, able to attack and defend, resulting in a match with 100% AI-controlled teams playing against each other.


Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.