О чем этот пример
Физический движок Arcade в Phaser предоставляет мощные инструменты для управления поведением мира. Эта статья покажет вам, как создавать интерактивные симуляции, динамически менять гравитацию, границы и отладку, используя панель управления dat.GUI. Вы научитесь тонко настраивать физику, что незаменимо при создании прототипов игр, отладке сложных взаимодействий и визуализации физических процессов прямо в браузере.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
graphics;
blocks;
balls;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ball', 'assets/sprites/yellow_ball.png');
this.load.image('block', 'assets/sprites/32x32.png');
}
create ()
{
// this.physics.world.setBounds(50, 50, 700, 500);
this.graphics = this.add.graphics();
this.blocks = this.physics.add.staticGroup({
key: 'block',
frameQuantity: 20
});
Phaser.Actions.PlaceOnRectangle(this.blocks.getChildren(), new Phaser.Geom.Rectangle(100, 100, 600, 400));
this.blocks.refresh();
this.balls = this.physics.add.group({
key: 'ball',
frameQuantity: 12,
collideWorldBounds: true,
bounceX: 1,
bounceY: 1,
velocityX: 200,
velocityY: 200
});
Phaser.Actions.RandomRectangle(this.balls.getChildren(), this.physics.world.bounds);
this.physics.add.collider(this.balls);
this.physics.add.collider(this.balls, this.blocks);
this.createWorldGui(this.physics.world);
}
update ()
{
this.physics.world.wrap(this.balls);
this.graphics.clear().fillStyle(0).fillRectShape(this.physics.world.bounds);
}
createWorldGui (world)
{
const gui = new dat.GUI({ width: 400 });
const bounds = gui.addFolder('bounds');
bounds.add(world.bounds, 'x', -400, 400, 10);
bounds.add(world.bounds, 'y', -300, 300, 10);
bounds.add(world.bounds, 'width', 0, 800, 10);
bounds.add(world.bounds, 'height', 0, 600, 10);
const check = gui.addFolder('checkCollision');
check.add(world.checkCollision, 'left');
check.add(world.checkCollision, 'up');
check.add(world.checkCollision, 'right');
check.add(world.checkCollision, 'down');
const defaults = gui.addFolder('defaults');
defaults.add(world.defaults, 'debugShowBody');
defaults.add(world.defaults, 'debugShowStaticBody');
defaults.add(world.defaults, 'debugShowVelocity');
defaults.addColor(world.defaults, 'bodyDebugColor');
defaults.addColor(world.defaults, 'staticBodyDebugColor');
defaults.addColor(world.defaults, 'velocityDebugColor');
const debug = gui.addFolder('debugGraphic');
debug.add(world.debugGraphic, 'visible');
debug.add(world.debugGraphic, 'clear');
gui.add(world, 'drawDebug');
gui.add(world, 'fixedStep');
gui.add(world, 'fps', 5, 300, 5).onChange((fps) => { world.setFPS(fps); });
gui.add(world, 'forceX');
const gravity = gui.addFolder('gravity');
gravity.add(world.gravity, 'x', -300, 300, 10);
gravity.add(world.gravity, 'y', -300, 300, 10);
// gui.add(world, 'isPaused');
gui.add(world, 'OVERLAP_BIAS', 0, 16, 1);
gui.add(world, 'pause');
gui.add(world, 'resume');
gui.add(world, 'timeScale', 0.1, 10, 0.1);
return gui;
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: 0x222222,
parent: 'phaser-example',
physics: {
default: 'arcade',
// https://newdocs.phaser.io/docs/3.55.2/Phaser.Types.Physics.Arcade.ArcadeWorldConfig
arcade: {
checkCollision: {
up: true,
down: true,
left: true,
right: true
},
debug: true,
debugBodyColor: 0xff00ff,
debugShowBody: true,
debugShowStaticBody: true,
debugShowVelocity: true,
debugStaticBodyColor: 0x0000ff,
debugVelocityColor: 0x00ff00,
fixedStep: true,
forceX: false,
fps: 60,
gravity: {
x: 0,
y: 0
},
height: 600,
isPaused: false,
maxEntries: 16,
overlapBias: 4,
tileBias: 16,
timeScale: 1,
useTree: true,
width: 800,
x: 0,
y: 0
}
},
scene: Example
};
const game = new Phaser.Game(config);
Настройка сцены и загрузка ассетов
Класс Example расширяет Phaser.Scene. В методе preload мы загружаем два спрайта: шарик (ball) и блок (block). Эти изображения будут использоваться для визуализации физических тел.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ball', 'assets/sprites/yellow_ball.png');
this.load.image('block', 'assets/sprites/32x32.png');
}
Метод create является сердцем примера. Здесь мы создаем статическую группу блоков и динамическую группу шаров, настраиваем их свойства и создаем панель управления для физического мира.
Создание статических и динамических тел
Сначала мы создаем графический объект this.graphics для отрисовки границ мира. Затем создается статическая группа this.blocks. Статические тела не подвержены силам, но с ними можно сталкиваться.
this.blocks = this.physics.add.staticGroup({
key: 'block',
frameQuantity: 20
});
Метод Phaser.Actions.PlaceOnRectangle расставляет все блоки по периметру заданного прямоугольника. После изменения позиций статических тел необходимо вызвать refresh(), чтобы физический движок обновил их границы для корректных коллизий.
Phaser.Actions.PlaceOnRectangle(this.blocks.getChildren(), new Phaser.Geom.Rectangle(100, 100, 600, 400));
this.blocks.refresh();
Динамическая группа this.balls создается с начальной скоростью и упругостью. Свойство collideWorldBounds: true заставляет шары отскакивать от границ мира. Метод RandomRectangle случайно размещает шары в пределах мировых границ.
this.balls = this.physics.add.group({
key: 'ball',
frameQuantity: 12,
collideWorldBounds: true,
bounceX: 1,
bounceY: 1,
velocityX: 200,
velocityY: 200
});
Phaser.Actions.RandomRectangle(this.balls.getChildren(), this.physics.world.bounds);
Коллизии настраиваются двумя вызовами this.physics.add.collider: первый — для столкновений шаров друг с другом, второй — для столкновений шаров с блоками.
Управление миром через dat.GUI
Метод createWorldGui создает интерактивную панель для управления свойствами объекта this.physics.world. Это позволяет менять параметры в реальном времени без перезагрузки.
**Границы мира (bounds):** Можно двигать (`x,y) и менять размер (width,height) физического мира. Изменение границ влияет на поведение тел с включеннымcollideWorldBounds`.
bounds.add(world.bounds, 'x', -400, 400, 10);
**Направления коллизий (checkCollision):** Можно отключать проверку столкновений с определенной стороной мира. Например, отключив up, шары перестанут отскакивать от верхней границы.
check.add(world.checkCollision, 'up');
**Гравитация (gravity):** Динамическое изменение силы тяжести по осям X и Y. Положительное значение `y` тянет объекты вниз.
gravity.add(world.gravity, 'x', -300, 300, 10);
**Отладка (debugGraphic, drawDebug):** Включает визуализацию хитбоксов тел (статических и динамических) и векторов скорости. Цвета отладки также можно менять.
debug.add(world.debugGraphic, 'visible');
gui.add(world, 'drawDebug');
**Другие важные параметры:**
* fixedStep: Фиксированный шаг физики. При false используется дельта-тайм.
* fps: Целевая частота обновления физики. Изменяется через world.setFPS(fps).
* timeScale: Глобальный множитель скорости времени для физики.
* OVERLAP_BIAS: Смещение для разрешения перекрытий.
* pause / resume: Кнопки для приостановки и возобновления симуляции.
Игровой цикл и визуализация границ
В методе update происходят два ключевых действия. Во-первых, метод this.physics.world.wrap(this.balls) обеспечивает телепортацию объектов на противоположную сторону мира, когда они его покидают. Это создает эффект замкнутого пространства.
update ()
{
this.physics.world.wrap(this.balls);
...
}
Во-вторых, мы постоянно перерисовываем черный прямоугольник, который точно соответствует текущим границам физического мира (this.physics.world.bounds). Это дает наглядную визуальную связь между параметрами в GUI и тем, что происходит на экране.
this.graphics.clear().fillStyle(0).fillRectShape(this.physics.world.bounds);
Конфигурация физического движка
В конфигурации игры (config) в разделе physics.arcade задаются начальные параметры мира. Именно эти значения позже можно менять через панель dat.GUI. Обратите внимание на свойство debug: true, которое изначально активирует отладочную отрисовку.
arcade: {
checkCollision: {
up: true,
down: true,
left: true,
right: true
},
debug: true,
debugBodyColor: 0xff00ff,
debugShowBody: true,
debugShowStaticBody: true,
debugShowVelocity: true,
gravity: {
x: 0,
y: 0
},
timeScale: 1
}
Свойство checkCollision по умолчанию активно со всех сторон. gravity изначально равна нулю. timeScale равен 1, что означает нормальную скорость симуляции.
Что попробовать дальше
Пример демонстрирует, что физический мир в Phaser Arcade — это гибкая и интерактивная система. Используя подобный инструмент отладки, вы можете быстро подобрать идеальные значения гравитации, упругости и границ для своей игры. Попробуйте экспериментировать: создайте группу тел с разной массой, включите силу ветра (forceX) и наблюдайте за хаотическим движением, или настройте OVERLAP_BIAS для решения проблем с "дрожанием" объектов при столкновениях. Это отличный способ почувствовать физику перед тем, как интегрировать её в реальный игровой процесс.
