# So, you want to make a game engine

Written in September 2023 in the middle of the Unity pricing controversy, when the argument was between "switch engines immediately" and "switching takes months or years" with "roll your own" as a third camp. lisyarus's position is that all three are right depending on the case, and that the interesting question underneath is what "make a game engine" even means.

His credentials are unusual: he has never worked with any engine except his own, psemek, which he has grown for three years to about 100k lines and shipped a dozen jam games and one commercial title (Costa Verde Transport Department) on. The distribution is under 2MB, nearly all of it `SDL2.dll`.

## The definition is deliberately useless

He works through the obvious candidates and rejects them. SDL2 is a platform abstraction library, OpenGL a low-level graphics API, stb_image an image loader — none is an engine. But a tiny project combining all three to move sprites around a window sounds exactly like one. Graphics can't be the criterion, since terminal games are games. User input can't be either, since a calculator isn't a game.

What he lands on: a game engine is *any set of tools, libraries and other stuff designed to help you make games*. Vague to the point of uselessness, and that is the point. A retro 2D pixel-art platformer needs input, images on screen, and some audio — two to thirty days of work depending on experience and stubbornness, and about 1% of the project. The other 99% is making the actual game. Engines can be a single specialized 100-line file or a 100GB industrial beast, and the small ones are still engines.

The reasons to write one are mostly not technical. Escaping corporate decisions, controlling the stack, small binaries, using a language you like — and, the two he ranks highest, learning a great deal and having fun. The one he singles out as an eye-opener is frustration: complaints about engines are usually about someone else's priorities, and once it is your engine, the missing feature has an obvious explanation. You didn't write it, because you were busy. It is harder to blame yourself for your own work than to blame others for theirs.

The reasons not to are the expected ones — it is hard, slow, you will never outcompete Unreal, your engine will lack features for years, and you can burn out doing engine work instead of game work. Costs can at least be amortized across projects if you plan for that.

## Going through the parts

The expectation list for a "big enough" engine is long: programming, window, game loop, input, graphics, audio, assets, physics, scripting, networking, AI, UI, architecture, tools, distribution. Most engines contain a subset and are still engines. The parts where he has an actual opinion:

**Programming.** Visual scripting is a real option that predates all of us, but supporting it means building specialized tools, which noticeably complicates the engine. Scripting in a language other than the engine's is possible, but if you are writing the engine anyway, a native API is easier. Which language doesn't matter — he uses C++ because he has fifteen years of it, and says pick Rust, C#, Python, Haskell or Java with equal seriousness. His preferred shape for an engine is a *programming library*: a dependency you pull into a game project, not a framework you live inside.

**Window and game loop.** Platform APIs like `CreateWindowEx` are ugly, non-beginner-friendly, and have to be rewritten per platform, which is what SDL2, glfw and sfml exist to absorb. The loop — poll input, simulate, draw — is what adds the time dimension; how it's exposed (explicit `while (engine::isRunning())`, a hidden `engine::run()`, a subclassed `application`) is taste. The one design axis with a real tradeoff is how many events the engine handles by itself: more handling is easier to use, but the user sometimes needs to override it, such as *not* quitting when the window close is requested, which he calls a deadly sin to hardcode.

**Graphics.** No single right level of abstraction. A 2D sprite engine wants `drawSprite(image, x, y)` with scaling, rotation and colour modification, implemented over raw pixels, cairo, or OpenGL. A 3D engine wants meshes, affine transforms, lighting and skeletal animation, which effectively requires OpenGL, Vulkan, Direct3D or WebGPU underneath. psemek does neither: it exposes a wrapper over OpenGL 3.3 (and now WebGPU) with *no rendering engine* in the usual sense, so he reimplements the renderer per project and keeps the freedom to experiment. Both of those APIs have a teaching resource covered here — [[learn-opengl]] for core-profile OpenGL, [[learn-webgpu-cpp]] for the WebGPU path in C++. Text is the sharp edge — you want FreeType or harfbuzz, and his advice on writing your own font loader is simply don't. A fixed ASCII bitmap font works right up until localization.

**Audio.** The libraries hand you an audio callback fired many times a second on a separate thread, expecting samples in some format. Everything above that is yours: separate streams for music, effects and voice, per-stream volume, and — he is emphatic — a compressor on the final output, or you get clicks and noise when several sounds overlap. psemek has its own mixing library built around abstract streams that combine into new streams, with a single output stream plugged in at the end.

**Assets.** Three options with different failure modes. Compiled into the executable: no loading code at all, but changing an asset means recompiling, and modding is effectively impossible. Loose files next to the binary with an asset manager doing caching and preprocessing: easiest during development and best for modding. An archive, ZIP or ad-hoc, which is what most big engines do.

**Physics.** Most games don't have any. Civilization 6 doesn't; city builders don't. Platformers and top-down RPGs need character movement and basic collision, which is easier to write and *much* easier to tune than a general physics engine. Too many collisions is a spatial-hashing or quadtree problem before it is a physics-engine problem. Actual physics means Box2D or Bullet.

**Scripting, networking, AI, UI.** Scripting earns its complexity through productivity (he cites Sapiens' David Frampton saying Lua made him orders of magnitude more productive), sandboxing, hot-reloading and modding. Networking he declines to have an opinion on, having never shipped multiplayer, beyond pointing at Asio and Valve's GameNetworkingSockets. AI is just code, so any engine that supports code supports AI; what you might want is a specific pattern like behavior trees. UI is the one he argues almost every game needs and that is genuinely hard to abstract — it lives in window coordinates rather than world coordinates and has expected behaviors (hover, click, drag) that game entities don't — and he notes that reimplementing it per project may beat generalizing it.

**Architecture and tools.** psemek has no architecture: it is a set of libraries designed to be as isolated as possible, and you take the ones you need. Monolithic designs with a base `Object` class and a global manager pay off for big engines with introspection and editing tools, and he doubts they help small ones. On tools: do not build another Unity, since an editor is roughly as much work as the engine. Small specialized tools are worth it — his one tool was a Python script converting Blender files to an ad-hoc mesh format, retired when he switched to glTF.

**Distribution**, which he calls the most important thing an engine can automate. Programs do not launch on random computers. Native binaries need their dynamic libraries shipped alongside (for him `SDL2.dll`, `libpng.dll` and a few more) and even those need a loader — `/lib64/ld-linux-x86-64.so.2` on Linux. Bytecode languages need the VM present; interpreted ones need the interpreter shipped. Multiple platforms multiply executable formats and library formats, so you build once per target. His setup is Ubuntu Docker containers plus mingw64 cross-compilation for Windows, glued with Bash and CMake, painful to set up and then a single command forever after. The fragility of that shipping story from the other end is [[win32-stable-abi]]: on Linux, the ABI that actually held still long enough for old game binaries to keep running was Win32 through Wine, not glibc.

## How to start

The advice is to not start. Attend a game jam and make a game from scratch with no engine. Then attend another one and make a new game from scratch, reusing nothing. After a few rounds of that you will find yourself wishing you already had some particular piece of code — that wish is the birth of your engine. Extract the parts that repeat, generalize them as you go, and repeat.

What you end up with is an engine tailored to your needs, that you know, that you are efficient with, and that you can change at will. Which is the same claim as the frustration argument at the top, arrived at from the other direction.
