My First Complete Game

Introduction

I've been working on network services since I was 15 years old. I started doing some HTML pages, than simple web sites with a single form for an order or a feedback, then more complicated web sites backed by some CMS like Joomla, after I switched to web frameworks and started making fully customized services, then just some network services which communicate over different protocols (sometimes even custom internal ones). However, initially I wanted to do games as probably most of the programmers of that epoch. I liked playing video games, and programming seemed like an ultimate game. Unfortunately, I couldn't get a chance go get a job in the field, so took the way I took. However, the desire to work with graphics never left me. This post is about me creating a tribute to Space Invaders.

The approach

I had a few attempts to the field in the past. I tried to go from bottom to top, trying to create my own game engine. I tried to go from top to bottom, taking Unreal Engine 4. I tried to start from somewhere in the middle, taking OGRE. However, I couldn't last for long time with any of them for various reasons. Feel free skipping the details until here, because those details have more emotions rather than technical value.

Going from scratch with OpenGL

Creating own 3D game engine is tough task for a regular student programmer. However, I had a lot of the enthusiasm and a great (in my opinion) idea. I knew that I needed OpenGL for graphics, OpenAL for sound, GLUT to create the window and handle the input devices. I'm locked and loaded, ready to engage. However, I mostly worked on how to render the scene rather than on the game itself. It was a pretty dark age, I had did something like glEnable(GL_LIGHT1);. Eventually, I found that I spend a lot of time just to render some simple things and changed the tactics.

Going with a graphics framework

For some reason, I remember that OGRE was defined as a graphics framework. Now I see that the proper definition of it is Graphics Rendering Engine. In fact, the abbreviation OGRE stands for Object-Oriented Graphics Rendering Engine. Anyway, with OGRE things were a bit better. The examples of the project inspired me enough to approach a game project again. However, I just couldn't understand why something doesn't work within my code, but does work within an example. By that time I already had some experience in web development. In fact, I had a little success at that field. That success also made me thinking I'm mature enough to approach a game project. I was fixing websites a lot. When a web site doesn't work, it's pretty straightforward to debug it. However, when I was dealing with a game and I saw some black spikes out of a mesh, and I had no idea what's wrong. Also, I didn't want to admit some things like the necessity to compile a dependency with debug symbols to be able what's under the hood of the dependency. We don't do in Python, why would we need it in C++? Nowadays, the answer clear as a day. The outcome wasn't good enough, so I tried to find something else.

Going with a game engine

Eventually, I approached Unreal Engine 4 when it became open source and available under GNU/Linux. However, there were two issues which demotivated me:

  • First, I had hard times with understanding some of the documentation, which wasn't a problem in web development. In fact, documentation in web development is kinda luxury. I had got used to inspect tones of code. Mostly I worked with Python 2.7, but I worked with C++ as well. In fact, C++ was my favorite language. After inspecting the source code of Unreal Engine 4 I realized that it's impossible to know the language good enough ever. The code behind macros like UCLASS or USTRUCT was incomprehensible. In addition, as it's macro code, the debugger can't really help you to walk through it.
  • Second, shader programming was a confusing thing. The math operation didn't seem intuitive at all. Despite that I have degree at math, I couldn't connect the ideas of a shader code with the output.
  • The last, the development environment for Unreal Engine 4 was different from I what I used to:
    • It has beautiful thing called Blueprints. It looks gorgeously, I've looked them after all these years and they caught my eye again. However, there two problems with them:
      • The graph grows big before your algorithm gets complex enough for splitting into more simple algorithms.
      • They are stored in the binary format. As a result, Git can't show you diffs and the .git directory grows too fast. The moment with Git disappointed a lot, because I rely on Git diffs a lot. Even, when I learn something I want to have a tool to spot the thing which breaks everything.
    • In addition to Blueprints, I couldn't get used to necessity of using an IDE. At that moment the best free option was Qt Creator. I had used it before I started working as web developer and discovered Vim for myself. By that moment Vim was already in my muscle memory. Qt Creator has the plugin FakeVim which gives the Vim motions, but it wasn't good enough. Registers, jump list, tags, quickfix list and change list worked differently.

There was some positive outcome, though. I created is the plugin for inverse leg kinematics. I even supported it for the newer versions of Unreal Engine 4. At certain moment I couldn't understand the changes in the API and I gave up. Eventually, the functionality became the part of the engine.

Filling the knowledge gaps

After the attempt to make a game with an engine, I realized miss something. Despite me being a competitive programmer in web development I felt like I'm far from being ready to make something else.

There is a saying: "Every programmer has to create a game, an operating system, a music player". At that moment I had a music player and failed to create a game. Therefore, I decided to approach an operating system. I hoped that it would teach me something to make games. I found the website OSDev.org which has the list of books required to create an OS. I showed the list to my CTO at that moment, who told me to take something else. He advised to start from:

It's been years since those days. I read those book and a couple more from the references. In particular, it worth to mention the series of articles What every programmer should know about memory. Eventually, all these materials helped me to understand what the capabilities of Linux, how to write code considering CPU caches, purposes of some data structures.

Copying a simple game

I came across the article Challenging projects every programmer should try. One of the challenges from the list is making a clone of Space invaders. After all these attempts, I decided that makes so much sense to make the first game as simple as possible. My first website had just 3 HTML pages with no interaction. Why did I expect to make an amazing game from the beginning? Let's make a simple game!

Choosing the setup

This is a magic moment when you are choose the available solutions on the internet to start your new project. What do I need?

softwareForGame.png
Figure 1: materials to create a game

Rendering picture as a core functionality

As the picture is the most important when you do graphics, I started looking for some software for rendering. Based on the previous experience, I decided that I'm not ready to create my own engine. Therefore, I'm not taking graphics API again. Also, I don't want to take a monster like Unreal Engine either. What do I take?

In web development things are clear. There are the following sets:

  • TCP socket library. This functionality managed to become the part of standard library near every language. It's the lowest level. You deal with she scheduling, parsing, connection-pool maintenance, sessions, load-balancing and other things. You can take a library to parse HTTP messages or Protobuf messages and make you own HTTP server. Regularly, you start from this point when you implement your own protocol.
  • HTTP servers implementations. Regularly it's a library or you might find it as a part of standard library in some languages too. From here you don't need to take care of things from the previous item, but you are limited to HTTP only. The library allows to set the routes and their handlers. With them you call the function to start your own HTTP server. Regularly, you start from this point when you implement your HTTP service which might be pretty minimal.
  • Web frameworks. Frameworks in general direct you, as opposed to libraries which give you some API which you use as you want. A web framework sets in you in boundaries, and makes it easier (not simpler) to implement things, which exist on a typical website. For example, the web framework Django provides you the functionality to make user sessions with a few lines. While a web server implementation would require to design your solution. You implement the solution and incorporate the functions of the web server implementation.
  • Website engines. This is a thing which prescribes the kind of the website you are doing. Once you set it up, you have a ready to use basic websites. Wordpress creates a blog, Opencart creates a web shop. The only thing left is to make visually distinguishing. The disadvantage of this foundation is the absence of the flexibility. There are some exceptions, though. For example, Wordpress can be turned into a web shop, but it requires to install a huge plugin maintained by some core developers of the engine.
webdevSoftwareClassification.png
Figure 2: The sets of the software in web development

Therefore, the relations between the software you take and possible application look this way

Software Possible application
TCP socket library Network service, HTTP service, Website, Blog
HTTP server implementation HTTP service, Website, Blog
Web framework Website, Blog
Blog engine Blog

The relations in game development was totally unclear to me. Intuitively I saw the following kinds of applications:

  • Graphical application
  • 3D graphical application
  • Game
  • Real-time strategy

However, the gap between 3D graphical application and a game seemed huge. Because, we might have a 3D graphical application, but with sound and other facilities. Also, I considered to create 2D game. Given that, I expected to have something which would allow me to make 2D graphics, but also allows me fairly simply transfer to 3D if feel like that. In addition, I remember an old thread somewhere with the message, saying that making games with DirectX is much easier than with OpenGL. Given all that, I had a strong that there is some kind of libraries which covers the gap.

Apparently, I've never considered DirectX, because I wanted to make a game for GNU/Linux. All in all, it's nice to have a tool to make a cross-platform application rather than for a single-platform. On the way, I did a little research about Vulkan, because I had heard that it's cross-platform. However, "hello world" in Vulkan is verbose enough to show that it's not an option. And still, Vulkan is only API.

I don't remember how, but eventually I found SDL. I mean that I knew about SDL, but I had an impression that it's a minimal 2D graphical library. However, I found this video of the talk from Steam Dev Days 2014. In that video, Ryan Gordon (@icculus) actually compared a "hello world" in SDL2 and DirectX. The video showed that it has support of everything I need: sound, user input, window creation etc. I felt like it's exactly what I need. However, I still wanted to have an ability to make 3D graphics. I found Raylib, but I read that it has some font rendering issues. I had been researching how to include 3D into SDL. A miracle happened after a couple of days! SDL3 (actually 3.2.0) had been released.

I'm still not sure about the classification, though. However, I have got an impression that the groups should be this way from the lowest level to the highest. I see the highest a level as a real-time strategy game engine as an example of a specific game genre. I brought a blog-engine as the highest level of software, but there are other kinds of websites and they have their engines. Anyway, here is the list:

Level Hardware setup Windows and user input OS interaction Graphics primitives Model loading Graphical user interface Sound playing
Graphics API NO NO NO NO NO NO NO
Graphics toolkit YES YES YES NO NO NO YES*
3D Graphics engine YES YES YES YES YES NO NO
Game library YES YES YES YES YES YES YES
  1. Graphics API. Examples: OpenGL, Vulkan, WebGPU. Allows to do any graphics software, but you have to build everything from scratch. This includes hardware setup, application window and user input, OS interaction, sound playing, graphics primitive system, model loading, graphical user interface.
  2. Graphics toolkit. Examples: SDL3. Provides some abstraction above the hardware, so you don't have to take care of it up to a certain point. Application window and user input, OS interactions are implemented, you can play sound, but you have to set up the hardware. There are some plugins for sound, though. Graphics primitive system, model loading and graphical user interface are on you.
  3. 3D Graphics engine. Examples: OGRE 3D. It's hard to judge if these software is higher than SDL3. It has graphics primitives and model loading. However, it doesn't have anything for sound playing.
  4. Game library. Examples: Raylib. It has everything from the previous levels. Therefore, you open your code editor and create a game.
  5. Game engine. Examples: Unreal Engine, Unity, Godot. At this level, you get some extra things. You get a graphical editor, where you can create levels, meshes, textures, shaders, scripts. You have AI engine, animation tools, internal scripting language. The list of the things is far from complete.
  6. Real-time strategy engine. Examples: Spring, Oxidator. This might not have as much functionality as the game engines from the previous level, but they are designed to create only real-time strategy games. Therefore, they I can't swap them with the previous level. The scope of problems of the game engines is broader.

The difference from web development

  • Define webdev. It's network development
  • Webdev vs Gamedev
    • libs, frameworks, engines. My search for a better OGRE
    • Debugging. GDB and scripting to get into the right state
    • techniques:
      • object oriented
      • data oriented (example from OGRE)
    • Shaders again (the amazing talk)

Process

  • Define the groups of software which is used. It allowed to define the starting point.
  • Setup of the tools.
  • Experiments and niceness of the graphical API.
  • Building for Linux against building for Windows.

Emscripten

Preparation

  • Added the library as

    // lib.addObjectFile(b.path(sdldependencies[2] ++ "/build/build-emscripten/libSDL3mixer.a")); wasm-ld said that libSDL3mixer.a is not a WASM object. Probably I missed the platform when I was building SDLmixer.

  • Added libSDL3mixer.a to emcc command

    wasm-ld: error: /home/antlord/Nest/study/sdllearn/current/dependencies/SDLmixer/build/build-emscripten/libSDL3mixer.a(musicwavpack.c.o): undefined symbol: WavpackSeekSample64 wasm-ld: error: /home/antlord/Nest/study/sdllearn/current/dependencies/SDLmixer/build/build-emscripten/libSDL3mixer.a(musicopus.c.o): undefined symbol: opseekable

    • found that the archive libSDL3mixer.a doesn't have the object files of the dependencies. Of course the symbols are undefined.
    • found that the missed dependencies are in build/build-emscripten/external. I add everything this way

      emcc.addFileArg(b.path(sdl_dependencies[2] ++ "/build/build-emscripten/libSDL3_mixer.a"));
      emcc.addFileArg(b.path(sdl_dependencies[2] ++ "/build/build-emscripten/external/libxmp-build/libxmp.a"));
      emcc.addFileArg(b.path(sdl_dependencies[2] ++ "/build/build-emscripten/external/ogg-build/libogg.a"));
      emcc.addFileArg(b.path(sdl_dependencies[2] ++ "/build/build-emscripten/external/opus-build/libopus.a"));
      emcc.addFileArg(b.path(sdl_dependencies[2] ++ "/build/build-emscripten/external/opusfile-build/libopusfile.a"));
      emcc.addFileArg(b.path(sdl_dependencies[2] ++ "/build/build-emscripten/external/wavpack-build/libwavpack.a"));
      

    Nevertheless, I have to disable the unused dependencies -DSDLMIXEROPUS=OFF -DSDLMIXERWAVE=OFF -DSDLMIXERMP3=OFF

  • Errors about undefined symbols from SDLTTF

    I had the option -sUSE_SDL_TTF=1 added to emcc The errors I got are error: undefined symbol: TTFCreateRendererTextEngine I tried to build SDLTTF with emscripten

    • The error I got /home/antlord/Nest/study/sdllearn/emsdk/upstream/bin/llvm-ar: error: unknown option E

    I tried to build with emmake ninja

    • The error is the same

    I tried to disable freetype, but SDLTTF requires it. It says that HarfBuzz is for better text-shaping, PlutoSVG for emojis

    • Disabled the building of examples
    • the resulting archive libSDL3ttf.a contains its dependencies. Why SDLmixer doesn't?
  • Errors about undefined symbol path_open

    Can be fixed linking libc to the object file of the game, but it leads to another error: undefined symbol errno. I gave up on wasi and built the project for emscripten.

Building obstacles

  1. –sysroot in Zig build, the project has built
  2. An exception occurs in the browser console log when I allocate some memory. I decided to go back and fix the compilation error which occurs when I build in Debug mode

    /home/home/wantlord/Apps/zig-linux-x86_64-0.14.0/lib/std/debug.zig:870:24: error: no field named 'base_address' in struct 'debug.SelfInfo.Module__struct_13803'
                    module.base_address,
                           ^~~~~~~~~~~~
    /home/home/wantlord/Apps/zig-linux-x86_64-0.14.0/lib/std/debug/SelfInfo.zig:798:27: note: struct declared here
        .wasi, .emscripten => struct {
                              ^~~~~~
    error: the following command failed with 1 compilation errors:
    /home/home/wantlord/Apps/zig-linux-x86_64-0.14.0/zig build-obj -ODebug -target wasm32-emscripten -mcpu baseline -I /home/antlord/Nest/study/sdllearn/current/dependencies/release-3.2.4/SDL-release-3.2.12/include -I /home/antlord/Nest/study/sdllearn/current/dependencies/SDL_image/include -I /home/antlord/Nest/study/sdllearn/current/dependencies/SDL_mixer/include -I /home/antlord/Nest/study/sdllearn/current/dependencies/SDL_ttf/include -I /home/antlord/Nest/study/sdllearn/current/emcc/cache/sysroot/include --dep errors --dep config -Mroot=/home/antlord/Nest/study/sdllearn/current/src/main.zig -ODebug -target wasm32-emscripten -mcpu baseline -Merrors=/home/antlord/Nest/study/sdllearn/current/libs/errors/errors.zig -Mconfig=/home/antlord/Nest/study/sdllearn/current/.zig-cache/c/273c9fa14190b77ebfd0ed345b547773/options.zig -lc --cache-dir /home/antlord/Nest/study/sdllearn/current/.zig-cache --global-cache-dir /home/antlord/.cache/zig --name sdllearn --zig-lib-dir /home/home/wantlord/Apps/zig-linux-x86_64-0.14.0/lib/ --listen=-
    
    
  3. Used callocator. No compilation errors. No exceptions in the browser console.

Fix the sound

  1. Browser console: Cannot find preloaded image /assets/StartBTN.png. Consider using STBIMAGE=1 if you want synchronous image decoding (see settings.js), or package files with –use-preload-plugins.
  2. No error in browser console, but still no image. An error from SDL "That operation is not supported". The message is the macro for XCode and iOS. Definitely not the case.
  3. Error from SDL: Parameter 'SDLCreateTextureFromSurface(): surface' is invalid; Again the macro of the error is defined for XCode and iOS.
  4. I built SDL image from sources. It worked, but the browser hanged.
  5. I added emscriptensleep(0); The start screen works. I have to update the rest of the screens.
  6. The sound works, but the game lags now.

Afterwards

  1. The –sysroot is not needed

Conclusion

  • Data oriented design
  • SDL3
  • Zig
  • GDB and scripting to get into the right state
  • Conditional compilation to debug. Lesson from UE4
  • Lessons from making the game
    • Collisions
    • States
    • Text rendering
  • Cross compiling for windows was easier than for Linux
    • Zig compiled it, but I couldn't run the process. I guess because the dynamic loader wasn't set as the interpreter of the ELF. As a result, the process failed to open the shared objects of X11 and Nvidia driver.