О чем этот пример
Механика перетаскивания — одна из базовых и востребованных в казуальных играх, пазлах и интерфейсах. В Phaser она реализуется быстро и элегантно, но есть нюансы, особенно когда объекты могут перекрывать друг друга. Эта статья на практическом примере со стопкой карт покажет, как сделать объекты интерактивными, управлять их порядком отрисовки и плавно перемещать по сцене, обеспечивая отзывчивый игровой процесс.
Версия 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.atlas('cards', 'assets/atlas/cards.png', 'assets/atlas/cards.json');
}
create ()
{
// Create a stack of random cards
const frames = this.textures.get('cards').getFrameNames();
let x = 100;
let y = 100;
for (let i = 0; i < 64; i++)
{
const image = this.add.image(x, y, 'cards', Phaser.Math.RND.pick(frames)).setInteractive();
this.input.setDraggable(image);
x += 4;
y += 4;
}
this.input.on('dragstart', function (pointer, gameObject)
{
this.children.bringToTop(gameObject);
}, this);
this.input.on('drag', (pointer, gameObject, dragX, dragY) =>
{
gameObject.x = dragX;
gameObject.y = dragY;
});
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 1024,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и загрузка ассетов
Работа начинается с подготовки сцены. В методе preload мы загружаем необходимые ресурсы. В данном примере используется атлас cards, который содержит все изображения карт в одном файле и сопутствующем JSON-файле с описанием кадров.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('cards', 'assets/atlas/cards.png', 'assets/atlas/cards.json');
}
Загрузка через this.load.atlas эффективна, так как позволяет работать с множеством спрайтов, используя одну текстуру. setBaseURL устанавливает базовый путь для загрузчиков, что упрощает указание относительных путей.
Создание стопки карт и настройка интерактивности
В методе create мы создаем визуальные объекты и делаем их интерактивными. Сначала получаем список всех кадров (имен карт) из текстуры атласа.
const frames = this.textures.get('cards').getFrameNames();
Затем в цикле создается 64 карты. Каждая новая карта смещается на 4 пикселя по осям X и Y относительно предыдущей, формируя эффект стопки. Ключевой момент — карта создается как Image и сразу делается интерактивной с помощью setInteractive().
let x = 100;
let y = 100;
for (let i = 0; i < 64; i++)
{
const image = this.add.image(x, y, 'cards', Phaser.Math.RND.pick(frames)).setInteractive();
this.input.setDraggable(image);
x += 4;
y += 4;
}
Метод Phaser.Math.RND.pick(frames) случайным образом выбирает кадр (масть и достоинство) для каждой карты. Вызов this.input.setDraggable(image) регистрирует этот конкретный объект как перетаскиваемый в системе ввода Phaser. Без этого шага события drag не будут на него реагировать.
Обработка событий перетаскивания
Phaser предоставляет удобную систему событий для управления перетаскиванием. Мы настраиваем обработчики для двух ключевых событий: начала перетаскивания (dragstart) и самого процесса перемещения (drag).
this.input.on('dragstart', function (pointer, gameObject)
{
this.children.bringToTop(gameObject);
}, this);
Событие dragstart срабатывает, когда пользователь начинает перетаскивать объект. В его обработчике мы вызываем this.children.bringToTop(gameObject). Это критически важный шаг: он перемещает перетаскиваемую карту на самый верхний слой отрисовки внутри списка детей сцены. Без этого карта оставалась бы под другими объектами во время движения, что выглядело бы неестественно. Обратите внимание на использование обычной функции и контекста this, переданного третьим аргументом, чтобы иметь доступ к this.children внутри обработчика.
this.input.on('drag', (pointer, gameObject, dragX, dragY) =>
{
gameObject.x = dragX;
gameObject.y = dragY;
});
Событие drag генерируется непрерывно во время перемещения мыши или касания. Обработчик получает координаты dragX и dragY, куда система ввода предлагает переместить объект. Мы просто присваиваем эти значения свойствам `xиy` игрового объекта, обеспечивая его плавное следование за указателем. Здесь использована стрелочная функция, которая автоматически сохраняет контекст сцены.
Конфигурация и запуск игры
Финальный шаг — стандартная конфигурация игры и ее инстанцирование. Конфиг определяет базовые параметры, такие как тип рендерера, размеры холста и главную сцену.
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 1024,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Phaser.AUTO позволяет движку самому выбрать лучший тип рендерера (WebGL или Canvas). Свойство parent указывает ID HTML-элемента, в который будет встроен canvas. Указание класса Example в поле scene делает эту сцену активной сразу после запуска игры.
Что попробовать дальше
Как видно, реализация перетаскивания в Phaser сводится к трем основным шагам: сделать объект интерактивным и объявить его перетаскиваемым, поднять объект наверх при начале перетаскивания и обновлять его координаты во время движения. Этот паттерн универсален и применим к любым игровым объектам. Для экспериментов попробуйте: добавить физическое тело объекту и использовать силы для "броска", реализовать ограничение зоны перетаскивания, добавить звуковые эффекты при dragstart и dragend, или организовать привязку (снеппинг) карт к определенным позициям на столе.
