Game Development with raylib C++ — Day #4
In this article, we are going to create an Agent Class that will represent our intelligence AIs and will form the core of our Agents. The class will have the properties of the agent, such as their position, velocity, colour, etc. It will also contain a list of behaviours with each behaviour in their own separate class.
At each update, the Agent class will execute the Update function of each behaviour in its list. In this article, we will create a FollowMouse behaviour to move our player. The Agent will apply an acceleration to their velocity that will affect their position.
We are going to build on top of the last project that you can download from here where we learned how to create a Game class. If you want to learn how to set up VSCode and raylib then go to this article.
The class diagram below depicts this basic implementation for this example
What is an Agent
Lets us take this moment to understand what an Agent is. An agent is a game object that has some form of intelligent behaviour and/or decision making process. When I say intelligent behaviour, it is still limited to reactions to pre-set events. The behaviour uses the Decision Cycle which is a series of repeated steps to make a decision.
There are many types of decision cycles but typically for games, we use the Robotic Paradigm. This has three common steps which are repeated during the agent’s update. They are Sense, Plan, and Act.
I could go on about this but let’s look at implementing a basic Agent class.
There will be some code that may rub certain coders the wrong way but go ahead and make variables private and create their getters and setters but for this article, I am going to keep it simple.
Create an Agent.h file and type the following:
The agent will have the following properties for the maximum speed the agent will accelerate to, the current acceleration and velocity, the position, size (radius) and colour.
The agent will be responsible to update and draw itself.
Create the Agent.cpp file and type the following:
Firstly, let’s start with the two constructors, one that set the defaults and the other that allows the position, radius and colour to be specified when instantiated.
The getter and setter for position
The Update is empty at the moment and we will move the drawing of the ball to the Draw method.
Now, let's open the Game.h file and include the Agent header file at the top and declare a private Agent object name player and change the Update method to have a deltaTime parameter.
In the Game.cpp file, in the Initialise function add the following to initialise the agent at a position, with a radius of 20 units and blue.
In the Update method, we are going to comment out the old code, update the player’s position and still call the player’s update even though it is empty.
Finally, in the GenerateOutput method, again comment out the old code and we will replace this with calling the Draw method on the player.
Run the program now and we should have the “agent” following the mouse position like before but using the agent class.
Now that the Agent is set up, we can move on to the second part of this article which is the behaviours.
The IBehaviour Class
The IBehaviour is an Interface that acts as a contract to any class that inherits it. The subclass must implement the pure virtual method, Update. Create an IBehaviour.h file and type the following.
The FollowMouseBehaviour Class
The first of our behaviour classes is the FollowMouseBehaviour that moves the agent towards the current mouse position.
Create a FollowMouseBehaviour.h file and type the following
Noticed that we have inherited from the IBehaviour, created an override for the Update method and a private variable to store the target agent.
Create the FollowMouseBehaviour.cpp and type the following:
Let’s break down what is happening here.
- Get the mouse position
- Get the vector direction from the mouse position to the agent (player).
- Normalise the direction
- Calculate the acceleration by multiplying the direction vector with the agent’s max speed by delta time.
We include the raymath.h to get access to the Vector2 function. I personally prefer the good old code below but it doesn’t work in raylib. We could create our own Utility class for that (maybe later).
With a behaviour implement, let’s update the Agent class to include behaviours.
Open the Agent.h, an agent may have more than one behaviour which we will get into in later articles where we move into State Machine where a behaviour transition into another. Let us add a list of behaviours.
We will also need a public method to add behaviours to the agent
Let us add the implementation to the Agent.cpp
We will now beef up the Update method to go through each behaviour and call their update method. We pass the agent to the behaviour’s update so it can update the agent’s acceleration that you may recall in the FollowMouseBehaviour Update method.
With the agent’s acceleration updated, we then update the agent’s velocity and position before resetting the acceleration. Don’t forget to add the #include “raymath.h” at the top to use those Vector2 methods.
Ok, the agent now processes behaviours. All we need to do is create instances for the behaviours and add them to the agent.
Just to keep things updated, rename the GameClassDemo.cpp to AgentDemo.cpp for this project.
Open the Game.h file and include the header file for the FollowMouseBehaviour and create an instance of the FollowMouseBehaviour.
Open the Game.cpp file and in the Initialise function, let's initialise the behaviour and add it to the player. I’ll also bump up the player’s speed to 100 units.
In the ProcessInput method, we will get rid of getting the mouse position since we now have a behaviour for this.
In the UpdateGame method, let us get rid of updating the player's position directly since this is handled by the behaviour.
Now run the program and you will notice a major difference in the way the blue ball moves. The agent now follows the mouse pointer and “Steers” towards the pointer.
We are now going to add some very simple behaviours called Seek and Flee.
The seek behaviour enhances the agent to move towards a particular target. This is very similar to the Follow Mouse Behaviour, if anything the same behaviour.
Create a SeekBehaviour.h file and you will notice that the code is very similar to the FollowMouseBehaviour.
Create a SeekBehaviour.cpp, we pass the target in the constructor
For the Update override method, you have seen this code before:
With the behaviour completed, let’s create a second agent that seeks out the player. In the Game.h, create an instance of a red enemy on the top right and set the player as the target.
In the Game.cpp, update the Initialise method
Now include the redEnemy in the Update and Draw methods
Run the program now and you will have the red enemy chasing the blue player. Easy pesy!
Fleeing the same as Seeking, just the direction is the opposite. We get the direction to the player and then we go the opposite to flee. We will add one more factor and where we will have the third agent idling and if the player comes with a certain distance, we will make the agent flee away.
Create a FleeBehaviour.h file and type the following:
Create the FleeBehaviour.cpp and type the following:
We get the other direction by swapping the agent and target when we calculate the distance. We check if the target is within 125 units before the agent starts to flee. If the player is out of range, the agent slows itself down to rest. We could add another property in the FleeBehaviour class and add a way to initialise this distance in the constructor or setter. Another improvement would be to create another list or array in the Game class and add agents to this and have a loop to iterate over it to update and draw the agents to make it scaleable.
Let’s add a new agent to the Game.h, update the Initialise, Update and Draw methods.
Run your program now and you should see our three agents, one following the mouse, one seeking, and the other fleeing when the player gets too close.
I hope you gained something from this article. In the next article in this series, we will cover State Machine based on the work we have done so far. You can find the source code here on Github.
If you enjoyed reading this article give me a Clap, also if you would like to see more, “Follow” me, so you may be notified of future releases. You may also send me a message if you need any further help. If you see something that I could code better, please let me know as I am always keen to learn and improve.