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

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

Версия 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.image('lemming', 'assets/sprites/lemming.png');
    }

    create ()
    {
        //  Here we've got 1 of each game object:
        const image = this.add.image(0, 0, 'lemming');
        const text = this.add.text(60, 0, 'Oh No!', { font: '16px Courier', fill: '#00ff00' });

        const container1 = this.add.container(200, 300).setExclusive(true);
        container1.add([ image, text ]);

        const container2 = this.add.container(400, 300).setExclusive(false);
        container2.add([ image, text ]);

        const container3 = this.add.container(600, 300).setExclusive(false);
        container3.add([ image, text ]);

        this.tweens.add({
            targets: [ container1, container2, container3 ],
            angle: 360,
            duration: 6000,
            repeat: -1
        });

        this.tweens.add({
            targets: container2,
            scaleX: 2,
            scaleY: 2,
            duration: 3000,
            yoyo: true,
            repeat: -1
        });

        this.tweens.add({
            targets: container3,
            alpha: 0,
            duration: 3000,
            yoyo: true,
            repeat: -1
        });
    }
}

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

const game = new Phaser.Game(config);

В чем суть флага `exclusive`?

При создании контейнера с помощью this.add.container() вы можете вызвать метод .setExclusive(). Этот метод принимает булево значение.

- **setExclusive(true) (значение по умолчанию)**: Объект может находиться только в этом контейнере. Попытка добавить его в другой контейнер удалит его из предыдущего. - **setExclusive(false)**: Объект может быть добавлен в несколько контейнеров одновременно. Его трансформации (положение, масштаб, поворот, прозрачность) будут комбинироваться из всех контейнеров, в которых он состоит.

В примере создается один спрайт image и один текстовый объект text, которые затем используются повторно.

Разбор кода: создание контейнеров

Давайте посмотрим, как в сцене создаются три контейнера с разными настройками эксклюзивности и анимации.

const image = this.add.image(0, 0, 'lemming');
const text = this.add.text(60, 0, 'Oh No!', { font: '16px Courier', fill: '#00ff00' });

Сначала создаются два независимых игровых объекта. Обратите внимание, их начальные координаты (0,0) и (60,0) заданы относительно будущих контейнеров.

const container1 = this.add.container(200, 300).setExclusive(true);
container1.add([ image, text ]);

const container2 = this.add.container(400, 300).setExclusive(false);
container2.add([ image, text ]);

const container3 = this.add.container(600, 300).setExclusive(false);
container3.add([ image, text ]);

container1 — эксклюзивный. Добавив в него объекты, мы «забираем» их себе. container2 и container3 — неэксклюзивные. Один и тот же спрайт и текст успешно добавляются во все три контейнера. Без setExclusive(false) на втором и третьем контейнере это вызвало бы ошибку или неожиданное поведение.

Наложение анимаций: магия неэксклюзивности

Самое интересное начинается, когда мы применяем анимации к контейнерам, содержащим одни и те же объекты. Tween-анимации в Phaser могут управлять свойствами контейнеров.

this.tweens.add({
    targets: [ container1, container2, container3 ],
    angle: 360,
    duration: 6000,
    repeat: -1
});

Эта анимация заставляет все три контейнера непрерывно вращаться. Объекты внутри container1 вращаются только с ним. Объекты внутри container2 и container3 вращаются дважды — по разным осям и с разными центрами (400,300 и 600,300), создавая сложную траекторию.

this.tweens.add({
    targets: container2,
    scaleX: 2,
    scaleY: 2,
    duration: 3000,
    yoyo: true,
    repeat: -1
});

Здесь только container2 пульсирует, изменяя масштаб. Поскольку он неэксклюзивен, объекты получают это масштабирование поверх вращения.

this.tweens.add({
    targets: container3,
    alpha: 0,
    duration: 3000,
    yoyo: true,
    repeat: -1
});

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

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

**Где это может пригодиться?** - **Сложные составные анимации**: когда объект должен одновременно вращаться вокруг одной точки, масштабироваться от другой и менять прозрачность. - **Эффекты наложения**: создание «призрачных» копий или следов без дублирования ресурсов в памяти. - **Интерфейсы**: один элемент UI (например, иконка), который должен появляться в разных местах экрана с разными эффектами.

**На что обратить внимание:** - Управлять свойствами самого игрового объекта (например, image.x) теперь бессмысленно, так как его конечная позиция на экране вычисляется из всех контейнеров. - Порядок отрисовки определяется порядком добавления контейнеров в сцену, а не порядком объектов внутри них. - Производительность: хотя объект в памяти один, его трансформации вычисляются несколько раз — по разу для каждого контейнера.

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

Неэксклюзивные контейнеры в Phaser — это нишевый, но чрезвычайно мощный инструмент для создания сложных, составных анимаций без дублирования ресурсов. Они позволяют применять несколько независимых пространственных преобразований к одному объекту. **Идеи для экспериментов:** 1. Добавьте один спрайт в 4-5 контейнеров, каждый из которых движется по своей круговой траектории с разной скоростью. Получится ли орбитальная система? 2. Используйте неэксклюзивный контейнер для создания эффекта «дрожания» (через быстрые случайные смещения), который можно наложить на любую другую анимацию объекта. 3. Попробуйте менять depth у разных контейнеров с одним объектом. Как это повлияет на отрисовку?