О чем этот пример

В создании плавных игровых сцен часто требуется, чтобы одна анимация естественно перетекала в другую — например, персонаж заканчивает цикл покоя, поворачивается и начинает идти. Ручной контроль таймингов и событий для такого перехода может быть утомительным. В этой статье мы разберем, как использовать встроенные методы 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.