This repository contains the source code for the game Quidmasters: Petbuilder.
- SFML >= 3.0.0
- C++23
The source code is over 1700 lines of code, so the following explanation is a brief overview of how the game is structured.
Let's explain how the game's inner workings are by looking at the main function.
We first open the SFML window and init the GameState
. This is a class which contains all the state of the game. Everything that the player sees is merely an interpretation of the GameState
.
Then we create an object of type Assets
which loads all of the game assets on its constructor.
We set the stage
(a field inside the GameStage
) to be at the menu (GameStage::Menu
).
We then begin the game loop, which is a piece of code that will execute every frame of the game (roughly 60 to 144 times per second, depending on your monitor). We can divide the game loop into two parts: update and draw.
- During the update phase we call functions that both read and modify the game state. We call each of these functions systems. It is here where we calculate the game logic.
- During the draw phase we call functions that read from the
GameState
and write into thesf::RenderWindow
.
This system is occupied of the player's movement. Depending on which keys are pressed (W, A, S or D) we create a vector pointing in the direction chosen by the player. We normalize this vector to avoid having a greater magnitude during diagonal movement. We add this vector to the player's position. Finally we also calculate the vector from the player to the mouse, to make the player look towards the mouse.
Occupied of the reducing the nourishment of the pet over time. This nourishment is refilled when killing zombies.
Makes the player lose sanity when going out of the viewing area. This is to discourage using the outside as an escape.
Checks if the either:
- the player's health is equal to or below 0
- the pet's nourishment is equal to or below 0
- the player's sanity is equal to or below 0
If any of these is true, we set the GameStage
to game over.
Occupied of checking the player's collision with coins on the floor. If this occurs, we add tha quid to the player's pouch, also playing a sound effect.
Makes the pet scold you when he's hungry, requesting food immedieately. Or else. Increases the volume of a heartbeat sound effect as your health lowers.
Advances all of the cooldown timers for the guns. Checks if the player is clicking, if so request to fire the currently selected gun. If the cooldown timer for this gun is over, then we can reset the cooldown timer and spawn bullets. Spawning bullets involves:
- Getting randomly generated values for the damage, knockback and bulletSpeed (according to the gun specification)
- Generating an amount of bullets with these values. The amount depends on the gun.
- Setting the position of these bullets to be at the player position.
- Setting the velocity to be the forward direction of the player.
- Adding the generated bullets to the global bullet vector (in
GameState
).
This system also handles gun switching. It checks if any of the 1-9 keys were pressed, and if so, it tries to switch. If the user does not yet have that gun, the switch is simply ignored.
Iterates through each bullet in the global bullet vector. For each bullet, we apply the velocity to the position. After that, we check if the bullet's collided with any enemy simply by checking the distances with all of them.
(Interjection: Yes. This is a bit slow. We don't use quadtrees or other optimizations like that. The time complexity is
$O(mn)$ ), where$m$ is the amount of bullets and$n$ the amount of enemies.)
If the bullet has collided with an enemy, we remove health from the enemy and play a sound to let the player know they have hit an enemy.
One weapon produces a special type of bullets: homing bullets. These bullets "chase" the enemies. They work by being affected by a gravitational/electric field generated by the enemies. This accelerational field is generated at a point
We approximate this field discretely by creating an 80x60 matrix of vectors, each element representing one square unit of the game in the viewing area of the screen. We use the previously shown formula to calculate the field for every point.
After, we iterate through each homing bullet and get where it lands on the matrix. If it does land on an entry in the matrix, we get the acceleration and apply it to the bullet, modifying its velocity.
In charge of spawning the enemies, starting and ending waves and breaks. The system begins by checking if we are in breaktime or not.
- If not in breaktime
- Check if all the enemies due on this wave have spawned already.
- If no enemies are alive and all the enemies have already spawned, the wave is over
- If this is so, we set the break time to 10 seconds and we set the
GameState.inBreak
to betrue
.
- If this is so, we set the break time to 10 seconds and we set the
- We advance the
enemySpawnTimer
. If it reaches 0:- We restart the timer.
- We spawn an enemy of each class.
- If in breaktime
- Advance the break time timer
- If the timer reaches 0:
- We set the set the new enemies due-to-spawn in this wave. (This basically consists of filling an array where each element corresponds to an enemy type with how many enemies of that type will spawn)
- We set
GameState.inBreak
tofalse
.
Accelerates the enemies towards the player. Limits each enemy's speed according to their max speed. Applies the velocity to each enemy's position. If the enemy is colliding with the player, apply damage to them.
If the enemies get too close between each other, we apply oppossing accelerations to separate them. This works to prevent all the enemies converting to a single blob, letting them be blobs instead.
We also limit their positions, to prevent them from straying too far from the viewing area.
Checks each enemy's health to see if it has gone below 0. If so, we remove the enemy from the game and spawn a coin drop.
Makes messages emitted by the pet disappear after a while.
Handles all of the store logic.
Cross-fades music tracks depending on the wave the player is in.
Enables three hacks:
- Pressing all the keys to write BINGO at once, gives the player 10k quid.
- Pressing the KL keys skips the wave and gives the player the quid that corresponded to the remaining enemies.
- Pressing the HSK keys skips the breaktime.
Install the respective development SFML package available in your distribution.
In MacOS you can install SFML using brew install sfml
.
Then you must clone the repository and run make:
$ git clone https://github.com/Sinono3/pou
$ cd pou
$ make run
The game should compile and run after these steps.
You need to install MSYS2 and download the 64-bit UCRT MinGW toolchain and SFML with it. You can do so by typing this in the MSYS2 UCRT64 terminal:
$ pacman -S base-devel mingw-w64-ucrt-x86_64-toolchain mingw-w64-ucrt-x86_64-sfml
After this simply run make run
inside the terminal and inside the project directory and the game should compile and run.
If you instead want to compile this using Visual Studio, you will have to create a project in Visual Studio and manually configure includes and linking for SFML.