О чем этот пример
Добавление интерактивности — ключевой элемент игрового процесса. В этой статье мы рассмотрим, как реализовать перетаскивание (drag & drop) для группы игровых объектов, используя контейнеры Phaser. Этот подход особенно полезен для создания сложных составных объектов, которые должны перемещаться как единое целое, например, персонажей со снаряжением или интерфейсных элементов. Мы разберем пример с ракетой и анимированным следом, которые помещены в один контейнер и могут быть перетащены по игровому полю одним движением мыши. Вы узнаете, как правильно настроить интерактивность для контейнера и обрабатывать события перетаскивания.
Версия 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('bg', 'assets/skies/gradient13.png');
this.load.atlas('rocket', 'assets/animations/rocket.png', 'assets/animations/rocket.json');
}
create ()
{
this.add.image(400, 300, 'bg');
this.add.text(16, 16, 'Drag the Rocket').setFontSize(24).setShadow(1, 1);
this.anims.create({ key: 'trail', frames: this.anims.generateFrameNames('rocket', { prefix: 'trail_', start: 0, end: 12, zeroPad: 2 }), repeat: -1 });
const container = this.add.container(400, 300);
// A container must have a size in order to receive input
container.setSize(120, 80);
container.setInteractive({ draggable: true });
const trail = this.add.sprite(-125, 0).play('trail');
const rocket = this.add.sprite(0, 0, 'rocket', 'rocket');
container.add([ trail, rocket ]);
container.on('drag', (pointer, dragX, dragY) => container.setPosition(dragX, dragY));
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что такое контейнер и зачем он нужен
Контейнер (Container) в Phaser — это специальный игровой объект, который может выступать в роли родителя для других объектов. Все дочерние элементы наследуют трансформации (позицию, масштаб, угол поворота) от своего контейнера. Это позволяет управлять группой объектов как одним целым.
В нашем примере мы создаем контейнер и помещаем в него два спрайта: саму ракету и анимированный след от нее. Без контейнера пришлось бы вручную обновлять позицию каждого спрайта при перетаскивании. Контейнер решает эту проблему.
Подготовка ассетов и создание анимации
Перед созданием интерактивного элемента необходимо загрузить ресурсы и подготовить анимацию. В методе preload загружаются фон (bg), изображение и JSON-атлас для ракеты.
Анимация следа создается с помощью this.anims.create. Мы используем кадры из атласа, имена которых соответствуют шаблону trail_00, trail_01 и так далее.
this.anims.create({
key: 'trail',
frames: this.anims.generateFrameNames('rocket', {
prefix: 'trail_',
start: 0,
end: 12,
zeroPad: 2
}),
repeat: -1
});
Метод generateFrameNames автоматически генерирует массив кадров на основе префикса и диапазона чисел. Параметр zeroPad: 2 гарантирует, что числа будут дополнены нулями до двух знаков (например, 00, 01). Параметр repeat: -1 задает бесконечное повторение анимации.
Создание и настройка интерактивного контейнера
Контейнер создается в указанных координатах (400, 300). Важный нюанс: по умолчанию контейнер не имеет физического размера для обработки ввода. Чтобы сделать его перетаскиваемым, необходимо явно задать размер с помощью метода setSize.
const container = this.add.container(400, 300);
container.setSize(120, 80);
container.setInteractive({ draggable: true });
Метод setInteractive с опцией { draggable: true } активирует возможность перетаскивания для этого контейнера. Размер 120x80 определяет область (хитбокс), в которой клик мыши будет считаться началом перетаскивания.
Затем создаются спрайты следа и ракеты. Обратите внимание, что их координаты (например, -125, 0 для следа) задаются относительно позиции контейнера. Они добавляются в контейнер с помощью метода container.add.
Обработка события перетаскивания
Когда пользователь начинает тянуть контейнер, генерируется событие 'drag'. Мы подписываемся на него, чтобы обновлять позицию контейнера в реальном времени.
container.on('drag', (pointer, dragX, dragY) => {
container.setPosition(dragX, dragY);
});
Коллбэк-функция получает объект pointer (представляющий мышь или касание) и целевые координаты dragX, dragY. Эти координаты автоматически рассчитываются движком. Вызов container.setPosition(dragX, dragY) перемещает весь контейнер и все его дочерние объекты в новую точку. Благодаря этому ракета и ее след перемещаются вместе, сохраняя свои относительные позиции внутри контейнера.
Конфигурация игры и запуск
Пример завершается стандартной конфигурацией игры Phaser и ее созданием. Конфиг определяет тип рендерера, размеры холста и основную сцену.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Параметр type: Phaser.AUTO позволяет Phaser самому выбрать оптиманый рендерер (WebGL или Canvas). Свойство parent указывает ID HTML-элемента, в который будет встроен canvas игры. Если элемента с таким ID не существует, он будет создан автоматически.
Что попробовать дальше
Использование контейнеров для реализации drag & drop значительно упрощает управление группами объектов в Phaser. Вы научились создавать контейнер, настраивать его для приема ввода, добавлять в него дочерние элементы и обрабатывать события перетаскивания.
Для экспериментов попробуйте:
1. Изменить размер хитбокса контейнера (setSize) и понаблюдайте, как это влияет на легкость захвата объекта.
2. Добавить в контейнер другие типы объектов, например, текст или графические примитивы.
3. Ограничить область перетаскивания, проверяя координаты dragX и dragY внутри обработчика события 'drag'.
4. Реализовать "привязку" (snap-to-grid) при отпускании контейнера, обрабатывая событие 'dragend'.
