Sprite-sheet animation
Open the live demo · Read the source · View on GitHub
A single sprite makes a particle a shape. A Flipbook makes it a small
animation instead: a grid of frames on one texture, one of them showing at a
time depending on how far through its life a particle is.
Step 1: An atlas of frames #
Four cells in a row stand in for four drawn frames of a spark burning out,
from bright to dark. A real atlas would be four pictures; the shape a
Flipbook reads is the same either way.
/// Four cells in a row, each a solid colour: a stand-in for four drawn
/// frames of a burning-out spark, from bright to dark.
ByteData _atlas() {
const List<List<int>> colours = <List<int>>[
<int>[255, 255, 220],
<int>[255, 190, 90],
<int>[220, 90, 40],
<int>[60, 20, 10],
];
final Uint8List bytes = Uint8List(_cellSize * _frames * _cellSize * 4);
final int width = _cellSize * _frames;
for (var y = 0; y < _cellSize; y++) {
for (var frame = 0; frame < _frames; frame++) {
final List<int> colour = colours[frame];
for (var x = 0; x < _cellSize; x++) {
final int at = (y * width + frame * _cellSize + x) * 4;
bytes[at] = colour[0];
bytes[at + 1] = colour[1];
bytes[at + 2] = colour[2];
bytes[at + 3] = 255;
}
}
}
return bytes.buffer.asByteData();
}
Step 2: Describe the grid #
Flipbook only needs to know the grid's shape. frames would trim it to
fewer cells than columns * rows if the last row were not full; here every
cell is used.
_flipbook = Flipbook(columns: _frames, rows: 1);
Step 3: What a cell is chosen by #
A flipbook is driven by how far through its life a particle is, not by a clock: a long-lived particle and a short one both play the whole sequence exactly once, which a frames-per-second animation could not promise either of them.
// A particle just born and one about to die should be showing different
// cells of the sheet: the whole point of a flipbook over a static sprite.
final Particle newborn = Particle()
..age = 0.0
..lifetime = 1.0;
final Particle dying = Particle()
..age = 0.99
..lifetime = 1.0;
if (_flipbook.cellFor(newborn).left == _flipbook.cellFor(dying).left) {
throw StateError('the flipbook should play across a particle\'s life');
}
Note. This is entirely vertex data. Billboarding already happens on the CPU in this engine, so a cell is a rectangle the corner coordinates are scaled into before they are written; no shader and no backend had to change for a flipbook to exist.