Reference
2D game engine features checklist: what an engine should give you
A practical checklist for choosing or building a 2D game engine: drawing, batching, layers, lighting, debug UI, assets, audio, particles, and the honest non-goals.
Maintained by Tendai Mukwena
Every engine page lists features. Fewer tell you which features matter for a small 2D game and which ones you will never touch. This is the checklist I use, either to judge an existing engine or to decide what to build when I write my own. It is organised the way a game actually uses an engine: first what you draw, then what makes it look good, then the tools around it, and finally the design goals and non-goals that decide how the whole thing feels to use.
Throughout I mention how different engines approach each item, in general terms. Comfy, the open-source 2D engine built on wgpu, is a useful reference because its own feature list was short and opinionated, and the engine comparison on this site covers the others in more depth.
Drawing
Immediate-mode drawing for shapes, sprites and text
The most valuable feature for a small game is the ability to draw something with one function call, every frame, without registering it anywhere first. Want a circle? Call a function that draws a circle. Want it gone? Stop calling it.
// Engine agnostic sketch of an immediate mode frame
fn draw(game: &Game, gfx: &mut Gfx) {
gfx.circle(game.player.pos, 0.5, Color::YELLOW, 10);
for coin in &game.coins {
gfx.sprite("coin", coin.pos, Color::WHITE, 5);
}
gfx.text("lives 3", Vec2::new(8.0, 8.0), Color::WHITE);
}
The last argument in two of those calls is a layer, which is the next item. The point of the sketch is that game state stays in your own structs and the drawing code reads it. There is no scene graph to keep in sync. macroquad, raylib and Comfy all work this way. Retained-mode engines like Godot or an ECS like Bevy keep drawable objects alive between frames instead, which is better when you have many static things and worse when everything changes all the time.
Automatic batching
Immediate mode only works if the engine batches. Each sprite call should append four vertices to a buffer, and the engine should submit one draw call per texture and shader combination, not one per sprite. Test this before you commit: draw 10,000 sprites from the same atlas and watch the frame time. If it falls over, the engine is issuing a draw per call.
Layers (z-index)
With immediate drawing, the naive rule is "last drawn is on top". That forces your game code to sort, which gets painful once particles, shadows and UI overlap. A built-in integer layer on each draw call fixes it. The engine collects the frame's calls, sorts them by layer (stable, so equal layers keep call order) and then batches inside each layer. Comfy made this a headline feature. In call-order frameworks you can build it yourself in about forty lines.
A simple 2D camera
You need a position, a zoom and a way to convert between screen and world coordinates, mainly for mouse picking. Shake and smooth follow are nice extras you can write yourself. Be clear on the coordinate convention: centre origin with y up in world units, or top left with y down in pixels.
Making it look good
HDR, tonemapping and bloom
This is the item that separates engines most. If the engine renders into a floating point target (16-bit float per channel is enough), colours can go above 1.0. A torch can be "three times white", bloom can pick out what is genuinely bright, and a tonemapping pass squeezes the result back into the screen's range. Without float targets, bright lights clip and bloom has to guess.
The catch is platform support. Older graphics APIs that some frameworks target for wide reach do not offer float render targets everywhere. wgpu-based engines such as Comfy and Bevy get them on desktop and on the web through WebGL 2, which is the main reason Comfy chose wgpu.
2D lighting
For small games, "lighting" usually means additive point lights with a radius and falloff, drawn into a light buffer and multiplied over the scene. Shadow-casting is a bonus. Check whether lights are a first-class feature or something you assemble from render targets and shaders.
Particles and trails
Two shapes of API are worth having. One spawns a single particle with a lifetime and velocity, useful for a one-off spark. The other configures a system: spawn rate, spread, colour over life, size over life. Trails (a ribbon mesh following a moving point, often with a scrolling texture) are rarer and lovely for sword swings and dashes.
Text
You need at least a default font and the ability to load a TTF. Watch how text is rendered: some engines draw text through their debug UI library, which is fine for prototypes and awkward for styled in-game text. Comfy started that way and later moved to its own text rendering, as covered in the notes on version 0.3.
The tools around it
Debug UI
An immediate-mode UI library like egui, built in and ready on frame one, pays for itself in a day. Sliders for gravity, a checkbox to show collision boxes, a window listing entity counts. Comfy shipped egui inside the engine. Elsewhere it is usually a plugin or an extra crate with a small integration step.
Asset loading
Look for: common image formats (PNG at minimum), common audio formats (WAV and OGG), loading on background threads so a level with fifty textures does not freeze the window, and hot reloading for textures and shaders during development. Parallel loading matters more than it sounds. A worked case: 60 textures at an average of 8 ms decode each is 480 ms on one thread, which is a visible hitch. Spread over four worker threads it is about 120 ms, which a loading screen can hide.
Audio
The bar is "play a sound by name with one call", plus volume, looping music, and a way to stop things. Comfy used kira for this. Positional audio and buses are good to have, not essential.
Utilities
Cooldowns, timers, random ranges, easing curves, colour helpers, a simple grid. None are hard, all are tedious, and engines that ship them save you a weekend.
A scoring sheet
Here is how I turn the checklist into a decision. Score each row from 0 (missing) to 2 (works well out of the box), weighted by how much your game needs it.
| Feature | Weight for a lit 2D action game | Weight for a puzzle game |
|---|---|---|
| Immediate drawing and batching | 3 | 3 |
| Layers | 2 | 1 |
| Camera | 2 | 1 |
| HDR, lighting, bloom | 3 | 0 |
| Particles and trails | 2 | 1 |
| Text | 1 | 3 |
| Debug UI | 2 | 1 |
| Asset loading and hot reload | 2 | 1 |
| Audio | 2 | 2 |
| Web build | 1 | 3 |
Multiply score by weight and add up. The maximum for the action game is 40, for the puzzle game 32. The same engine can easily come first for one and third for the other, which is the honest answer to "which engine is best".
Design goals that matter more than features
Features are visible. Design goals decide whether you enjoy the engine six weeks in.
- Ergonomics first. The common case should be short. Drawing a sprite should not need a builder, a handle and a resource lookup.
- No magic. Code should do what it looks like it does. Hidden scheduling, implicit system ordering and macros that generate surprising code all cost debugging time.
- Sensible defaults. A camera, a clear colour, a default font and working audio before you configure anything.
- Exposed internals. Public struct fields and an escape hatch to the underlying renderer when you outgrow the defaults.
- Reasonable compile times. Small games are built through many quick iterations. An engine that adds a long wait to each change is taxing your best tool. Avoiding mandatory procedural macros helps.
Comfy's own philosophy leaned hard on the first two, even accepting global state and interior mutability (RefCell) where they made game code shorter. You may or may not like that trade, but it is a coherent one, and coherence is what you are really evaluating.
Deliberate non-goals
A good engine says what it will not do. These three are common and reasonable for a small 2D engine.
High-end 3D. Models, materials, skeletal animation and shadows multiply complexity. A 2D engine that stays 2D can keep its API small.
Mandatory ECS. An ECS is a fine tool for some games and a tax on others. Offering one as an option (Comfy embedded hecs with a few helpers) is friendlier than requiring it.
Modularity for its own sake. A plugin system that lets you swap the renderer sounds flexible, but every seam is code to maintain and an API to learn. An opinionated toolkit with fixed parts can be more pleasant.
There is a fourth worth stating plainly: maximum performance. An engine optimised for ergonomics will not be the fastest way to draw a million quads, and that is fine if it draws the ten thousand you need. Profile your actual game before deciding an engine is too slow.
For a hands-on version of this list, the small examples page shows most of these features as short engine-agnostic snippets. Pick the three rows with the highest weight for your game and build a ten-minute test of each in your candidate engine before anything else.