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

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

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.spritesheet('mummy', 'assets/sprites/metalslug_mummy37x45.png', { frameWidth: 37, frameHeight: 45, endFrame: 17 });
    }

    create ()
    {
        this.anims.create({
            key: 'run',
            frames: 'mummy',
            frameRate: 20,
            repeat: -1
        });

        const container = this.add.container(-100, 0);

        container.setScale(2);

        const sprites = [];

        let x = 0;
        let y = 50;

        for (let i = 1; i <= 18; i++)
        {
            const sprite = this.add.sprite(x, y, 'mummy').play('run').setInteractive();

            y += 100;

            sprites.push(sprite);

            if (i % 3 === 0)
            {
                x -= 100;
                y = 50;
            }
        }

        container.add(sprites);

        this.tweens.add({
            targets: container,
            x: 1800,
            duration: 20000,
            repeat: -1
        });

        this.input.on('gameobjectup', (pointer, gameObject) =>
        {

            gameObject.setTint(Math.random() * 0xffffff);

        });

        this.add.text(10, 10, 'Click sprites to interact', { font: '16px Courier', fill: '#00ff00' });
    }
}

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

const game = new Phaser.Game(config);

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

Вся работа начинается в методе create сцены. Первым делом создаётся анимация 'run' из загруженного спрайтшита 'mummy'. Затем создаётся объект контейнера. Контейнер — это специальный игровой объект, который может содержать в себе другие объекты. Мы создаём его с начальной позицией за левой границей экрана и сразу же увеличиваем его масштаб в 2 раза.

const container = this.add.container(-100, 0);
container.setScale(2);

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

Далее в цикле создаётся 18 спрайтов. Каждый спрайт создаётся с помощью this.add.sprite(), ему сразу запускается анимация 'run' методом .play('run'), и он делается интерактивным с помощью .setInteractive(). Это ключевой момент — без вызова setInteractive() объект не будет генерировать события ввода. Спрайты расставляются в сетку 3x6 (по 3 в столбце). Все созданные спрайты сохраняются в массив sprites.

const sprite = this.add.sprite(x, y, 'mummy').play('run').setInteractive();

Группировка в контейнер и анимация

После создания всех спрайтов, весь массив sprites добавляется в контейнер одним вызовом метода container.add(). Теперь все 18 спрайтов являются дочерними элементами контейнера. Когда контейнер перемещается или трансформируется, все его дети трансформируются относительно него. Мы создаём твин, который плавно перемещает весь контейнер по горизонтали за пределы экрана и обратно в бесконечном цикле.

container.add(sprites);
this.tweens.add({
    targets: container,
    x: 1800,
    duration: 20000,
    repeat: -1
});

Обработка кликов по игровым объектам

Несмотря на то что спрайты теперь находятся внутри движущегося контейнера, их интерактивность сохраняется. Мы настраиваем глобальный слушатель события gameobjectup на this.input. Когда происходит клик (отпускание кнопки мыши) над любым интерактивным игровым объектом, вызывается callback-функция. В ней объект, по которому кликнули (gameObject), получает случайный цветовой оттенок с помощью setTint. Важно, что событие приходит именно на конкретный спрайт, а не на контейнер.

this.input.on('gameobjectup', (pointer, gameObject) => {
    gameObject.setTint(Math.random() * 0xffffff);
});

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

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

  1. Добавить вращение или масштабирование контейнеру в твине
  2. Сделать интерактивным сам контейнер и обрабатывать его клики
  3. Вложить один контейнер с объектами в другой для создания сложных иерархий