Custom 2D Game Engine

100% custom built game engine for 2D games. Fully written in C++. DX12 is used to render the game.


Project summary

This project is a small DX12 engine written together with two other students. I worked on the renderer while they worked on AI and engine/tools. This was the first time I had to work with DX12, which turned out to be fairly difficult for me to pick up (lots of boilerplate code, memory management, etc.). Additionally, this was my very first custom game engine, so that made things even more difficult.

Most important things I learned

  • Creating a DX12 application.
  • The systems that are needed to build a game engine.
  • Working with others on a single codebase.
  • Documenting my work using Doxygen.

Role:Graphics Programmer
When:2017
Duration:1 month
Skills:C++, Direct3D 12 (DX12)
Software:Perforce, Visual Studio, CMake, RenderDoc, NSightGraphics
Team size:3 team members

BUILDING AN IN-HOUSE GAME ENGINE IN 8 WEEKS

During the first trimester of the second year at university, we were given the assignment to build a custom game engine. Teams of three programmers each were formed, and every student had his or her own role: engine and tools, graphics, or AI.

Every team chose a genre to specialize the engine for. We decided to tailor the engine towards the creation of 2D rogue-like games.

THE GOAL OF THE PROJECT

Simply put, the goal of the project was to build a game using a custom game engine.

We did really over scope the engine. The toolset we wanted to create was, in some regards, close to that of Unity3D. It is great to have such a vision, but we should have realized that we could not possibly deliver such a broad set of features. Sure, we decreased the scope of the project during the trimester, but in the end, the project still did not deliver everything we wanted to deliver.

Part of the reason the engine “failed” to deliver everything that was listed in the project brief was that I used the Direct3D 12 API for the first time during this trimester, and so, it was hard to create a proper render. Direct3D 12 requires the programmer to manage a lot of things that were previously managed by the driver and/or API.

SUMMARY OF MY TASKS

I have been interested in graphics programming for quite some time now. So, it made sense to pick this role.

Being a graphics programmer on the game engine team meant that I was responsible for everything that is visible in the engine. It was tough to get up to speed because I had to work with Direct3D 12. My previous experience with computer graphics was based on OpenGL 3.3.

After I managed to render a solid triangle, the “Hello, World!” of graphics programming, I tried to learn as much about the Direct3D 12 API as I could. I managed to do things like depth testingrendering moving cubestexturingblending, and the like. But, not all of this was needed for our game engine. I started from scratch and built a renderer specifically for 2D graphics.

So, I managed to get the triangle showing up again, I decided that it was time to implement Dear ImGui. It was much harder to get this library up and running than I initially expected. At the time, I had a pretty bad understanding of the Direct3D 12 API. As a result, I could not implement Dear ImGui properly.

I eventually gave up on trying to implement Dear ImGui properly. Instead of constructing the descriptor in the shader resource view descriptor heap properly, I decided to upload a “dummy” texture. This texture would be overwritten by the generated texture of Dear ImGui. It was so much easier to just sacrifice that one texture, rather than figuring out how to implement the Dear ImGui binding properly.

To this day, I still enjoy the way I solved that problem. It was a terrible solution, but I managed to solve it in time by doing it that way.

Anyhow, after I implemented Dear ImGui, I started working on adding sprites (textured quads) and animating them. First, I focussed on the sprites. A quad was easy to construct. Just add 4 vertices and a couple of indices and you are all set. Then, the sprite animations had to be implemented… The way my renderer worked was that it uploaded the vertex data (positions and texture coordinates) once, and modify the positions by multiplying each vertex by the “model-view-projection (MVP) matrix in the shader. The downside of this was that I could not update the texture coordinates easily.

The solution was to either upload new vertex data every frame or find a way to transform the texture coordinates. I opted for the latter. Sending a texture transformation matrix in the constant buffer allowed me to modify the texture coordinates in the vertex shader.

void Renderable::setSpriteTextureCoordinates(float posX, float posY, float rectW, float rectH)
{
    m_spriteData.uvMatrix =
    {
        rectW,  0.0f,   0.0f,   0.0f,
        0.0f,   rectH,  0.0f,   0.0f,
        0.0f,   0.0f,   1.0f,   0.0f,
        posX,   posY,   0.0f,   1.0f
    };
}

The reason for choosing to use a 4×4 matrix instead of a 3×3 matrix is that it would conflict with the packing rules of HLSL. A 4×4 matrix (16 floats) was easier to work with.

Now that I could change sprite texture coordinates, it was time to implement the sprite sheet animation system. Personally, I am really happy with the way the animation tool turned out. The tool could save the animations to the disk, while the engine could load and play them at any given time.

We used the Cereal library to serialize the data in our engine. Reason being that Cereal is easy to integrate, use, and exports to quite a few different file formats. This allowed me to serialize the animation data with ease.

I have also worked on some miscellaneous renderer/engine features, as well as a tilemap. However, I could not get the tilemap done in time. Next time, I will try to keep the scope small.

ACHIEVEMENTS

LEARNED THE BASICS OF THE DIRECT3D 12 API

At the start of the project, I also started working with Direct3D 12 for the very first time. In fact, this is the first time I have tried any of the Direct3D APIs. However, I did have a bit of experience with OpenGL 3.3 (although that was nothing more complex than simple 3D rendering). Moving to the DX12 API was tough. There were a ton of new concepts to learn, and let alone all the memory management that is required when using DX12.

I started out by rendering simple geometry, such as triangles and quads. Slowly I moved on to indexing and rendering cubes. Bear in mind that this took me almost half of the trimester. It was just hard to memorize and understand all the different concepts.

After a couple of weeks, I started to get a better understanding of the way the API works. It is not that bad after all. You just need to do a lot of things yourself now. I have learned more during this trimester than I ever did before. I think this is mainly caused by the fact that a team was depending on me. This additional pressure kept me focussed at all times. I am proud of all the new things I learned in such a (relatively) short time-span, and that is why it is one of my biggest achievements of this trimester.

CREATING A USEFUL TOOL

Up until now, I had never really bothered creating any tools to improve my workflow. Most of the time, I would just create a nice class and use that to easily implement things such as animations. Now that I have written a tool for sprite sheet animation using Dear ImGui, I am thinking about doing that more often.

It was a shame that the engine was not really finished by the time the trimester ended, but I was quite happy with the result of the animation tool. It was easy to use and allowed me to speed up the sprite animation process quite a bit.

The thing I am most happy with is the way the tool can be used. Everybody can use it without having to learn all kinds of interfaces, as the fields in the editor window are quite user-friendly.

DOCUMENTING MY WORK

For the first time ever, I have made use of an automatic documentation generation tool. We decided to use Doxygen. The program is quite popular and often used by various companies and projects. We really liked working with it.

After I studied the documentation, I could work with it right away. The user interface was nice, and the way you can use a comment in your code for Doxygen is really nice. What surprised me is that the style of commenting code for Doxygen is almost the same as regular comments in C++. The only difference is the addition of an exclamation mark.

  1. //! Regular, small comment
  2. /*! More details on the thing that was mentioned above… \n
  3. \param int paramOne tell the user what “paramOne” is used for \n
  4. \return nothing is returned by this function \n
  5. \sa stands for: see also, like to another function */
  6. void functionName(int paramOne)
  7. {
  8. std::cout << “Heyo!” << std::endl;
  9. }

As you can see, it is quite easy to change your regular comments into Doxygen documentation. Of course, there are loads of other things you can add to the code, but these are the things we mostly used. The big advantage of using these comments in the code is that you barely have to remember yourself to write documentation, as it will be generated by Doxygen whenever you scan the folder again.

CHALLENGES

WORKING UNDER PRESSURE

Even though I just said that the pressure of having a team depend on me helped me focus on learning the DX12 API as quickly as I could, it also induced stress every now and then. Mainly because I knew that the longer it would take me to implement Dear ImGui and sprite rendering, the less time the engine and AI programmer would have to implement their things properly.

It surely was challenging to get things up and running quickly. As you can imagine, the rendering is one of the most important parts of the game engine in this early stage. Without graphics, you cannot show tools, or try to get AI, collisions, or gameplay working.

In the end, I think I learned a lot from it. Especially that it is okay to focus on one particular problem, but not for too long. I know that I can often get stuck on small details, even though it slows down the team. This trimester has helped me focus more on the really important things, and fix the small, somewhat insignificant, issues later. Hopefully, this will allow me to work more efficiently on solo projects, as well as future team projects.

WRITING MAINTAINABLE CODE

One of the things I know I am not that good at is writing maintainable code. In the first few weeks, everything looks fine and is easy to read. But, as the deadline approached, I was starting to work faster. Working faster meant sacrificing code maintainability.

My short-term goal is to practice writing maintainable and readable code. I could definitely use some improvement in this area. And, as a general point of improvement, I also need to start working on my code architecture. I still do not plan my code well enough, resulting in a non-flexible code-base.

WORKING WITH AN EXISTING CODE-BASE

While I was busy learning DX12, the engine programmer in my team was already working on the architecture of the engine. Of course, we have had team meetings regarding the game engine architecture, but I never really had to have a deep understanding of the underlying architecture. Just a high-level overview was sufficient for the time being.

A couple weeks after, I got my project, containing a small renderer, up and running. I was ready to integrate it with the existing engine code-base. How hard could it be? Well, it was not terrible, but we did have to fix a bunch of things. My rendering code was simply not fully compatible with the engine architecture.

In the end, it all worked out fine, but it was a bit difficult to get the two parts of the engine running together nicely.

Leave a Reply

Your email address will not be published. Required fields are marked *