О чем этот пример
При создании игр часто требуется не просто зафиксировать факт столкновения объектов, а понять, с какой стороны оно произошло. Например, чтобы нанести удар только при ударе сверху или активировать прыжок по платформе. Встроенный движок Arcade Physics в Phaser предоставляет для этого простой и эффективный механизм. Эта статья покажет, как использовать свойства `touching` и `blocked` для определения направления контакта и создания реактивного игрового процесса на практическом примере.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
platform;
cursors;
ground;
stars;
player;
preload ()
{
// this.load.setBaseURL('https://cdn.phaserfiles.com/v385');
this.load.image('sky', 'src/games/firstgame/assets/sky.png');
this.load.image('ground', 'src/games/firstgame/assets/platform.png');
this.load.image('star', 'src/games/firstgame/assets/star.png');
this.load.spritesheet('dude', 'src/games/firstgame/assets/dude.png', { frameWidth: 32, frameHeight: 48 });
}
create ()
{
this.add.image(400, 300, 'sky');
this.ground = this.physics.add.staticImage(400, 568, 'ground').setScale(2).refreshBody();
this.platform = this.physics.add.image(400, 400, 'ground');
this.platform.setImmovable(true);
this.platform.body.allowGravity = false;
this.player = this.physics.add.sprite(100, 450, 'dude');
this.player.setBounce(0.2);
this.player.setCollideWorldBounds(true);
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'dude', frame: 4 } ],
frameRate: 20
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
frameRate: 10,
repeat: -1
});
this.cursors = this.input.keyboard.createCursorKeys();
this.stars = this.physics.add.group({
key: 'star',
frameQuantity: 12,
maxSize: 12,
active: false,
visible: false,
enable: false,
collideWorldBounds: true,
bounceX: 0.5,
bounceY: 0.5,
dragX: 30,
dragY: 0
});
this.physics.add.collider(
this.player,
this.platform,
(player, platform) =>
{
if (player.body.touching.up && platform.body.touching.down)
{
this.createStar(
player.body.center.x,
platform.body.top - 16,
player.body.velocity.x,
player.body.velocity.y * -3
);
}
});
this.physics.add.collider(this.player, this.ground);
this.physics.add.collider(this.stars, this.ground);
this.physics.add.collider(this.stars, this.platform);
this.physics.add.overlap(this.player, this.stars, this.collectStar, null, this);
}
update ()
{
if (this.cursors.left.isDown)
{
this.player.setVelocityX(-180);
this.player.anims.play('left', true);
}
else if (this.cursors.right.isDown)
{
this.player.setVelocityX(180);
this.player.anims.play('right', true);
}
else
{
this.player.setVelocityX(0);
this.player.anims.play('turn');
}
if (this.cursors.up.isDown && this.player.body.touching.down)
{
this.player.setVelocityY(-360);
}
}
collectStar (player, star)
{
star.disableBody(true, true);
}
createStar (x, y, vx, vy)
{
const star = this.stars.get();
if (!star) { return; }
star
.enableBody(true, x, y, true, true)
.setVelocity(vx, vy);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 },
debug: false
}
},
scene: Example
};
const game = new Phaser.Game(config);
Зачем определять направление столкновения?
Простая регистрация коллизии через this.physics.add.collider() говорит лишь о том, что объекты пересеклись. Но для геймдизайна этого часто недостаточно. Нужно знать, *как* они столкнулись.
Представьте платформер: прыжок на врага сверху должен его уничтожить, а касание сбоку — отнять жизнь у игрока. Или, как в нашем примере, звезда должна появляться только тогда, когда игрок приземляется на платформу сверху, а не когда ударяется об нее сбоку. Для таких реакций и нужна проверка направления.
Ключевые свойства: `body.touching` и `body.blocked`
После столкновения Arcade Physics обновляет два объекта у тела (body) каждого участника: touching и blocked. Они содержат булевы флаги для каждой из четырех сторон.
* `touching.{up, down, left, right}`: Становится `true` в том кадре, когда происходит *первоначальный контакт* с объектом с соответствующей стороны. На следующих кадрах, если объекты продолжают соприкасаться, значение может стать `false`.
* `blocked.{up, down, left, right}`: Остается `true` все время, пока движение тела *заблокировано* другим телом с этой стороны. Это более стабильный индикатор для проверок.
В нашем примере используется touching, так как нужно среагировать именно в момент соударения.
if (player.body.touching.up && platform.body.touching.down) {
// Код выполнится, если игрок коснулся платформы своей верхней стороной,
// а платформа — своей нижней. То есть, игрок приземлился сверху.
}
Разбор практического примера: создание звезд при приземлении
Исходный код демонстрирует классическую механику: персонаж, управляемый с клавиатуры, прыгает по платформе. При приземлении на движущуюся платформу сверху создается звезда, которую можно собрать.
Вот как это работает пошагово:
1. Создается коллайдер между игроком (`this.player`) и платформой (`this.platform`).
2. В качестве функции обратного вызова передается не просто метод, а стрелочная функция с логикой проверки.
3. Внутри функции условие `if (player.body.touching.up && platform.body.touching.down)` гарантирует, что звезда появится только при столкновении, где игрок — сверху, а платформа — снизу.
4. Если условие выполнено, вызывается `this.createStar()`, который берет объект из пула (`this.stars.get()`), активирует его и задает начальную скорость.
this.physics.add.collider(
this.player,
this.platform,
(player, platform) =>
{
if (player.body.touching.up && platform.body.touching.down)
{
this.createStar(
player.body.center.x,
platform.body.top - 16,
player.body.velocity.x,
player.body.velocity.y * -3
);
}
});
Обратите внимание на параметры для createStar. Позиция (player.body.center.x, platform.body.top - 16) размещает звезду над платформой, по центру игрока. Скорость по оси X наследуется от игрока, а по оси Y (player.body.velocity.y * -3) — инвертируется и усиливается, создавая эффект "отскока" при ударе.
Оптимизация с помощью Group и пула объектов
Создавать и уничтожать множество игровых объектов (как звезды) в реальном времени — дорого для производительности. Phaser предлагает использовать Physics Group как пул (pool).
Группа инициализируется с набором неактивных объектов. Когда нужна новая звезда, мы "достаем" ее из пула методом .get(), активируем и настраиваем. Когда звезда собрана, мы не удаляем ее, а деактивируем методом .disableBody(), возвращая в пул для повторного использования.
// Создание группы-пула
this.stars = this.physics.add.group({
key: 'star',
frameQuantity: 12, // Создать 12 неактивных звезд
maxSize: 12, // Максимальный размер пула
active: false, // Создать неактивными
visible: false, // Создать невидимыми
enable: false, // Отключить физическое тело
// ... другие настройки физики
});
// Получение объекта из пула и его активация
const star = this.stars.get();
if (star) {
star.enableBody(true, x, y, true, true);
}
// Возврат объекта в пул (при сборе)
star.disableBody(true, true);
Что попробовать дальше
Использование body.touching и body.blocked открывает путь к созданию сложного и отзывчивого поведения в играх на Phaser. Вы можете легко реализовать разный урон в зависимости от стороны атаки, поверхностей, от которых можно оттолкнуться, или контекстные взаимодействия.
**Идеи для экспериментов:**
1. Измените пример: пусть звезда создается не при приземлении, а когда игрок ударяется о платформу *сбоку*, и летит в противоположном направлении.
2. Реализуйте "скользкую" поверхность: если player.body.touching.down и platform.body.touching.up, временно увеличьте трение игрока (setDragX).
3. Создайте врага, которого можно победить только прыжком сверху (проверяя touching.down у игрока и touching.up у врага), а касание сбоку будет наносить урон.
