DEV Community

Cover image for Writing Super Mario Bros in C++
Raviola
Raviola

Posted on

Writing Super Mario Bros in C++

I decided to learn C++ as step towards understanding more about the fundamentals of computer science, programming languages and graphics programming in general.

Game development has been a hobby of mine for a long time. writing a game seemed like a fun project to start with. After reading some books on C++ I decided to clone the Italian plumber platformer. Find the source code here

In this post, I want to talk about aspects I found interesting about learning C++ coming from the JVM world. I also want to shed some light into the process of building a “simple” game using the Entity Component System Architecture. If you are not a game developer I hope my article serves as a general introduction to how simple games like this are put together.

Starting out

I love JetBrains IDE’s so the first step for me was downloading CLion. CLion uses CMAKE to build C++ projects by default. CMake is a build system that builds build systems. It’s cross platform and incredibly unintuitive but it’s your best bet if you want your software to run in multiple platforms. It came as a surprise that a language with this much history doesn’t have a more sophisticated solution in place. Coming from the Java (Kotlin) world, I have a newfound appreciation for Gradle.

I decided to use Simple Direct Media Layer 2 (SDL2) to get access to audio, keyboard, mouse, and graphics hardware. I considered using OpenGL directly, but I think the scope of the project was quickly getting out of hand as it was.

alt

Next, I set off to define an architecture for the game. Plain Object Oriented design seems like a perfect fit for games. You have a bunch of objects in your game interacting with each other. You can map those objects to classes and take advantage of inheritance to avoid repeating code. Unfortunately, this naive approach brings with it some problems when building games of relative complexity. I don’t want to get too deep into what those problems are but here’s a quick summary:

  • Using inheritance usually results in inflexible architectures that sooner or later will lead into the Deadly diamond problem.
  • Efficiency. Objects often are scattered around memory and make poor use of CPU cache both in terms of cache coherency and prefetching. (this might not be a huge deal depending on the kind of games you’re trying to make)
  • Encapsulation. It’s hard to define where logic should live and what has access to what, resulting in spaghetti code.

I might expand on these points on a following article. For now I want to show you how the architecture I picked works and how it overcomes these problems.

The ECS Architecture

I struggled in the past trying to architect my games in a way that they would scale. When I first learned about the Entity-Component-System architecture I immediately wanted to try it out. Let’s start by defining each term on its name.

Entity

Everything in your game world is represented by an entity. This includes the player, the enemies, sounds, music and and even the camera. An entity is just a bag of components and contains no logic:

Component

Components are plain old structs that also contain no logic. They are just data holders. Common components you’ll find in most games are PositionComponent, PlayerComponent and TextureComponent.

Notice that some components don’t even hold data (see PlayerComponent for example) they just serve as “labels” that help identify certain entities in the game world.

Components can be assigned-to and removed from entities at run-time.

The components above are pretty general, but you will most likely be creating components specific to your game. As an example, a SuperMarioComponent is assigned to the mario entity whenever he eats a mushroom.

System

Lastly, systems are where the action happens. They are very domain specific. They hold logic and keep it encapsulated. I think this will make more sense with some examples of the types of system I defined in my game:

- The render system
- The camera system
- The physics system
- The animation system
- etc.

The system class is not much complicated than the rest of the classes in the architecture. On it most basic form it overrides an update(world* world) function that takes in a game world as a parameter. We’ll talk about the World class next.

Take a look at the AnimationSystem in my game for a simple real example of the logic illustrated above.

Putting things together

As hinted above, Systems, Entities and Components interact with each other using the World class.

As you might have guessed, the world class models our gameworld, and as such, contains all our game Entities. We can define what happens to those entities by registering systems to the gameworld. Both entities and systems can be added/removed at run time.

The world class is as straightforward as the rest of the classes in the architecture:

I think you can guess what most of these methods do. The first one is just the default constructor. The next two just add/remove Systems to to the world. The following two ones do something similar but for Entities.

The last method, find(), is perhaps the one that deserves more explanation. It takes care of finding all entities currently in the gameworld that match a specific criteria. That criteria is usually what type of components are assigned to them:

// Find all entities with Texture and Position data.
auto entities = world->find<TextureComponent, PositionComponent>();

Find takes advantage of function templates to define the search. In Java/Kotlin if you want a class or function to operate on a arbitrary class you can make use of generics. Function and class templates are a bit like generics only more powerful. Function templates are instructions to the compiler to write code for you, also known as metaprogramming. This allows us to create a function template whose functionality can be adapted to more than one type or class.

This is the definition of find in my code:

There’s a few interesting things going on here. The first one is the use of template<typename… Components> in the function signature. This is telling the compiler to generate a function called find at compile-time. The compiler will generate such function each time it encounters a distinct invocation of find:

For the code above, the compiler will automatically generate the following for us:


void find(const std::function<void(Entity*)>& callback) {
   for (auto entity : entities) {
      if (entity->has<AnimationComponent, TextureComponent>) {       
         callback(entity);
      }
   }
}

As I hinted before this happens at compile time, so there’s no run time penalty. I must admit I didn’t like this C++ feature when I first encounter it. I still don’t, but I understand the performance benefits they bring along. The reason I don’t like them is I think they result in hard to read code. I don’t like weak-typing systems and this looks awfully like it.

The rest of the function should be relatively easy to follow. Let me know in the comments if this is not the case.

Conclusion

You should now have a rough understanding ECS works. We no longer rely on inheritance to define behavior, we rely on composition instead. This means we got rid of potentially long and inflexible hierarchies in favor of lightweight reusable components that we can add and remove at runtime.

The efficiency problem I mentioned before is also mitigated. Our entities and components are now tightly packed in contiguous memory, so iterating over them makes much better use of CPU caches. There are techniques to improve this even further but I haven’t had the need to implement them for this game.

Our third problem, encapsulation, is also fixed. It is now very clear where the logic should live, how the different parts our architecture should communicate and what should have access to what. As an example, it makes sense for the RenderSystem to make use of classes such as renderer.cpp or window.cpp, but it would be immediately obvious something is off if those classes are required by systems like the PhysicsSystem.cpp or the SoundSystem.cpp.

You should know have an idea of how my code is structured. Feel free to poke around and create PR to improve any bug or mistake I might have made. I’m still very new with C++, so I’m sure my code has a lot of room for improvement.

Learning C++ was a fun experience but I believe I have still a long way to go to feel comfortable using it. Future steps for this project include re-writing it using Rust or Kotlin-native. I would also love to compile this project to WASM to get it to run in a browser, if you have any experience with any that I would love to chat with you!

Thank you for reading, and Happy coding!

Personal website
Twitter

Top comments (2)

Collapse
 
sswam profile image
Sam Watkins • Edited

Thanks for sharing, it's interesting to read about the entity component system method. Your game is very playable and feels the same as the original as far as I can tell.

FYI, I had to make these minor changes to get it to build on Linux. There's a case-sensitivity issue with SDL_ttf, and I had to list some extra secondary libraries on target_link_libraries. I don't know why that latter was necessary, and I suppose that there is a better way to do it.

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3d987ba..6511ffb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -26,5 +26,5 @@ set_property(TARGET smb PROPERTY CXX_STANDARD 17)
 set_target_properties(smb PROPERTIES OUTPUT_NAME smb-${CMAKE_BUILD_TYPE})
 target_include_directories(smb PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
 target_include_directories(smb PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor)
-target_link_libraries(smb PRIVATE ${SDL2_MIXER_LIBRARY} ${SDL2} ${SDL2_IMAGE} ${SDL2_TTF})
+target_link_libraries(smb PRIVATE ${SDL2_MIXER_LIBRARY} ${SDL2} ${SDL2_IMAGE} ${SDL2_TTF} jpeg png tiff webp freetype harfbuzz)

diff --git a/cmake/FindSDL2_ttf.cmake b/cmake/FindSDL2_ttf.cmake
index 7caf028..fea142e 100644
--- a/cmake/FindSDL2_ttf.cmake
+++ b/cmake/FindSDL2_ttf.cmake
@@ -1,5 +1,5 @@
 # Find path to the include (header) files #########
-find_path(SDL2_TTF_INCLUDE_DIR SDL_TTF.h PATH_SUFFIXES SDL2 include/SDL2 include)
+find_path(SDL2_TTF_INCLUDE_DIR SDL_ttf.h PATH_SUFFIXES SDL2 include/SDL2 include)
 set(SDL2_TTF_INCLUDE_DIRS ${SDL2_TTF_INCLUDE_DIR})

 # Find path to the library (static) ###############
@@ -15,7 +15,7 @@ endif ()
 list(APPEND CMAKE_PREFIX_PATH "/usr/local/opt/bzip2/bin/")


-find_library(SDL2_TTF_LIBRARY NAMES SDL2_TTF PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX})
+find_library(SDL2_TTF_LIBRARY NAMES SDL2_ttf PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX})

 # SDL2_TTF intrinsic dependencies
 find_library(FREETYPE freetype PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX})
Enter fullscreen mode Exit fullscreen mode
Collapse
 
feresr profile image
Raviola

Thanks for reading Sam! glad you could make it work!

"It works in my machine"™ - I barely understand how cmake works.
I'll add those dependencies in for the next person who wants to give it atry.

ps: there's a WASM version now: feresr.github.io/smb/smb-.html