Space Marine Project.
The repository contains the game illustrated gifs below, the assets are free and can be found on PixelGameArt.
My main goal when I first started this game was to make a 2d-platformer with a decent code architecture, something which I would be able to reuse code in the future or extend to a “real project”. By no means, I wanna say a real project has to have a perfect architecture or be completely extendable. I believe every project has its purpose and by the time I started this one I wanted to make it as much maintainable, manageable and clean as possible. Something 100% doable!
I wanted as well to apply the appropriated design patterns to the common problems I would have during the development. Because its super easy to do, they are very well documented everywhere!
As you can tell this point, as I progressed over time, I’ve found more things to do in my life and I couldn’t keep the same excitement as I had at the beginning. The project definitely doesn’t have the “best ofs”, but in my point of view its good enough to be maintainable and extendable, the code is also clean and well organized, I have implemented a few patterns for common problems and I tried to respect SOLID as much as possible, but for sure someone can find mistakes here and there. In my defense, a few facts have to be taken into account, this is a project done in a few months during my free time on the weekends, early mornings or evenings after working 8h and with no hopes to make money from it.
A final note, the game is still unfinished, there is plenty of stuff that can be done regarding the behaviors or the enemies, guns, upgrades, achievements, craft system, audio and all the game design stuff that demands a lot of tweaks and is time consuming. Feel free to make pull requests and contribute. I eventually will make some progress on my own, but don’t expect a release date.
With that being said, let’s get into it.
The Game’s Architecture Overview:
Since there are a few things to cover I split it into different parts which I will talk separately:
- The Scenes of the game;
- The MVC of the game;
- The entity system: player, enemies, doors, bullets and game mechanics;
- The initialization of the systems and the static data;
The game is separated into two different scenes that work independently:
- Opening - It contains a short introduction of the game, displays a cut scene with a background, a spacecraft and text dialogs that receive input to go ahead and write the next sentence. The presentation ends with a dark screen fade which makes the transition to the playable part of the game. The scene is mostly driven by the script OpenSceneSequence, which contains the instructions to move the spacecraft and show the text dialog.
- Game - Contains the playable part of the game: monsters, player, doors, rooms and elevator are all here.
The MVC and Communication between Model and View
The Model is mostly driven by a pure C# class named Game and the implementation follows the component pattern splitting all the game mechanics into smaller classes and injecting their dependencies through the constructor.
The UI elements that in someways interact with the player inherit from these two base classes, the first always interact with the player and the second has states and can be switched on, off or inactive.
The Model and UI communication is done using the following Interfaces/Events and the Observer Pattern which allows to remove the coupling between both layers of the application. In shorter words, the UI scripts implement an interface and subscribe to the events they are interested in, and the Model classes dispatch those events when they happen.
As the Observer code shows, once a listener subscribes the event register, all its events/interfaces are now ready to be notified by the game model.
Most of the simple elements of the UI inherit from UiGameEventListener and subscribe to the observer after the Start() engine callback. However, more complex entities that have their own base class and perform their own subscription, such as UiRoom.
At the bottom of the class Door we have an example of the model code notifying the events that handle the door damage and destruction, OnTakeDamage() and Destroy() methods respectively.
The Game Entities
Inside the model, enemies inherit from the base class RuntimeEnemy conceiving the Subclass Sandbox Pattern. Doors and Enemies can be shot, in that way, both implement IAttackable interface. Rooms are populated with doors and enemies during the initialization, then events are emitted to instantiate the views of these entities which are Prefabs kept by an Object Pool where the code can be found here.
In order to use some gizmos and make the placing of the objects in the world easier all the UI of the rooms are already in the scene with their own IDs set when the editor starts, with that and reading the static Data used to initialize each room the gizmos elements appear in the scene according to each door, enemy, floor, position of the camera and walls present in the UIRoom. See picture below.
Although I didn’t tweak values of the jump, movement and gun, The player UI is mostly done but not all the enemies are implemented, I stopped the work on the Bipedal UI behaviors and other four enemies still have to be done.
Besides the internal initialization of each Monobehavior done on the Awake() method, the main initialization is done by the GameController instance placed in the scene, see below:
The script GameData receives the unity callback Start(), then creates an instance of the game model and broadcasts the game event ICreateGame.
The GameController script receives the ICreateGame event and then starts the game properly.
To initialize most of the game entities some external information is needed, this data means different things for different entities, they could be the position where a enemy would be placed, the total health, an ids or all the enemies inside the room. All these meta data about an object is stored into a specific scriptable object which is shared across all the entities of that type, as the constructor of the class enemy illustrates, the data is a dependency injected during the creation of the object.
A playable version is available here.
See you space cowboy