О чем этот пример
При разработке карточных или настольных игр часто возникает задача управлять объектами, которые перекрывают друг друга. Как обработать клик именно по верхней карте в стопке и красиво её переместить? Пример из официальной документации 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++)
{
this.add.image(x, y, 'cards', Phaser.Math.RND.pick(frames)).setInteractive();
x += 4;
y += 4;
}
this.input.on('gameobjectdown', function (pointer, gameObject)
{
// Will contain the top-most Game Object (in the display list)
this.tweens.add({
targets: gameObject,
x: { value: 1100, duration: 1500, ease: 'Power2' },
y: { value: 500, duration: 500, ease: 'Bounce.easeOut', delay: 150 }
});
}, this);
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 1024,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Подготовка атласа и создание стопки
В методе preload загружается атлас cards, который содержит все изображения карт в одном файле PNG и данные о кадрах в JSON. Это эффективнее, чем загружать каждую карту отдельно.
В create сначала получаем список всех имен кадров (фреймов) из текстуры cards. Эти имена понадобятся для случайного выбора карты.
const frames = this.textures.get('cards').getFrameNames();
Затем в цикле создается стопка из 64 карт. Каждая следующая карта смещается на 4 пикселя по осям X и Y относительно предыдущей, создавая эффект разложенной веером колоды. Ключевой момент — каждой карте сразу назначается интерактивность с помощью метода .setInteractive(). Без этого карты не будут генерировать события ввода (клики).
this.add.image(x, y, 'cards', Phaser.Math.RND.pick(frames)).setInteractive();
x += 4;
y += 4;
Обработка клика по верхнему объекту
Самая важная часть логики — обработчик события gameobjectdown. Это событие генерируется, когда указатель (мышь или касание) нажимает на интерактивный игровой объект (gameObject).
Система ввода Phaser автоматически определяет, какой объект находится *визуально сверху* (выше в порядке отрисовки) в точке клика, и передает именно его в функцию-обработчик. Это избавляет нас от необходимости вручную вычислять порядок и проверять пересечения.
this.input.on('gameobjectdown', function (pointer, gameObject) {
// gameObject здесь — это верхняя карта в стопке, на которую кликнули
}, this);
Обратите внимание на третий аргумент this. Он задает контекст, в котором будет выполняться функция-обработчик. В данном случае это гарантирует, что внутри обработчика this будет ссылаться на текущую сцену (Example), что необходимо для доступа к менеджеру твинов this.tweens.
Создание составной анимации с помощью твинов
Когда карта выбрана, она анимируется с помощью системы твинов. В примере используется один твин с двумя анимируемыми свойствами (`xиy), но для каждого свойства задана своя длительность и функция плавности (ease`). Это создает эффект составного движения.
this.tweens.add({
targets: gameObject,
x: { value: 1100, duration: 1500, ease: 'Power2' },
y: { value: 500, duration: 500, ease: 'Bounce.easeOut', delay: 150 }
});
* targets: объект для анимации (наша карта gameObject).
* `x: карта плавно перемещается в точку X=1100 за 1500 мс с ускорениемPower2`.
* `y: движение по оси Y начинается с задержкой (delay) в 150 мс, длится всего 500 мс и завершается эффектом отскока (Bounce.easeOut`).
Такое разделение параметров делает движение более живым и "игровым", чем просто линейное перемещение из одной точки в другую.
Конфигурация игры и запуск сцены
Код завершается стандартной конфигурацией игры (config) и её созданием. В конфиге указывается тип рендерера, элемент-контейнер, размеры холста и стартовая сцена.
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 1024,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Это базовая структура любого проекта на Phaser. Сцена Example, описанная выше, автоматически запускается при старте игры, вызывая по очереди свои методы preload, create, а затем update. В данном примера логика содержится только в preload и create.
Что попробовать дальше
Пример наглядно показывает два мощных принципа Phaser: автоматическое определение верхнего объекта при клике и простоту создания сложных анимаций через твины. Для экспериментов попробуйте изменить логику в обработчике: например, не перемещайте карту, а переворачивайте её (setFlipX), отправляйте "в сброс" или меняйте её глубину (depth), чтобы тапнуть следующую карту в стопке. Также можно поиграть с другими событиями, такими как gameobjectup или gameobjectover, чтобы добавить эффекты наведения.
