Relentless Game Engine (C++)
Built over the course of six months and two classes, the Relentless Engine is the product of my work in DePaul’s Game Engine Programming sequence. The engine’s core features include an Update and Draw system, Input processing, an optimized collision detection system, a Scene system, and Scene Management. Some of the hallmark features of the engine are detailed below.
The Combo System
At the end of the course, we were tasked with researching, architecting and implementing an engine feature of our choosing. I decided to implement a Street Fighter-style combo system.
I also created a visualizer for the combo system specifically for demonstration purposes. Drawing inspiration from popular fighting game “training mode” features, the visualizer puts the features of the combo system on display in explicit detail for programmers to understand how a player’s inputs will be read.
The Input Readout
Visualized on the bottom left of the screen, this displays the current input being received by the system. The left side represents the direction, controlled with WASD keys, and will point in one of nine directions (including neutral, represented by “N”). The right side represents the six buttons used by Street Fighter: punch and kick, in three different strengths (light, medium, heavy). A button will light up on screen when it is pressed.
The Input Readout also includes an image representing which way the player character is facing. Horizontal and diagonal input directions are all relative, being listed in terms of “forward” and “back,” so this is important information for developers to understand to know if their inputs are being read correctly.
The Move List
Visualized in the center of the screen, the move list is divided into three categories: Normal, Special and Unique. This allows for large move lists to be displayed by category, without running off the edge of the screen. It displays the user-defined name of an attack, and the input string required to execute the attack.
The Input Buffer
Visualized on the left side of the screen, the Input Buffer is one of the foundational elements of any fighting game. It tracks a single player’s inputs over time for future reference when reading for combos. Every input has a “direction” component and a “button” component and is held for a certain amount of time. Each row in the visualization shows the direction, any and all buttons that were pressed, and how many frames that input was held for, with the bottom row being the most recent, and the top row being the oldest.
The Attack Timeline
Visualized on the top edge of the screen, this is the final component of the visualizer that brings everything together. Every attack has three different phases: the startup animation (when the player is vulnerable), the active period (when it can deal damage), and the recovery animation (the player is also vulnerable in this stage). Representing this in a timeline is absolutely vital in the absence of any animation.
Each bar along the timeline represents one frame. A yellow bar represents a startup frame, a green bar an active frame, and a red bar a recovery frame. When a move is executed, the timeline fills with colored bars from left to right, and the name of the attack will be listed above the timeline (along with its user-defined frame data).
Special Cancelling
Special cancelling can also be represented on the timeline. However, special cancelling one attack into another requires that the first attack hit the opponent. For testing purposes, a fake hit detection system will register a “hit” on the first frame of active. The second row of the timeline will be used to represent the frame data of the attack which was cancelled into.
Optimized Collision System
There are three primitive collision volumes available to the users of the Relentless Engine: Bounding Sphere (BSphere), Axis-Aligned Bounding Box (AABB), and Oriented Bounding Box (OBB). For each GameObject, the user can pick one of the three volumes to use for collision detection. In order to optimize collision detection in a worst-case scenario, GameObjects are grouped by class and multiple (less expensive) intersection tests are performed, before the user-selected volume is even tested.
Each class grouping gets its own AABB. If one group’s AABB intersects another’s AABB, then testing proceeds to the next tier.
Every GameObject in the first group has its default BSphere (non-user-defined) tested against the other group’s AABB. If they intersect, testing proceeds further.
Every GameObject in the second group has its default BSphere tested against the GameObject from group 1. If they intersect, testing proceeds to the final tier.
Finally, the two GameObjects’ user-selected collision volumes are tested for intersection against each other. If intersection occurs here, then a collision is detected between two objects, and a user-defined callback function is called, which contains the desired “on-collision” behavior.
Terrain
The terrain system allows users to use a TGA image as a heightmap, and a separate TGA image as the texture. Every scene can have up to one designated terrain. Terrains also have a special collision detection algorithm, which was designed with a similar tier-based optimization technique. The following terrain properties are available to the user:
A square, grayscale TGA image for heightmap.
A separate TGA image for the desired texture of the terrain.
Y-axis offset, for vertically positioning the terrain in world space.
Max height multiplier, for fine tuning the intensity of the peaks and valleys.
Side length specifier, for determining exactly how big the terrain should be.
U- and V-repeat values, for specifying how many times the texture should repeat in both directions.