О чем этот пример
При создании игр часто возникает необходимость привязать один визуальный объект к другому, который управляется физическим движком. Например, вы хотите, чтобы частица эффекта, значок состояния или след всегда находились в определенной точке на движущемся спрайте. Прямое изменение координат дочернего объекта через `setPosition` не всегда работает корректно с физическими телами. В этой статье мы разберем практический подход из официальных примеров Phaser, который использует утилиты для работы с границами спрайтов, чтобы обеспечить точное и плавное следование.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
block;
flower;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('block', 'assets/sprites/block.png');
this.load.image('flower', 'assets/sprites/flower-exo.png');
}
create ()
{
this.block = this.physics.add.image(400, 100, 'block')
.setVelocity(100, 200)
.setBounce(1, 1)
.setCollideWorldBounds(true);
this.flower = this.add.image(0, 0, 'flower');
}
update ()
{
Phaser.Display.Bounds.SetCenterX(this.flower, this.block.body.center.x);
Phaser.Display.Bounds.SetBottom(this.flower, this.block.body.top);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: { debug: true }
},
scene: Example
};
const game = new Phaser.Game(config);
Проблема: независимость спрайта и физического тела
В Phaser, когда вы создаете спрайт с физикой через this.physics.add.image, он получает свойство body, которое управляет его положением в физическом мире. Если вы попробуете просто добавить обычный спрайт (this.add.image) и в update() установить его позицию равной позиции физического тела, вы можете столкнуться с дёрганной или неточной привязкой. Это происходит из-за того, что физический движок вычисляет положение тела на каждом кадре, и нам нужно синхронизировать с этим визуальный спрайт.
В предоставленном примере у нас есть два объекта:
- `this.block`: физический спрайт (блок), который отскакивает от границ мира.
- `this.flower`: обычный спрайт (цветок), который должен следовать за блоком.
Решение: утилиты Phaser.Display.Bounds
Ключ к решению — использование статических методов класса Phaser.Display.Bounds. Эти методы позволяют задавать положение визуальных границ (bounds) одного игрового объекта относительно свойств другого.
В методе update() сцены выполняются два вызова:
Phaser.Display.Bounds.SetCenterX(this.flower, this.block.body.center.x);
Phaser.Display.Bounds.SetBottom(this.flower, this.block.body.top);
Давайте разберем, что делает каждая строка:
1. SetCenterX устанавливает **горизонтальный центр** спрайта flower равным значению center.x физического тела block.body. Это обеспечивает идеальное выравнивание по горизонтали.
2. SetBottom устанавливает **нижний край** спрайта flower равным значению top (верхней координате Y) физического тела block.body. В результате цветок "висит" прямо над блоком, касаясь его верхней границы.
Использование именно body.center.x и body.top гарантирует, что мы берем актуальные, рассчитанные физическим движком Arcade, координаты тела, а не просто позицию спрайта.
Анализ кода сцены
Рассмотрим полный код примера для понимания контекста. В create() инициализируются оба объекта.
Физический блок создается и настраивается:
this.block = this.physics.add.image(400, 100, 'block')
.setVelocity(100, 200)
.setBounce(1, 1)
.setCollideWorldBounds(true);
Здесь:
- this.physics.add.image создает спрайт с телом Arcade Physics.
- setVelocity(100, 200) задает начальную скорость.
- setBounce(1, 1) делает отскок абсолютно упругим по обеим осям.
- setCollideWorldBounds(true) включает столкновение с границами игрового мира.
Цветок создается как обычное изображение в точке (0, 0), так как его позиция будет полностью контролироваться в update():
this.flower = this.add.image(0, 0, 'flower');
Конфигурация игры и физики
Для работы примера необходима правильная настройка игры, в частности, активация физического движка Arcade.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: { debug: true }
},
scene: Example
};
Обратите внимание на секцию physics:
- default: 'arcade' подключает Arcade Physics как движок по умолчанию.
- debug: true включает отладочную визуализацию тел (показывает контуры и центры), что очень полезно при разработке и настройке подобных взаимодействий.
Практическое применение и вариации
Этот паттерн не ограничивается следованием "сверху". Используя разные методы из Phaser.Display.Bounds и различные свойства body, вы можете реализовать разнообразные привязки.
Например, чтобы сделать спрайт "тенью" под движущимся объектом:
// Поместить тень (spriteShadow) под центром тела
Phaser.Display.Bounds.SetCenterX(spriteShadow, movingBody.body.center.x);
Phaser.Display.Bounds.SetTop(spriteShadow, movingBody.body.bottom);
Или заставить значок "здоровья" следовать за центром спрайта:
// Значок над центром объекта
Phaser.Display.Bounds.SetCenterX(healthIcon, player.body.center.x);
Phaser.Display.Bounds.SetCenterY(healthIcon, player.body.center.y - 50);
Доступные методы Bounds включают SetLeft, SetRight, SetTop, SetBottom, SetCenterX, SetCenterY. Свойства тела (body), такие как position, center, left, right, top, bottom, предоставляют все необходимые точки для привязки.
Что попробовать дальше
Использование Phaser.Display.Bounds для привязки визуальных элементов к физическим телам — это надежный и точный метод, который избавляет от проблем с ручным позиционированием. Он обеспечивает плавное и синхронизированное следование, используя актуальные данные физического движка.
**Идеи для экспериментов:**
1. Попробуйте привязать не один, а несколько спрайтов (например, частицы системы) к разным точкам тела (углам, центру).
2. Добавьте небольшой сдвиг (offset) к вычисляемым координатам, чтобы создать эффект параллакса или "болтания" предмета за персонажем.
3. Скомбинируйте этот подход с анимациями на самом следующем спрайте (например, мигающий предупреждающий значок).
4. Проверьте, как метод работает с телами, вращающимися через setAngularVelocity.
