I am done with collision manager. It only needs to be updated as the game is progressing.
I haven’t really finished a new artifact yet, because it is a lot of job and we have helped each other in the group a lot this week with programming issues.
Today I will try to explain a finite state machine (FSM) for AI states that I am making.
I received various books from an acquaintance of mine. One of them is called “Programming AI by Example” by Mat Buckland. This book features examples of FSM’s and different path finding algorithms, among them A* ( A-Star).
I have used the FSM example in the book to create the different behaviors of our enemy fish. This FSM uses static instances (objects) to change states and if you need it, you can also do a global state to go with that. I know that some of you might react in suspicion to both the static instances and the global state, but none of them will be available for anything else than the EnemyFishObject.
The use of static instances make it easier to avoid memory leaks because the instance you create is a regular object, which means that when you call the state’s static instance, the object is created and it’s reference is returned to a pointer to that object. There is less allocation and there is only that single instance of that state available. The constructors of the states are private. This is called a Singleton because of that.
The global state might sound more dangerous than it is. The class in not created in the main-file, above the main function so that it can be accessed from the whole program as the name might suggest. This is a state that enables the enemy (in this case) to do a specific thing from any existing state.
For example, if you had a guard that can just hang around and be idle, chase and patrol and if you wanted it to be able to tie his shoes in every current state, you would need to fill the existing states with the logic to change state from any state to the tie shoes state. With a global state you can have this logic in one place and use this state every time the guard’s shoelaces are untied. This may be a bad example, but games such as The Sims might use a global state like this every time a sim needs to go to the bathroom. The global state in our game might not be used as we don’t really need it. I decided to do it any way because I want to make it reusable with a template class, and since I have both the book with examples of this and the source code from the book I think the difference in time including the global state compared to skipping it is small.
To explain how the state machine works I need to explain how I used this book example in an earlier project. In that project I only used the following states; one abstract base state(cannot be instantiated) and four states that inherits from this class. The states were: Idle, Chase, RunAway and Return home. Each of these states contained the logic to change to another state via the static instance of the next state. The states used a pointer to the enemy class to call the appropriate functions. For example:
//Omitted the rest
The enemy also had the change state function. This made the enemy sort of the state manager and I wanted to avoid that this time.
This is how the state manager, called AIStateMachine in this project looks:
This is a template, you can choose your own data type to use. It is the same syntax for a container vector for example.
m_pOwner in this code is the enemy fish object. It owns an instance of this class to let this class manage the state transitions. From here you can go back to your previous state, to the next, or to another state.
This pointer is a member of EnemyFishObject.h and it will be used to call the functions in the state machine. It would only need to call the state machine’s Update function. There it will execute both the global state (probably looking for the conditions to go to “tie shoelace state” or “go to bathroom state”) and the current state, set in the enemy’s constructor. In this project it would be idle state. When this is done, the actual states execute functions the enemy stores and changes the current state to another from within them selves. The next state uses other functions in the enemy class and changes states when it is time to etc.
As you can see my Idle state is not much right now, but you can see there are functions to enter the state and execute AI-logics and an exit function. You can also see its Singleton instance at the bottom.
So why am I doing all this? I want to make not only an FSM, but also one I can use in other projects without changing very much or doing a completely new one. It is also much more efficient to use this way for an enemy AI than to have everything in its update function. Also much easier to add new behaviors and easier to read once comfortable with it.
I apologize for this incomplete post but the artifact is not yet completed. I thought this would be better to blog about than various fixes in the program, very important fixes and great teamwork, but this is the artifact I am working on. I just wished I could have started this earlier so that my weekend and the rest of my waking hours would not go into this artifact.