How I made a game engine and game from (almost) scratch
The making of Hockey Slam
TL;DR: I built an Android game in my free time using C++ and minimal libraries (tinygltf, stb_image and stb_truetype),
Everything else from the memory allocator, the physics system, the OpenGL shaders to the UI was built from scratch. I got help with the 3D assets and the music but everything else I made myself.
You can download the game from the Google Play Store. thanks for trying it out!
The Unity prototype (summer 2017)
In the summer of 2017 I was noodling around with a few project ideas in Unity. One of the ideas was a simple ice hockey "shootout" game where you would go one-on-one and try to get the puck past an AI goalie. A tiny scope for the game makes it easier to develop but allows for adding more features later on. I tried it out and it seemed to have promise.
The goal of the design was that the player automatically moves forward, only controlling moving the puck left and right and trying to juke the goalie. Finally they aim and shoot, hopefully scoring a goal.
For a technical goal I wanted to make a 3D game, as I had already released a 2D-game and learning about 3D was something I had wanted to do for a long time.
Damn you Unity, I'll build my own!
I found out that one of the animation features (different animations running for different bones of an armature) was behind a premium paywall in Unity. My feelings for Unity were (and are) lukewarm at best, so I decided to ditch it in favour of writing my own engine.
I'll make my own animation system, how hard can it be?
3.5 years later I had my answer: kind of hard but worth it.
First steps (early 2018)
Building your own game engine is often discouraged, and for good reason. It's a gargantuan task that takes years and, when done for the wrong reasons, for little benefit. I understood this going into it, and decided to focus on:
- Learning as much as I could by doing as much as I could myself
- Finishing the game, if needed by cutting features of the game and engine to bare essentials
A spinning guy gains leg motion (September - December 2018)
Having tried out OpenGL as teenager, and remembering nothing of it, I did the first thing anyone learning OpenGL does. I opened up Learn OpenGL and started going through the tutorials. I had some notion of how 3D graphics work but actually making it happen is a whole different thing. I was determined how everything worked, instead of just following tutorials and then moving on to the next thing.
Instead of using the recommended GLEW and GLM libraries I implemented extension handling and 3D math operations myself. The extensions were quite easy although a bit cumbersome. Working on the math library however I noticed huge problems in my math skills. They made some things (like understanding matrix operations) extremely difficult and I would grind out Khan Academy math courses whenever I could to fix these gaps in my knowledge. I'm still not great at math but I am better at it. Building a solid base meant brushing up on the very basics, starting from the order of operations.
After struggling for a few weeks I was rewarded by a model I made in Blender spinning in a window.
Even though I was inspired by what Casey was doing I decided to not go for 100% Handmade (no libraries at all) direction. Importing PNG or 3D model files are full of edge cases and monotonous work and I felt they would needlessly slow me down at the start. I can always come back and write them again when I have a better understanding of the whole problem space. So after writing a parser for .obj files and immediately outgrowing the format, I decided to use the popular Assimp library for loading 3D models, and the excellent stb_image header.
After I had something moving on the screen I proceeded to writing a rudimentary memory allocator, starting on an animation system, thinking about how the code could be laid out, and UTF-8 text. I had come across problems related to UTF-8 and I had heard that handling it was complex, hard and something you should avoid. I decided to remove part of the problem using another one of the stb_ header libaries: stb_truetype. This way the really messy file-format stuff (rasterizing a codepoint to a texture) can be avoided, and I can focus on handling the text data.
Writing the code for parsing, handling and caching the codepoints turned out to be a fun exercise. Handling memory as bits and bytes seemed more manageable. Additionally, I felt more optimistic in exploring problems that I had heard spooky stories about being "too complex". Don't get me wrong, there is plenty of complexity in text handling and I would end up rewriting the system I had made. But solving the problem even in a shitty way will increase you skills more than just sidestepping it using someone elses solution.
With text on screen, a simple animation system and a basic lighting implemented I again had visible progress to give me a burst of motivation. These features were far from finished. I just had no idea.
Everythings all wrong (January - April 2019)
Weird glitches with animations, text not lining up quite as it's supposed to, models not appearing at all, memory leaks. Issues started cropping up. My first versions of different parts of the engine had started to show their cracks. The first months of 2019 I would rewrite large parts of the systems. I also improved on the development iteration loop and debugging tools. I split the renderer into it's own hot-loadable DLL. This had the added benefit of improving the architecture. Now I would have to first generate a list of commands for the renderer, instead of performing an unholy marriage of gameplay and rendering code in order to cut corners, only to later discover that their horrible spaghetti offspring had infested the whole codebase. The rewrites and bug fixes were tedious. I understood going forward that my first attempt at any new system would suck, and that could not be helped. I won't understand the problem well enough to solve it if I am just introduced to it. Thats fine, just plan to rewrite it again.
Some problems seemed to be impervious to me poking around the code. Models and especially their animations would look different in Blender than in game. To find and fix those issues I made changes to the code. I made changes to the models in Blender. I tried changes to my modeling workflows discussed in Assimp issue trackers. I tried different file formats. I tried someone elses models.
I would fix an issue, only to load another model and see a new and a completely different issue! 3D model formats are numerous, all with their different problems. Assimp also handles them the best they can but it also has it's own baggage, bugs and flags that need to set just right. With bind poses, inverse bind matrices and everything else needed to animate a skeleton, you either get all of it right or you get some sort of mutated shivering spiky ball with no indication of where you went wrong.
I had no idea if the bugs would be due to the my code, the model format, Assimp, Blender exporting or all of the above! I was still learning about 3D basics, modeling and animations, so this seemed like a huge obstacle to debug. Frustrated, I began to look for alternative ways to fix these issues.
Goodbye Assimp, hello GLTF (April - June 2019)
I needed something simpler, something that limits the problems I could have and I could learn from without having to learn all of it at once. Enter GLTF, the 3D model format developed by the same folks that developed the OpenGL specification. It solves a lot of the problems other 3D formats have, and at it's core its extremely simple! I opted again to use a library for the parsing (TinyGLTF) but I could easily compare individual values from the cleartext .gltf file, and the what my code is actually doing. I had to write a lot more parsing logic than in Assimp but since I could actually understand the input data and the desired output, at each step of the way I could validate I was doing the right thing. After implementing GLTF, using a simple 2-bone "snake" and carefully looking at the input data I slowly but surely fixed the animation issues. And they didn't come back.
I would return to the animation system multiple times, reworking how it's laid out, and fixing bugs. But I wouldn't stumble around aimlessly in the dark looking for answers on any forums or issue trackers. By doing more myself I had gained back control over what was happening on screen.
Sweeping bodies or how to simulate the world (July - November 2019)
Time to add the puck. The puck can't move unless there is a force acting on it. The game needs physics. Realizing this simple inference would take most of my development time, and I still find the physics system one of the more lacking things in the game. Physics simulation, especially in 3D is tough. Really tough. The minimum that you could call "physics" in a game, consists of a couple of objects interacting somewhat realistically. This but without everything jiggling through everything else took multiple attemps. I limited features heavily, only using Axis Aligned Bounding Boxes (everything is shaped like a box, even balls. Nothing rotates).
You'll spend weeks testing, improving and testing again. You'll set up simulations that makes sure you fix all tunneling (things going through other, solid things) issues.
Then you'll give it to someone else to playtest and nothing works. Bugs in physics systems are tough to find and even tougher to fix.
Collision testing using sweep tests and aiming to keep things simple, yet robust eventually got me to a point where the puck only rarely spazzed out and slipped the net, the walls or the surly bonds of earth.
And thats all anyone can ask for in a game about ice hockey.
After adding the physics I could add very simple game logic to test the basic idea of how the game would behave. Shooting the puck around was the first bit of gameplay that felt fun.
Shadows, Androids and something game-like emerges (November 2019 - February 2020)
Graphics programming is a curious thing. You plan to implement something, lets say colored lighting. You declare to yourself: "This is the final feature the graphics system needs to look as you wanted". You start and research, experiment and work through adding the feature as you want. And it looks great! But somethings off. It doesn't look quite right. It's obviously missing shadows! Again you declare: "This is the final feature the graphics system needs to look as you wanted", and off you go. The rabbit hole will never end. You just need to decide when you're deep enough.
Shadows are a must though, and sort of complex. There are many ways to do them, and to do a good job you have to spend quite a lot of time tweaking and testing them and finding the perfect one for your use case. I chose to use shadow mapping, where a texture is created from the point of view of the light source and everything the light "sees", is illuminated, and everything not, well isn't. Even shadow mapping has a multitude of techniques, with varying results and trade-offs.
Shadows in video games are basically smoke and mirrors. None of it has anything to do with how light works in real life (apart from games using ray tracing).
You just make up an approximation and work on it enough that most people are fooled. However you and anyone else who has worked on realtime shadows will immediately
spot errors in your implementation and you'll spend weeks getting rid of "shadow acne" and "shadow swimming". Shadow mapping was a challenge for me, every time I got them working they just didn't seem up to snuff.
Shadows, along with animations and physics was something I would come back to multiple times. I would always notice when they were cast not quite right, or looked different on Android, and I would have to fix them before moving on.
It's such a small part of the graphics but getting it right is really worth the effort. And afterwards you're cursed with seeing the CSM changing to the next texture in the distance in your favourite game.
Immediate mode UI or who needs "Dear I-M-Gooey" anyway? (March - April 2020)
The neat thing in building your own game engine is that you're free to pursue any careless idea that pops in to your head.
I had read about Immediate Mode UIs and although they're mostly(?) used for debugging tools, and not for the actual game UI, I wanted to do one anyway.
I knew the UI would be extremely simple and this would give me the opportunity to see for myself what the buzz was about.
And to my surprise my UI library worked quite well. Building it went well with refactoring text rendering yet again and I found that I could get the components up and running quite quickly. Even things like animations were easy to handle and I only did minor adjustments to the system once the bulk of it had been finished. I suppose for some cases IMUI's are a bad fit but for my simple little game it was perfect.
It's nice to get a win that doesn't require weeks of grueling reworking and bug hunting. On with the next one! We'll get back to the weeks of grueling work soon enough.
Modeling, lighting, gaming (June - July 2020)
I knew going into this project that I would hire someone to do the 3D assets. I do kind of know my way around Blender, and I do enjoy making something goofy-looking but making actual art is out of my wheelhouse. However I wasn't quite sure how I wanted the game to look, so hiring an artist at this stage was not smart. What would I tell them, since I didn't even know what I wanted?
I also started to feel worn out by the project. I knew the end was nowhere in sight, and I had spent a lot of time on working on things I felt weren't exciting. Developing the game felt like work I had to do after a day of my actual work.
Around this time browsing HN I found an excerpt from "Surely you're joking, Mr. Feynman", where he mentions finding the spark to work on physics problems again by "playing" with them.
This is a great piece of advice. I started this project and even programming because its fun. It's work that can feel like playing.
So the summer of 2020 I spent just dicking around. Testing gameplay ideas and doing models in Blender, trying to find out an art direction. I made animations of the characters doing cartoony, silly stuff and didn't worry about the quality. I added a slow motion mode since it felt like a fun thing to do.
Playing around with the engine and assets really gave a good energy boost to keep working. It's easy to get bogged down by looking at the list of bugs to fix or the even longer list of things that are still missing. I also found a talented artist on reddit's /r/gamedevclassifieds and they started working.
By the end I decided the game really needed a graphics update including colored lighting, normal mapping and improved lighting. I also kept working on the Android "port". The game had been built on Windows for easier development and I wasn't even completely sure how it would run on phones. I did get everything sorted however. Now about that grueling work...
You finished a feature? Great! Time to tear it down and build something else (July - August 2020)
The graphics and lighting currently in the game are sort of "old school". Blinn-Phong lighting was all the rage in the late 90s and 2000s but games technology had moved on. Blender, GLTF and most everything else now utilizes PBR, or Physically Based Rendering. I had been eyeing the tutorials for it going through the Learn OpenGL tutorials but felt that I didn't really need it. I could manage with older tech, games before PBR looked just as great. No really, I'm making a mobile game and PBR is for high-end stuff. It would mean redoing most of the graphics engine. I'm fine thanks!
Anyway, I started implementing PBR. The first model I got from the artist I used PBR and looked great in Blender. I wanted to make sure they looked great in game as well. After a relatively short time (I must be getting better at this graphics thing) I had the first version working and loaded up some model of a knight I got off the internet for testing.
And it. Blew. My. Mind.
I had no idea something could look this good inside my game engine. So far in my mind the tech inside my game was closer to Bubsy 3D than Unreal.
But this was different. Changing over to PBR shading really showcased that code I wrote could really display some impressive things. My shitty programming art just hid the fact.
Of course this was far from Unreal but at the same time it wasn't.
Cavarly has arrived (September - November 2020)
By September of 2020 I finally got the assets made by the artist inside the engine. Of course it only revealed that more work had to be done but I was confident that graphics-wise no more large refactors needed to be done and no major features were missing (for real this time!).
After you have the main components you can start assembling your game. I had already made some placeholder logic for the flow of the game including the AI for the goalkeeper. I say placeholder since you can't really finalize anything until you have everything. So the git commit history for fall of 2020 looked like this:
- Memory work
- Camera work
- UI Work
- Launcher work (launcher being the "meat" of the game)
- Physics work
- Animation work
By the end I had something resembling the final product but we're not there yet! Not even close.
Once you have the "game", you start the process of adding details, endless tweaking and polishing. This is an important step as work done here really shows as an improvement of the overall quality of the game. The bulk of my games performance optimization was done here. I also added a multithreaded job system for animations. Why? Why not, who's gonna stop me? The more time you spend here, the better.
Did you see and hear that performance?! Let's watch it again (November 2020 - August 2021)
I hadn't really given out builds of this game yet. I wanted to wait until I was reasonably sure that it was somewhat representative of the final product. I also had no idea how the game would perform on actual Android phones. I had kept the old Blinn-Phong shading codepath in case PBR was too rough for phone hardware.
So it was time to build a performance test version of my game and hand it out. But to do that I needed something that was also missing from the gameplay. Instant replays are a core part of any ice hockey game and I didn't have those. With all sorts of different hardware my game would run on, my physics system and generally it being a non-trivial problem, I knew replays were going to be difficult to implement. And they were.
By this time progress on the game had slowed down considerably, and it really felt like a steep climb to finish the replay system at all. It's a real pain to try to sync up the different runs of the same physics simulation even on your own machine. A small desync would mean the puck could hit the goal posts (instead of sliding past them into the net like in the actual game), and still register as a goal. A replay system where the outcome changes is not useful at all.
In the end, I ended up just turning off the physics, and replaying every single position of the puck. A fixed-step physics system meant this would look stable and interpolating between the positions kept the flight of the puck smooth.
I also stored any other interesting actions (for example the goalie reacting) and used the same clock for everything to keep them in sync.
Not the most elegant solution but hey, it works. Luckily my game is small and the tracked objects can be counted with 1 hand.
I also added a (banger) soundtrack as the only audio in the game. Altough audio is super important in a game, I cut this corner and kept the audio system simple. Also most people don't really play mobile games with the sound on.
Launching in v1, v2, v3, v4...
The movement of the player and the shooting of the puck are all contained within a "launcher". I knew this would have the most impact on the feel of the game, and the fun, so I planned redoing this over and over. I also wasn't quite sure how the aiming would work with touch controls. My only goal for it was to be playable with 1 finger. I didn't want any double joystick action.
Some versions of the launcher worked better, some worse. Playtesting became quite important in seeing this in action. Over the years I've gotten quite good at my game (1v1 me) so I've lost all and any ability to judge if the moving and aiming works or not.
In the beginning I had hoped I wouldn't need to add a crosshair to assist with the aiming. Instead I had hoped to find a control scheme intuitive enough that players would know where the puck would go. This however did not happen. I ended up adding a crosshair and immediately the game felt better to play. The final launcher (v5) was actually a mix of the older launchers, those that hadn't worked without a crosshair.
The game to me still feels sort of easy but hopefully hard enough to engage the player for 15 minutes or so. Nearing the end the lack of depth in my game became apparent.
Had I been working all these years for a couple minutes of game time?
And the answer is well, yes. But my goal was finishing a game that I built myself. If on top of this I planned to add hours of immersive content I would not release anything ever.
So what's left? Plenty! Bugs need to be fixed, things need to be smoothed out and UI assets need tweaking. The pace of the work has grinded to a halt. It's time to grit teeth and push though the last tough bit.
Release and final thoughts
In 2021 I didn't work on the game a lot but I was determined to finish it. Nearing release doubts creep in: the simplicity of the game, the bugs not fixed, the audio system being a single track on a loop.
You should work on it some more before releasing it, right?
You've done so much, it's just another hassle to release it. Just keep it in your projects folder and start another.
You spent all that time working on the animation system? Who's gonna see it?
All the things that are pale in comparison to all the things that could be. My simple little project, which took years to make will just be a small blip within the hundreds of games released on that day. Better check my list of goals.
- Learning as much as I could by doing as much as I could myself - ✔
- Finishing - ✔
In all honesty, I am ecstatic about finishing the project. Building a game engine and a game with it is no small feat and is something I've dreamed about ever since the phrase "Hello World" appeared on my monitor. On a more practical side, it has deepened my knowledge on skills relating to my career in software development and I have gained confidence that even problems that people describe as "complex" and "hard" are not in fact impossible. The internet is full of people working on their own game engines. Hopefully reading about some of my struggles helps someone keep working on theirs. I know posts like this helped me.
If you're still here, thanks for reading! You can find Hockey Slam on Google Play Store
You can find me on: