О чем этот пример
В создании плавных игровых сцен часто требуется, чтобы одна анимация естественно перетекала в другую — например, персонаж заканчивает цикл покоя, поворачивается и начинает идти. Ручной контроль таймингов и событий для такого перехода может быть утомительным. В этой статье мы разберем, как использовать встроенные методы Phaser `playAfterRepeat` и `chain` для автоматизации и создания последовательностей анимаций, что сделает ваш код чище, а поведение персонажей — более предсказуемым и профессиональным.
Версия 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.atlas('alien', 'assets/animations/alien.png', 'assets/animations/alien.json');
this.load.image('bg', 'assets/pics/space-wreck.jpg');
}
create ()
{
this.add.image(400, 300, 'bg');
const text = this.add.text(400, 32, "Click to toggle sequence", { color: '#00ff00' }).setOrigin(0.5, 0);
// Our global animations, as defined in the texture atlas
this.anims.create({ key: 'idle', frames: this.anims.generateFrameNames('alien', { prefix: '01_Idle_', end: 17, zeroPad: 3 }), repeat: -1, repeatDelay: 500, frameRate: 18 });
this.anims.create({ key: 'turn', frames: this.anims.generateFrameNames('alien', { prefix: '02_Turn_to_walk_', end: 3, zeroPad: 3 }), frameRate: 12 });
this.anims.create({ key: 'walk', frames: this.anims.generateFrameNames('alien', { prefix: '03_Walk_', end: 12, zeroPad: 3 }), repeat: -1, frameRate: 18 });
const ripley = this.add.sprite(400, 300, 'alien').play('idle');
this.input.on('pointerdown', function () {
if (ripley.anims.getName() === 'idle')
{
// When the current animation repeat ends, we'll play the 'turn' animation
ripley.anims.playAfterRepeat('turn');
// And after that, the 'walk' look
ripley.anims.chain('walk');
}
else
{
ripley.anims.play('idle');
}
});
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Создание набора анимаций
Перед тем как выстраивать очередь, необходимо создать сами анимации. В примере используется спрайт-лист (atlas), и анимации генерируются из кадров с определенными именами. Ключевые параметры: key (идентификатор), repeat (количество повторов, -1 для бесконечного) и repeatDelay (пауза между повторами).
this.anims.create({ key: 'idle', frames: this.anims.generateFrameNames('alien', { prefix: '01_Idle_', end: 17, zeroPad: 3 }), repeat: -1, repeatDelay: 500, frameRate: 18 });
this.anims.create({ key: 'turn', frames: this.anims.generateFrameNames('alien', { prefix: '02_Turn_to_walk_', end: 3, zeroPad: 3 }), frameRate: 12 });
this.anims.create({ key: 'walk', frames: this.anims.generateFrameNames('alien', { prefix: '03_Walk_', end: 12, zeroPad: 3 }), repeat: -1, frameRate: 18 });
Анимация idle (покой) воспроизводится бесконечно (repeat: -1) с задержкой в 500 мс между циклами. Анимация turn (поворот) проигрывается единожды (по умолчанию repeat: 0), а walk (ходьба) снова зациклена.
Метод playAfterRepeat: запуск анимации после завершения цикла
Основная задача playAfterRepeat — запланировать запуск следующей анимации не немедленно, а после того, как текущая завершит свой полный цикл повторения. Это особенно полезно для плавных переходов из зацикленных состояний.
ripley.anims.playAfterRepeat('turn');
В примере, пока персонаж находится в состоянии idle, клик мыши вызывает эту строку. Она указывает системе анимации: "Как только текущий повтор анимации idle завершится, немедленно начни проигрывать анимацию turn". Без этого метода пришлось бы вручную отслеживать событие ANIMATION_REPEAT или ANIMATION_COMPLETE и запускать следующую анимацию, что менее удобно.
Метод chain: создание последовательности анимаций
Метод chain позволяет выстроить цепочку анимаций. Он добавляет одну или несколько анимаций в очередь, которые будут проигрываться автоматически после завершения текущей.
ripley.anims.chain('walk');
В нашем коде после playAfterRepeat('turn') вызывается chain('walk'). Это создает следующую логическую цепочку:
1. Завершить текущий цикл idle.
2. Воспроизвести turn (один раз).
3. Сразу после завершения turn начать бесконечный цикл анимации walk.
Важно, что chain работает именно с очередью, которую можно построить из нескольких анимаций.
Логика переключения состояний
Вся логика инкапсулирована в обработчике клика мыши. Она проверяет текущую анимацию спрайта и в зависимости от этого либо запускает цепочку переходов, либо возвращает персонажа в исходное состояние.
this.input.on('pointerdown', function () {
if (ripley.anims.getName() === 'idle') {
ripley.anims.playAfterRepeat('turn');
ripley.anims.chain('walk');
} else {
ripley.anims.play('idle');
}
});
Метод ripley.anims.getName() возвращает ключ текущей анимации. Если это idle, мы планируем переход к turn и walk. В любом другом состоянии (например, во время walk) клик просто переключает анимацию обратно на idle с помощью стандартного play, прерывая все текущие цепочки.
Что попробовать дальше
Использование playAfterRepeat и chain делает управление сложными последовательностями анимаций в Phaser интуитивно понятным и декларативным. Вы можете легко создавать многошаговые скрипты поведения для персонажей ("постой → оглянись → иди → прыгни") без громоздких таймеров и обработчиков событий. Для экспериментов попробуйте: создать цепочку из 3 и более анимаций с помощью chain(['anim1', 'anim2', 'anim3']); использовать playAfterDelay для отложенного старта; или сбросить очередь анимаций методом stopAfterRepeat.
