Animation
three-flatland supports frame-based animation using spritesheets. The AnimatedSprite2D class works with SpriteSheetLoader to load and play animations.
import { AnimatedSprite2D, SpriteSheetLoader, Layers } from 'three-flatland';
// Load spritesheet (texture presets automatically applied)const spriteSheet = await SpriteSheetLoader.load('/sprites/character.json');
// Create animated spriteconst sprite = new AnimatedSprite2D({ spriteSheet, animationSet: { fps: 10, // Default fps for all animations animations: { idle: { frames: ['idle_0', 'idle_1', 'idle_2', 'idle_3'], fps: 8, loop: true, }, run: { frames: ['run_0', 'run_1', 'run_2', 'run_3'], fps: 12, loop: true, }, jump: { frames: ['jump_0', 'jump_1', 'jump_2'], fps: 10, loop: false, }, }, }, animation: 'idle', // Start with idle animation layer: Layers.ENTITIES,});
sprite.scale.set(64, 64, 1);scene.add(sprite);
// In your animation loopfunction animate() { const deltaMs = /* time since last frame */; sprite.update(deltaMs); renderer.render(scene, camera);}import { Suspense, useRef } from 'react';import { extend, useFrame, useLoader } from '@react-three/fiber/webgpu';import { AnimatedSprite2D, SpriteSheetLoader, Layers } from 'three-flatland/react';
extend({ AnimatedSprite2D });
function Character() { // useLoader suspends while loading, presets applied automatically const spriteSheet = useLoader(SpriteSheetLoader, '/sprites/character.json'); const spriteRef = useRef<AnimatedSprite2D>(null);
useFrame((_, delta) => { spriteRef.current?.update(delta * 1000); });
return ( <animatedSprite2D ref={spriteRef} spriteSheet={spriteSheet} animationSet={{ fps: 10, animations: { idle: { frames: ['idle_0', 'idle_1', 'idle_2', 'idle_3'], fps: 8, loop: true }, run: { frames: ['run_0', 'run_1', 'run_2', 'run_3'], fps: 12, loop: true }, }, }} animation="idle" layer={Layers.ENTITIES} scale={[64, 64, 1]} /> );}
// Wrap with Suspense in parentfunction App() { return ( <Suspense fallback={null}> <Character /> </Suspense> );}Animation Set Definition
The animationSet option provides a structured way to define multiple animations:
animationSet: { fps: 12, // Default fps (optional) animations: { idle: { frames: ['frame_0', 'frame_1', 'frame_2'], // Frame names from spritesheet fps: 8, // Override default fps (optional) loop: true, // Loop animation (default: true) pingPong: false, // Play forward then backward (optional) }, attack: { frames: ['attack_0', 'attack_1', 'attack_2'], fps: 15, loop: false, }, },}Playback Control
sprite.play('run'); // Play animation by namesprite.pause(); // Pause at current framesprite.resume(); // Resume paused animationsprite.stop(); // Stop and resetsprite.gotoFrame(3); // Jump to specific frame
// Check animation statesprite.isPlaying(); // Is any animation playing?sprite.isPlaying('run'); // Is 'run' animation playing?sprite.currentAnimation; // Get current animation namePlayback Speed
sprite.speed = 1.5; // 1.5x speedsprite.speed = 0.5; // Half speedsprite.speed = 2; // Double speedAnimation Callbacks
Use the play() method’s options to handle animation events:
sprite.play('attack', { onFrame: (frameIndex) => { console.log('Frame:', frameIndex); if (frameIndex === 2) { // Trigger damage on specific frame dealDamage(); } }, onComplete: () => { // Animation finished (non-looping only) sprite.play('idle'); },});Loading Spritesheets
The SpriteSheetLoader supports Aseprite JSON format. Texture presets (like NearestFilter for pixel art) are automatically applied:
import { SpriteSheetLoader } from 'three-flatland';
// Load spritesheet (presets automatically applied)const spriteSheet = await SpriteSheetLoader.load('/sprites/character.json');
// Access frame dataconst frame = spriteSheet.getFrame('idle_0');console.log(frame); // { x, y, width, height, sourceWidth, sourceHeight, ... }Update Loop
The update(deltaMs) method must be called each frame to advance the animation:
let lastTime = performance.now();
function animate() { const now = performance.now(); const deltaMs = now - lastTime; lastTime = now;
// Update all animated sprites sprite.update(deltaMs);
renderer.render(scene, camera); requestAnimationFrame(animate);}