Arcade Games

Developed in November 2019

C++ Custom-Engine Data-Structures Gameplay Physics UML


bannerImage
Arcade Games
Introduction
Galaxian
        Star Background
        Enemy Behavior
        Fake Sprite Rotation
Gauntlet
        General Design
        Custom XML Parser
        Interpreting Tiled Data
        Enemy Movement
        Player Collision
Bomb Jack
        Enemy Movement
        Player Movement
        Player Animation
        Bomb Pickups
Similar Projects

Introduction

For the first block of my first year in university, we were tasked with recreating a set of arcade games within a simple template that provided a window and a simple interface to draw images to that window. These arcade games were Galaxian, Gauntlet, and Bomb Jack.

This was my first real university project and I was still very much a novice at C++ at the time.


Galaxian

Gameplay of the Galaxian project

The first project of this block was to replicate the game Galaxian in 2 weeks. This project had some interesting challenges like the star background, fake rotation of sprites, and the state-based behavior of the enemies.

Star Background

The star background is simply done by maintaining a list of starts that move down over time. Their position is randomly chosen on each axis and when they reach the bottom of the screen they are simply moved back to the top. Their color is randomly chosen from a list of hardcoded values.

Enemy Behavior

Flowchart of the state-based enemy behavior in Galaxian

Flowchart of the state-based enemy behavior in Galaxian

The enemy behavior is a simple state machine. They change between the Formation, Deploying, Attacking, Returning, and Dying states.

The Formation state is active when they’re in the enemy formation and moving left and right with it.
The deploying state is active when the enemy is starting to move out of the formation.
The Attacking state is active when the enemy is moving down in a sine-wave-based pattern to attack the player. They also shoot projectiles at this time.
The Returning state is active when the enemy has moved to the bottom of the screen unharmed and they return to the formation from the top of the screen.
The Dying state is active when the enemy was shot or touched by the player and their explosion animation is running.

Showcase of the different states of enemies in Galaxian

Showcase of the different states of enemies in Galaxian

Fake Sprite Rotation

In Galaxian, the enemies look like they rotate when they move around. This isn’t actually true, however, the enemies have a set of sprites facing several different directions that are used based on where they should be looking.

Example of the sprites available for rotating an enemy

Example of the sprites available for rotating an enemy

When the enemy deploys itself from the formation, it moves in an arc and turns around while doing so. When they are Attacking, they usually face the player as they move down.

Enemies have a float for their rotation in degrees (these days I would use radians instead).

The available animation frames account for 90 degrees of rotation. So first I look at the rotation within the current quadrant. Then I can divide this by 90 degrees to get a [0-1] value for how much the enemy has rotated within this quadrant. Then I simply multiply this by the number of frames to get the frame I should use.

After picking the frame, I simply check which quadrant of rotation the enemy is in and flip the sprite on the x- and y-axes accordingly. This part is hardcoded, but it doesn’t matter too much since there won’t ever be more than four quadrants.


Gauntlet

Gameplay of the Gauntlet project

For the second project of this block, we had to create a replica of the game Gauntlet in 6 weeks. This project was a bit more involved than the Galaxian one, as it included map loading, enemy movement, blocking collision, and several tile-based mechanics.

I decided about halfway through the project to start over from scratch. I did this because, after some very long conversations with one of my teachers, I finally understood how I could apply some architectural principles like dependency injection to improve my design a lot. I felt that this would be the most educational direction to go in for me. I was able to copy a lot of code for specific processes like XML parsing, so I thankfully didn’t waste all of my time.

General Design

UML class diagram of the general structure of my implementation of Gauntlet[br](slightly simplified)

UML class diagram of the general structure of my implementation of Gauntlet
(slightly simplified)

Custom XML Parser

Diagram of the structure of an XMLDocument in my XML library

Diagram of the structure of an XMLDocument in my XML library


UML class diagram of the XML library

UML class diagram of the XML library

In this project, we were meant to limit the libraries we used. I wanted to load my levels from files created by the Tiled map editor. This was my first time parsing a file myself and I didn’t know where to start. I decided that it made sense to start with XML, since Tiled supports it, I had experience with using the format, and it looked a lot easier to parse than JSON.

What I came up with was a basic tree structure of XMLNodes with a single root, while also treating each set of siblings like a doubly-linked list so that they could be iterated through in all directions. The list of attributes of a node is also treated as a doubly-linked list, but there is no need to implement them as a tree structure since there’s no such thing as a child attribute.

The parsing was done through recursion in the constructor of the XMLNode class. The node would open a file and read it character by character. When it encountered the start of an attribute, it would create one and pass the file pointer to its constructor which would read the name and value, after exiting that constructor the file pointer would already be placed after the attribute so the node constructor could keep reading. When it encountered the start of a child node, it creates one and passes the file pointer to its constructor, this is where the recursion happens. Anything in the body of a node that isn’t part of a child node is considered the node’s value.

To prevent client code from wrongly creating or destroying nodes or attributes, I decided to use the unpopular friend feature of C++. The XMLDocument is a friend of XMLNode so it can call the constructor and destructor of the root, and the XMLNode is a friend of XMLAttribute so it can create and destroy its own attributes. This sacrifice of encapsulation inside the library, creates a more encapsulated interface for the library as a whole, as it has become impossible for client code to wrongly create and destroy nodes and attributes.

As a final step, I also added support for adding, removing, and editing attributes and nodes in an XMLDocument. This is entirely done through interface functions. I added this along with a function to write a document to a file so that the same library could be used for basic serialization for saving game data.

Interpreting Tiled Data

A level being changed in Tiled and then loaded by the game


UML class diagram of the different Tilemaps in the code

UML class diagram of the different Tilemaps in the code

Once I could parse XML files, loading the levels became as easy as finding the nodes with the level data and figuring out what to do with it.

First, I would have to load all the tilesets that were used by a map. These tilesets include the image and any custom properties that are tied to any global tile ID. Then, I could simply go through the layers of the map, check the ID of every tile, and take the tileset data it came from to create a tile. Tilesets are usually stored in a separate .tsx file (also XML-based), but sometimes they are embedded in the map file. I added support for both.

On the right, you can see how I avoided code duplication by creating a templated base class Tilemap<T> to support all three tile-based mechanics. The OccupancyTileMap is used for enemy movement, which I will discuss later. The TerrainTilemap is used to draw the world and store which tiles are solid. The TileEntityMap is used for objects in the world that are aligned with the tile grid but react to players or projectiles in different ways.

For the terrain, I used a custom tile property to mark certain types of tiles (like walls) as solid. The code for player and enemy movement uses the terrain map interface to see if they can move onto a tile or not, which handles most of the game’s collisions.

Since the terrain will never change, the terrain map has a buffer image that contains the entire map, and it simply dumps the visible part of this data onto the screen when the terrain needs to be drawn.

For the tile entities and non-aligned objects like enemies, I added a custom property that matched up with an enumerator in my code. This is clearly not ideal from an architectural perspective, but it was all I could come up with at the time.

I had a separate layer for these entities and I would go through the tiles on this layer, see what entity it is, and spawn it as either a GameObject or as a TileEntity.

Enemy Movement

Enemy movement can be seen in action in the previously-showed gameplay video


Pseudocode for the enemy movement

Pseudocode for the enemy movement

The movement of enemies is quite simple in Gauntlet. The enemies simply try to move toward the player as much as possible, while not going into solid or occupied tiles. There is no pathfinding involved, meaning they don’t take walls into account at all.

In my version of the game, the enemies simply move towards their target tile. If they don’t have one or have reached their target, they iterate through all neighbouring tiles (including diagonal neighbours) and find the non-solid and non-occupied tile (so it’s not a wall and there’s no other enemy on it) that is closest to the player. If they can’t find one they do nothing, otherwise they have a new target to move to.

Which tiles are occupied by an enemy is kept track of in the previously mentioned occupancy map. This is a tilemap, aligned with the grid, that has an Enemy* for each tile. If it’s nullptr, there is no enemy in the tile. Every enemy occupies a tile when they choose it as a target and deoccupy their previous tile at the same time. This causes the cascading flow of enemies you can see when there’s a large group of them.

Player Collision

Another challenge in Gauntlet was the player collision, but because of its tile-based nature, it could be handled in a pretty nice way. The player has to collide with walls, enemies, and doors. Doors are a special case because they don’t cover an entire tile and are considered tile entities in my codebase.

Collision with terrain is quite simple. The player just checks what tiles they will enter and asks the terrain map if those tiles are solid, if they are the player won’t move into them. This is done for each axis separately to allow for sliding when walking into a wall diagonally.

Collision with enemies isn’t as simple, since enemies aren’t aligned to the tile grid. They do, however, already keep track of where they approximately are within the tile grid through the occupancy map used for their movement. So the player can simply look at the tiles around it to find the enemies it might collide with. Then it uses the same principle as for the terrain, but with an AABB instead of a tile.

Collision with doors is simply handled by adding a game object when a door is added. That game object will be handled together with all other game objects that aren’t aligned to the grid in any way (like projectiles). This is sub-optimal since the doors are technically aligned to the grid and don’t need to be queried quadratically, but there’s such a low amount of normal game objects that it really doesn’t matter that much in this specific project.


Bomb Jack

Gameplay of the Bomb Jack project

Gameplay of the Bomb Jack project

The third project that we worked on this block was Bomb Jack. This was part of weekly workshops we got and meant to be a vehicle for explaining basic C++ and game programming concepts to us. It had a much lower scope for me than the other two projects, and I restarted this one in the middle of the block as well. There is a lot more that could have been added (the real Bomb Jack is a much more feature-rich game than my version), but I decided to focus more on the main projects instead.

Enemy Movement

Not a lot was different in this project, and I didn’t find it very educational at the time. It has the same kind of tile-based environment that I handled in the same kind of way as in Gauntlet. However, this was used in a slightly different way to handle the enemy movement, which works by checking if the tile under the one it’s moving into is solid, effectively checking if there will be a floor or not, and by flipping its direction if not.

Player Movement

The player movement is a state machine, much like the enemy movement of Galaxian. The player states are Standing, Walking, Falling, Gliding, Dying, and Jumping.

As with any platformer, there needs to be some kind of way to check if the player is on the floor or not when it tries to jump. In this game, the player sets a bit in a certain bitfield to true when colliding with the terrain from the bottom, and it sets it back to false when jumping or when transitioning to the Falling state.

The player uses a Vec2 for velocity, which is updated each frame to add gravity. The y-component of the velocity is set to 0 whenever the player hits their head or touches the floor to avoid game-breaking velocity buildups. The same is true for the x-component when hitting a wall on the side.

Player Animation

The animation of the player is based on its states (described under “Player movement”). The animator has a sprite for each state and will simply loop through its frames whenever that state is active. Some animations have a single frame, in which case there is no looping.

Bomb Pickups

The bomb pickups are done exactly the same as items in Gauntlet. They are stored as tile entities in a tile entity map and receive a function call when the player enters their tile. This map is a little different in Bomb Jack though because it has a different size and origin than the terrain map. This isn’t a very big difference though.


Project tags: #C++, #Custom-Engine, #Data-Structures, #Gameplay, #Physics, #UML


Similar projects:

Cloth Simulation Thumbnail

Cloth Simulation

Keep reading

University Intake: Ice Game Thumbnail

University Intake: Ice Game

Keep reading

Reboot: Cross-Platform Modular ECS Game Engine Thumbnail

Reboot: Cross-Platform Modular ECS Game Engine

Keep reading

More projects...