CR_cover.jpg

Cat Rush

A bizarre boss rush game in which you play as a cat to eradicate the food monsters threatening you

 

Engine: A custom-made engine based on SDL
Language: C++
Team: 1 programmer + 1 artist
Year: 2017

Source repository
Windows build

Cat Rush is an intense action game in which you will constantly try to avoid projectiles while taking down the boss. My main focus with this project was not on the game itself, but rather on making a robust, flexible and well-structured 2D game engine. The game itself is a C++ port of a game I made years ago in C# and Windows Forms (yes, I know, Windows Forms is terrible for games). My old C# code was very poorly structured, so I have made the new engine completely from the ground up.

 
 
Gameplaygif.gif

Objects and components

The engine uses a component-based system for game objects, with JSON serialization/deserialization to read from and write to world files or object prefab files. Each game object contains a list of arbitrary components, all of which are updated each frame. When components are loaded, special components like collider components and graphics components are added to grid-based data structures to keep track of collision and which objects should be rendered.

One of the trickiest problems I had to tackle was how to handle dependencies and communication between game objects and different components. I decided that dependencies of components should be declared in a component's json object, to be able to detect at load time if the required component exists or not to be able to initialize the dependent component. The components also communicate via messages, implemented via the C++17 std::variant template, to allow for more loose coupling without much additional latency. The observer pattern is also for communication in some other parts of the code, for example between game objects and GUI.

Input

Since the engine is built on top of SDL, all input is retrieved from SDL events. On top of that, I have a layer for mapping hardware input to game actions, according to a configuration file which holds all the mappings. To do this I divided the input into the three categories: actions, states and ranges. Actions represent keypresses, states represent continuous presses and ranges represents axis values, for example joystick positions. For this I used three maps for which hardware input corresponds to which actions/states/ranges and three maps to see the current values for those actions/states/ranges.

I wanted the game to be playable with both a keyboard and a gamepad, so I had to make sure that the user could transition between using a keyboard and a gamepad. This could either be done by letting all devices be used at the same time, or by having a keyboard mode and a gamepad mode which are triggered by events from the corresponding device. I went with the latter approach, to make sure that an unused device doesn't interfere with the input without the user noticing it, and to be able to show different icons depending on the currently used device (this game doesn't have any input icons, but at least the input engine is prepared for it). To do this, I wrapped the input mappings into different contexts, and allowed the input handler to contain multiple input contexts, in this case one for the gamepad and one for the keyboard/mouse. It also differentiates between different gamepads so that only one can be active at once. If an input event from an unused device is fired and the values from the event is above a deadzone threshold, the input mode is seamlessly switched to that device.