Step 1 of 5 · Reading · ~4 min
Read
Production
Production Game Engines
A toy engine runs the loop; a production engine also builds, packages and ships the game, and most of that work happens before the player ever starts it. After this lesson your program will do one step of that work, the one every 2D game depends on: take loose sprite images, pack them into a single atlas, and produce a manifest with a content hash that says whether anything changed.
What a production engine adds
Everything from this course exists in a shipped engine, plus the machinery around it:
- Modules with clear borders: core (math, memory, files, threads), renderer, physics, audio, input, animation, networking, scripting.
- An asset pipeline that turns source files into runtime files.
- An editor: scene view, component inspector, asset browser, play-in-editor.
- A build pipeline that compiles code, bakes assets, packages for each platform and signs the result.
- Platform layers so one codebase runs on Windows, consoles and phones.
- A frame budget every feature must respect: 16.7 ms for 60 fps, shared by all of the above.
Writing your own makes sense when the engine is the point (learning), or when the game needs something no engine offers (custom physics, a retro platform). When the goal is to ship a normal game, an existing engine saves years.
Source assets versus baked assets
Artists work in PNG, FBX and WAV because those are easy to edit and exchange. The game wants formats that are cheap to load: textures already compressed for the GPU, meshes in the exact vertex layout, sounds already decoded or streamed. The pipeline bakes one into the other ahead of time. Baking the same inputs must give the same bytes every time, because a build that changes by itself cannot be cached, diffed or patched.
Texture atlases and shelf packing
Switching textures between draw calls is slow, so many small sprites are packed into one large image, an atlas. The simplest packer is the shelf algorithm:
- Sort the sprites tallest first, then widest first, then by name, so the order never depends on the order files were found.
- Fill a row (a shelf) from the left. When the next sprite no longer fits in the width, start a new shelf below the tallest slot of the shelf just finished.
- A sprite wider than the atlas cannot be placed at all and is reported.
Two real-world details. Add padding around every sprite: with bilinear filtering and mipmaps the GPU samples neighbouring pixels, and without a margin a sprite shows a fringe of its neighbour. A sprite of w by h with padding p occupies a slot of (w + 2p) by (h + 2p) and is drawn p pixels inside it. And many GPUs prefer power-of-two textures, so the final height is rounded up to the next power of two.
Atlas width 64, padding 1, sprites tree 20x30, rock 24x10, gem 10x10, key 10x10:
sorted: tree (h 30), rock (h 10, wider), gem, key (equal, by name)
tree slot 22x32 at (0,0) drawn at (1,1)
rock slot 26x12 at (22,0) drawn at (23,1)
gem slot 12x12 at (48,0) drawn at (49,1)
key slot 12x12 would end at x=72 > 64: new shelf at y=32, drawn at (1,33)
used height 32 + 12 = 44, rounded up to 64
Content hashes
A manifest lists where every sprite landed. Hashing its text gives a short fingerprint: if two builds print the same hash, the atlas is identical and nothing needs re-uploading or re-patching. Engines use hashes of exactly this kind to skip unchanged work. The hash used here is FNV-1a, 32 bits: start at 0x811C9DC5; for each byte, XOR the byte in, then multiply by 0x01000193 and keep the low 32 bits. The published check values: the text a hashes to e40c292c and foobar to bf9cf968.
Common mistakes
- Packing in input order. The result then changes when the file list does, and so does the hash.
- Forgetting padding on the right and bottom, or adding it only once.
- Sorting names case-insensitively. Byte order puts
Zedbeforeapple. - Computing the hash in a language whose multiply panics or widens on overflow instead of wrapping to 32 bits.
- Rounding the used height instead of the whole atlas, or rounding a height that is already a power of two up again.
Your exercise
Implement pack, next_pow2 and fnv1a32 as described in the exercise. The starter parses the commands, prints the manifest and assembles the summary line. The most likely mistake is the tie-breaking order.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…