О чем этот пример
Статические тела в физическом движке Arcade от Phaser — отличный способ создавать неподвижные препятствия, такие как платформы или стены, которые не тратят ресурсы на симуляцию физики. Однако, если вы попытаетесь изменить их положение или размер во время выполнения простым присваиванием свойств `x` и `y`, вы столкнетесь с призрачными столкновениями: спрайт будет сталкиваться с телом на его старом месте. Эта статья покажет, как правильно и безопасно обновлять статические тела, чтобы физический движок корректно учитывал их новое состояние.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
static3;
static2;
static1;
sprite;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('bar', 'assets/sprites/healthbar.png');
this.load.image('mushroom', 'assets/sprites/mushroom2.png');
}
create ()
{
this.sprite = this.physics.add.image(100, 100, 'mushroom');
this.static1 = this.physics.add.staticImage(700, 100, 'bar');
this.static2 = this.physics.add.staticImage(100, 400, 'bar');
this.static3 = this.physics.add.staticImage(500, 300, 'bar');
this.sprite.body.setVelocity(100, 200).setBounce(1, 1).setCollideWorldBounds(true);
this.input.on('pointerdown', () =>
{
this.static1.x -= 50;
// static1.setScale(3, 5);
this.static1.refreshBody();
}, this);
}
update ()
{
this.physics.world.collide(this.sprite, [ this.static1, this.static2, this.static3 ]);
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
parent: 'phaser-example',
physics: {
default: 'arcade',
arcade: {
gravity: { y: 200 },
debug: true
}
},
scene: Example
};
const game = new Phaser.Game(config);
Что такое статическое тело и зачем его обновлять?
В физическом движке Arcade статические тела (Static Body) предназначены для объектов, которые не должны двигаться под воздействием физики (гравитации, импульсов). Они служат идеальными препятствиями для динамических тел. Их создание требует меньше вычислительных затрат.
Однако иногда возникает необходимость изменить такое тело после создания — например, сдвинуть платформу по триггеру или изменить размер барьера. Прямое изменение свойств sprite.x или sprite.y обновляет визуальное положение спрайта, но не физический хитбокс тела. Это приводит к рассогласованию: спрайт рисуется в новом месте, а столкновения рассчитываются для старого.
Пример создания статического тела:
this.static1 = this.physics.add.staticImage(700, 100, 'bar');
Проблема: призрачное столкновение
Рассмотрим код из примера. При клике мыши (pointerdown) мы сдвигаем первое статическое тело static1 на 50 пикселей влево.
this.input.on('pointerdown', () => {
this.static1.x -= 50; // Визуальное смещение
}, this);
Если на этом остановиться, то в методе update будет постоянно выполняться проверка столкновений sprite с массивом статических тел.
this.physics.world.collide(this.sprite, [ this.static1, this.static2, this.static3 ]);
Физический движок Arcade продолжит считать, что static1 находится в точке (700, 100), хотя спрайт уже отрисован в (650, 100). Динамический спрайт mushroom будет сталкиваться с невидимым хитбоксом на старом месте, создавая иллюзию «призрака» или невидимой стены. Это ключевая ошибка, которую нужно исправить.
Решение: метод refreshBody()
Phaser предоставляет метод refreshBody() для статических тел. Этот метод сообщает физическому движку, что геометрия тела (его положение, размер, масштаб) была изменена вручную, и движку необходимо пересчитать его хитбокс для корректных дальнейших столкновений.
Исправленный обработчик клика выглядит так:
this.input.on('pointerdown', () => {
this.static1.x -= 50; // 1. Меняем свойство
this.static1.refreshBody(); // 2. Обновляем тело в физическом движке
}, this);
Порядок действий важен: сначала изменяем свойства спрайта (координаты `x,y,scaleX,scaleYи т.д.), а затем вызываемrefreshBody()`. После этого вызова движок Arcade будет использовать новые параметры тела для всех расчетов столкновений и перестанет учитывать старое положение.
**Важное замечание из кода:** строка // static1.setScale(3, 5); закомментирована. Если бы мы изменили масштаб, для обновления физического тела также потребовался бы вызов refreshBody().
Полный цикл обновления сцены
Давайте соберем весь рабочий цикл примера воедино, чтобы понять последовательность.
1. **Создание тел:** В create() создается одно динамическое тело (sprite) и три статических (static1, static2, static3). Динамическому телу задается скорость и отскок.
this.sprite.body.setVelocity(100, 200).setBounce(1, 1).setCollideWorldBounds(true);
2. **Игровой цикл:** В update() на каждом кадре проверяются столкновения между спрайтом и всеми статическими телами.
this.physics.world.collide(this.sprite, [ this.static1, this.static2, this.static3 ]);
3. **Пользовательское взаимодействие:** По клику изменяется static1.x и немедленно вызывается refreshBody(). На следующем кадре в update() движок использует уже новые координаты static1 для проверки столкновений.
Без вызова refreshBody() на шаге 3 система столкновений в шаге 2 работала бы некорректно.
Когда еще нужен refreshBody()?
Метод refreshBody() требуется вызывать после любого прямого изменения свойств, влияющих на геометрию статического спрайта:
* **Изменение позиции:** sprite.x, sprite.y.
* **Изменение размера:** sprite.setScale(scaleX, scaleY), sprite.scaleX.
* **Изменение смещения (origin) или размера фрейма текстуры** — если это влияет на расположение хитбокса.
**Для чего НЕ нужен refreshBody():**
* Для динамических тел. Их положением управляет движок физики. Если вам нужно резко переместить динамическое тело, используйте методы body.setPosition() или body.reset().
* При изменении чисто визуальных свойств, не связанных с физикой: alpha, tint, flip.
Что попробовать дальше
Использование метода refreshBody() — обязательное правило при ручном изменении статических тел в Phaser Arcade Physics. Это небольшое, но критически важное действие синхронизирует визуальное представление объекта с его физической моделью, избавляя игру от багов с невидимыми стенами. Для экспериментов попробуйте
- изменять не только `x
, но иy` координату статического тела - раскомментировать строку с
setScaleи посмотреть, как меняется хитбокс (включенныйdebug: trueв конфиге поможет) - создать цепочку из нескольких статических тел, которые последовательно двигаются по клику, обновляя каждое
