О чем этот пример
При создании игр с физикой часто нужно не просто зафиксировать факт столкновения, но и понять, с какой именно стороны объекта оно произошло. Это критически важно для реалистичной реакции: персонаж должен остановиться, упершись в стену, а не провалиться сквозь нее, а мяч — отскочить от пола. Пример из официальной документации Phaser наглядно демонстрирует, как Arcade Physics отмечает стороны (`faces`) динамического тела, которые контактируют с другим объектом. Мы разберем, чем отличаются свойства `blocked` и `touching`, и как визуализировать эту информацию для отладки и создания сложной игровой логики.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
text;
graphics;
block;
atari;
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');
this.load.image('atari', 'assets/sprites/atari800.png');
}
create ()
{
this.atari = this.physics.add.staticImage(400, 300, 'atari');
this.block = this.physics.add.image(0, 0, 'block');
this.block.setVelocity(200, 200);
this.block.setBounce(1, 1);
this.block.setCollideWorldBounds(true);
this.graphics = this.add.graphics();
this.text = this.add.text(0, 0, '-');
// During a Body vs. Static Body collision or overlap the Body becomes 'blocked' (and also 'touching').
// It's easier to see during an overlap:
this.physics.add.overlap(this.atari, this.block);
}
update ()
{
this.graphics.clear();
this.draw(this.atari);
this.draw(this.block);
this.text.setText([
'Box:',
'',
JSON.stringify(
Phaser.Utils.Objects.Pick(this.block.body, [
'blocked',
'touching',
'embedded'
]),
null,
2
)
]);
}
draw (gameObject)
{
this.graphics.lineStyle(5, 0xffff00, 0.8);
this.drawFaces(gameObject.body, gameObject.body.touching);
this.graphics.lineStyle(5, 0xff0000, 0.8);
this.drawFaces(gameObject.body, gameObject.body.blocked);
}
drawFaces (body, faces)
{
if (faces.left)
{
this.graphics.lineBetween(body.left, body.top, body.left, body.bottom);
}
if (faces.up)
{
this.graphics.lineBetween(body.left, body.top, body.right, body.top);
}
if (faces.right)
{
this.graphics.lineBetween(body.right, body.top, body.right, body.bottom);
}
if (faces.down)
{
this.graphics.lineBetween(body.left, body.bottom, body.right, body.bottom);
}
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: {
debug: false,
gravity: { y: 200 }
}
},
scene: Example
};
const game = new Phaser.Game(config);
Суть примера: отслеживание сторон контакта
В этом примере создаются два объекта: статичный спрайт atari и динамичный блок block, который движется с отскоком от границ мира. Между ними регистрируется взаимодействие через this.physics.add.overlap().
Ключевая задача — в реальном времени отрисовывать цветные линии по краям (faces) блока, которые в данный момент контактируют со статичным объектом. Для этого используются свойства тела (body) физического объекта: touching и blocked. Визуализация помогает понять, как движущееся тело "воспринимает" столкновение.
Настройка физики и создание объектов
В первую очередь настраивается сцена с Arcade Physics. Обратите внимание на конфигурацию: гравитация включена, но отладка (debug) отключена, так как визуализацию мы сделаем самостоятельно.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: {
debug: false,
gravity: { y: 200 }
}
},
scene: Example
};
В методе create() создаются объекты. atari — статичный, он служит препятствием. block — динамичный, ему задается начальная скорость, упругость (bounce) и включен отскок от границ холста.
create ()
{
this.atari = this.physics.add.staticImage(400, 300, 'atari');
this.block = this.physics.add.image(0, 0, 'block');
this.block.setVelocity(200, 200);
this.block.setBounce(1, 1);
this.block.setCollideWorldBounds(true);
// ...
}
Важный момент: для отслеживания контакта используется overlap, а не collider. Это сделано специально, чтобы объекты не раздвигались физическим движком, и мы могли наглядно наблюдать состояние blocked.
Чем отличаются `touching` и `blocked`?
Это центральные свойства для понимания примера. Оба являются объектами с полями up, down, left, right, которые принимают булевы значения.
* **touching** указывает, какая сторона тела *касается* другого тела в текущий кадр. Это "мгновенный" контакт.
* **blocked** указывает, какая сторона тела *заблокирована* другим телом. Состояние blocked возникает при столкновении динамического тела со статическим (как в нашем примере) или кинематическим и сохраняется, пока контакт не прекратится. Это "постоянный" контакт, препятствующий движению.
В методе update() состояние этих свойств для блока выводится на экран в виде форматированного JSON.
this.text.setText([
'Box:',
'',
JSON.stringify(
Phaser.Utils.Objects.Pick(this.block.body, [
'blocked',
'touching',
'embedded'
]),
null,
2
)
]);
Утилита Phaser.Utils.Objects.Pick создает новый объект, выбрав из тела блока только указанные свойства, что удобно для логирования.
Визуализация контактов с помощью Graphics
Чтобы увидеть контактные стороны, используется объект this.graphics. В каждом кадре (update) он очищается и заново отрисовывает линии.
Метод draw(gameObject) вызывается для каждого физического объекта. Сначала желтым цветом (0xffff00) рисуются стороны из свойства touching, затем красным (0xff0000) — из blocked.
draw (gameObject)
{
// Желтый для touching
this.graphics.lineStyle(5, 0xffff00, 0.8);
this.drawFaces(gameObject.body, gameObject.body.touching);
// Красный для blocked
this.graphics.lineStyle(5, 0xff0000, 0.8);
this.drawFaces(gameObject.body, gameObject.body.blocked);
}
Метод drawFaces принимает тело и объект с флагами сторон. Он проверяет каждый флаг (left, up, right, down) и, если он истинен, рисует линию вдоль соответствующего края хитбокса тела, используя его координаты (body.left, body.top и т.д.).
if (faces.left)
{
this.graphics.lineBetween(body.left, body.top, body.left, body.bottom);
}
Практическое применение в играх
Понимание blocked и touching открывает возможности для сложной игровой логики:
* **Платформер:** Проверка blocked.down — классический способ определить, стоит ли персонаж на земле. blocked.left и blocked.right помогут реализовать скольжение по стенам.
* **Головоломки:** Можно определить, в какую сторону игрок толкнул объект, и разрешить движение только по свободным направлениям.
* **Стрелялки:** При столкновении снаряда со стеной (touching) можно создать частицы разлета именно с той стороны, куда он пришелся.
Для этого в update() вашей игры нужно проверять соответствующие свойства тела.
update() {
const body = this.player.body;
this.isGrounded = body.blocked.down;
this.isAgainstWall = body.blocked.left || body.blocked.right;
// Далее используем эти флаги для анимаций и управления
}
Что попробовать дальше
Свойства blocked и touching в Arcade Physics — это мощный низкоуровневый инструмент для реакции на столкновения. Они дают точную информацию о том, *как* объекты соприкасаются, что необходимо для создания убедительной физики платформеров, головоломок и аркадных игр.
**Идеи для экспериментов:**
1. Замените overlap на collider и понаблюдайте, как изменится поведение: блок будет отталкиваться, а состояние blocked будет мигать.
2. Создайте сцену с несколькими статичными платформами и проверяйте, как меняются флаги при сложном движении.
3. Реализуйте простого платформерного персонажа, который может прыгать только когда blocked.down === true.
