Animations - samme/phaser3-faq GitHub Wiki

A spritesheet containing a knight animation

In Phaser, an animation is a timed texture–frame sequence played on a Sprite game object.

Animations can use any texture frame, even from different textures. Each animation frame references one unique texture frame. There is no implicit reference to a texture or a Sprite.

Creating animations

Animations need to be created (defined) before they can be used. You can store them in the global Animations Manager or on a Sprite. If an animation is used on only one sprite, and that sprite doesn't live throughout the game, you can save a bit of memory by storing the animation on the sprite instead of globally.

Animations are identified by key. At minimum, you must give a key and frames.

Every frame in a texture

// Create animation 'mummyWalk' from every frame in texture 'mummy':
this.anims.create({
  key: 'mummyWalk',
  frames: 'mummy'
});

Choosing texture frames

You will usually need to know the texture frame names. If you forgot, use

console.assert(this.texture.exists('mummy'));
console.log(this.textures.get('mummy').getFrameNames());
this.anims.create({
  key: 'mummyWalk',
  frames: [ { key: 'mummy', frame: 0 },
            { key: 'mummy', frame: 1 },
            { key: 'mummy', frame: 2 }  ]
});
// We have 3 textures with identical frame patterns.
const textureKeys = ['giant', 'elf', 'goblin'];

// We will create 9 animations from 3 sequences.
const anims = {
  idle: [0],
  walk: [1, 2],
  attack: [3, 4]
};

for (const textureKey of textureKeys) {
  for (const suffix of anims) {
    // 'giant:idle', 'giant:walk', etc.
    this.anims.create({
      key: `${textureKey}:${suffix}`,
      frames: anims[suffix]
    });
  }
}

Timing

You can give either frameRate (default 24 fps) or a duration for the whole animation. The frame duration is calculated as 1000 / frameRate or duration / frames.length.

If you set per-frame durations, these are added to the calculated frame durations. So if you want to set only frame durations, use an animation duration of 1ms.

this.anims.create({
  key: "spikes",
  repeat: -1,
  defaultTextureKey: "spikes",
  duration: 1, // 1 ms, close enough to 0
  frames: [
    { frame: 0, duration: 3000 },
    { frame: 1, duration: 250 },
    { frame: 2, duration: 250 },
    { frame: 3, duration: 3000 },
    { frame: 2, duration: 250 },
    { frame: 1, duration: 250 }
  ]
});

generateFrameNumbers() and generateFrameNames()

The helper methods generateFrameNumbers() and generateFrameNames() both select frame names from a texture according to a pattern and then generate an array of AnimationFrame objects that can be passed as the frames property. generateFrameNumbers() selects spritesheet-style frame names (indexes) and generateFrameNames() selects atlas-style frame names. They never select frame names that don't exist in the texture.

For example,

this.anims.generateFrameNumbers('mummy', { frames: [ 0, 1, 2 ] })

or

this.anims.generateFrameNumbers('mummy', { start: 0, end: 2 })

would produce an array of the 3 frame configs that we wrote above.

The generateFrameNames() config has all the options of generateFrameNumbers(), plus prefix, suffix, and zeroPad.

Log the output of these methods if you run into trouble.

Retrieving animations

You can get an animation from the manager or the sprite it was created on.

const mummyWalkAnim = this.anims.get('mummyWalk');

Animation frames

Unlike texture frames, animation frames are indexed, not named. But they contain the texture frame name (textureFrame) and object (frame).

For the setCurrentFrame() and stopOnFrame() methods, you need to pass an actual animation frame object. You can get these like

const mummyWalkFrame1 = this.anims.get('mummyWalk').getFrameAt(1);

Mixes

An animation mix is a delay you choose for transitioning from one animation to another.

this.anims.create({ key: 'animA' /* etc. */ });
this.anims.create({ key: 'animB' /* etc. */ });

this.anims.addMix('animA', 'animB', 200);

The delay is applied automatically if you play the second animation while the first is playing.

sprite.play('animA');
// Later:
sprite.play('animB');

It's very similar to

if (sprite.anims.isPlaying && sprite.anims.getName() === 'animA') {
  sprite.anims.playAfterDelay('animB', 200);
}

Playing animations

This animation is always playing but never advancing, because it starts anew each update:

function update () {
  sprite.play('run');
}

You probably wouldn't write that, but you might write

function update () {
  if (runKeyIsDown) {
    sprite.play('run');
  } else {
    sprite.play('idle');
  }
}

Same problem. Pass true as the ignoreIfPlaying argument.

function update () {
  if (runKeyIsDown) {
    sprite.play('run', true);
  } else {
    sprite.play('idle', true);
  }
}

But if you specifically want an animation to restart, omit ignoreIfPlaying:

sprite.on('pointerdown', function () {
  sprite.play('smile'); 
}

Chaining

chain() schedules an animation to play once the current one completes or stops. Repeated chaining adds to the end of the queue. If an animation is repeating, only stop() will advance the queue. If you want a final stop, do

sprite.chain().stop();

Be careful of chaining too many animations by mistake.

function update () {
  // Choose a reasonable maximum.
  if (sprite.anims.nextAnimsQueue.length > 10) {
    throw new Error('Too many chained animations');
  }
}

Internally, the next chained animation is in nextAnim and any additional ones are in nextAnimsQueue.

playAfterDelay() and playAfterRepeat()

playAfterDelay() and playAfterRepeat() schedule an animation to play once the condition is met or the current animation stops, adding an animation to the start of the queue.

These methods have no ignoreIfPlaying control. Don't call these repeatedly without some additional logic, or the queue will grow too long.

Events

Be very careful with event handlers. For most cases you will want to add handlers using once(), not on(). Bear in mind that most events fire for any animation, but you can examine the animation key in the callback arguments.

Trouble

If you get errors loading images or creating textures, stop and fix those first before creating animations.

Test your animations before you add game logic.

this.add.sprite(32, 32).play('idle');
this.add.sprite(32, 64).play('walk');
this.add.sprite(32, 96).play('run');
// etc.

Errors

TypeError: undefined is not an object (evaluating 'animationFrame.frame.texture')

in setCurrentFrame(). Usually you created an animation with a bad texture key.

TypeError: undefined is not an object (evaluating 'state.currentFrame.duration')

in getFirstTick(). You're playing an empty animation (no frames). This is usually because generateFrameNames() or generateFrameNumbers() couldn't select any frames; look for preceding warnings in the console.

TypeError: undefined is not an object (evaluating 'this.anims.play')

You're playing on a destroyed Sprite or a non-Sprite game object (lacking anims).

TypeError: sprite.play is not a function

You're playing on a non-Sprite game object.