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

В играх часто возникает необходимость глобально управлять анимациями — например, при открытии меню или паузы игры. Ручная остановка каждого анимированного спрайта неэффективна и усложняет код. Phaser предоставляет мощный инструмент — глобальный менеджер анимаций (`this.anims`), который позволяет управлять всеми воспроизводимыми анимациями сценой централизованно. В этой статье мы разберем, как создать множество анимированных объектов и затем поставить их все на паузу или возобновить воспроизведение одной строчкой кода, используя методы `pauseAll()` и `resumeAll()`.

Версия 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('soldier', 'assets/animations/soldier.png', 'assets/animations/soldier.json');
        this.load.image('bg', 'assets/skies/grass.jpg');
    }

    create ()
    {
        this.add.image(400, 300, 'bg');
        this.add.text(400, 32, 'Click to pause all animations', { color: '#ffffff' }).setOrigin(0.5, 0);

        this.anims.create({
            key: 'shoot1',
            frames: this.anims.generateFrameNames('soldier', { prefix: 'Soldier_2_shot_up_', start: 1, end: 11 }),
            frameRate: 10,
            repeat: -1
        });

        this.anims.create({
            key: 'shoot2',
            frames: this.anims.generateFrameNames('soldier', { prefix: 'soldier_3_shoot_front_', start: 1, end: 11 }),
            frameRate: 10,
            repeat: -1
        });

        //  Generate some random toy soldiers
        for (let i = 0; i < 32; i++)
        {
            const x = Phaser.Math.Between(0, 800);
            const y = Phaser.Math.Between(200, 550);
            const rd = Phaser.Math.Between(200, 2000);
            let troop;

            if (i < 16)
            {
                troop = this.add.sprite(x, y, 'soldier', 'Soldier_2_shot_up_1');
                troop.setDepth(y);

                troop.playAfterDelay({ key: 'shoot1', repeatDelay: rd }, rd);
            }
            else
            {
                troop = this.add.sprite(x, y, 'soldier', 'soldier_3_shoot_front_1');
                troop.setDepth(y);

                troop.playAfterDelay({ key: 'shoot2', repeatDelay: rd }, rd);
            }
        }

        this.input.on('pointerdown', function () {
            //  Every single animation in the Animation Manager will be paused:
            if (this.anims.paused)
            {
                this.anims.resumeAll();
            }
            else
            {
                this.anims.pauseAll();
            }
        }, this);
    }

}

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

const game = new Phaser.Game(config);

Подготовка анимаций и создание спрайтов

В методе preload загружаются необходимые ресурсы: атлас с кадрами анимации солдата и фон. В методе create создаются две анимации shoot1 и shoot2 из кадров атласа. Ключевые параметры: repeat: -1 делает анимацию зацикленной, а frameRate задает скорость воспроизведения.

Затем в цикле создается 32 спрайта-солдата в случайных позициях. Первые 16 спрайтов используют анимацию shoot1, остальные — shoot2. Важный нюанс — для создания эффекта "роя" используется метод playAfterDelay, который запускает анимацию каждого солдата со случайной задержкой.

this.anims.create({
    key: 'shoot1',
    frames: this.anims.generateFrameNames('soldier', { prefix: 'Soldier_2_shot_up_', start: 1, end: 11 }),
    frameRate: 10,
    repeat: -1
});
for (let i = 0; i < 32; i++) {
    const x = Phaser.Math.Between(0, 800);
    const y = Phaser.Math.Between(200, 550);
    const rd = Phaser.Math.Between(200, 2000);
    let troop;

    if (i < 16) {
        troop = this.add.sprite(x, y, 'soldier', 'Soldier_2_shot_up_1');
        troop.setDepth(y);
        troop.playAfterDelay({ key: 'shoot1', repeatDelay: rd }, rd);
    }
}

Глобальная пауза и возобновление анимаций

Вся магия управления происходит в обработчике клика мыши (pointerdown). Вместо перебора всех спрайтов на сцене, мы обращаемся к глобальному менеджеру анимаций сцены — this.anims. Этот объект отслеживает все активные анимации.

Метод this.anims.pauseAll() приостанавливает воспроизведение каждой анимации, которая в данный момент играет. Состояние анимаций (текущий кадр, прогресс) сохраняется. Метод this.anims.resumeAll(), наоборот, возобновляет воспроизведение всех анимаций с того места, где они были остановлены.

Для реализации переключателя "пауза/продолжить" используется флаг this.anims.paused. Этот флаг автоматически устанавливается в true при вызове pauseAll() и сбрасывается в false при вызове resumeAll().

this.input.on('pointerdown', function () {
    if (this.anims.paused) {
        this.anims.resumeAll();
    } else {
        this.anims.pauseAll();
    }
}, this);

Особенности работы и важные замечания

Методы `pauseAll()` и `resumeAll()` влияют только на анимации, созданные через менеджер анимаций Phaser (`this.anims.create` и `sprite.play`). Они не затрагивают:
*   Анимации, реализованные вручную через смену кадров в `update`.
*   Tween-анимации (их управление осуществляется через `this.tweens`).

Флаг paused является свойством самого менеджера анимаций (AnimationManager) и отлично подходит для проверки текущего глобального состояния. Это избавляет от необходимости вести собственный булевый флаг в коде сцены.

Важно помнить, что пауза анимаций через pauseAll() не приостанавливает игровое время или логику. Обновление сцены (update) продолжает работать, физика обрабатывается. Остановлены только смена кадров спрайтов.

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

Использование this.anims.pauseAll() и resumeAll() — это чистый и эффективный способ поставить на паузу все анимации на сцене. Этот подход идеально подходит для реализации игровой паузы, модальных окон или кат-сцен, где визуальная активность должна быть временно приостановлена. Для экспериментов попробуйте: 1. Связать глобальную паузу анимаций с паузой физического мира (this.physics.pause()). 2. Реализовать отдельные группы анимаций, которые можно ставить на паузу независимо друг от друга, используя пользовательские фильтры перед вызовом pauseAll. 3. Добавить визуальную индикацию (например, полупрозрачный затемняющий слой) в момент, когда анимации поставлены на паузу.