In university I took a course on the architecture of real-time systems. The main project was to replicate the original Space Invaders arcade game using various design patterns, all without relying on included containers and arrays.
These patterns include Singleton, State, Strategy, Visitor, Proxy, Observer, Command, Composite, Flyweight, Iterator, Null Object, Factory, Adaptor, Object Pools, and Template.
Screenshots:
![]() | ![]() | ![]() | ![]() |
IteratorThis recreation of Space Invaders heavily relies on linked lists and iterating through them due to the array limitation. There is also the tree structure for the arrangement of aliens and other objects in the scene. Some actions in the game include requesting assets from their respective managers, moving all of the aliens at once, and calling an update method for a list of attached objects. In order to traverse these lists in a more user-readable way, we use the iterator pattern. | ![]() |
StrategyFor Space Invaders, the strategy pattern is used to describe the behavior of the bombs falling from the aliens. This allows for unique animation between each bomb type, where one inverts its x scale, and another inverts its y scale. | ![]() |
FactoryIn Space Invaders, there are 55 aliens that need to be instantiated and attached to their respective columns and sprite batches. It can become difficult to keep track of how each alien and their grid is created, so a solution would be to create a factory that handles these operations. When creating the objects, they are attached to their appropriate sprite batches which eliminates the need to do it later. | ![]() |
ProxyFor Space Invaders, this pattern is used to mitigate unnecessary copies of instances of SpriteGame. Before the proxy, a SpriteGame would need to be created for each alien or object on the screen, even though the only difference between each object is the position. With the proxy, when the sprite needs to be rendered, the proxy applies the new position onto the referenced SpriteGame and then calls for the sprite to be rendered onto the screen. With this new system, SpriteGame objects can be created once per alien and other objects, then referenced by the proxy objects, limiting costly allocations whenever we want a new object on the screen | ![]() |
StateFor this project, the State pattern was used to describe the behavior of scenes such as the initial attractor, the main game loop, and the game over sequence. In addition to behavior, these scene objects may also store instances of object, such as ScenePlay containing the player ship or SceneAttractor storing its animated letters to be reset when entering the scene. | ![]() |
VisitorFor Space Invaders, the visitor pattern is used for the collision system to determine what two objects have collided and specialize their behavior. Each derived GameObject class inherits from CollisionVisitor, which contains the necessary abstract functions Accept() and Visit() to describe specific functionality. | ![]() |
ObserverFor Space Invaders, the input system involves creating subjects with certain keyboard keys, then attaching observer objects to those subjects. For every frame, the subjects are updated to check for whether a key has been pressed or held down, and if they are, the subject's observer list is notified. | ![]() |
CommandFor Space Invaders, the command pattern is used in atleast a couple of areas, notably the timer system. When a timer is created, it is passed a command object which then gets put onto the timer manager's list to be executed later when the timer has elapsed. | ![]() |
CompositeIn Space Invaders, the aliens and shields are arranged in grids. With the sheer volume of objects, collision processing becomes difficult and unnecessarily slow due to checking all of the objects even when some do not have a chance of colliding. To solve this problem, we use the composite pattern to arrange the aliens and shield bricks in trees and do the collision processing on the tree. | ![]() |
Object PoolsIn the Space Invaders project, there are many instances of objects needing to be removed and then later be reused. Allocating memory constantly is not ideal for real-time systems. The solution is to use object pools. | ![]() |