Animating Games with Flame: Sprite Sheets, Effects, and Components
Flame 1.38.2 gives Flutter games first-class animation primitives: SpriteAnimation, SpriteAnimationComponent, and declarative Effects.
Published on • October 7, 2026
AI Assistant

Flutter is an excellent UI toolkit, but a game needs a game loop, a component tree, collision detection, and frame-based animation. Flame provides exactly that — a minimalist Flutter game engine that runs entirely in Dart, with no platform channels, inside a normal GameWidget.
The current stable release is flame 1.38.2 (with 2.0.0-dev.1 in preview), a Flutter Favorite with the #animations topic front and center in its pub.dev listing. Here’s how its animation system actually works.
Sprite Animations: Frames from a Sprite Sheet
The core primitive is SpriteAnimation — an ordered sequence of frames with a per-frame duration. The usual way to build one is from a sprite sheet using SpriteAnimationData.sequenced:
class EmberPlayer extends SpriteAnimationComponent
with HasGameReference<EmberQuestGame> {
EmberPlayer({required super.position})
: super(size: Vector2.all(64), anchor: Anchor.center);
@override
void onLoad() {
animation = SpriteAnimation.fromFrameData(
game.images.fromCache('ember.png'),
SpriteAnimationData.sequenced(
amount: 4, // number of frames
textureSize: Vector2.all(16), // pixels per frame
stepTime: 0.12, // seconds per frame
),
);
}
}
Three constructors cover most cases:
SpriteAnimation.spriteList(...)— frames from separate images.SpriteAnimation.fromFrameData(image, data)— the sprite-sheet path above.SpriteAnimationData.sequenced(...)— the config object withamount,stepTime,textureSize.
Frames from individual files look like this:
final sprites = [0, 1, 2].map((i) => Sprite.load('player_$i.png'));
final animation = SpriteAnimation.spriteList(
await Future.wait(sprites),
stepTime: 0.01,
);
final player = SpriteAnimationComponent(
animation: animation,
size: Vector2.all(64.0),
);
Stateful Animation with SpriteAnimationGroupComponent
Characters rarely have one animation. SpriteAnimationGroupComponent<T> holds a map of animations keyed by a state enum, and you switch states by assigning current:
enum RobotState { idle, running, jumping }
class Robot extends SpriteAnimationGroupComponent<RobotState>
with HasGameReference<RobotGame> {
@override
Future<void> onLoad() async {
animations = {
RobotState.idle: await loadSpriteAnimation(
'robot_idle.png',
SpriteAnimationData.sequenced(amount: 4, textureSize: Vector2.all(32), stepTime: 0.15),
),
RobotState.running: await loadSpriteAnimation(
'robot_run.png',
SpriteAnimationData.sequenced(amount: 6, textureSize: Vector2.all(32), stepTime: 0.08),
),
RobotState.jumping: await loadSpriteAnimation(
'robot_jump.png',
SpriteAnimationData.sequenced(amount: 2, textureSize: Vector2.all(32), stepTime: 0.1),
),
};
current = RobotState.idle;
}
}
Under the hood, SpriteAnimationTicker ticks the animation and exposes an onFrame callback. Multiple components can share a single animation object, and resetOnRemove: true rewinds to frame 1 when the component leaves the tree.
Need animation outside the game canvas? SpriteAnimationWidget (from package:flame/widgets) renders a SpriteAnimation in the regular Flutter widget tree — handy for loading screens and HUD portraits.
Declarative Effects: Tween Without the Boilerplate
Sprite frames handle character animation; Effect handles everything else — movement, rotation, scale, opacity, color. Effects replace hand-written interpolation in update():
add(
MoveToEffect(
Vector2(300, 200),
EffectController(duration: 0.8, curve: Curves.easeOutCubic),
),
);
add(
SequenceEffect([
ScaleEffect.by(Vector2.all(1.2), EffectController(duration: 0.15)),
ScaleEffect.by(Vector2.all(1 / 1.2), EffectController(duration: 0.15)),
RemoveEffect(),
]),
);
The built-in catalog includes MoveByEffect, MoveToEffect, MoveAlongPathEffect, RotateEffect.by/.to, ScaleEffect.by/.to, SizeEffect.by/.to, AnchorByEffect, AnchorToEffect, OpacityToEffect, OpacityByEffect, ColorEffect, SequenceEffect, CombinedEffect, RemoveEffect, and FunctionEffect.
Useful properties:
removeOnFinish(defaulttrue) — the effect detaches itself when done.onComplete/completed— chain logic after an effect finishes.pause()/resume()/reset()— runtime control.
Custom effects extend Effect (or ComponentEffect<T> for component-specific work) and implement apply(double progress), where progress runs 0→1.
class WobbleEffect extends Effect {
WobbleEffect() : super(EffectController(duration: 0.5));
@override
void apply(double progress) {
final angle = sin(progress * pi * 4) * 0.2;
parent!.transform.rotation = angle;
}
}
How It Fits Together
A typical animated entity wires up like this:
- Extend
SpriteAnimationComponent(orPositionComponentfor effects-only entities). - In
onLoad(), assignanimationfrom a sprite sheet. - Add
Effects for transient motion — knockback, dash, fade-out. - Swap animations on state change via
SpriteAnimationGroupComponent.current. - Mount the whole thing with
runApp(GameWidget(game: MyGame())).
Note that Flame’s effects are independent of Flutter’s Tween/AnimationController — they run on the game clock, so they stay synchronized with your game loop even if frame rates vary. The old AnimationComponent name is gone; SpriteAnimationComponent is the modern replacement.
The Ecosystem Around Animation
Flame’s modular design means animation concerns plug in cleanly: flame_audio for sound synced to effects, flame_rive and flame_lottie for vector animations authored in external tools, flame_tiled for tilemap-driven scenes, and flame_forge2d for physics-based motion. Each is optional — the core flame package depends only on collection, meta, ordered_set, vector_math, and Flutter itself.
Getting Started
dependencies:
flame: ^1.38.2
Then run the official codelab, Intro to Flame with Flutter (Brick Breaker), or explore runnable demos at examples.flame-engine.org. Full docs live at docs.flame-engine.org.
The animation API surface is deliberately small — sprite sheets for frames, groups for states, effects for motion. Learn those three, and most game animation problems decompose cleanly.