О чем этот пример
Анимация — это сердце любой живой 2D-игры. Умение не только создавать плавные циклы, но и точно управлять их воспроизведением — ключевой навык. В этом примере Phaser демонстрирует, как создать анимацию из спрайтшита, запустить её с возможностью паузы, перезапуска и отслеживания внутреннего состояния. Это полезно для отладки сложных анимационных сцен, синхронизации действий с анимацией и создания интерактивных демо.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.spritesheet('mummy', 'assets/animations/mummy37x45.png', { frameWidth: 37, frameHeight: 45 });
}
create ()
{
// Frame debug view
this.frameView = this.add.graphics({ fillStyle: { color: 0xff00ff }, x: 32, y: 32 });
// Show the whole animation sheet
this.add.image(32, 32, 'mummy', '__BASE').setOrigin(0);
const config = {
key: 'walk',
frames: this.anims.generateFrameNumbers('mummy'),
frameRate: 8,
yoyo: true,
repeat: -1
};
this.anim = this.anims.create(config);
this.sprite = this.add.sprite(400, 300, 'mummy').setScale(4);
this.sprite.anims.load('walk');
// Debug text
this.progress = this.add.text(100, 500, 'Progress: 0%', { color: '#00ff00' });
this.input.keyboard.on('keydown-SPACE', function (event) {
this.sprite.anims.play('walk');
}, this);
this.input.keyboard.on('keydown-P', function (event) {
if (this.sprite.anims.isPaused)
{
this.sprite.anims.resume();
}
else
{
this.sprite.anims.pause();
}
}, this);
this.input.keyboard.on('keydown-R', function (event) {
this.sprite.anims.restart();
}, this);
}
update ()
{
this.updateFrameView();
const debug = [
'SPACE to start animation, P to pause/resume, R to restart',
'',
'Progress: ' + this.sprite.anims.getProgress() + '%',
'Accumulator: ' + this.sprite.anims.accumulator,
'NextTick: ' + this.sprite.anims.nextTick
];
this.progress.setText(debug);
}
updateFrameView ()
{
this.frameView.clear();
this.frameView.fillRect(this.sprite.frame.cutX, 0, 37, 45);
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#4d4d4d',
pixelArt: true,
scene: Example
};
const game = new Phaser.Game(config);
Загрузка ресурсов и подготовка
Всё начинается с метода preload. Здесь загружается спрайтшит — единое изображение, содержащее все кадры анимации. Важно правильно указать размер одного кадра.
this.load.spritesheet('mummy', 'assets/animations/mummy37x45.png', { frameWidth: 37, frameHeight: 45 });
После загрузки, в create, для наглядности отрисовывается весь спрайтшит с помощью метода this.add.image. Ключ '__BASE' указывает на исходное, неразрезанное изображение.
this.add.image(32, 32, 'mummy', '__BASE').setOrigin(0);
Создание и конфигурация анимации
Анимация в Phaser создаётся как объект конфигурации, который передаётся в менеджер анимаций сцены this.anims.create(config).
const config = {
key: 'walk',
frames: this.anims.generateFrameNumbers('mummy'),
frameRate: 8,
yoyo: true,
repeat: -1
};
this.anim = this.anims.create(config);
- key: Уникальное имя анимации для последующего обращения.
- frames: Массив кадров. generateFrameNumbers автоматически создаёт его из всех кадров загруженного спрайтшита 'mummy'.
- frameRate: Скорость воспроизведения в кадрах в секунду.
- yoyo: При true анимация проигрывается в прямом и обратном порядке.
- repeat: Количество повторений. -1 означает бесконечный цикл.
Спрайт и управление воспроизведением
Создаётся спрайт, который будет использовать анимацию. Анимация предварительно загружается в его собственный анимационный компонент.
this.sprite = this.add.sprite(400, 300, 'mummy').setScale(4);
this.sprite.anims.load('walk');
Управление привязано к клавиатуре. Обратите внимание на проверку isPaused для реализации переключателя паузы.
// Запуск
this.input.keyboard.on('keydown-SPACE', function (event) {
this.sprite.anims.play('walk');
}, this);
// Пауза/возобновление
this.input.keyboard.on('keydown-P', function (event) {
if (this.sprite.anims.isPaused) {
this.sprite.anims.resume();
} else {
this.sprite.anims.pause();
}
}, this);
// Перезапуск с начала
this.input.keyboard.on('keydown-R', function (event) {
this.sprite.anims.restart();
}, this);
Отладка и визуализация в реальном времени
Метод update вызывается каждый кадр игры и идеально подходит для отладочной информации.
update() {
this.updateFrameView();
const debug = [
'SPACE to start animation, P to pause/resume, R to restart',
'',
'Progress: ' + this.sprite.anims.getProgress() + '%',
'Accumulator: ' + this.sprite.anims.accumulator,
'NextTick: ' + this.sprite.anims.nextTick
];
this.progress.setText(debug);
}
- getProgress(): Возвращает прогресс проигрывания текущего повтора от 0 до 1.
- accumulator и nextTick: Внутренние тайминговые счетчики движка анимации. Их отслеживание помогает понять, как накапливается время между кадрами.
Отдельный метод updateFrameView визуализирует текущий отображаемый кадр на изображении всего спрайтшита.
updateFrameView() {
this.frameView.clear();
this.frameView.fillRect(this.sprite.frame.cutX, 0, 37, 45);
}
Что попробовать дальше
Этот пример даёт полный контроль над жизненным циклом анимации. Вы можете не только запускать и останавливать её, но и получать детальную служебную информацию. Для экспериментов попробуйте: изменить frameRate и yoyo на лету в ответ на события игры; привязать перезапуск анимации restart() к попаданию по врагу; использовать getProgress() для синхронизации звуковых эффектов или вызова логики в определённый момент анимации (например, в момент удара).
