-
Notifications
You must be signed in to change notification settings - Fork 4
Animations
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.
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
.
// Create animation 'mummyWalk' from every frame in texture 'mummy':
this.anims.create({
key: 'mummyWalk',
frames: 'mummy'
});
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());
// Create animation 'mummyWalk' from frames 0, 1, 2 of texture 'mummy':
this.anims.create({
key: 'mummyWalk',
frames: [ { key: 'mummy', frame: 0 },
{ key: 'mummy', frame: 1 },
{ key: 'mummy', frame: 2 } ]
});
You can of course create animations using any technique.
// 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 animName of anims) {
this.anims.create({
// 'giant:idle', 'giant:walk', etc.
key: `${textureKey}:${animName}`,
frames: anims[animName]
});
}
}
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 }
]
});
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.
You can get an animation from the manager or the sprite it was created on.
const mummyWalkAnim = this.anims.get('mummyWalk');
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);
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);
}
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');
}
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()
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.
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.
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.
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), or you're playing a startFrame
that's out of range. If the animation has no frames, this is usually because generateFrameNames()
or generateFrameNumbers()
couldn't select any; 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.