1. Code
  2. Coding Fundamentals
  3. Game Development

Create a Hockey Game AI Using Steering Behaviors: Defense

Scroll to top
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: Attack
Create a Hockey Game AI Using Steering Behaviors: Game Mechanics

This tutorial is the final part in the process of coding a hockey game using steering behaviors and finite state machines. Here, we will improve our athletes' artificial intelligence to allow them to defend their goal against their opponents. We'll also make our athletes perform some attack tactics while they are defending, so they can recover the puck and terminate the opponent's offensive.

A Few Words About Defending

In a competitive game like hockey, the defense process is much more than just rushing to the team's goal area to prevent the opponent from scoring. Prevent the opponent from scoring is just one of the many tasks involved.

If a team focuses on score prevention tactics alone, all athletes will become merely obstacles along the way. The opponent will keep pushing, trying to find a spot in the defense formation. It will take time, but eventually the opponent will score.

The defense process is a mixture of defensive and offensive actions. The best way to terminate the opponent's attack, which is the defense objective, is to attack while defending. It might sound a bit confusing, but it makes perfect sense.

An athlete defending his team should move towards his goal, preventing the opponent from scoring. Along the way, he must try to steal the puck from the opponent who is carrying it, or intercept the puck when it is exchanged among the opponents. That's exactly what this tutorial will try to implement: a defensive tactic with attack actions.

Combining Attack and Defense

In order to achieve a defensive behavior that has some attack aspects in it, we'll add two new states to the AI finite-state machine:

A stack-based finite state machine representing the attack and the defense processes..

The defend state will be the foundational stone in the defense process. While in that state, athletes will move towards their side of the rink, always trying to recover the puck to terminate the opponent's offensive. 

The patrol state will complement the defense process. It will prevent athletes from standing still when they reach their defense position in the rink. This state will keep athletes moving and patrolling the area, which will produce a more convincing result.

Understanding the Defend State

The defend state is based on a very simple idea. When it is active, each athlete will move towards their initial position in the rink. We already used this position, described by the mInitialPosition property in the Athlete class, to implement the prepareForMatch state in the first tutorial in this series.

While moving towards his initial position, an athlete will try to perform some attack actions against the opponent if he is close enough and is carrying the puck. For instance, if the athlete is moving and the opponent's leader (the one with the puck) becomes a neighbor, the defend state will be replaced with something more appropriate, such as the stealPuck state.

Since athletes tend to be spread through the whole rink while attacking, when they switch to defend and start returning to their initial position, they will cover a significant area, ensuring a convincing defense pattern:

Athletes performing attack actions while returning to their initial defense positions.

Some athletes will not encounter opponents along the way, so they will just move towards their initial position. Other athletes, however, might get close to some interesting opponents, such as the leader (the one carrying the puck).

Implementing the Defend State

The defend state will have four transitions:

The defend state and its transitions in the FSM describing the defense process..

Three of them, team has the puck, close to opponent leader, and puck has no owner, are related to attack actions. They will be responsible for making athletes look like they are attacking opponents while moving to defend the team's goal. The in position transition will be triggered when the athlete finally arrives at his initial position in the rink.

The first step in implementing the defend state is to make the athlete move towards his initial position. Since he must slow down as he gets closer to the destination, the arrive steering behavior is a perfect fit:

1
class Athlete {
2
    // (...)

3
 
4
    private function defend() :void {
5
		var aPuckOwner :Athlete = getPuckOwner();
6
		
7
		// Move towards the initial position, arriving there smoothly.

8
		mBoid.steering = mBoid.steering + mBoid.arrive(mInitialPosition);
9
		
10
		// Does the puck has an owner?

11
		if (aPuckOwner != null) {
12
			// Yeah, it has. Who has it?

13
			if (doesMyTeamHasThePuck()) {
14
				// My team has the puck, time to stop defending and start attacking!

15
				mBrain.popState();
16
				mBrain.pushState(attack);
17
				
18
			} else if (Utils.distance(aPuckOwner, this) < 150) {
19
				// An opponent has the puck and he is close to us!

20
				// Let's try to steal the puck from him.

21
				mBrain.popState();
22
				mBrain.pushState(stealPuck);
23
			}
24
		} else {
25
			// No, the puck has no owner, it is running in the rink.

26
			// There is no point to keep defending the goal, because nobody has the puck.

27
			// Let's switch to 'pursuePuck' and try to get the puck to our team.

28
			mBrain.popState();
29
			mBrain.pushState(pursuePuck);
30
		}
31
	}
32
     
33
    // (...)

34
}

The arrive behavior will create a force that will push the athlete towards his initial position (mInitialPosition) while the defend state is active. After the arrive force calculation, in this code, we run a sequence of tests that will check the puck's ownership and the proximity of opponents, popping the defend state from the brain and pushing a new one according to the situation.

If the puck has no owner, it is probably moving freely in the rink. In that case, the pursuePuck state will be pushed into the brain (line 29). If the puck has a team owner, it means the defense process is over and it is time to attack (line 16). Finally if the puck's owner belongs to the opponent team and he is close enough, stealPuck will be pushed into the brain (line 22).

The result is a team that is able to defend their goal, pursuing and trying to steal the puck from the opponent carrying it. Below is a demonstration of the current defend implementation:

Patrolling the Area

The current defense behavior is acceptable, but it can be tweaked a little bit to be more convincing. If you analyse the previous demo, you may eventually notice that athletes will stop and stand still after they reach their initial position while defending. 

If an athlete returns to his initial position without encountering any opponents along the way, he will remain still until an opponent with the puck passes by or the team recovers the puck. 

We can improve this behavior by adding a patrol state, which gets pushed into the brain by the defend state when the athlete reaches his initial position:

The patrol state and its transitions in the FSM describing the defense process.

The patrol state is extremely simple. When active, it will make athletes move around randomly for a short time, which visually mimics the expected behavior from an athlete trying to defend a spot in the rink.

When the distance between the athlete and his initial position is grater than 10, for instance, patrol pops itself from the brain and pushes defend. If the athlete arrives at his initial position again while defending, patrol is pushed once more into the brain and the process repeats:

Demonstration of the patrol state.

The random movement pattern required by the patrol state can be easily achieved with the wander steering behavior. The implementation of the patrol state is:

1
class Athlete {
2
    // (...)

3
 
4
    private function patrol() :void {
5
		mBoid.steering = mBoid.steering + mBoid.wander();
6
		
7
		// Am I too far away from my initial position?

8
		if (Utils.distance(mInitialPosition, this) > 10) {
9
			// Yeah, I am. It's time to stop patrolling and go back to

10
			// my initial position.

11
			mBrain.popState();
12
			mBrain.pushState(defend);
13
		}
14
	}
15
     
16
    // (...)

17
}

The distance check (line 8) ensures that the athlete will patrol a small area around his initial position instead of leaving his initial defense position completely unattended.

The results of using the patrol state is a more convincing behavior:

Putting It All Together

During the implementation of the stealPuck state in the previous tutorial, there was a situation where athletes should switch to the defend state. However that state was not implemented back then.

While trying to steal the puck (the stealPuck state), if the opponent is too far away from the athlete, it's pointless to keep trying to steal the puck. The best option in that situation is to pop the stealPuck state and push defend, hoping that a teammate will be closer to the opponent's leader to steal the puck.

The stealPuck state must be changed (lines 28 and 29) to allow athletes to push the defend state in that situation:

1
class Athlete {
2
    // (...)

3
 
4
    private function stealPuck() :void {
5
		// Does the puck has any owner?

6
		if (getPuckOwner() != null) {
7
			// Yeah, it has, but who has it?

8
			if (doesMyTeamHasThePuck()) {
9
				// My team has the puck, so it's time to stop trying to steal

10
				// the puck and start attacking.

11
				mBrain.popState();
12
				mBrain.pushState(attack);
13
			} else {
14
				// An opponent has the puck.

15
				var aOpponentLeader :Athlete = getPuckOwner();
16
				
17
				// Is the opponent with the puck close to me?

18
				if (Utils.distance(aOpponentLeader, this) < 150) {
19
					// Yeah, he is close! Let's pursue him while mantaining a certain

20
					// separation from the others to avoid that everybody will ocuppy the same

21
					// position in the pursuit.

22
					mBoid.steering = mBoid.steering + mBoid.pursuit(aOpponentLeader.boid);
23
					mBoid.steering = mBoid.steering + mBoid.separation(50);
24
					
25
				} else {
26
					// No, he is too far away. Let's switch to 'defend' and hope

27
					// someone closer to the puck can steal it for us.

28
					mBrain.popState();
29
					mBrain.pushState(defend);
30
				}
31
			}
32
		} else {
33
			// The puck has no owner, it is probably running freely in the rink.

34
			// There is no point to keep trying to steal it, so let's finish the 'stealPuck' state

35
			// and switch to 'pursuePuck'.

36
			mBrain.popState();
37
			mBrain.pushState(pursuePuck);
38
		}
39
	}
40
     
41
    // (...)

42
}

After updating the stealPuck state, athletes are now able to organize attack and defense tactics, making two AI-controlled teams able to play against each other.

The result is demonstrated below:

Conclusion

In this tutorial, we implemented a defense tactic used by athletes to defend their goal from opponents. We then improved the defend state by adding some attack actions, such as to attempt to steal the opponent's puck, which made the defense tactic feel more natural and convincing.

We also improved the feel of the defense behavior by adding an extremely simple, yet powerful state, the patrol. The idea is to prevent athletes from standing still while defending their team's goal.

And with that, we've created a full AI system for our hockey game! 

References

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.