What is Astro-Pirates?
Astro-Pirates is a 2D, space-themed, arcade-style game which involves combat against alien minions and their bosses. The player must navigate from area to area, battling alien minions. Every three areas cleared, the player will face a boss. The difficulty increases with progression.
"You are a space peacekeeper tasked with clearing out the aggressive local alien pirates! You are overwhelmingly outnumbered. Are you up to the task?"
Inspiration
Astro-Pirates was inspired by old-school retro space shoot-em-ups. Due to the hackathon's close deadline, I decided that a project of this type would be ideal to avoid scope creep.
How was Astro-Pirates built?
The game engine for Astro-Pirates was developed in C++ and SFML.
SFML was used as an interface for the keyboard, mouse, audio, and display.
Lessons learned
Developing Astro-Pirates expanded my knowledge on optimization, data structures, algorithms, and design patterns.
One challenge I faced was in regards to communication between states in the game's distinct finite state machines. For instance: when the user clicks on the 'Play' button on the main menu, I need a way to communicate to the driver class to start the game. I was able to address this challenge using the 'Event Queue' design pattern, which not only resolved the issue, but allowed me to decouple the classes.
Due to the possibility of large amounts of certain objects (particles, bullets, enemies) being frequently added, moved, and removed in memory, I opted to implement the pool design pattern as a manager for such objects. Pools enable the frequent addition and removal of objects without causing memory fragmentation.
I experienced difficulty with implementing the menu and game's parallax background due to graphical transformation considerations. However, I ultimately decided to decouple the parallax background's graphical transformations from any game transformations, instead making any transformations in relation to the output display's size. This generalized the parallax background, which allowed me to implement it in other applications, such as a scrolling main menu parallax background.
During development of the Minion and Boss classes, I realized that I needed a way to programmatically relate them so that they could both be handled by the same data classes, and so that they could both be handled by the Event Queue and Objective classes as if they were the same class. I could not simply use one class (such as an Enemy class) to represent both the Minion and Boss classes because Minion(s) and Boss(es) have distinct functionality and member variables. Using one class for both would be a waste of memory due to unused member variables in either instance. I solved this issue by creating a base class called Enemy, of which Minion and Boss are children. The Enemy class contains a pure virtual Clone function, which returns a pointer object instance at a certain X and Y position in the world, and is overridden by both Minion and Boss. This solution resolved every issue I had, and simplified the process of enemy wave generation by allowing both Minion(s) and Boss(es) to be represented by the same ID system in the EnemyData class. As a result, I did not have to change anything else in my codebase in order to implement this solution.
Since the data classes throughout the codebase are frequently referred to, sometimes at 60 times a second, I opted to design them using maps that correlate an ID to its respective object. To be specific, I used the C++ standard template library's unordered_map, which has an amortized constant time complexity. This design choice significantly optimized the game's performance.
Since SFML requires that Textures be held in memory for the entire duration of their use (they may not be destroyed at any point), and since Textures are used throughout the entire codebase, I decided that this is a decent instance to employ the "singleton" design pattern. As a result, I created the TextureData class, which is a singleton that holds each Texture. When a Sprite requires a Texture during initialization, it refers to the TextureData class to access the correct sprite in amortized constant time. Since TextureData no longer has to be passed to the majority of functions and constructors, this change resulted in cleaner, more readable codebase with optimized performance.
During development I encountered a design choice regarding data storage and loading. Specifically, the two choices I had for texture, sound, music, and font storage/loading were hard-coded file paths, or interfacing with data files stored on the disk in the game files. I opted for the latter, and it resulted in a more readable and flexible codebase. I interfaced with the data files using data classes, which handled reading from the disk and loading the data into memory.
I frequently made use of C++ move semantics throughout the codebase for optimized memory manipulation. This optimized running time by cutting down on constructor calls.
Built With
- audacity
- c++
- gimp
- sfml
- sfxr
- visual-studio

Log in or sign up for Devpost to join the conversation.