The idea of the first course I used this in was to create a game similar to a space shooter and optimize it. The course assignment after was about using design patterns in a game created with SDL2. In that assignment, I got permission to use and expand on this project. Currently, it is my go to project when trying out new ideas and expand my knowledge of AI that is useable in games.
This project is the older version of "TheOneTrueKing" which I am using to practice AI for games.
Since I wanted to have different types of enemies in this project, the factory pattern seemed like a fitting design pattern when creating enemies. I am using an EnemyManager to add, remove and update all the active enemies.
The idea is to have a base class that has the default variables along with getters and setters that is used by all enemies, for example health, attack damage and attack range. Then subclasses will change the values based on that specific enemy. I am using an enum class to differentiate the type of enemy when creating them.
I am also using an ObjectBase which EnemyBase, PlayerCharacter, Projectile and WeaponComponent inherits from, since they all were using similar variables. I have Managers for projectiles and weapons as well, it also handles the ObjectPools I'm using for respective class.
Factory is useful since I only need one class with the basic variables and functions, while having other classes use them through inheritance instead of creating the same type of variables and functions for every class.
As an edition to the object creation, I am using an object pool. I had a game project some time ago that used an object pool, but I had never created or used one myself. This was my first attempt at creating and using one.
With an object pool, you create all the objects at the start of the game and place them in a vector of deactivated objects. When an enemy needs to be spawned, it takes an enemy from the object pool and places it in a vector used in the update function. This vector contains all the enemies that are currently active. When an enemy dies, it is placed back to the object pool and removed from the vector of active enemies.
An object pool is useful since it takes much less performance to place existing objects in vectors and setting its values compared to creating a new object in runtime. Especially when handling hundreds or thousands of enemies. projectiles and weapons are handled the same way, with an object pool that uses them as a template type. I'm storing the active enemies in an unordered map with their object ID as key, to easily access the enemy through the ID.
One of the most performance-heavy parts of the project was the collision between projectiles and enemies. When working on the naive implementation, I didn't have a good way to check the collision. I knew what quadtree was but hadn't created one myself, so I decided to try and create one for the collision.
A quadtree could be described as a dynamic grid. If the number of objects inside the quadtree goes over the set limit, it will split into four parts called child nodes. Then it goes through the positions of all objects that are inside each node to see if they collide with each other. So instead of looping through all the objects in the game, it loops through smaller groups that are inside each node. One issue that occurred was that all enemies stacked up at the exact same position, which made the quadtree ineffective. My solution for this was using the separation movement behavior, which prevents them from stacking up on each other.
A suggestion I got when handling the different enemy types was using the component design pattern. I had two types of enemies at the time. One was a sword user and the other was a mage. Both were very similar in the way they worked. The only thing that was different was their attack. I decided to create a weapon component that handles the enemy's attack.
At spawn, the human enemy picks a weapon based on a weapon type enum. The weapon handles the attack and has a health modifier which raises or lower the health based on the type. Just like for the enemies, I am using a factory and object pool for the weapons. There are currently five weapons one of them is a stronger version of the staff, which the player is using.
When handling the different game states (main menu, in-game, pause screen and game over screen), I thought a state stack would be useful to handle the states of the game and make it easy to add and remove states.
The state stack uses a vector to keep track of the current state, it updates and renders the last state in the vector. I am using a game state handler that can add and remove states based on the function I call. At the start, I add the main menu state to the vector and then certain functions are called based on which buttons the player clicks on. The main menu button removes all states aside from the first one added, which is the main menu. The play button adds an in-game state to the vector. The restart button goes back to the main menu to prevent multiple states from stacking up and then adds a new in-game state. The resume button removes the current state.
Feel free to contact me on LinkedIn or via mail.