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

Управление ресурсами — ключевой навык при разработке игр. В Phaser можно не только создавать анимации, но и удалять их во время работы игры, чтобы освободить память или динамически менять контент. Эта статья покажет, как корректно удалить анимацию через глобальный менеджер `Animation Manager` и как подписаться на событие `REMOVE_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');

    }

    create ()
    {
        const text = this.add.text(400, 32, 'Click to remove the Square animation', { 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.anims.on(Phaser.Animations.Events.REMOVE_ANIMATION, function (key, anim) {

            text.setText('Animation ' + key + ' has been removed');

        });

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

            //  We'll now remove the square animation from the global Animation Manager
            this.anims.remove('square');

        }, this);
    }
}

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

const game = new Phaser.Game(config);

Создание набора анимаций

В примере в методе create создаются четыре анимации на основе одного атласа gems. Ключевой момент — использование метода this.anims.create. Каждая анимация получает уникальный ключ (key), например, 'square'. Затем эти ключи помещаются в массив, и в цикле создаются спрайты, которые случайным образом проигрывают одну из анимаций.

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

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

Глобальный менеджер анимаций this.anims является экземпляром Phaser.Animations.AnimationManager и эмиттером событий. Чтобы отследить момент удаления анимации, используется событие Phaser.Animations.Events.REMOVE_ANIMATION. В обработчик события передаются два аргумента: ключ удалённой анимации и её объект. В примере это событие используется для обновления текста на экране.

this.anims.on(Phaser.Animations.Events.REMOVE_ANIMATION, function (key, anim) {
    text.setText('Animation ' + key + ' has been removed');
});

Удаление анимации по событию ввода

Удаление анимации инициируется по клику мыши. Метод this.input.once регистрирует одноразовый обработчик для события pointerdown. Внутри него вызывается this.anims.remove('square'). Это удаляет анимацию с ключом 'square' из менеджера. Важно: все спрайты, которые в этот момент проигрывали данную анимацию, перестанут её отображать, так как ссылка на объект анимации будет удалена.

this.input.once('pointerdown', function () {
    this.anims.remove('square');
}, this);

Поведение спрайтов после удаления анимации

После вызова remove('square') анимация square больше не существует в менеджере. Спрайты, которые её проигрывали, не получают автоматически новую анимацию — они просто перестают обновлять кадры. Визуально они "заморозятся" на том кадре, который был активен в момент удаления. Это поведение нужно учитывать: если требуется плавный переход, перед удалением стоит остановить анимацию на спрайтах или назначить им новую.

// Пример: перед удалением можно остановить анимацию на всех спрайтах
// Для этого потребуется хранить ссылки на созданные спрайты.

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

Удаление анимаций через this.anims.remove() и обработка события REMOVE_ANIMATION дают контроль над ресурсами в runtime. Это можно использовать для динамической выгрузки неиспользуемых анимаций в больших играх или для смены визуального стиля уровня. Поэкспериментируйте: попробуйте удалять анимации не по клику, а по таймеру, или создайте систему, которая автоматически удаляет анимации, не использовавшиеся определённое время. Также можно модифицировать пример, чтобы при удалении анимации square все affected спрайты плавно переключались на анимацию diamond.