Creating Monty: The start of an “AI” character

From the early discussions of our game idea, we knew we wanted to give the player a companion. Whether this was given to the player from the start, or they meet it along the way, we knew that it was a vital addition to the game that allowed us to create a bubbly personality for the player to interact with.

If you’ve been following our tweets and Instagram, then you’ll know that Monty is our loveable dog companion. He’s got a mind of his own but we’re sure you’ll love him none the less (this is definitely not an excuse for a buggy AI character who doesn’t listen). Anyways, we wanted to plan out exactly what Monty can do and how he interacts with the player. We wanted to give the player more control than just “pet” so we came up with a few extra features.

  • Sit
  • Follow
  • Roam
  • Fetch
  • Canoe

Have a guess as to which one is our favourite!

Using a nice big whiteboard, we drew out what’s called a Finite State Machine (FSM), which is a visual diagram of what modes or “states” Monty has, and what conditions are required to switch from one state to the other. This led to big discussions about what would seem right, and what best matches Monty’s excitable attitude.

Also, having a detailed plan makes programming so much easier.

I started by creating 3 separate scripts. A manager that controls when Monty switches from one state to another, an “actions” script that contains all the code for each action, and a “variables “script that contains additional functions such as calculating the distance between Monty and the Player.

Within the “State Manager” script, a separate SwitchState() method, that can be called whenever the state needs to changed. A string called currentState is then used an identifier as to what state to switch to. For example, if the function if the identifier is changed to “roam”, the function then gets called and runs the Roam condition in the method.

public void SwitchState()
	{
		switch (currentState)
		{
			case "roam":
				stateActions.Roam();
				break;

			case "sit":
				stateActions.Sit();
				break;

			case "fetch":
				stateActions.Fetch();
				break;

			case "wait":
				stateActions.Wait();
				break;

			case "move towards":
				stateActions.MoveTowards();
				break;

			case "follow":
				stateActions.Follow();
				break;

			case "canoe":
				stateActions.Canoe();
				break;

			default:
				stateActions.Roam();
				break;
		}
	}

You can see that each case of the switch statement runs a method within the state action script.

The next step is to write the conditions that change the states. Let’s say that if the player walks away a certain distance from Monty, Monty will start to follow the player again. Within the state variables script, a function can be written to calculate the distance between the two.

public float CalculateDistance()
	{
		distFromPlayer = Vector2.Distance(transform.position, player.transform.position);
		return distFromPlayer;
	}

This method returns the distance variable, which can then be used to calculate the if the player has walked too far.

if (stateVariables.distFromPlayer > statevariales.distToFollow)
	{
		currentState = "follow";
		SwitchState();

	}

Of course, Monty needs a way of returning back to waiting by the player.

if (stateVariables.distFromPlayer <= statevariales.distToFollow)
	{
		currentState = "wait";
		SwitchState();

	}

At the moment, this a basic start to the AI, but it allows for a large amount of flexibility and create complex conditions that switch to different states. Keeping it separated with different scripts also keeps the code tidy. I can also create a reference to the variables script from the player, or from a different controller to reduce the amount of code reuse.

My next few blogs are going to be about how I’ve improved the AI by adding more functionality to each of the actions.

Leave a Comment

Your email address will not be published. Required fields are marked *