Game++. Part 1.1: C++, game engines, and architectures

# cpp# gamedev# programming
Game++. Part 1.1: C++, game engines, and architecturesUnicorn Developer

This book offers insights into C++, including algorithms and practices in game development, explores...

This book offers insights into C++, including algorithms and practices in game development, explores strengths and weaknesses of the language, its established workflows, and hands-on solutions. C++ continues to dominate the game development industry today thanks to its combination of high performance, flexibility, and extensive low-level control capabilities.

1361_pt_1/image1.png

Why C++?

Despite its seemingly archaic nature and complexity, C++ remains the mainstream language for developing games and game engines. The reason lies in the unique balance between low-level control and high-level abstractions, which is critically game development.

On the one hand, C++ provides full control over system resources. Developers can manage every memory byte, optimize processor cache usage, and build synchronization and parallel execution systems tailored for specific architectures. C++ allows developers to create code that efficiently runs on low-end PCs, past gen consoles, or mobile devices. It also makes it possible to manually manage memory and data placement to maximize cache efficiency, which is crucial for achieving high performance.

On the other hand, modern C++ provides powerful high-level abstractions for building complex systems. Features such as classes, inheritance, templates, containers, smart pointers, RAII, and lambdas enable the development of large-scale architectures with modular logic, entity systems, and event-driven mechanisms. When used wisely—without excessive use of templates and metaprogramming—the code remains expressive and maintainable.

Other high-level programming languages, such as Python, JavaScript, C#, and Java, offer undeniable advantages, including automatic memory management, convenient syntactic sugar, extensive standard libraries, and significantly improved readability. This is why they are popular among designers, technical artists, and UI programmers for scripting tasks in games. However, these languages ​​have serious drawbacks when it comes to developing game engines. Their reliance on virtual machines or managed runtime (JVM. CLR, etc), unpredictable garbage collector behavior that can block execution at the worst possible moment, lack of precise control over execution time, dependence on external solutions, and suboptimal memory layout schemes less suitable for performance-critical subsystems.

Such limitations are unacceptable in the realm of game development, especially at the engine level. Garbage collectors, extra memory overhead from virtual machines, or inefficient use of the processor cache can cause random freezes and frame rate drops. Physics simulations, rendering systems, netcode, and core engine logic require predictable performance, maximum resource efficiency, and low tolerance for latency spikes and instability.

C++ remains relevant in the gaming industry, not because it is the best option, but because the alternatives are too abstract to solve the real technical challenges of game systems. This is a conscious trade-off: the language is complex and potentially dangerous, but when used correctly, it offers the efficiency necessary for creating modern games. As long as performance and resource control requirements remain mission-critical, C++ will continue to be a key player in the game development industry.

What is a game engine?

Back in the day, when game engines were simple, writing your own was a no-brainer because if you didn't have an engine, you basically didn't have a game. Some people would buy someone else's engine and enjoy the beauty of reinvented wheels, while others would spend months or even years creating their own.

To understand how, and more importantly why, certain mechanisms are used within a game or engine to identify, locate, and eliminate performance and architecture issues, it's necessary to understand how game engines work and how they are created.

John Carmack once said, "The right move is to build your own engine," though many would argue that it's not that simple. However, Doom's "father" is widely known for his contributions to game engine development. He also frequently criticized off-the-shelf engines in general, advocating for in-house technological solutions. His philosophy is consistent: having an in-house engine gives developers complete control over the technology and the power to create solutions tailored to their projects' specific needs.

Well, everybody's got a point, and there's no single way to do things—it depends on the situation. Perhaps creating an engine isn't as difficult as it seems? But why bother doing it at all? What does it really mean to "create a game engine"?

I've worked with various game engines and built a couple of my own from scratch, two of which ended up in the trash. Three proprietary games were released on my third one but saw little success, and that engine was sold to a studio for some sum 15 years ago. I've seen developers using other off-the-shelf engines, complaining about issues, and devising their own solutions. I've studied—and continue to do so—the source code of other engines, both open-source and proprietary.

My experience may be somewhat biased, though: I've always wanted to create strategy games; however, most of the projects I've worked on have been shooters.

The first key question is: why do you want to do it?

Deciding to create a game or a game engine may seem like either a crazy idea or a genius one. Well, it's actually both. This is one of the cases when the journey is way more exciting than its destination.

...Firstly, secondly, and thirdly, it's a lot of fun, I promise!

Developing an engine is like playing with LEGO, but for adults and usually alone. You can add rendering, particle systems, and hot-reloading for scripts. You'll laugh, be amazed, and possibly even curse, but one thing's for sure: it won't be boring!

...Fourthly, companies will pay good money for such professional experience.

The fact you went through all the trial and error means you've gained something you can't learn in any textbook—a hands-on understanding of how a game engine works under the hood. Real-world experience is the greatest asset in the job market, and game studios are willing to pay handsomely for an in-depth knowledge of how game technologies work. While most people know how to use off-the-shelf solutions, when an issue pops up at the engine level, they need someone who's been there and done that—someone who knows where to look for the root of the problem.

...You'll definitely learn a lot along the way.

This will help you better understand not just engines, but other things as well. You'll develop real engineering intuition, grasp the concept of "heavy algorithms," learn why loading textures in a frame is a bad idea, and what are cache-friendly data structures designed to maximize the usage of processor cache memory.

This kind of knowledge comes from personal experience, not books. When you eventually stop working on your engine—which you most likely will—you will approach game and software development differently.

...By the way, speaking of a trash can...

Based on experience, that's where your first engine will end up, and so will your second one. This is okay—it's just how learning works. You might've heard of the "three systems" rule: the first system is a trial run, the second tries to do everything "right," and the third one actually works. So, don't be afraid to code, get rid of bad ideas, and start over. Each "failed" attempt doesn't waste your time; it gives you invaluable experience that brings you closer to creating that third system that will actually work.

...Your engine is as free as your spare time after work.

As it turns out, it's not free at all. These are hundreds of hours spent in the evenings and late at night. These are internal debates. These are refactorings of what you enthusiastically wrote two weeks ago. These are "nearly functional" prototypes, and while you may say, "I'll finish the UI later," what you gain in return is understanding and confidence in your decisions.

...You can and should change everything to suit your wants and needs.

Want to hot-reload JavaScript configurations? Then you'll need to learn how JavaScript and its virtual machine work. Tired of the old way to load textures? Create your runtime atlas directly from disk files. The only real limits here are your skills and the amount of time you have. Bizarre ideas are good—they create unique solutions.

...You have full control over all the inner workings.

What systems are involved, how the world update works, and what data structure stores game objects. You won't have a "black box," only a call stack and an urge to streamline everything.

...You can use your favorite programming language.

Like Rust? Use it! Prefer C++? Welcome to the pain club. Some people write game engines in Go. I've seen engines written in Lua, Python, Haskell, and even Erlang. I haven't seen one written in COBOL yet, but I bet it exists somewhere. What matters most is that you know why you picked the particular language.

...You can keep the distribution tiny.

For example, the binary file for my pet project is about 2 MB. It contains scripts, settings, and even some assets. The rest loads from the disk. For a small game, this is a luxurious dream, especially compared to engines where even a "Hello World" script weighs 200 MB.

...You can release your engine to the public.

And that's a great idea. Firstly, to see how others use it. Secondly, to find those who can actually compile it. Only a few will succeed—about 0.1% of those who downloaded the engine. They are the right people to bring into your team, though. By the way, Epic Games did something similar: active contributors to Unreal Engine were offered official jobs.

...You can even sell it.

Just be careful, because supporting an engine for other people's whims is a different kind of hell. They'll inevitably ask you to "make it just like in Unity, but different." This is an endless struggle.

...You'll never be disappointed that the engine developers didn't implement the feature you want!

You're the developer here. Want a new navigation system? Implement it. Need to expand the save system? Go ahead. You won't get turned down with these bug report replies like, "Scheduled for Q5 next year."

...Finally, about tech interviews.

Having your own game engine almost always sparks an interesting conversation. Now, instead of just answering questions, you can confidently ask some of your own: "How do you update game objects? How does asset loading work?" At that point, people start listening. They realize they're talking to an engineer who has created their own solution and isn't afraid to discuss it—not just a programmer.

For me, the first two points and the last one are the most important. Learning is fun, learning while creating a game is twice as fun. I also really enjoy programming, pushing the boundaries of what a language allows, and delving into the complex details and subtleties of algorithms and systems. Well, meaningful conversations during interviews are a nice bonus.

Game developers always complain about engines, no matter how powerful and convenient they are: this doesn't work, that is broken, this new feature doesn't support that old one thing, and that old feature was requested ten years ago, but still nothing has been added. Of course, this is all for a reason—if you've ever worked for a big company, you'll totally get what I mean. However, blaming someone else is always easier than taking responsibility. If you need some new feature, why not just implement it yourself? Well, you can't, the engine doesn't belong to you. It's someone else's, but the disappointment is still yours. This is hardly surprising. Just as it's hardly surprising that the desired feature will be added to your engine when you really need it.

By the early 2000s, the term "game engine" had become part of developers' vocabulary. However, most projects were still custom-built, and almost all of the technology was written from scratch, including custom rendering engines, animation systems, and physics engines. It used to be common practice to build an engine tailored to a specific game's genre, platform, and technical limitations. The industry has changed significantly since then: games have evolved into commercially viable, mass-market products, and development technologies have become more standardized and modular. Modern game engines, such as Unreal Engine, Unity, CryEngine, and Source, as well as the proprietary tools of major studios, have evolved into comprehensive SDKs that empower developers to create entire game franchises across various genres on a unified technological foundation rather than just a single project.

Although engine implementations can greatly vary, a relatively stable set of key subsystems had emerged by the mid-2000s: graphics, physics, animation, audio, AI, UI, system logic, replication and networking, in-game scripting, data systems, etc. Moreover, specific patterns and architectural approaches have begun to emerge within these components, recurring from project to project. These include ECS (Entity-Component System), behavior trees (BT), deferred rendering, and rollback-based concurrency control system. However, developers had to gather information on these topics piece by piece: an article here, a talk at GDC there, or a code snippet from GitHub.

There were books on graphics, physics, and AI architecture, but those were just scattered bits of knowledge. Almost no resources tied everything together by explaining not only what these things are, but also how they work under the hood and how to design game architecture while balancing technical limitations and production realities, as they did at GEA.

The second key question is: do you really need it?

Personal projects are always challenging and require crazy amounts of time. Of course, a lot depends on your end goals, but don't expect to build an Unreal Engine clone in a year. After all, it took a team of highly skilled engineers twenty years to develop the engine as we know it today, and the community has also contributed to its development. You'll always feel like something is missing in your own game engine. It takes many years of hard work to achieve even a fraction of capabilities and user-friendliness that mature tools offer.

A custom-built game engine can rarely compete with major industry ones such as Unreal Engine, Unity, CryEngine, or Godot. A game engine can compete only if there's a large, well-funded studio behind it. However, this book is probably not for those folks, since they've likely already got their own tool or are using one of the ones listed above.

Keep in mind that rarely pays off for small teams. There won't be a moment when it suddenly starts generating generous profit. No, it's unprofitable even for industry leaders. Games make money, not engines... According to Epic Games' report, the cost of developing Unreal Engine over the past four years alone exceeded $160 million. It's as if every year they release a new AAA game that we can't even play!

In 99/100 cases, game engine developers never make it to the actual game if their goal is to develop the engine itself. This path leads straight to the game being canceled. Most people may consider someone who wastes time developing their own engine to be a silly person.

Advice

If you want to release your game as quickly as possible, just go with a ready-made engine and use the features it offers. If you want to grow as a developer and lay the foundation for future projects beyond game development—and if you have enough time, energy, and patience—then go for it! A custom-built game engine is probably the best way to apply your knowledge. Plus, once you've created a solid base, you can reuse it as much as you want, improving it with each new game.

Libraries + cmake != Engine

Since you've read this far, I haven't convinced you to quit this thankless job. Well, let's move on then...

Can we call SDL/SFML/Allegro game engines, considering that tons of indie games have been made using them? Definitely not—these are libraries for wrapping platform code, although they do enable many of the basic game engine features. What about Vulkan/DX12? Absolutely not—these are graphics APIs. And what about FMOD/WWISE?

Again, no—however, they can be used to create a two- or three-dimensional scene, albeit an audio one. So, let's summarize:

Libraries + API != an engine either

Now, imagine a small project about exterminating spiders and building a base on another planet, which combines Allegro, DX11, and stb (a set of different header-only solutions for all kinds of situations) to draw moving sprites on the screen. You probably recognized Factorio from this description. It all started with the demo version on Allegro and just a few sprites:

Allegro + API + stb + talent == Factorio

Is it a game engine? The answer is still no, but we're getting closer: a game engine doesn't exist without a game (or games) built on it. Until a game is released, it's just a set of libraries glued together using CMake. Can a game engine be just a combination of graphics, animation, and user input processing? So, the conclusion is as follows:

Factorio != the engine && Factorio == the game

A game engine without a game is just a collection of tools, libraries, and other components designed to help create games. A game without an engine is still a game. If you think that such a definition of an engine without a game is vague and almost useless, you're right.

Do you really need complex material editors and miles of shaders, seamless geometry and global illumination for it, 3D physics with inverse kinematics, and network game support if you're making a 2D pixel-art platformer?

Unreal Engine has all these features, and they work pretty well, but do you really need them all? They require experience, time to get used to, and knowledge of how to configure and use them. A simple platformer game must only handle input, render sprites, maybe even display raw .png files on the screen, and play sound. You can learn all this in a couple of days from free tutorials on YouTube. You'll spend these evenings doing something useful, rather than struggling with the character controller in the editor. Finally, you've reached the main point of the whole endeavor—creating your game.

But what if you'd want to create an AAA blockbuster with next-gen graphics, physics, and so on? Well, then you'd need all these complex systems. It would also take a few decades of free time, or a team of talented engineers working full-time exclusively on the engine, to bring it all to life. Yes, modern game engines have indeed moved hopelessly far away from retro platformers.

Still, most game engines have an architecture that integrates all these subsystems into one framework. Not all of them—at least not yet—but it's important to understand that a game engine can contain only some of these features and still be considered a game engine. Not every game needs all these features at once; otherwise, no indie game would ever be released. You can design a small engine specifically for your game. But let's be honest—big game engine capabilities spoiled us, so we expect the same performance from everything else.

Some useful stuff

However, if the engine was designed specifically for a game, then having fewer features is perfectly acceptable. No matter how you develop games—whether on your own or someone else's engine, from scratch, or building on the ruins of a previous project—you'll start to notice recurring patterns. Everything that seemed like a "temporary solution" at first will eventually either become part of the foundation or be tossed in the trash. This is how the standard set of essential components emerges. First things first—when it comes to visual level and UI editors—no need to manually write levels, even if it's just a quick prototype in an evening.

When you already have something moving on the screen, the next issue inevitably appears—updating the logic. A rather simple approach is to put everything in the update function (Game::Update()), updating whatever reaches it in time. Then, you add the update priority, followed by the dependencies between systems, and then the question arises: if physics runs separately, what about the collision-based logic? Even if the game is small, it's better to design the logic as an independent system than as a series of "crutches" dependent on the array element order. Components work best if they know nothing about each other—they subscribe to events and don't care what invokes them.

Next comes the universal issue of the scene/level system. It seems obvious: you have a menu, gameplay, and cutscenes. However, when a level loads within another level, then one game mode switches to another without exiting to the menu; when it's necessary to "freeze" a scene, overlay a tutorial, and then return back—that's when the scene system falls apart if it wasn't designed for these cases. A scene isn't just a list of objects; it's a managed container of logic, data, dependencies, and transitions.

Speaking of objects, it's impossible to overlook the entity manager. Though it may seem useless and redundant at first, it proves to be the core of the entire architecture: It's responsible for creation, destruction, identification, tagging, access to components, and sometimes even debugging.

By the way, when it comes to saving, there's a game state serialization system. Some people think the only reason to use it is to save the game, but it's actually essential for everything. Auto-saving, passing states between scenes, saving configurations, replays, and rollbacks—it's amazing how many problems disappear when you can "put everything in a box and restore it."

And finally, the variable inspector. I'm not referring to a full-fledged debugger, but rather a panel where you can see variable values, ticks, and flags in real time. Where you can pause to check what a specific NPC is doing, change its behavior, enable debug information, and see how many bugs disappear simply because you finally noticed what's going on in the game logic.

Programming

It may seem strange, but traditional programming practices aren't mandatory for implementing game logic, and it'd be enough to explore visual programming systems (Blueprints, VisualScript, and ICS), which have been around for decades and allow developers to create complex mechanics without writing code manually. These systems are especially popular among designers and artists who want to prototype ideas quickly, without diving into the programming language syntax.

Designers do write code; visual programming is a legitimate form of programming, it's just that constructs like class, if, and for have another form. However, the essence lies not in forms, but in practical aspects. If you want to implement visual scripting in the game engine, you'll need to create specialized tools: node editors, visual debuggers, data-flow diagrams, optimizers, and much more. This significantly complicates engine development. For example, in Unreal Engine, Blueprints are backed by a huge amount of low-level C++ code, comparable in scale to the renderer or animation engine.

Classic code remains the most versatile and flexible way to implement logic, but it's gradually losing ground. It doesn't depend on the limitations of visual tools and allows working closer to system-level resources. However, the classic approach entails well-known problems, such as recompilation, halting the engine for rebuilds, and the time lost in these processes. And it's worth noting that training designers to work with different programming languages (for example, C++) results in high financial and time investment, and the cost of a mistake is the same as for a game programmer—an error can still crash the engine.

Main window

Most games need a window to run in—even browser games are no exception here. The window can be either the entire web page or a specific <canvas> element where the game is executed. Windows are usually created using platform-dependent mechanisms. These low-level interfaces are often cumbersome and inconvenient. What's more, you need to write separate code once for each platform (Windows, Linux, macOS, iOS, or Android) to create a window—but you rarely revisit this block afterward.

You need not only the window; there is also a graphics API context (OpenGL, Vulkan, or DirectX), which is also created via messy platform-dependent calls. Once again: you either implement them once yourself, or use a ready-made library (SDL2, GLFW, SFML, or one of many others). After creating the window, the next step is to set up the game loop—this is what will handle events, update the game logic, and render each frame.

Gameplay loop

The gameplay loop is what unites all games and game engines, although it may be veiled within the engine, replaced by abstractions or handler functions (callbacks). Ultimately, the gameplay loop code looks like this:

int main()
{
  createWindow();
  init();

  while (isRunning())
  {
    handle(inputEvent());
    updateFrame();
    drawFrame();
  }

  shutdown();
  destroyWindow();
}
Enter fullscreen mode Exit fullscreen mode

The gameplay loop runs the game—without it, the main function would just end, causing the game to crash. There are many ways to expose a loop using the engine's API: it can be fully explicit, when the engine user writes something like while(engine::isRunning()) in their main function, or find it in the depths of the engine, for example, in some function like engine::run(), which in turn calls some user-defined functions (callbacks). Whatever you choose, you'll still need a gameplay loop.

User input

This is one of the foundations of any game, even if the player only has to click a pixelized cookie. The most basic input processing relies on platform-dependent APIs (WinAPI, X11, or Cocoa), which are often awkward to work with: they're not complex, but very verbose and require separate code for each supported platform. However, almost all of these APIs can extract events into an event-handling function (for example, Game::pollEvents()), which can be used to retrieve user events sequentially.

When it's time to integrate input into the game engine, you can use various approaches. One option is to provide users with direct access to the event system. In this case, the game developer calls events via pollEvent() and decides how to handle them. The basic structure of event processing usually looks like this: in the gameplay loop, the event-handling function is called at each iteration, parsing all incoming events are sequentially. Depending on the event type, the corresponding functions are called or appropriate actions are performed—for example, handling mouse movement, keyboard input, or window closing. This approach provides maximum flexibility but requires the developer to write numerous code lines and understand the event structure.

In practice, most game developers rely on cross-platform libraries or built-in input systems in game engines like Unity, Unreal Engine, or Godot. These solutions handle the low-level, messy interaction with the OS and expose a unified and user-friendly interface for processing keyboard, mouse, gamepad, and touch input. Here's an example:

while (engine.isRunning())
{
  Event event;

  while (engine.pollEvent(event))
  {
    switch (event.type)
    {
      case Event::MouseMove: 
        application.onMouseMove(event.mouseX, event.mouseY);
        break;

      case Event::KeyPressed:
        application.onKeyPressed(event.keyCode);
        break;

      case Event::WindowClose:
        engine.quit(); 
        break;
    }
  }

  engine.update(); // Game logic
  engine.render(); // Frame rendering
}
Enter fullscreen mode Exit fullscreen mode

Another option is to integrate event handling directly into the engine and provide the user only with calls to handler functions. It accelerates and streamlines early development. However, it requires a well-designed architecture for registering these functions.

This approach makes life much easier for game developers, especially at the early stages of a project. Instead of manually querying input devices and processing complex event logic, developers simply register handlers for the events they're interested in—key presses, mouse movements, or screen touches.

The appropriate handlers are invoked at the right moment, with all the necessary event information about passed to them. This approach works well for simple games and prototypes, where rapid development is a priority and complex interaction logic isn't yet required. Popular libraries (frameworks) implement this event-driven model, but as a project grows in complexity, this architecture can become a source of problems. The main difficulty lies in creating a flexible system for registering and managing event handlers—callbacks must be easy to add and remove during execution, priorities may need to be set, and events may need to be filtered by game context. With many registered handlers, each event can trigger a chain of calls that were not initially anticipated. Here's an example of code:

void onMouseMove(MouseEvent event) {
  application.onMouseMove(event.mouseX, event.mouseY);
}

void onKeyPressed(KbEvent event) {
  application.onKeyPressed(event.keyCode);
}

void onWindowClose(AppCloseEvent ev) {
  engine.quit();
}

int main()
{
  engine.subscribe(onMouseMove);
  engine.subscribe(onKeyPressed);
  engine.subscribe(onWindowClose);

  while (engine.isRunning()) {
    Event event; updateFrame();
    engine.update(); // Game logic
    engine.render(); // Frame rendering
  }
}
Enter fullscreen mode Exit fullscreen mode

Graphics

You got the main window and even teach your program to react on mouse and keyboard, but... You can't see its response—graphics comes to the stage. Honestly, I struggle with 3D graphics and am always jealous of my friend, @megai2, who can easily render everything for any platform, from consoles to mobile devices. Fortunately, most libraries for creating windows, like SDL, already provide support for 2D graphics. You can draw directly to the window, use textures, or even fill pixels with any colors. It's basic but honest.

Let's say the engine is optimized for 2D games built around tiles and sprites. Then, most likely, you'll have to implement something like drawSprite(image, x, y), possibly with support for all sorts of delightful features: scaling, rotation, blending, desaturation, and alpha blending. Internally, you can manually implement it either by directly copying pixels (with/without evaluations and blitting), or by relying on another library like Cairo. It turns out to be a very good renderer, capable of doing if not everything, then at least a great deal. You can also do it via OpenGL/DX/Vulkan, if your heart craves hardcore performance.

That's not the only option, you might also choose not to specialize at all and simply give the user a set of general-purpose APIs for the engine, as most SDL/SFML/Allegro libraries do—let them figure it out for themselves. In my implementation for the Pharaoh project, there is a wrapper over SDL (which supports native GAPI), but there is no full-fledged renderer in the usual sense. I wrote a new rendering system for each engine. On the one hand, it wasn't easy, but on the other, I had a chance to experiment with graphics to my heart's content.

No matter how the rendering system is built, sooner or later, you'll need to display text for the interface, debugging information, and other purposes. Yes, you can draw letters manually pixel by pixel, but it's better to use a standard library like FreeType. For eager fans of bizarre adventures and Unicode, HarfBuzz is worth exploring.

Resources

Once upon a time, you finalize another level, try to run the build, and... it takes about a whole minute to run. Then two. And then you notice that some stage "weighs" 2 GB, because each tree is an object with a unique copy of texture and models. That's when it becomes clear: it's time for resource management.

Resource management is hidden from users, but defines whether the game will be responsive and even run at all. First, it would be wise to pack resources into archives: opening a thousand files from a disk is a major drawback of OS interaction. The platform may be slow, the file system may not be very responsive, and the player's SSD might not match what you expected in the studio.

Therefore, everything gets packed: textures, models, sounds, levels, and scripts. Using a custom container format with indexing and compression is widespread, but some people use ready-made ones like Oodle, ZIP. Others create virtual file systems tailored to the engine's needs. Large commercial engines, by the way, usually follow this path. Archives are easy to distribute, easy to cache, and no one digs around... except the ones who really want to.

For example, you can compile all resources into one large binary blob and embed them into the executable file, as many games have done and continue to do. That's what Xbox and Sony did at first. What are the advantages? Everything is on hand and fast—no extra loadings, no paths to files—just grab a byte array and work with it as you would with a resource. In some projects, this approach quite literally saved releases and helped pass certification. Sure, that might leave you with a 700 MB binary file, but it's all because of the "my cat fell on the keyboard" moment and "the junior who wrote the templates crashed everything.".

Indeed, there are drawbacks: you can't change any asset without recompiling the entire game. Even if you just want to adjust a pixel color in a texture, you have to wait for a rebuild. It also makes modding virtually impossible: how would players add their funny custom hats if all content is "sealed" inside the .exe file?

You can store resources as regular files in the assets/ directory next to the executable file. Then the engine simply loads the necessary file when the level starts, if you haven't factored in performance. This is the best approach for development: it's fast, clear, and you can open the folder at any time, swap a texture, and see the changes in the game. And it's also a real gift for modders: they can just take it, tweak it, add to it—everything is open.

Regardless of how assets are stored, they still need to be converted from raw bytes into meaningful data. For this, you typically rely on format libraries—for example, stb_image to convert PNG to pixel arrays or various JSON libraries to extract information about entities from .json-like configuration files, if you haven't yet become disillusioned with JSON as a format for such files. At some point, the team realizes that a purely declarative configuration format is not enough, and then a VM (Virtual Machine) for JS/Lua/Squirrel/AngelScript is added to the engine. These are scripting languages, but in this context, they're used for parsing configurations.

In many projects, developers eventually move to custom formats or adopt a higher-level language, which, in practice, also leads to using a virtual machine or interpreter.

And you can—I would say you should—enable hot reloading of assets as a second step after the configurations, so that you don't have to restart the game a thousand times a day. You change the picture, sound, or configuration, and boom—they're right in your game. Pure magic? No, it's just another reason why the resource manager grows into a monster that affects the entire engine.

Resources aren't just a development concern—they also play a key role in the release process. The system must generate builds for different platforms: Windows, Linux, consoles, Steam Deck, and others. Each platform comes with its own constraints and requirements for data alignment, supported formats, and packaging rules. For example, some platforms do not allow MP3 files, while others do not support certain texture formats.

Finally, one of the trendiest and most important features is automatic updates and patches. The player must not have to download extra 10 GB if you changed one texture. My build system calculates deltas: which files have changed, how they can be compressed, and where to place them. The launcher checks versions and downloads only what is missing. And if something goes wrong, it can roll back. And honestly, players appreciate this more than almost anything else, when the game "updates itself and just works".

Audio output

In case you didn't know, audio output is a nightmare with platform-dependent APIs. They're ugly, unintuitive, and downright capricious; XBox has a habit of throwing exceptions if you write too much to the buffer, but... well, if you write less than the buffer's allocated size, it'll throw exceptions too. Audio output is just my hobby...I mean, I'm not an expert here, but even a little hands-on experience was enough to understand: if you just want to reproduce sound, get ready to suffer.

Or... you can just use a library that hides all this nightmare behind a clean interface, for example, OpenAL or SDL. Recently, I've been using the latter because it's very forgiving of mistakes, even if you blundered. These libraries usually expect the engine to provide the audio callback, a function that the system will call (usually from a separate thread) dozens or even hundreds of times per second.

Each call requires returning a small chunk of audio data. Maybe you just want silence. Maybe a single MP3 playing in the background. But most likely, you'll need a little more: music, effects, voice. Each in different streams, with different volume, with smooth fades, no clicks or pops, no tremors, add some reverb or other effects... well, you catch the point.

To ensure its stability, you need a mixer. Not a cooking machine, of course (though sometimes that wouldn't hurt), but an audio mixer—a library that will combine multiple audio streams into one, carefully control their volume, filter sharp peaks, apply effects, and prevent the sound from turning into a mess when a battle breaks out with 15 different unit sounds, fanfare, and alarming music.

A good engine usually implements its own audio mixer. It can be large and complex—with crossfading, reverb, effects, and post-processing—or it can be simple, with just a few channels and background music. But the idea is always the same: the mixer works with abstract sound streams, combines them, and produces a single output stream sent to the audio device.

Writing your own mixer at least once is a great way to understand how it works. Of course, you can also rely on existing libraries that already handle mixing, balancing, compression, and other audio processing tasks.

Physics

To be honest... most games don't actually need physics. I mean real physics—with forces, momentum, friction, and all that scientific stuff. Most people want something simpler: animations, logic, and scripts—things that look like physics without having much to do with it. For example, look at Civilization VI, one of the most complex strategy games of our time—the game has no need in physics implementation at all. Or take a peek at various city-building simulators (such as SimCity)—obviously, houses don't collapse, roads don't crack, and no one calculates the fall vector of a water tower because it's not necessary. So, why complicate things? Yet, somehow both Civ and Cities Skylines drag dependencies onto PhysX, maybe they actually use them, maybe not. Who knows.

Platformers or 2D RPGs don't need much physics too—it usually consists of character movement and simple collisions with walls, enemies, and boxes. You can write that in a couple of evenings, unless, of course, you get stuck on every line, as I did, trying to wrap my mind around it. The main thing is that everything will be under control: if you want the hero to slide like they're on ice, please do so. Enemies should bounce off when colliding like rubber ducks? Come on, give it a try.

If collisions start pilling up and performance drops, you can optimize it. Spatial hashing, quadtrees, and distance constraints from MC. But... if you're stubborn and know for sure that you need real physics, go for a ready-made library: Box2D for 2D or Bullet for 3D. Don't reinvent the wheel; you will have plenty of time to create them on the self-written engine.

And if you want to understand how such engines work under the hood, it's best to watch a YouTube video from the Box2D creator. He explains physics with impulses in a way that provides a rare insight into the mechanics. I used to think that physics was about mass, speed, and formulas, but now I know that real physics is when you're up at 3 a.m. trying to figure out why a character is stuck on a staircase because the collision mesh didn't load.

Author: Sergei Kushnirenko

Sergei has over 20-year experience in coding and game development. He graduated from ITMO National Research University and began his career developing software for naval simulators, navigation systems, and network solutions. For the past fifteen years, Sergei has specialized in game development: at Electronic Arts, he worked on optimizing The Sims and SimCity BuildIt, and at Gaijin Entertainment, Sergei headed up the porting of games to the Nintendo Switch and Apple TV platforms. Sergei actively participates in open-source projects, including the ImSpinner library and the Pharaoh (1999) game restoration project.