О чем этот пример
В Phaser 3 игровые объекты могут находиться либо на основном дисплейном списке сцены (`Display List`), либо внутри `Container`. Умение динамически перемещать объекты между этими структурами — ключ к гибкому управлению группами спрайтов, их видимостью, трансформациями и порядком отрисовки. Эта статья на практическом примере покажет, как переключать объект между контейнером и основным списком по клику, что полезно для реализации инвентаря, перемещаемых панелей или динамических слоёв интерфейса.
Версия 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.image('beer', 'assets/sprites/beer.png');
this.load.image('watermelon', 'assets/sprites/watermelon.png');
this.load.image('cake', 'assets/sprites/cake.png');
}
create ()
{
this.add.text(10, 10, 'Click Sprite to move between Container');
const size1 = this.add.text(10, 48);
const size2 = this.add.text(410, 48);
this.add.line(400, 300, 0, 0, 0, 600, 0xffffff);
const container = this.add.container();
const clickSprite = function ()
{
if (this.parentContainer === container)
{
// Remove from Container, this places it back on the Display List
container.remove(this);
this.x -= 400;
}
else
{
// Move from Display List to Container
// Doing this will automatically remove it from the Display List
container.add(this);
this.x += 400;
}
size1.setText('Display List size: ' + this.scene.children.length);
size2.setText('Container size: ' + container.length);
}
for (let i = 0; i < 8; i++)
{
const x1 = Phaser.Math.Between(64, 336);
const y1 = Phaser.Math.Between(128, 536);
const x2 = Phaser.Math.Between(464, 736);
const y2 = Phaser.Math.Between(128, 536);
const sprite1 = new Phaser.GameObjects.Sprite(this, x1, y1, 'cake');
const sprite2 = new Phaser.GameObjects.Sprite(this, x2, y2, 'watermelon');
sprite1.addToDisplayList();
container.add(sprite2);
sprite1.setInteractive();
sprite2.setInteractive();
sprite1.on('pointerdown', clickSprite);
sprite2.on('pointerdown', clickSprite);
}
size1.setText('Display List size: ' + this.children.length);
size2.setText('Container size: ' + container.length);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Зачем нужны Container?
Container — это специальный игровой объект, который выступает как родитель для других объектов. Все дочерние элементы наследуют его трансформации (позицию, масштаб, вращение, видимость). Это позволяет управлять целой группой объектов как единым целым. В то же время, объекты на основном Display List сцены (доступном через this.children) управляются независимо.
Главное различие: объект может находиться либо на дисплейном списке сцены, либо внутри контейнера, но не в обоих местах одновременно. Методы add() и remove() контейнера автоматически обновляют эти связи.
Структура примера и загрузка ресурсов
В примере создаётся сцена с двумя областями, разделёнными вертикальной линией. Слева объекты находятся на основном Display List, справа — внутри Container. Загружаются три спрайта, но используются только два: 'cake' и 'watermelon'.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('beer', 'assets/sprites/beer.png');
this.load.image('watermelon', 'assets/sprites/watermelon.png');
this.load.image('cake', 'assets/sprites/cake.png');
}
Обрати внимание, что спрайт 'beer' загружается, но не используется в коде — это нормально для демонстрации.
Создание контейнера и текстовых счётчиков
В методе create() создаются текстовые метки для отображения количества объектов в каждой структуре и разделительная линия. Контейнер создаётся с помощью this.add.container().
this.add.text(10, 10, 'Click Sprite to move between Container');
const size1 = this.add.text(10, 48);
const size2 = this.add.text(410, 48);
this.add.line(400, 300, 0, 0, 0, 600, 0xffffff);
const container = this.add.container();
Текстовые объекты size1 и size2 будут динамически обновляться. Линия (this.add.line) визуально разделяет экран на зону Display List (x < 400) и зону Container (x >= 400).
Функция-обработчик клика и логика перемещения
Ключевая функция clickSprite вызывается при клике на любой спрайт. Она проверяет, где сейчас находится объект (в каком родительском контейнере), и перемещает его в другую структуру, одновременно сдвигая по горизонтали для визуального эффекта.
const clickSprite = function ()
{
if (this.parentContainer === container)
{
// Удаляем из Container, объект возвращается на Display List
container.remove(this);
this.x -= 400;
}
else
{
// Перемещаем с Display List в Container
// Это автоматически удалит объект из Display List
container.add(this);
this.x += 400;
}
// Обновляем текстовые счётчики
size1.setText('Display List size: ' + this.scene.children.length);
size2.setText('Container size: ' + container.length);
}
Проверка this.parentContainer === container определяет, находится ли спрайт внутри контейнера. Свойство parentContainer будет равно null, если объект на основном Display List. Метод container.remove(this) удаляет объект из контейнера, и он автоматически возвращается в this.scene.children. Метод container.add(this), наоборот, забирает объект с основного списка в контейнер. После перемещения координата X спрайта корректируется на 400 пикселей, чтобы он оставался в своей визуальной зоне. Свойство container.length показывает текущее количество объектов в контейнере.
Создание и первоначальное размещение спрайтов
В цикле создаются 16 спрайтов (8 'cake' и 8 'watermelon'). Каждая пара размещается в случайных позициях в левой и правой частях экрана. Спрайты 'cake' добавляются на основной Display List с помощью addToDisplayList(), а 'watermelon' сразу помещаются в контейнер методом container.add().
for (let i = 0; i < 8; i++)
{
const x1 = Phaser.Math.Between(64, 336);
const y1 = Phaser.Math.Between(128, 536);
const x2 = Phaser.Math.Between(464, 736);
const y2 = Phaser.Math.Between(128, 536);
const sprite1 = new Phaser.GameObjects.Sprite(this, x1, y1, 'cake');
const sprite2 = new Phaser.GameObjects.Sprite(this, x2, y2, 'watermelon');
sprite1.addToDisplayList();
container.add(sprite2);
sprite1.setInteractive();
sprite2.setInteractive();
sprite1.on('pointerdown', clickSprite);
sprite2.on('pointerdown', clickSprite);
}
Обрати внимание: спрайты создаются через new Phaser.GameObjects.Sprite, а не через фабричные методы вроде this.add.sprite. Поэтому для добавления на дисплейный список сцены у sprite1 явно вызывается addToDisplayList(). Оба спрайта делаются интерактивными (setInteractive()) и на них вешается один и тот же обработчик clickSprite.
Что попробовать дальше
Пример наглядно демонстрирует, как объекты в Phaser 3 могут динамически менять свою принадлежность к Display List или Container, обеспечивая гибкую архитектуру управления группами. Для экспериментов попробуй
- Добавить в контейнер трансформации (масштаб, поворот) и понаблюдать, как они применяются ко всем его дочерним элементам при перемещении
- Создать несколько контейнеров и реализовать перемещение объектов между ними
- Использовать это поведение для реализации перетаскивания предметов между разными панелями интерфейса или слоями игры
