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

В Phaser система анимаций построена вокруг центрального менеджера — `this.anims`. Часто возникает задача автоматически отреагировать, когда в игре создается новая анимация. Например, вы хотите немедленно создать спрайт и проиграть на нем только что добавленную анимацию. В этой статье мы разберем, как использовать событие `ADD_ANIMATION` для создания реактивной логики, которая делает ваш код чище и более модульным, избавляя от необходимости вручную связывать создание анимации и её использование.

Версия 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('gems', 'assets/tests/columns/gems.png', 'assets/tests/columns/gems.json');
        // Local variable
        this.y = 160;
    }

    create ()
    {
        this.add.text(400, 32, 'Click to create animations', { color: '#00ff00' })
            .setOrigin(0.5, 0);

        //  Each time a new animation is added to the Animation Manager we'll call this function
        this.anims.on(Phaser.Animations.Events.ADD_ANIMATION, this.addAnimation, this);

        this.i = 0;

        //  Click to add an animation
        this.input.on('pointerup', function () {
            switch (this.i)
            {
                case 0:
                    this.anims.create({ key: 'diamond', frames: this.anims.generateFrameNames('gems', { prefix: 'diamond_', end: 15, zeroPad: 4 }), repeat: -1 });
                    break;

                case 1:
                    this.anims.create({ key: 'prism', frames: this.anims.generateFrameNames('gems', { prefix: 'prism_', end: 6, zeroPad: 4 }), repeat: -1 });
                    break;

                case 2:
                    this.anims.create({ key: 'ruby', frames: this.anims.generateFrameNames('gems', { prefix: 'ruby_', end: 6, zeroPad: 4 }), repeat: -1 });
                    break;

                case 3:
                    this.anims.create({ key: 'square', frames: this.anims.generateFrameNames('gems', { prefix: 'square_', end: 14, zeroPad: 4 }), repeat: -1 });
                    break;
            }
            this.i++;
        }, this);
    }

    addAnimation (key)
    {
        this.add.sprite(400, this.y, 'gems')
            .play(key);
        this.y += 100;
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example
};


const game = new Phaser.Game(config);

Подписка на событие добавления анимации

Ядро механики — это событие Phaser.Animations.Events.ADD_ANIMATION. Менеджер анимаций this.anims является экземпляром EventEmitter, что позволяет подписываться на различные события его жизненного цикла.

Мы регистрируем обработчик в методе create, что гарантирует его готовность до первого клика пользователя.

this.anims.on(Phaser.Animations.Events.ADD_ANIMATION, this.addAnimation, this);
Ключевые моменты:
- `Phaser.Animations.Events.ADD_ANIMATION` — это константа, представляющая имя события.
- `this.addAnimation` — метод, который будет вызван при срабатывании события.
- Третий аргумент `this` задаёт контекст, в котором будет выполнен метод-обработчик. Без этого контекст был бы потерян, и мы не смогли бы получить доступ к `this.add.sprite` или `this.y` внутри `addAnimation`.

Создание анимаций по требованию

В примере анимации создаются не сразу, а в ответ на действие пользователя — клик мышью. Это отлично демонстрирует асинхронную природу события: мы не знаем заранее, когда и какая анимация будет добавлена.

Вот как устроен обработчик клика:

this.input.on('pointerup', function () {
    switch (this.i)
    {
        case 0:
            this.anims.create({ key: 'diamond', frames: this.anims.generateFrameNames('gems', { prefix: 'diamond_', end: 15, zeroPad: 4 }), repeat: -1 });
            break;
        case 1:
            this.anims.create({ key: 'prism', frames: this.anims.generateFrameNames('gems', { prefix: 'prism_', end: 6, zeroPad: 4 }), repeat: -1 });
            break;
        case 2:
            this.anims.create({ key: 'ruby', frames: this.anims.generateFrameNames('gems', { prefix: 'ruby_', end: 6, zeroPad: 4 }), repeat: -1 });
            break;
        case 3:
            this.anims.create({ key: 'square', frames: this.anims.generateFrameNames('gems', { prefix: 'square_', end: 14, zeroPad: 4 }), repeat: -1 });
            break;
    }
    this.i++;
}, this);

Каждый вызов this.anims.create() инициирует цепочку событий. Сразу после успешного создания анимации в менеджере срабатывает событие ADD_ANIMATION. Обратите внимание на параметры generateFrameNames: они используются для автоматической генерации кадров анимации из атласа, используя префикс имени и конечный индекс.

Обработчик события и его логика

Метод addAnimation — это наш реактивный обработчик. Он автоматически получает ключ (key) созданной анимации в качестве аргумента.

Вот его реализация:

addAnimation (key)
{
    this.add.sprite(400, this.y, 'gems')
        .play(key);
    this.y += 100;
}
Что происходит:
1.  Создается новый спрайт в позиции `(400, this.y)`. Текстура спрайта — тот же атлас `'gems'`, из которого сделаны кадры анимации.
2.  На свежесозданном спрайте немедленно запускается анимация с только что переданным ключом `key` с помощью метода `.play()`.
3.  Переменная `this.y` увеличивается на 100 пикселей, чтобы следующий спрайт появился ниже, создавая вертикальную колонку.

Таким образом, связь между созданием анимации и её визуальным представлением на сцене становится декларативной и управляемой событиями.

Практические сценарии применения

Шаблон «событие при добавлении анимации» выходит за рамки простого демо. Вот где он действительно полезен:

* **Динамическая загрузка контента:** Если вы загружаете данные об анимациях из внешнего JSON-файла или с сервера и создаете их партиями, событие ADD_ANIMATION может триггерить предзагрузку связанных ресурсов или обновление UI (например, добавление иконки способности в меню). * **Централизованная отладка и логирование:** Вы можете добавить глобальный обработчик, который логирует в консоль каждую созданную анимацию, её ключ и параметры, что бесценно при отладке больших проектов. * **Автоматическая привязка к объектам пула:** В играх с объектным пулом (например, пулевых стрелялках) вы можете настроить систему так, чтобы при создании анимации взрыва 'explosion' все спрайты из пула «взрывов» автоматически получали на неё ссылку и могли её проиграть по требованию.

// Пример логирования всех создаваемых анимаций
this.anims.on(Phaser.Animations.Events.ADD_ANIMATION, function(key, animation) {
    console.log('Анимация создана:', key, animation.frames.length, 'кадров');
}, this);

Обратите внимание, что полная сигнатура обработчика может также включать вторым аргументом сам экземпляр класса Animation.

Что попробовать дальше

Использование события ADD_ANIMATION — это мощный паттерн для создания слабосвязанных и реактивных систем в Phaser. Он позволяет отделить код, который определяет анимации, от кода, который их использует. Для экспериментов попробуйте

  1. создать систему, где анимации добавляются не по клику, а по таймеру
  2. модифицировать обработчик так, чтобы спрайты появлялись в случайных позициях на экране
  3. подписаться на другие события менеджера анимаций, например, REMOVE_ANIMATION, чтобы очищать ресурсы