In this blog, I will discuss the game I made using the game engine systems that I developed and integrating a couple of other systems developed by my fellow students. I will discuss what the game is and what is the gameplay loop, the systems I used both made by me and systems I integrated into my engine developed by other developers, and any challenges that I encountered during the development.
Following is the final game.
Flapper Game
The flapper is very similar to the game flappy bird where the player controls a bird and tries to avoid obstacles. The gameplay loop is, as a player you try to avoid as many obstacles as possible and try to survive the longest you can. There are a couple of minor differences from the flappy bird the obstacles are not pipes they are cages and unlike the flappy bird, they can spawn in any position vertically within the game bounds. There is also a small narrative behind the game in which you are an escaped bird from a bird zoo and the zoo is trying to catch you again using the cages.
The reason I choose this game is that it is simple, clear, and fun to play. I have to finish the game in two weeks so I needed something which does not have a huge scope. The game also gave me a clear idea of the systems I need to use to build this game so that I am not in a confusion to pick and try to fit that system in the game. I had fun playing flappy bird so I thought it would be a good idea to replicate it using my engine.
My Experience Making Flapper
After deciding on the game it was clear that I will be needing my ECS & Graphics systems for rendering, player movement, physics, & tagging. Apart from this, I needed a collision detection system to detect collisions between cages and the bird(aka Flappy) and an audio system for background music and some other game sounds.
The first thing I did is setting up the game and player movement using the graphics & ECS system I developed. It was a little bit easy because of the previous simulation game that I worked on where I just loaded some objects with models and added movement using my ECS system. So I have less work to do in Flapper as all the core components like RenderingComponent, RigidbodyComponent, TagComponent which will be used with the ECS system to create entities(game objects) are already created and tested.
I put some extra care in setting up the flapper game so that it is easy to make changes in the future & also for debugging. For example, I added useful methods in the flapper game to set up the player character, background objects, Update the game state, etc. This helped me during debugging, when I have any issue I can just remove all the background objects just by commenting a single call if I need to debug something only in the player character or vice versa. This is one of the important things I learned during this course always try to create meaningful & useful interfaces/methods that make it easy for the developer to use your API.
While using the ECS system I noticed the advantages and disadvantages of the way I designed my ECS system(Check this blog for my ECS implementation). The advantage is that it was very easy to add some custom behavior based on component data. All I need to do is get the component data for each entity using the custom iterator provided by the ECS system and perform the custom operation.
The disadvantage I noticed is that because of the way I designed my ECS all the components data is stored inside the registry so it is not possible to view the component data of a particular entity during debugging(ex: if you have a breakpoint and want to watch all components of an entity), you have to look inside the registry to find the component data of the particular entity. So it is a little difficult to debug without understanding how the ECS system works. Apart from this, I did not notice any other issue and I did not want to change anything in my ECS system or the engine in general.
Integrating Audio & Collision Detection
Integrating the Audio system developed by Yuhan Wu(click here for the Audio system) was easy. There were clear instructions on how to use the system so all I need to do was understanding the structure for human-readable files and how to use the builder to generate the corresponding binary files and use the Audio API. The API was simple & self-explanatory I could understand what I can do with the loaded audio files. In my opinion, self-documenting code is always good and the Audio API has self-documented code avoiding unnecessary comments.
Integrating the 2D physics system(click here for the Physics System) developed by Dong Wang was a little tricky because of the way my game was set up. The 2D physics system requires a Gameobject class for providing rigid body details and OnCollision virtual methods for collision resolving, which I don't use as all the objects are created using my ECS in my game. So, I had to make changes to Dong's Physics system to work with my ECS system. It was not that complicated to modify the Physics system, I was able to understand what the physics system is doing after spending some time with code.
The API was clear in terms of readability and the instructions to use it were good but it would have been even easier to use the system with more detailed instructions like where to call the CheckCollision method or how can the system be used if someone is not planning on using the Gameobject class or stating that the Gameobject class is a must and the system cannot be used without it. These instructions would give a clear picture of the system and tells the user what needs to be done to get it working. I had to spend some time with the system to get a better understanding of it.
Once I understood the system it was a little easy to modify it. I am still using Dong's Collider class and Collision checking methods but I have a new component called ColliderComponent which contains the collider data required for collision checking and I am using other components I already have for any other data required for collision detection and resolving the collisions. After fixing that I had to update the rectangular collision detection method as it was not working properly. I have made a couple of other changes that I felt are useful and probably are some suggestions for the physics system. Currently, during the collision resolution, there is no information provided about with which object we have collided so it is not possible to do something like play this sound when you collide with X object. I think most of the modern game engines provide some kind of information about the collided object so I felt it is really useful to have that information so I have added it.
One other thing that I noticed is that currently all the Deregistered colliders are removed after checking for the collision but this might create a problem where a collider might be deregistered but it still participates in collision check which might result in incorrect behavior or even cause exceptions if the data required for checking collision is not available anymore or freed. I think the first thing that needs to be checked is if there are any colliders to be removed from the collision check and remove them, once it is done then you can run the collision check.
What I Learned From This Course
I have learned many new things during this semester listening to the lectures and working on the assignments from setting up project dependencies properly, writing clean platform-dependent & platform-independent interfaces that are easy to understand & setting up automated build processes. All the assignments in the course are focused on teaching all these good practices and processes that help us in becoming a better engine programmer by giving a better understanding of how things work and why is it important to follow these practices and processes.
I was a little familiar with some of the things we learned in the course but I understood their importance during this semester like why it is important to set up project dependencies properly and only add references to those projects that are actually required. Before this class, I would not put much thought when adding a reference I used to add it if I thought my project is dependent on some project (even though I knew that adding a reference does two things setting up a project dependency and linking with that project) which is bad because not all the times you would need to link with the other project sometimes you might only need a dependency. Now understanding these details better I only add a reference when it is required. On the other hand, if we miss adding references also causes issues like linker issues so it is important to know when to add a dependency and when to add a reference.
I also learned about creating good platform-dependent and independent interfaces and the different ways to separate the platform-specific code either using macros or using separate CPP files based on if it is a big difference or a small difference. I have not worked on multiple platforms like this before so it was a good learning experience.
Another thing that I learned during this semester is things to keep in mind when trying to work with multiple threads. Though we did not create any complicated job systems with job runners what I worked on in the assignment helped me in understanding how to work with threads like keeping the shared data between threads as small as possible and using sync points to sync shared data.
Another major thing I learned is setting up the build processes that build the content required for the game and understanding the importance of human-readable files and how they help developers when debugging and also how efficient it is to use binary files. I also learned when to use each file and how to convert content from a human-readable file to a binary file. Overall I think all these learnings made me a more mature engineer and now when I am working on a project I think about creating better interfaces that are easy for others to use. Thinking of ways to automate any steps or trying to optimize any functionality if I could.
Thoughts on Architecture/Design
In my opinion, good architecture and design are always important no matter what we are trying to develop. Based on what we are trying to develop I think the design and architecture approach might change. Some systems might upfront need more time to design before starting any implementation but some other systems might not need that much time and you can design as you go. I personally try to spend at least some time initially to figure out the base design for a system I want to develop and once the base design is done I start implementing it and add things as I go. I use this approach because it is hard to design a system thinking of all the use cases and design all at once upfront and sometimes it is also not possible to start directly implementing the system without any basic design. For the ECS system, I initially knew that I was going to use storage pools but I was not sure how do I create them or where to store them. So I spent some time and researched other ECS systems and created the registry and storage classes. As I was implementing the ECS system I thought of creating the custom iterator class which will be useful to iterate over components so I designed the ComponentStorageIterator and implemented it as I go.
One other important thing to understand as a software engineer while designing an architecture for a system/software is understanding what makes an architecture good or bad and what are the things you need to consider when designing a system. In my opinion following or some of the factors/characteristics of a good architecture
It should be scalable. scalability is one of the important characteristics of good architecture. In most cases, your design will need to accommodate a large scale so you should always keep that in mind.
It should be flexible and maintainable. Your architecture should always be ready to accommodate for new additions or changes it should not be like adding a new change to the existing system is really hard or adding a change breaks the existing design.
It should be simple. It might sound silly but not always the most complex systems are the well-designed systems. Some people might think you have to design a complex architecture to make a better system but it is actually the other way around the more simple your architecture could be the easier it is for people to understand the design of your system. Sometimes it might be tough to come up with a simpler architecture when working with few systems but it's fine, try to make it as simple as possible but don't try to make it complex without any need for it.
On the other hand not being scalable, not being flexible, and having complexity are some of the bad characteristics of a design in my opinion.
You can download the Flapper game from the links below
Controls:
ESC – exit application.
You can view the level in the editor mode using the controls below but once you are in the game mode below controls don't work. You will be in editor mode whenever your game restarts and until you press Space to start the game again.
W/A/S/D/Q/E - Move Camera Forward/Left/Backward/Right/Down/Up
Space - Just like flappy bird use space to push the character up and try to avoid obstacles and try not to fall or go out of bounds.
Comments