Animations - samme/phaser3-faq GitHub Wiki
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.