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

В разработке игр часто возникает задача точечного контроля над анимациями объектов. Например, нужно заморозить все взрывы или остановить мигание всех монеток на уровне, не затрагивая другие эффекты. Phaser предоставляет для этого мощный, но не всегда очевидный механизм — управление самим экземпляром анимации (Animation instance), который влияет на все спрайты, его использующие. Эта статья покажет, как создавать глобальные анимации и централизованно ставить их на паузу или возобновлять по клику. Такой подход экономит ресурсы, упрощает логику и позволяет создавать сложные синхронные эффекты.

Версия 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');
    }

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

        const diamond = this.anims.create({ key: 'diamond', frames: this.anims.generateFrameNames('gems', { prefix: 'diamond_', end: 15, zeroPad: 4 }), repeat: -1 });
        const prism = this.anims.create({ key: 'prism', frames: this.anims.generateFrameNames('gems', { prefix: 'prism_', end: 6, zeroPad: 4 }), repeat: -1 });
        const ruby = this.anims.create({ key: 'ruby', frames: this.anims.generateFrameNames('gems', { prefix: 'ruby_', end: 6, zeroPad: 4 }), repeat: -1 });
        const square = this.anims.create({ key: 'square', frames: this.anims.generateFrameNames('gems', { prefix: 'square_', end: 14, zeroPad: 4 }), repeat: -1 });

        //  square added twice just to make sure there are more of them
        const keys = [ 'diamond', 'prism', 'ruby', 'square', 'square' ];

        let x = 100;
        let y = 116;

        for (let i = 0; i < 35; i++)
        {
            this.add.sprite(x, y, 'gems').play(keys[Phaser.Math.Between(0, 4)]);

            x += 100;

            if (x === 800)
            {
                x = 100;
                y += 100;
            }
        }

        this.input.on('pointerdown', function () {

            //  Every sprite using the global 'square' animation will now pause,
            //  because we're pausing the Animation instance itself:

            if (square.paused)
            {
                square.resume();
            }
            else
            {
                square.pause();
            }

        });
    }
}

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

const game = new Phaser.Game(config);

Создание анимаций как глобальных объектов

В отличие от присвоения анимации конкретному спрайту, Phaser позволяет создать анимацию как самостоятельный объект с помощью менеджера анимаций this.anims. Этот объект затем можно использовать для множества спрайтов. Ключевой метод здесь — this.anims.create().

const square = this.anims.create({
    key: 'square',
    frames: this.anims.generateFrameNames('gems', { prefix: 'square_', end: 14, zeroPad: 4 }),
    repeat: -1
});

В этом примере: - key: 'square' — уникальный идентификатор для обращения к анимации. - frames — кадры анимации, сгенерированные из атласа 'gems' по шаблону имен файлов. - repeat: -1 — анимация будет зациклена бесконечно.

Созданный объект square — это экземпляр класса Animation. Он становится глобальным ресурсом сцены.

Применение анимации к множеству спрайтов

После создания анимаций нужно привязать их к спрайтам. В примере создается 35 спрайтов, каждый из которых случайным образом получает одну из пяти созданных анимаций (включая два экземпляра 'square'). Для воспроизведения используется метод .play().

for (let i = 0; i < 35; i++) {
    this.add.sprite(x, y, 'gems').play(keys[Phaser.Math.Between(0, 4)]);
    x += 100;
    // ... логика переноса строки
}

Здесь keys — это массив с ключами созданных анимаций. Phaser.Math.Between(0, 4) случайным образом выбирает индекс, определяя, какую анимацию получит спрайт. Важно: спрайты, которые начинают проигрывать анимацию с ключом 'square', ссылаются на один и тот же глобальный объект Animation.

Централизованное управление через paused и resume

Самая важная часть примера — обработчик клика. Вместо перебора всех спрайтов на сцене и проверки их анимации, мы управляем самим объектом анимации square. У экземпляра Animation есть свойства paused и методы pause() / resume().

this.input.on('pointerdown', function () {
    if (square.paused) {
        square.resume();
    } else {
        square.pause();
    }
});

При вызове square.pause() все спрайты, которые в данный момент проигрывают анимацию с ключом 'square', останавливаются на текущем кадре. Вызов square.resume() возобновляет анимацию для всех этих спрайтов одновременно. Это работает, потому что они используют один общий экземпляр, который управляет своим внутренним состоянием.

Практические отличия от управления отдельными спрайтами

Почему бы просто не перебрать все спрайты в массиве? Этот подход имеет ключевые преимущества: 1. **Производительность:** Нет необходимости хранить ссылки на спрайты или выполнять их поиск по сцене. 2. **Удобство:** Логика управления инкапсулирована в одном месте — в объекте анимации. 3. **Динамичность:** Если во время паузы будет создан новый спрайт с этой анимацией, он также будет заморожен, так как анимация уже находится в состоянии паузы.

// Альтернативный, менее эффективный способ:
// this.children.each(sprite => {
//     if (sprite.anims.currentAnim.key === 'square') {
//         sprite.anims.pause();
//     }
// })

Прямое управление экземпляром Animation — это работа на уровне системы, а не на уровне отдельных игровых объектов.

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

Управление анимацией через ее глобальный экземпляр — мощный паттерн в Phaser для синхронного контроля над визуальными эффектами. Он идеально подходит для анимаций фона, повторяющихся эффектов частиц или любых объектов, которые должны реагировать на события согласованно. **Идеи для экспериментов:** - Создайте анимацию «мигание врага при получении урона» и ставьте ее на паузу, когда игрок использует способность «заморозка времени». - Реализуйте кнопку «пауза только для эффектов», которая останавливает все анимации с определенным тегом, не затрагивая движение персонажей. - Используйте square.restart() для одновременного перезапуска анимации на всех связанных спрайтах, создавая волнообразные эффекты.