Phaser 动画系统 #
动画概述 #
Phaser 提供了两种主要的动画系统:帧动画(Sprite Animation)和补间动画(Tween)。
text
┌─────────────────────────────────────────────────────────────┐
│ 动画系统架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 帧动画 (Frame Animation) │
│ ├── 精灵表动画 │
│ ├── 按帧播放 │
│ ├── 适合角色动作 │
│ └── 由 Animation Manager 管理 │
│ │
│ 补间动画 (Tween) │
│ ├── 属性插值 │
│ ├── 平滑过渡 │
│ ├── 适合移动、缩放、透明度 │
│ └── 由 Tween Manager 管理 │
│ │
└─────────────────────────────────────────────────────────────┘
帧动画 #
加载精灵表 #
javascript
preload() {
this.load.spritesheet('dude', 'assets/dude.png', {
frameWidth: 32,
frameHeight: 48
});
this.load.spritesheet('explosion', 'assets/explosion.png', {
frameWidth: 64,
frameHeight: 64,
endFrame: 16
});
this.load.atlas('game', 'assets/game.png', 'assets/game.json');
}
创建动画 #
javascript
create() {
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'dude', frame: 4 } ],
frameRate: 20
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
frameRate: 10,
repeat: -1
});
}
动画配置 #
javascript
this.anims.create({
key: 'walk',
frames: frames,
frameRate: 12,
duration: null,
repeat: -1,
repeatDelay: 0,
yoyo: false,
delay: 0,
showOnStart: false,
hideOnComplete: false,
skipMissedFrames: true
});
生成帧 #
javascript
const frames = this.anims.generateFrameNumbers('dude', {
start: 0,
end: 7
});
const frames = this.anims.generateFrameNumbers('dude', {
start: 0,
end: 7,
first: 4
});
const frames = this.anims.generateFrameNumbers('dude', {
frames: [0, 1, 2, 3, 4, 3, 2, 1, 0]
});
const frames = this.anims.generateFrameNames('atlas', {
prefix: 'walk_',
start: 0,
end: 7,
suffix: '.png'
});
const frames = this.anims.generateFrameNames('atlas', {
prefix: 'player_',
suffix: '.png',
zeroPad: 2,
start: 1,
end: 10
});
const frames = this.anims.generateFrameNames('atlas', {
frames: ['walk_01', 'walk_02', 'walk_03']
});
播放动画 #
javascript
const sprite = this.add.sprite(100, 100, 'dude');
sprite.anims.play('left');
sprite.anims.play('left', true);
sprite.anims.play('left', false, 0);
sprite.anims.playAfterDelay('right', 2000);
sprite.anims.playAfterRepeat('turn', 3);
sprite.anims.chain(['walk', 'run', 'jump']);
sprite.anims.stop();
sprite.anims.stopOnFrame(sprite.anims.currentAnim.frames[3]);
sprite.anims.pause();
sprite.anims.resume();
sprite.anims.restart();
动画事件 #
javascript
const sprite = this.add.sprite(100, 100, 'dude');
sprite.on('animationstart', (animation, frame) => {
console.log('Animation started:', animation.key);
});
sprite.on('animationcomplete', (animation, frame) => {
console.log('Animation completed:', animation.key);
});
sprite.on('animationcomplete-walk', (animation, frame) => {
console.log('Walk animation completed');
});
sprite.on('animationrepeat', (animation, frame) => {
console.log('Animation repeated:', animation.key);
});
sprite.on('animationupdate', (animation, frame, gameObject) => {
console.log('Frame updated:', frame.textureFrame);
});
sprite.on('animationstop', (animation, frame) => {
console.log('Animation stopped:', animation.key);
});
动画管理 #
javascript
const anim = this.anims.get('walk');
const exists = this.anims.exists('walk');
this.anims.remove('walk');
const allAnims = this.anims.getAll();
this.anims.pauseAll();
this.anims.resumeAll();
this.anims.getGlobalTimeScale();
this.anims.setGlobalTimeScale(0.5);
动画实例属性 #
javascript
const sprite = this.add.sprite(100, 100, 'dude');
sprite.anims.play('walk');
const currentAnim = sprite.anims.currentAnim;
const currentFrame = sprite.anims.currentFrame;
const frameRate = sprite.anims.frameRate;
const duration = sprite.anims.duration;
const msPerFrame = sprite.anims.msPerFrame;
const progress = sprite.anims.progress;
const totalFrames = sprite.anims.totalFrames;
sprite.anims.setTimeScale(2);
sprite.anims.setDelay(1000);
sprite.anims.setRepeat(5);
sprite.anims.setRepeatDelay(500);
补间动画(Tween) #
基本补间 #
javascript
this.tweens.add({
targets: this.player,
x: 400,
duration: 2000,
ease: 'Power2'
});
this.tweens.add({
targets: this.player,
x: 400,
y: 300,
duration: 2000
});
补间配置 #
javascript
this.tweens.add({
targets: this.player,
x: 400,
y: 300,
duration: 2000,
ease: 'Power2',
delay: 0,
repeat: 0,
repeatDelay: 0,
yoyo: false,
hold: 0,
paused: false,
onStart: function(tween, targets) {
console.log('Tween started');
},
onUpdate: function(tween, targets) {
console.log('Tween updating');
},
onComplete: function(tween, targets) {
console.log('Tween completed');
},
onRepeat: function(tween, targets) {
console.log('Tween repeated');
},
onYoyo: function(tween, targets) {
console.log('Tween yoyo');
},
callbackScope: this
});
缓动函数 #
javascript
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Linear',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Power0',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Power1',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Power2',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Power3',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Power4',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Quad.easeOut',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Cubic.easeInOut',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Bounce.easeOut',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Elastic.easeOut',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Back.easeOut',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Sine.easeInOut',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Expo.easeOut',
duration: 2000
});
this.tweens.add({
targets: this.player,
x: 400,
ease: 'Circular.easeInOut',
duration: 2000
});
补间属性 #
javascript
this.tweens.add({
targets: this.player,
x: 400,
y: 300,
duration: 2000
});
this.tweens.add({
targets: this.player,
alpha: 0,
duration: 1000
});
this.tweens.add({
targets: this.player,
scaleX: 2,
scaleY: 2,
duration: 1000
});
this.tweens.add({
targets: this.player,
angle: 360,
duration: 2000
});
this.tweens.add({
targets: this.player,
rotation: Math.PI * 2,
duration: 2000
});
this.tweens.add({
targets: this.player,
tint: 0xff0000,
duration: 1000
});
相对值 #
javascript
this.tweens.add({
targets: this.player,
x: '+=100',
duration: 1000
});
this.tweens.add({
targets: this.player,
x: '-=50',
y: '+=100',
duration: 1000
});
多目标补间 #
javascript
this.tweens.add({
targets: [this.player, this.enemy, this.npc],
x: 400,
y: 300,
duration: 2000
});
this.tweens.add({
targets: this.enemies.getChildren(),
alpha: 0,
duration: 1000
});
补间事件 #
javascript
this.tweens.add({
targets: this.player,
x: 400,
duration: 2000,
onStart: (tween, targets) => {
console.log('Started');
},
onUpdate: (tween, targets) => {
console.log('Progress:', tween.progress);
},
onComplete: (tween, targets) => {
console.log('Completed');
},
onRepeat: (tween, targets) => {
console.log('Repeated');
},
onYoyo: (tween, targets) => {
console.log('Yoyo');
},
onPause: (tween, targets) => {
console.log('Paused');
},
onResume: (tween, targets) => {
console.log('Resumed');
},
onLoop: (tween, targets) => {
console.log('Looped');
}
});
补间控制 #
javascript
const tween = this.tweens.add({
targets: this.player,
x: 400,
duration: 2000
});
tween.pause();
tween.resume();
tween.stop();
tween.restart();
tween.complete();
tween.seek(0.5);
const progress = tween.progress;
tween.setProgress(0.5);
const elapsed = tween.elapsed;
tween.setElapsed(1000);
const timeScale = tween.timeScale;
tween.setTimeScale(2);
循环与 Yoyo #
javascript
this.tweens.add({
targets: this.player,
x: 400,
duration: 1000,
repeat: -1
});
this.tweens.add({
targets: this.player,
x: 400,
duration: 1000,
repeat: 3
});
this.tweens.add({
targets: this.player,
x: 400,
duration: 1000,
yoyo: true
});
this.tweens.add({
targets: this.player,
x: 400,
duration: 1000,
repeat: -1,
yoyo: true
});
this.tweens.add({
targets: this.player,
x: 400,
duration: 1000,
repeat: 3,
repeatDelay: 500,
yoyo: true,
hold: 200
});
延迟执行 #
javascript
this.tweens.add({
targets: this.player,
x: 400,
duration: 1000,
delay: 500
});
this.tweens.add({
targets: [this.player, this.enemy, this.npc],
x: 400,
duration: 1000,
delay: (target, key, value, index) => index * 200
});
时间线(Timeline) #
创建时间线 #
javascript
const timeline = this.tweens.createTimeline();
timeline.add({
targets: this.player,
x: 400,
duration: 1000
});
timeline.add({
targets: this.player,
y: 300,
duration: 1000
});
timeline.play();
时间线配置 #
javascript
const timeline = this.tweens.timeline({
targets: this.player,
tweens: [
{
x: 400,
duration: 1000
},
{
y: 300,
duration: 1000
},
{
alpha: 0,
duration: 500
}
]
});
timeline.play();
时间线事件 #
javascript
const timeline = this.tweens.timeline({
targets: this.player,
tweens: [
{ x: 400, duration: 1000 },
{ y: 300, duration: 1000 }
],
onComplete: () => {
console.log('Timeline complete');
}
});
补间管理器 #
获取补间 #
javascript
const tweens = this.tweens.getAllTweens();
const tween = this.tweens.getTweensOf(this.player);
const exists = this.tweens.isTweening(this.player);
全局控制 #
javascript
this.tweens.pauseAll();
this.tweens.resumeAll();
this.tweens.stopAll();
this.tweens.killTweensOf(this.player);
this.tweens.killAll();
const timeScale = this.tweens.timeScale;
this.tweens.setTimeScale(0.5);
实用动画效果 #
淡入淡出 #
javascript
fadeIn(sprite, duration = 1000) {
sprite.setAlpha(0);
this.tweens.add({
targets: sprite,
alpha: 1,
duration: duration,
ease: 'Linear'
});
}
fadeOut(sprite, duration = 1000) {
this.tweens.add({
targets: sprite,
alpha: 0,
duration: duration,
ease: 'Linear'
});
}
fadeOutAndDestroy(sprite, duration = 1000) {
this.tweens.add({
targets: sprite,
alpha: 0,
duration: duration,
ease: 'Linear',
onComplete: () => sprite.destroy()
});
}
弹跳效果 #
javascript
bounce(sprite) {
this.tweens.add({
targets: sprite,
scaleX: 1.2,
scaleY: 0.8,
duration: 100,
yoyo: true,
repeat: 2,
ease: 'Power2'
});
}
bounceIn(sprite) {
sprite.setScale(0);
this.tweens.add({
targets: sprite,
scaleX: 1,
scaleY: 1,
duration: 500,
ease: 'Bounce.easeOut'
});
}
抖动效果 #
javascript
shake(sprite, intensity = 10, duration = 100) {
const originalX = sprite.x;
const originalY = sprite.y;
this.tweens.add({
targets: sprite,
x: originalX + intensity,
y: originalY + intensity,
duration: duration,
yoyo: true,
repeat: 5,
ease: 'Linear',
onComplete: () => {
sprite.setPosition(originalX, originalY);
}
});
}
闪烁效果 #
javascript
flash(sprite, duration = 100, times = 3) {
this.tweens.add({
targets: sprite,
alpha: 0,
duration: duration,
yoyo: true,
repeat: times * 2 - 1,
ease: 'Linear'
});
}
flashTint(sprite, color = 0xffffff, duration = 100, times = 3) {
let count = 0;
const maxCount = times * 2;
const timer = this.time.addEvent({
delay: duration,
callback: () => {
if (count % 2 === 0) {
sprite.setTint(color);
} else {
sprite.clearTint();
}
count++;
if (count >= maxCount) {
timer.remove();
sprite.clearTint();
}
},
loop: true
});
}
脉冲效果 #
javascript
pulse(sprite, minScale = 0.9, maxScale = 1.1, duration = 500) {
this.tweens.add({
targets: sprite,
scaleX: maxScale,
scaleY: maxScale,
duration: duration,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut'
});
}
浮动效果 #
javascript
float(sprite, distance = 10, duration = 1000) {
this.tweens.add({
targets: sprite,
y: sprite.y - distance,
duration: duration,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut'
});
}
旋转效果 #
javascript
rotate(sprite, duration = 2000) {
this.tweens.add({
targets: sprite,
angle: 360,
duration: duration,
repeat: -1,
ease: 'Linear'
});
}
rotateBackAndForth(sprite, duration = 1000) {
this.tweens.add({
targets: sprite,
angle: 15,
duration: duration,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut'
});
}
完整示例 #
UI 动画 #
javascript
class MenuScene extends Phaser.Scene {
create() {
this.createTitle();
this.createButtons();
}
createTitle() {
const title = this.add.text(400, 100, 'My Game', {
fontSize: '64px',
fill: '#fff'
}).setOrigin(0.5).setAlpha(0);
this.tweens.add({
targets: title,
alpha: 1,
y: 150,
duration: 1000,
ease: 'Power2'
});
this.tweens.add({
targets: title,
y: title.y + 10,
duration: 2000,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut'
});
}
createButtons() {
const buttons = [
{ text: 'Start Game', y: 300 },
{ text: 'Settings', y: 380 },
{ text: 'Credits', y: 460 }
];
buttons.forEach((btn, index) => {
const button = this.add.text(400, btn.y + 50, btn.text, {
fontSize: '32px',
fill: '#fff',
backgroundColor: '#333',
padding: { x: 20, y: 10 }
}).setOrigin(0.5).setAlpha(0).setInteractive();
this.tweens.add({
targets: button,
alpha: 1,
y: btn.y,
duration: 500,
delay: 500 + index * 200,
ease: 'Power2'
});
button.on('pointerover', () => {
this.tweens.add({
targets: button,
scaleX: 1.1,
scaleY: 1.1,
duration: 100
});
});
button.on('pointerout', () => {
this.tweens.add({
targets: button,
scaleX: 1,
scaleY: 1,
duration: 100
});
});
});
}
}
下一步 #
现在你已经掌握了动画系统,接下来学习 瓦片地图,了解如何创建大型游戏世界!
最后更新:2026-03-29