О чем этот пример
Метод `overlap` в Arcade Physics позволяет обнаруживать пересечение объектов. Но что если вам нужно не просто факт пересечения, а проверка по сложной логике? Например, столкновение должно считаться только если объект попал внутрь спрайта, а не просто коснулся его прямоугольного контейнера (bounding box). В этой статье мы разберем пример, где используется четвертый, необязательный аргумент `processCallback` функции `this.physics.add.overlap`. Этот колбэк выступает в роли кастомного процессора, который решает, считать ли конкретное пересечение валидным. Мы увидим, как это позволяет реализовать точную пиксельную (или геометрическую) проверку столкновений поверх стандартной физики.
Версия 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.image('atari', 'assets/sprites/atari800.png');
this.load.image('chunk', 'assets/sprites/chunk.png');
}
create ()
{
const atari = this.physics.add.image(400, 300, 'atari')
.setAngularVelocity(30);
atari.setBodySize(280, 280);
const chunks = this.physics.add.group({
key: 'chunk',
quantity: 240,
bounceX: 1,
bounceY: 1,
collideWorldBounds: true,
velocityX: 100,
velocityY: 100
});
Phaser.Actions.RandomRectangle(chunks.getChildren(), { x: 0, y: 0, width: 800, height: 100 });
const atariRect = new Phaser.Geom.Rectangle(0, 0, atari.width, atari.height);
this.physics.add.overlap(
atari,
chunks,
null,
function process (_atari, chunk)
{
const { x, y } = _atari.getLocalPoint(chunk.body.center.x, chunk.body.center.y);
return atariRect.contains(x, y);
}
);
}
update ()
{
const bodies = Array.from(this.physics.world.bodies);
for (const body of bodies)
{
body.debugBodyColor = body.touching.none ? 0x00ff00 : 0xff0000;
}
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: {
debug: true,
debugShowVelocity: false
}
},
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и создание объектов
В методе preload загружаются два изображения: 'atari' (большой спрайт) и 'chunk' (маленький спрайт-частица).
В create мы создаем физические объекты. Спрайт 'atari' помещается в центр и получает постоянную угловую скорость.
const atari = this.physics.add.image(400, 300, 'atari')
.setAngularVelocity(30);
Ключевой момент: мы изменяем размер физического тела (body) объекта 'atari', делая его меньше визуального спрайта. Это нужно для базовой оптимизации проверки пересечений.
atari.setBodySize(280, 280);
Далее создается группа из 240 физических частиц 'chunk' с отскоком и случайной начальной скоростью.
const chunks = this.physics.add.group({
key: 'chunk',
quantity: 240,
bounceX: 1,
bounceY: 1,
collideWorldBounds: true,
velocityX: 100,
velocityY: 100
});
Частицы случайным образом распределяются в верхней части игрового поля.
Phaser.Actions.RandomRectangle(chunks.getChildren(), { x: 0, y: 0, width: 800, height: 100 });
Создание кастомного процессора для overlap
Стандартный вызов overlap проверит пересечение прямоугольных тел 'atari' и каждого 'chunk'. Но нам нужна точность: столкновение должно засчитываться только если частица находится в пределах визуальных границ спрайта 'atari', а не его уменьшенного физического тела.
Для этого мы создаем геометрический прямоугольник, соответствующий реальным размерам спрайта 'atari'.
const atariRect = new Phaser.Geom.Rectangle(0, 0, atari.width, atari.height);
Затем настраиваем проверку пересечения с кастомной функцией-процессором process. Первые два аргумента — объекты для проверки. Третий аргумент (collideCallback) — null, так как нам не нужна реакция на столкновение в этом примере. Четвертый аргумент — наша функция process.
this.physics.add.overlap(
atari,
chunks,
null,
function process (_atari, chunk) { ... }
);
Логика работы процессора
Функция process вызывается для каждой потенциальной пары объектов, чьи тела пересекаются. Она получает два аргумента: объекты, переданные в overlap.
Задача функции — вернуть true или false. true означает, что пересечение валидно и должно быть обработано (например, вызовом collideCallback). false — пересечение игнорируется, хотя физические тела и перекрываются.
В нашем примере логика следующая:
1. Мы берем глобальные координаты центра тела частицы (chunk.body.center).
2. С помощью метода getLocalPoint преобразуем эти глобальные координаты в локальные относительно спрайта 'atari'. Это дает координаты (x, y) точки частицы внутри системы отсчета вращающегося спрайта 'atari'.
const { x, y } = _atari.getLocalPoint(chunk.body.center.x, chunk.body.center.y);
3. Мы проверяем, попадает ли эта точка в прямоугольник atariRect, который описывает визуальные границы спрайта.
return atariRect.contains(x, y);
Таким образом, даже если физическое тело частицы пересекается с уменьшенным телом 'atari', столкновение засчитается только если центр частицы находится в пределах реального изображения 'atari'. Это имитирует проверку на попадание в форму спрайта.
Визуализация в update
Метод update служит для наглядной отладки. Он проходит по всем физическим телам в мире и меняет их цвет отладки.
const bodies = Array.from(this.physics.world.bodies);
for (const body of bodies) {
body.debugBodyColor = body.touching.none ? 0x00ff00 : 0xff0000;
}
Если тело ни с чем не контактирует в данный кадр (body.touching.none), оно подсвечивается зеленым (0x00ff00). Если есть контакт (касание или overlap) — красным (0xff0000). Благодаря нашему процессору, частицы будут красными только когда их центр внутри спрайта 'atari'.
Что попробовать дальше
Использование processCallback в this.physics.add.overlap открывает тонкий контроль над логикой столкновений. Вы можете реализовать проверку по маске, сложной геометрии, состоянию объекта или случайности.
**Идеи для экспериментов:**
1. Вместо прямоугольника atariRect используйте Phaser.Geom.Circle для проверки попадания в круглую зону.
2. Добавьте в функцию-процессор вероятность срабатывания, чтобы только каждая вторая проверка возвращала true.
3. Используйте processCallback вместе с collideCallback для создания сложного поведения (например, объект наносит урон только при попадании в "уязвимую зону").
