О чем этот пример
Управление множеством игровых объектов — частая задача в разработке игр. Phaser 3 предлагает мощный инструмент: Контейнеры (`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 swap 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 container1 = this.add.container();
const container2 = this.add.container();
const clickSprite = function ()
{
if (this.parentContainer === container1)
{
container2.add(this);
this.x += 400;
}
else
{
container1.add(this);
this.x -= 400;
}
size1.setText('Container 1 size: ' + container1.length);
size2.setText('Container 2 size: ' + container2.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, 'beer');
container1.add(sprite1);
container2.add(sprite2);
sprite1.setInteractive();
sprite2.setInteractive();
sprite1.on('pointerdown', clickSprite);
sprite2.on('pointerdown', clickSprite);
}
size1.setText('Container 1 size: ' + container1.length);
size2.setText('Container 2 size: ' + container2.length);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Создание и наполнение контейнеров
В Phaser контейнер (Container) — это специальный игровой объект, который может выступать родителем для других объектов. Все дочерние элементы наследуют его трансформации (позицию, масштаб, вращение), но при этом остаются независимыми для обработки событий.
В примере создаются два пустых контейнера и разделительная линия для наглядности.
const container1 = this.add.container();
const container2 = this.add.container();
this.add.line(400, 300, 0, 0, 0, 600, 0xffffff);
Затем в цикле создаются спрайты и сразу добавляются в разные контейнеры. Обратите внимание: спрайты создаются через конструктор new Phaser.GameObjects.Sprite, а не через фабрику сцены (this.add.sprite). Это потому, что фабрика автоматически добавляет объект в Display List сцены, а нам нужно контролируемое добавление в контейнер.
const sprite1 = new Phaser.GameObjects.Sprite(this, x1, y1, 'cake');
container1.add(sprite1);
Механика взаимодействия: обработка клика
Ключевая логика примера заключена в функции clickSprite. Она назначается как обработчик события 'pointerdown' для каждого спрайта.
Функция проверяет текущий родительский контейнер спрайта через свойство this.parentContainer. В зависимости от результата, спрайт перемещается в другой контейнер с помощью метода add(), а его координата `x` сдвигается на 400 пикселей, чтобы визуально перейти за разделительную линию.
const clickSprite = function ()
{
if (this.parentContainer === container1)
{
container2.add(this);
this.x += 400;
}
else
{
container1.add(this);
this.x -= 400;
}
// ... обновление текста
}
Важный нюанс: при вызове container.add(sprite) спрайт автоматически удаляется из своего предыдущего родительского контейнера. Вручную удалять его не нужно.
Отслеживание состояния: свойство length
После каждого перемещения спрайта обновляется текстовый вывод, показывающий количество объектов в каждом контейнере. Для этого используется свойство container.length.
size1.setText('Container 1 size: ' + container1.length);
size2.setText('Container 2 size: ' + container2.length);
Свойство length доступно только для чтения и возвращает текущее количество дочерних элементов в контейнере. Это удобно для логики игры, например, чтобы определить, когда контейнер (как ячейка инвентаря) заполнен.
Практическое применение и вариации
Рассмотренный паттерн — основа для многих игровых механик.
* **Инвентарь:** Контейнеры могут представлять собой слоты инвентаря или сумки. Перемещение спрайта предмета между ними — это перекладывание вещей. * **Сортировка или пазлы:** Игрок может распределять объекты по правильным категориям-контейнерам. * **Динамический UI:** Элементы интерфейса можно перегруппировывать в реальном времени.
Для расширения функционала можно проверять не только родителя, но и другие условия.
// Пример: проверка, есть ли место в целевом контейнере
if (this.parentContainer === container1 && container2.length < 5)
{
container2.add(this);
}
// Пример: анимация перемещения с помощью Tween
this.tweens.add({
targets: this,
x: this.parentContainer === container1 ? this.x + 400 : this.x - 400,
duration: 300
});
Что попробовать дальше
Контейнеры в Phaser 3 — это не просто группы для отрисовки, а полноценные объекты с иерархией, которые можно гибко перестраивать в рантайме. Показанный пример с динамической сменой родительского контейнера по клику открывает путь к созданию сложных интерактивных систем. Для экспериментов попробуйте: добавить ограничение на количество объектов в контейнере, реализовать перетаскивание (drag and drop) между контейнерами или привязать к событию перемещения игровую логику (например, начисление очков за правильную сортировку).
