О чем этот пример
При разработке игр с прокруткой или большими уровнями вы можете столкнуться с неожиданным поведением: спрайты с физикой начинают двигаться медленнее обычных, хотя их скорость задана одинаково. Эта статья разбирает конкретный пример (баг #6293), который демонстрирует, как настройки камеры и мира могут влиять на работу Arcade Physics. Понимание этой проблемы поможет вам избежать багов с движением и правильно настраивать большие игровые пространства.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
var config = {
type: Phaser.AUTO,
width: 1500,
height: 500,
parent: 'phaser-example',
physics: {
default: "arcade",
arcade: {
fixedStep: false,
useTree: false,
//debug: true,
debugShowBody: true
}
},
scene: {
preload: preload,
create: create,
update: update
}
};
var game = new Phaser.Game(config);
var speed = 250;
var ship;
const W = 10000;
const H = 1000;
function preload() {
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ship', 'assets/sprites/thrust_ship.png');
}
function create() {
this.cameras.main.setBounds( 0, 0, W, H );
bg = this.add.tileSprite( W/2, H/2, W, H, 'ship' );
bg.alpha = 0.3;
ship = this.physics.add.image(200, 120, 'ship');
ship.scale = 5;
ship.setVelocity( speed * 2, 0 );
ship2 = this.add.sprite(200, 300, 'ship');
ship2.scale = 5;
}
let lastX = 0;
function update( timeMS, dt ) {
//const dX = Math.abs( ship.x - lastX );
//console.log( `dX = ${ dX.toFixed( 2 ) }, FPS = ${ this.game.loop.actualFps.toFixed( 4 ) }` );
//lastX = ship.x;
const speedAccel = 2.0;
//const dir = this.input.activePointer.x > config.width/2 ? 1 : -1;
//ship.setVelocity( speed * speedAccel, 0 );
if ( ship.x > config.width ) ship.x = 0;
ship2.x += 0.25025 * speedAccel * dt;
if ( ship2.x > config.width ) ship2.x = 0;
//this.cameras.main.centerOn( ship.x, ship.y );
}
Суть проблемы: мир больше камеры
В примере создаётся огромный игровой мир шириной 10 000 пикселей (W = 10000), но физический движок Arcade по умолчанию оптимизирован для работы только с областью, которая находится в пределах камеры. Это сделано для производительности. Однако, когда объект с физикой (ship) выходит за границы видимой области камеры (которая по умолчанию равна размеру холста — 1500x500), движок может некорректно обрабатывать его положение и скорость.
Обычный спрайт (ship2) не зависит от физического движка, поэтому его движение рассчитывается напрямую в update и работает стабильно, даже если он далеко от камеры.
Ключевые настройки в коде
В конфигурации сцены важно обратить внимание на два момента: установку границ мира для камеры и создание объектов.
this.cameras.main.setBounds( 0, 0, W, H );
Этот метод задаёт границы, в пределах которых может двигаться камера. Но важно: он **не** изменяет границы для расчётов физического движка Arcade по умолчанию.
ship = this.physics.add.image(200, 120, 'ship');
ship2 = this.add.sprite(200, 300, 'ship');
Первый объект создаётся через фабрику физики (this.physics.add.image), второй — как обычный спрайт. Это коренное отличие в их поведении.
Как движется каждый объект
Обновление позиции объектов происходит в функции update. Разница в подходе критична.
ship.setVelocity( speed * 2, 0 );
Скорость физическому телу задаётся один раз в create. Дальше движок Arcade должен сам обновлять позицию объекта каждому кадру, но его вычисления могут давать сбой, если объект далеко от камеры.
ship2.x += 0.25025 * speedAccel * dt;
if ( ship2.x > config.width ) ship2.x = 0;
Обычный спрайт движется за счёт прямого изменения свойства .x с учётом дельты времени (dt). Это предсказуемо и не зависит от системы физики или положения камеры.
Решение: настройка физического мира
Чтобы физический движок корректно работал с огромным миром, ему тоже нужно явно указать границы. Это делается через свойство world физики.
// В функции create()
this.physics.world.setBounds(0, 0, W, H);
Установка этих границ сообщает Arcade Physics, что он должен учитывать объекты во всей этой области, а не только в зоне видимости камеры. После этой настройки физический корабль (ship) будет двигаться с постоянной ожидаемой скоростью.
Также стоит убедиться, что для отладки включена опция debugShowBody: true, чтобы видеть хитбоксы физических тел.
Что попробовать дальше
Основной вывод: когда вы работаете с миром, размеры которого превышают видимую область камеры, необходимо вручную задавать границы (setBounds) не только для камеры, но и для физического мира. Иначе Arcade Physics, оптимизированный для производительности, может проигнорировать объекты за пределами кадра, что приведёт к их неправильному движению. Для экспериментов попробуйте
- отключить
fixedStepв настройках физики и посмотреть на разницу - активно двигать камеру, следуя за кораблём с помощью
centerOn, и - сравнить потребление CPU с включёнными и выключенными границами мира
