A sprite is a two-dimensional image representing a character, an enemy, an object or an effect. To animate it, slightly different images are shown in quick succession, like a flipbook. Instead of saving each frame in a separate file, games gather the images into a sprite sheet: a single image containing all the frames, arranged in a grid or packed compactly.
Why a single image
Loading and drawing dozens of separate files is inefficient for two reasons:
- asset management: one file loads faster and is easier to organise;
- rendering: the graphics card works better when many sprites share the same texture, because it can draw them in a single call (batching) without constantly changing state.
Drawing a frame therefore means copying to the screen only one rectangle of the sprite sheet, identified by its source coordinates.
Regular grid or packed atlas
Regular grid
Each frame occupies a cell of the same size, for example 32 × 32 pixels. The rectangle for frame n is computed with a simple division:
columns = image_width / cell_width
sx = (n % columns) * cell_width
sy = (n / columns) * cell_height // integer division
This is the simplest format to create and read, ideal for pixel art with characters of constant size.
Packed atlas
When sprites vary greatly in size, a grid wastes space. Texture packing tools arrange images compactly, trim transparent borders and produce a metadata file, often in JSON, with the name, position and size of each frame. Because trimming the transparent border shifts the image, the metadata also includes the offset needed to draw it in the correct position.
Technical issues to know about
Bleeding between frames
With bilinear filtering or non-integer coordinates, the graphics card may sample pixels from a neighbouring frame, creating thin coloured lines at the edges. The classic countermeasures:
- leave one or two pixels of empty space between frames (padding);
- repeat the edge colour into the separating area (extrusion);
- for pixel art, use nearest neighbour filtering and draw at integer coordinates.
Anchor point
Every sprite has an origin, the point corresponding to the object's position in the world. For a character the centre of the feet is usually best: that way frames of different heights stay planted on the ground and horizontal flipping does not shift the figure.
Flipping
Often only right-facing animations are drawn and then flipped horizontally for the left, halving the work. Watch out for asymmetric details, though, such as a sword always in the right hand or lettering on a T-shirt.
Timing the animation
An animation is a sequence of frames with a duration. The simplest version uses a constant rate:
time += dt
frame = floor(time * animation_fps) % frame_count
The value of animation_fps is independent of the game's frame rate: many pixel-art animations use rates between 8 and 12 frames per second while the game runs at 60. A low rate is not a flaw but an aesthetic choice.
Animation tools also allow a different duration for each frame, which is very useful for rhythm: in an attack, the wind-up can last a long time, the strike very little, and the recovery longer again. The result is far more readable and punchy than a sequence at a constant pace.
Looping and one-shot animations
You need to distinguish between:
- looping animations: walking, running, idle, which start over from the beginning;
- one-shot animations: attack, death, landing, which stop on the last frame or hand control back to another animation.
For one-shot animations it is useful to emit an event at the end, or on a specific frame. An attack's damage, for instance, should be applied on the frame in which the weapon visually strikes, not at the start of the animation.
Linking animation and state
Which animation to show depends on the character's state:
| State | Animation | Type |
|---|---|---|
| Standing on the ground | Idle | Looping |
| Moving on the ground | Run | Looping |
| Negative vertical velocity | Jump | One-shot or short loop |
| Positive vertical velocity | Fall | Looping |
| Attack in progress | Attack | One-shot, with event |
A common mistake is restarting the animation every frame because it is being set continuously: it should change only when the state really changes, preserving the elapsed time in the current one.
Workflow with artists
Agreeing early on a few conventions avoids a lot of repeated work: cell size, anchor point position, animation names, frame order, palette. Many pixel-art editors export sprite sheets with metadata directly, which the code can read without manual intervention. That way an artist can edit an animation and see it in the game without touching the code.
In short
- A sprite sheet gathers all frames into one image, which is more efficient to load and draw.
- A regular grid is simple; a packed atlas saves space but requires metadata.
- Padding, extrusion and integer coordinates prevent bleeding at the edges.
- Animation rate is independent of the game's rate and can vary per frame.
- Change the animation only when the state changes, using events for key moments.