My experience making a game with SFML


For three weeks, I was doing C++ game programming to further increase my skills. This resulted in the game I call HedronSpace. My first time making a game in C++ was BlockDodger, but there were a few challenges with the engine code. BlockDodger used SDL for the low-level application code and it was very tricky to find a way to get audio to play and to get HUD-style font drawing to work out of the box.

That was one of the reasons why I made this new game in SFML. SFML offers an easy-to-use and object-oriented interface to make desktop applications. However, not everything was a walk in the park. SFML uses OpenGL for its low level graphics needs, but only offers a user-friendly interface to create application with 2D graphics. Fortunately, you can make 3D applications with SFML, but you’ll be mostly on your own.

That prompted me to make a little engine (I use that term loosely here) that wraps around SFML and offers easy to use 3D functionality for games (but only for simple abstract games). It’s called SF3DEW (which stands for SFML 3D Engine Wrapper) and took up most my 3 weeks doing C++ game programming.

SF3DEW might be a very simple and limited engine, but it’s a tremendous improvement over the engine I made for BlockDodger (which I’ll generically call “BlockDodger’s Engine”). In BlockDodger, the code design suffered from what I call the “monolith object”. This especially pertained to the code responsible for graphics.

In BlockDodger, there was a class called “GraphicsCtrlr” that handled everything dealing with OpenGL graphics. It handled one shader, textures, a cube mesh, and drawing. If anyone has ever used OpenGL, they couldn’t deny that OpenGL isn’t very object-oriented, which makes it hard to make a robust, object-oriented graphics system (especially if you’re following OpenGL tutorials).

In SF3DEW, this problem is mitigated by putting different parts of the graphics system into classes. Shaders are their own class and so are Textures and Meshes. There is also an object called the ObjectRenderer that takes a certain Shader, Texture, and Mesh, makes them active, and draws them. It was a little tricky to implement, but it allows for far more robust graphics features than BlockDodger’s Engine.

In BlockDodger’s Engine, I made a GameObject system that was really simple and allowed for different objects to exist and interact with each other. However, it wasn’t flexible. To make a distinct entity in the game (like an enemy or the player), you had to inherit from GameObject. A better solution is for GameObjects to have modular components and add only the desired properties. This is what was implemented in SF3DEW. It uses a component-based GameObject system that supports essential built-in components and also custom-made components you define (like making a new script in Unity3D).

Another big difference between SF3DEW and BlockDodger’s Engine is the use of pointers. SF3DEW opts for smart pointers. There are a few benefits to smart pointers over raw pointers (that BlockDodger used). The memory management code can be greatly simplified, allowing for clean, reliable code.

But there is an even bigger benefit to using smart pointers then easy memory management. Smart pointers allow the programmer to have more control over memory ownership. Memory ownership ought to be a very important feature for programmers because it restricts who can clean the memory. Controlling memory ownership is nearly impossible with raw pointers because anyone can delete a raw pointer on accident and can potentially make debugging a nightmare.

In order for memory ownership to work with C++ smart pointers, two different kinds of smart pointers are needed. They are “shared_ptr” and “weak_ptr”. “shared_ptr” is the strong smart pointer that keeps a reference count. When this pointer goes out of scope and the reference count goes to zero, “shared_ptr” will delete the memory it points to. The “weak_ptr” is the weak smart pointer and simply holds a pointer to memory held by a “shared_ptr” or another “weak_ptr”. When “weak_ptr” goes out of scope, it does not delete the memory it points to.

When these two kinds of smart pointers are used, you can make a memory managed program that also has good memory ownership control. The object that owns the pointer should hold a “shared_ptr” to that memory, and if it needs to share this pointer, it simply passes it around as a “weak_ptr” so that it doesn’t share ownership of that memory space. SF3DEW uses this technique to prevent both memory leaks and dangling pointers.

I learned a lot from making HedronSpace and SF3DEW. The source-code for both are available on Github. (HedronSpace’s Github) (SF3DEW’s Github)