A sprite is an image or a sequence of images in a game. It refers to a single visual entity on the screen.
In Ignis, SpriteNode draws Sprites. Sprite defines properties inherent to the art: the number and size of its frames, how fast it should play, and whether it loops.
SpriteNode acts as a controller, selecting the appropriate frame on demand. The process is the same whether you want to render an image or play an animation:
preload assets, create a Sprite, then pass it to a SpriteNode.
There are several kinds of Sprite, depending on what you need to draw.
SpriteImagerepresents one whole image.SpriteSheetcuts an image into frames for animation.SpriteGrouplays severalSprites together, end to end.
These Sprite implementations automatically work with the local asset bundle, allowing
SpriteNode to reload images in live scenes as they change on disk.
Sprite itself is abstract, so it is straightforward to implement one for images stored in complex ways, such as when packed optimally by an art application or sprite packer.
SpriteSheet cannot hold animations that wrap across multiple rows or skip frames. A single animation must be specified contiguously on one row.
Lineage
The APIs for SpriteSheet and SpriteGroup are heavily inspired by their corresponding classes in Flame.
Examples#
A single image#
final fire = SpriteNode(sprite: SpriteImage('assets/images/bonfire.png'));
Playing a sheet#
final fire = SpriteNode(
sprite: SpriteSheet('assets/sheets/bonfire.png', BONFIRE_SIZE, fps: 16),
);
Combining sprites#
final smoke = SpriteNode(
sprite: SpriteSheet('assets/sheets/bonfire_smoke.png', BONFIRE_SIZE, fps: 10),
);
final flame = SpriteNode(
sprite: SpriteSheet('assets/sheets/bonfire_flame.png', BONFIRE_SIZE, fps: 16),
);
final wood = SpriteNode(
sprite: SpriteSheet('assets/sheets/bonfire_wood.png', BONFIRE_SIZE, fps: 6),
);
Packing a sheet#
final slime = SpriteNode(
sprite: SpriteSheet(
'assets/sheets/slime.png',
SLIME_SIZE,
fps: 16,
rows: [
.new(frames: 14), // idle
.new(frames: 30), // jump
.new(frames: 25), // jump_forward
.new(frames: 17), // spit
.new(frames: 30), // spike
.new(frames: 12), // injured1
.new(frames: 13), // injured2
.new(frames: 13), // injured3
.new(frames: 45), // splat_wall
.new(frames: 27), // recover
.new(frames: 49), // death
],
),
);
final taps = TapInput(shape: .rectangle(DEMO_SIZE));
taps.onTap(() => slime.play(row: (slime.row + 1) % slime.sprite.rows));
Naming rows#
final slime = SpriteNode(
sprite: SpriteSheet(
'assets/sheets/slime.png',
SLIME_SIZE,
fps: 16,
rows: [
.new(key: 'idle', frames: 14),
.new(key: 'jump', frames: 30),
],
),
);
final taps = TapInput(shape: .rectangle(DEMO_SIZE));
taps.onTap(() => slime.play(key: slime.row == 0 ? 'jump' : 'idle'));
Setting a rate per row#
final sheet = SpriteSheet(
'assets/sheets/slime.png',
SLIME_SIZE,
fps: 16,
rows: [
.new(frames: 14, fps: 5), // idle
.new(frames: 30), // jump
],
);
final idle = SpriteNode(sprite: sheet);
final jump = SpriteNode(sprite: sheet)..play(row: 1);
Playing part of a row#
final slime = SpriteNode(
sprite: SpriteSheet(
'assets/sheets/slime.png',
SLIME_SIZE,
fps: 12,
rows: [
.new(start: 6, frames: 6), // idle
],
),
);
Timing frames by hand#
final slime = SpriteNode(
sprite: SpriteSheet(
'assets/sheets/slime.png',
SLIME_SIZE,
fps: 0,
rows: [
.timed([0.8, 0.06, 0.06, 0.06, 0.06, 0.06]), // idle
],
),
);
Scaling the rate#
final sheet = SpriteSheet('assets/sheets/bonfire.png', BONFIRE_SIZE, fps: 16);
final fire = SpriteNode(sprite: sheet);
final embers = SpriteNode(sprite: sheet, speed: 0.25);
Switching sheets#
final creature = SpriteNode(
sprite: SpriteGroup([
SpriteImage('assets/images/bonfire.png', key: 'fire'),
SpriteSheet.single('assets/sheets/slime_jump.png', SLIME_SIZE, fps: 16, key: 'slime'),
]),
);
final taps = TapInput(shape: .rectangle(DEMO_SIZE));
taps.onTap(() => creature.play(key: creature.row == 0 ? 'slime' : 'fire'));
Reporting progress#
final slime = SpriteNode(
sprite: SpriteSheet('assets/sheets/slime_jump.png', SLIME_SIZE, fps: 16),
);
var count = 0;
slime.onFrame((frame) => log1('onFrame $frame'));
slime.onLoop(() => log2('onLoop ${count += 1}', .orange));
Playing once#
final taps = TapInput(shape: .rectangle(DEMO_SIZE));
taps.onTapDown((event) {
add(
SpriteNode(
sprite: SpriteSheet(
'assets/sheets/explosion.png',
EXPLOSION_SIZE,
fps: 20,
loop: false,
),
cleanup: true,
position: event.scene,
anchor: .bottomCenter,
),
);
});
