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

В играх часто требуется, чтобы анимация, например взрыва, проигралась один раз, после чего объект исчез. Вручную управлять видимостью через колбэки утомительно и увеличивает код. Phaser предлагает элегантное встроенное решение — параметр `hideOnComplete` для анимации. Эта статья покажет, как использовать его для создания эффекта уничтожения врагов по клику, после которого они бесследно исчезают.

Версия 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.spritesheet('invader', 'assets/tests/invaders/invader1.png', { frameWidth: 32, frameHeight: 32 });
        this.load.spritesheet('boom', 'assets/sprites/explosion.png', { frameWidth: 64, frameHeight: 64, endFrame: 23 });
    }

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

        var config1 = {
            key: 'move',
            frames: 'invader',
            frameRate: 4,
            repeat: -1
        };

        var config2 = {
            key: 'explode',
            frames: 'boom',
            hideOnComplete: true
        };

        this.anims.create(config1);
        this.anims.create(config2);

        var colors = [ 0xef658c, 0xff9a52, 0xffdf00, 0x31ef8c, 0x21dfff, 0x31aade, 0x5275de, 0x9c55ad, 0xbd208c ];

        //  Create a load of random sprites
        for (var i = 0; i < 128; i++)
        {
            var x = Phaser.Math.Between(50, 750);
            var y = Phaser.Math.Between(100, 550);

            var ship = this.add.sprite(x, y, 'invader');

            ship.play('move');

            ship.setTint(Phaser.Utils.Array.GetRandom(colors));

            ship.setInteractive();

            ship.once('pointerdown', function () {

                this.clearTint();

                //  Sprite will have visible = false set when the animation finishes repeating because of 'hideOnComplete' property
                this.play('explode');

            });
        }
    }
}

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

const game = new Phaser.Game(config);

Загрузка ресурсов и создание сцены

В методе preload() загружаются два спрайтшита: один для анимированного врага (invader), другой для взрыва (boom). Важно отметить, что для взрыва указан параметр endFrame: 23, который сообщает Phaser, что анимация состоит из 24 кадров (с 0 по 23).

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.spritesheet('invader', 'assets/tests/invaders/invader1.png', { frameWidth: 32, frameHeight: 32 });
    this.load.spritesheet('boom', 'assets/sprites/explosion.png', { frameWidth: 64, frameHeight: 64, endFrame: 23 });
}

Создание анимаций: ключевое отличие

В методе create() создаются две анимации. Первая, 'move', зациклена (repeat: -1) и будет постоянно проигрываться для врагов. Вторая, 'explode', является ключевой для нашего эффекта.

var config1 = {
    key: 'move',
    frames: 'invader',
    frameRate: 4,
    repeat: -1
};

var config2 = {
    key: 'explode',
    frames: 'boom',
    hideOnComplete: true
};

Анимация 'explode' не имеет явно заданных frameRate и repeat. По умолчанию она проиграется один раз со стандартной скоростью (60 FPS). Параметр hideOnComplete: true указывает Phaser автоматически установить свойство visible спрайта в false сразу после завершения последнего кадра анимации. Это избавляет нас от необходимости слушать события вроде 'animationcomplete' и вручную скрывать объект.

Создание интерактивных спрайтов

Далее в цикле создается 128 спрайтов-врагов. Каждому назначается случайный оттенок из палитры (setTint), запускается циклическая анимация движения (play('move')) и добавляется интерактивность (setInteractive).

var ship = this.add.sprite(x, y, 'invader');
ship.play('move');
ship.setTint(Phaser.Utils.Array.GetRandom(colors));
ship.setInteractive();

Обработка клика и запуск уничтожения

На каждый спрайт вешается обработчик события 'pointerdown' с помощью once. Это гарантирует, что обработчик сработает только один раз, что логично для уничтожения врага.

ship.once('pointerdown', function () {
    this.clearTint();
    this.play('explode');
});

При клике с спрайта снимается оттенок (clearTint()), и запускается анимация взрыва 'explode'. После того как анимация взрыва проиграет все кадры до конца, благодаря параметру hideOnComplete: true, свойство visible спрайта автоматически станет равным false. Спрайт перестанет отрисовываться, создавая эффект полного исчезновения. Физическое тело или другие компоненты остаются, но визуально объект пропадает.

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

Параметр hideOnComplete — это мощный инструмент для управления жизненным циклом визуальных объектов, особенно для одноразовых эффектов. Он делает код чище и избавляет от рутинной работы. Для экспериментов попробуйте: комбинировать hideOnComplete с delay для отложенного исчезновения; вместо скрытия уничтожать спрайт полностью через destroy() в событии 'animationcomplete'; или использовать hideOnComplete для элементов интерфейса, которые должны плавно исчезнуть после показа.