9 min read

Many of the most popular games on the planet have one thing in common: they all have rich, vivid worlds for the player to inhabit and interact with. This doesn’t just mean a huge terrain or an extensive map (although it might do), it could simply be how things appear within the world. Similarly, it’s not just about the environment – it’s also about characters who are able to react in different ways according to the game.

The only way to achieve an impressive level of ‘realism’ is through powerful artificial intelligence. This isn’t easy, but it can be done. And learning how to do it will be well worth it, as it will create a much more engaging end product for players.

This tutorial is taken from the book Practical Game AI Programming by Micael DaGraca. This book teaches you to create Game AI and implement cutting-edge AI algorithms from scratch.

Let’s take a look at how we can use AI to create rich environments.

Breaking down the game environment by area

When we create a map, often we have two or more different areas that could be used to change the gameplay, areas that could contain water, quicksand, flying zones, caves, and much more. If we wish to create an AI character that can be used in any level of our game, and anywhere, we need to take this into consideration and make the AI aware of the different zones of the map. Usually, that means that we need to input more information into the character’s behavior, including how to react according to the position in which he is currently placed, or a situation where he can choose where to go.

Should he avoid some areas? Should he prefer others? This type of information is relevant because it makes the character aware of the surroundings, choosing or adapting and taking into consideration his position. Not planning this correctly can lead to some unnatural decisions.

For example, in Elder Scrolls V: Skyrim developed by Bethesda Softworks studio, we can watch some AI characters of the game simply turning back when they do not have information about how they should behave in some parts of the map, especially on mountains or rivers.

Depending on the zones that our character finds, he might react differently or update his behavior tree to adapt to his environment. The environment that surrounds our characters can redefine their priorities or completely change their behaviors.

This is a little similar to what Jean-Jacques Rousseau said about humanity: “We are good by nature, but corrupted by society.” As humans, we are a representation of the environment that surrounds us, and for that reason, artificial intelligence should follow the same principle.

Let’s pick a  soldier and update his code to work on a different scenario. We want to change his behavior according to three different zones, beach, river, and forest. So, we’ll create three public static Boolean functions with the names Beach, Forest and River; then we define the zones on the map that will turn them on or off.

 public static bool Beach;
 public static bool River;
 public static bool Forest;

Because in this example, just one of them can be true at a time, we’ll add a simple line of code that disables the other options once one of them gets activated.

if(Beach == true) 
{
 Forest = false;
 River = false;
 }
if(Forest == true){
Beach = false;
River = false;
}

if(River == true){
Forest = false;
Beach = false;
}

Once we have that done, we can start defining the different behaviors for each zone. For example, in the beach zone, the characters don’t have a place to get cover, so that option needs to be taken away and updated with a new one. The river zone can be used to get across to the other side, so the character can hide from the player and attack from that position.

To conclude, we can define the character to be more careful and use the trees to get cover. Depending on the zones, we can change the values to better adapt to the environment, or create new functions that would allow us to use some specific characteristics of that zone.

if (Forest == true)
 {// The AI will remain passive until an interaction with the player occurs
 if (Health == 100 && triggerL == false && triggerR == false && triggerM == false)
 {
 statePassive = true;
 stateAggressive = false;
 stateDefensive = false;
 }

// The AI will shift to the defensive mode if player comes from the right side or if the AI is below 20 HP

if (Health <= 100 && triggerR == true || Health <= 20)
{
statePassive = false;
stateAggressive = false;
stateDefensive = true;
}

// The AI will shift to the aggressive mode if player comes from the left side or it’s on the middle and AI is above 20HP

if (Health > 20 && triggerL == true || Health > 20 && triggerM == true)
{
statePassive = false;
stateAggressive = true;
stateDefensive = false;
}

walk = speed * Time.deltaTime;
walk = speedBack * Time.deltaTime;
}

Advanced environment interactions with AI

As the video game industry and the technology associated with it kept evolving, new gameplay ideas appeared, and rapidly, the interaction between the characters of the game and the environment became even more interesting, especially when using physics. This means that the outcome of the environment could be completely random, where it was required for the AI characters to constantly adapt to different situations.

One honorable mention on this subject is the video game Worms developed by Team17, where the map can be fully destroyed and the AI characters of the game are able to adapt and maintain smart decisions.

The objective of this game is to destroy the opponent team by killing all their worms, the last man standing wins. From the start, the characters can find some extra health points or ammunition on the map and from time to time, it drops more points from the sky.

So, there are two main objectives for the character, namely survive and kill. To survive, he needs to keep a decent amount of HP and away from the enemy, the other part is to choose the best character to shoot and take as much health as possible from him. Meanwhile, the map gets destroyed by the bombs and all of the fire power used by the characters, making it a challenge for artificial intelligence.

Adapting to unstable terrain

Let’s decompose this example and create a character that could be used in this game. We’ll start by looking at the map. At the bottom, there’s water that automatically kills the worms. Then, we have the terrain where the worms can walk, or destroy if needed. Finally, there’s the absence of terrain, specifically, the empty space that cannot be walked on. Then we have the characters (worms) they are placed in random positions at the beginning of the game and they can walk, jump, and shoot.

The characters of the game should be able to constantly adapt to the instability of the terrain, so we need to use that and make it part of the behavior tree. As demonstrated in the diagram above, the character will need to understand the position where he is currently placed, as well as the opponent’s position, health, and items.

Because the terrain can be blocking them, the AI character has a chance of being in a situation where he cannot attack or obtain an item. So, we give him options on what to do in those situations and many others that he might find, but the most important is to define what happens if he cannot successfully accomplish any of them. Because the terrain can be shaped into different forms, during gameplay there will be times that it is near impossible to do anything, and that is why we need to provide options on what to do in those situations.

For example, in this situation where the worm doesn’t have enough free space to move, a close item to pick up, or an enemy that can be properly attacked, what should he do? It’s necessary to make information about the surroundings available to our character so he can make a good judgment for that situation.

In this scenario, we have defined our character to shoot anyway, against the closest enemy, or to stay close to a wall. Because he is too close to the explosion that would occur from attacking the closest enemy, he should decide to stay in a corner and wait there until the next turn.

Using raycast to evaluate decisions

raycast

Ideally, at the start of the turn, the character has two raycasts, one for his left side and another for the right side. This will check if there’s a wall obstructing one of those directions. This can be used to determine what side the character should be moving toward if he wants to protect himself from being attacked. Then, we would use another raycast in the aim direction, to see if there’s something blocking the way when the character is preparing to shoot. If there’s something in the middle, the character should be calculating the distance between the two to determine if it’s still safe to shoot.

So, each character should have a shared list of all of the worms that are currently in the game; that way they can compare the distance between them all and choose which of them are closest and shoot them. Additionally, we add the two raycasts to check if there’s something blocking the sides, and we have the basic information to make the character adapt to the constant modifications of the terrain.

public int HP;
 public int Ammunition;
public static List<GameObject> wormList = new List<GameObject>(); 
//creates a list with all the worms
public static int wormCount; //Amount of worms in the game
public int ID; //It's used to differentiate the worms

private float proximityValueX;
private float proximityValueY;
private float nearValue;
public float distanceValue; //how far the enemy should be

private bool canAttack;
void Awake ()
{
wormList.Add(gameObject); //add this worm to the list
wormCount++; //adds plus 1 to the amount of worms in the game
}

void Start ()
{
HP = 100;
distanceValue = 30f;
}
void Update ()
{
proximityValueX = wormList[1].transform.position.x - this.transform.position.x;
proximityValueY = wormList[1].transform.position.y - this.transform.position.y;
nearValue = proximityValueX + proximityValueY;
if(nearValue <= distanceValue)
{
canAttack = true;
}

else
{
canAttack = false;
}
Vector3 raycastRight = transform.TransformDirection(Vector3.forward);

if (Physics.Raycast(transform.position, raycastRight, 10)) 
print("There is something blocking the Right side!");

Vector3 raycastLEft = transform.TransformDirection(Vector3.forward);

if (Physics.Raycast(transform.position, raycastRight, -10)) 
print("There is something blocking the Left side!");
}

In this post, we explored different ways to interact with the environment. First, we learned how to break down the game environment by area. Then we learned about the advanced environment interactions with AI.

To learn about manipulating animation behavior with AI read our book  Practical Game AI Programming.

Read Next

Content Marketing Editor at Packt Hub. I blog about new and upcoming tech trends ranging from Data science, Web development, Programming, Cloud & Networking, IoT, Security and Game development.

LEAVE A REPLY

Please enter your comment!
Please enter your name here