✚ Game development

Sprite sheets and frame-by-frame animation in 2D games

How to organise sprite sheets and texture atlases, time frame-by-frame animation and link it to a character's states in a 2D game, avoiding bleeding.

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.
MM
Marco Marzaduri

Marco Marzaduri works in cybersecurity, digital forensics and web development. A computer science graduate, he combines rigorous technical analysis with attention to the people who use technology.

Read also