О чем этот пример
В игровом движке Phaser физическая система Arcade предоставляет простой, но мощный способ добавить динамику объектам. Этот пример наглядно демонстрирует, как создать сцену со множеством физических тел, которые отскакивают от границ мира и друг от друга. Вы научитесь настраивать мир физики, массово создавать анимированные спрайты с физикой и управлять камерой для навигации по обширной игровой области — ключевой навык для разработки экшен-игр, платформеров или симуляторов с большим количеством объектов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
controls;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('gems', 'assets/tests/columns/gems.png', 'assets/tests/columns/gems.json');
}
create ()
{
this.physics.world.setBounds(0, 0, 800 * 4, 600 * 4);
const spriteBounds = Phaser.Geom.Rectangle.Inflate(Phaser.Geom.Rectangle.Clone(this.physics.world.bounds), -100, -100);
this.anims.create({ key: 'diamond', frames: this.anims.generateFrameNames('gems', { prefix: 'diamond_', end: 15, zeroPad: 4 }), repeat: -1 });
this.anims.create({ key: 'prism', frames: this.anims.generateFrameNames('gems', { prefix: 'prism_', end: 6, zeroPad: 4 }), repeat: -1 });
this.anims.create({ key: 'ruby', frames: this.anims.generateFrameNames('gems', { prefix: 'ruby_', end: 6, zeroPad: 4 }), repeat: -1 });
this.anims.create({ key: 'square', frames: this.anims.generateFrameNames('gems', { prefix: 'square_', end: 14, zeroPad: 4 }), repeat: -1 });
// Create loads of random sprites
const anims = [ 'diamond', 'prism', 'ruby', 'square' ];
for (let i = 0; i < 100; i++)
{
const pos = Phaser.Geom.Rectangle.Random(spriteBounds);
const block = this.physics.add.sprite(pos.x, pos.y, 'gems');
block.setVelocity(Phaser.Math.Between(200, 400), Phaser.Math.Between(200, 400));
block.setBounce(1).setCollideWorldBounds(true);
if (Math.random() > 0.5)
{
block.body.velocity.x *= -1;
}
else
{
block.body.velocity.y *= -1;
}
block.play(Phaser.Math.RND.pick(anims));
}
const cursors = this.input.keyboard.createCursorKeys();
const controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
acceleration: 0.06,
drag: 0.0005,
maxSpeed: 1.0
};
this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
this.add.text(0, 0, 'Use Cursors to scroll camera.\nQ / E to zoom in and out', { font: '18px Courier', fill: '#00ff00' });
}
update (time, delta)
{
this.controls.update(delta);
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
parent: 'phaser-example',
pixelArt: true,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 100 },
debug: true
}
},
scene: Example
};
const game = new Phaser.Game(config);
Настройка мира физики и границ
Первым делом мы задаем границы физического мира. Они могут значительно превосходить размеры видимой области экрана, что позволяет создавать большие уровни.
this.physics.world.setBounds(0, 0, 800 * 4, 600 * 4);
Здесь границы мира устанавливаются в четыре раза больше размеров окна игры (800x600). Это создает обширную область для движения объектов. Далее, чтобы спрайты не появлялись слишком близко к краям этой области, мы создаем уменьшенную копию границ.
const spriteBounds = Phaser.Geom.Rectangle.Inflate(Phaser.Geom.Rectangle.Clone(this.physics.world.bounds), -100, -100);
Метод Phaser.Geom.Rectangle.Clone создает копию прямоугольника границ, а Inflate "сжимает" его со всех сторон на 100 пикселей. Полученный spriteBounds будет использоваться как область для случайного появления объектов.
Создание анимаций и управляемой камеры
Перед созданием спрайтов необходимо определить их анимации. В примере используется атлас gems, из которого генерируются кадры для четырех типов анимаций.
this.anims.create({ key: 'diamond', frames: this.anims.generateFrameNames('gems', { prefix: 'diamond_', end: 15, zeroPad: 4 }), repeat: -1 });
Ключевые параметры: key (уникальное имя анимации), frames (массив кадров, сгенерированных по именам из атласа) и repeat: -1 для зацикливания.
Для навигации по большому миру реализовано плавное управление камерой с помощью SmoothedKeyControl.
const controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
acceleration: 0.06,
drag: 0.0005,
maxSpeed: 1.0
};
this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
Обновление состояния управления происходит в методе update. Это позволяет камере плавно ускоряться и замедляться.
Массовое создание физических спрайтов
Сердце примера — цикл, который создает 100 физических спрайтов со случайными параметрами.
for (let i = 0; i < 100; i++)
{
const pos = Phaser.Geom.Rectangle.Random(spriteBounds);
const block = this.physics.add.sprite(pos.x, pos.y, 'gems');
block.setVelocity(Phaser.Math.Between(200, 400), Phaser.Math.Between(200, 400));
block.setBounce(1).setCollideWorldBounds(true);
// ... разворот скорости и воспроизведение анимации
}
1. Phaser.Geom.Rectangle.Random(spriteBounds) возвращает случайную точку внутри заданной области появления.
2. this.physics.add.sprite создает спрайт, которому сразу же добавляется физическое тело Arcade.
3. setVelocity задает случайную начальную скорость по осям X и Y.
4. setBounce(1) делает отскок абсолютно упругим (коэффициент восстановления 1). setCollideWorldBounds(true) включает столкновение с границами мира, что заставляет спрайты отскакивать от краев.
5. Последующий условный блок случайным образом инвертирует одну из компонент скорости (block.body.velocity.x или `y`), добавляя разнообразия в начальное движение.
6. block.play(Phaser.Math.RND.pick(anims)) запускает случайную анимацию из подготовленного массива.
Конфигурация игры и физики
Ключевые настройки физической системы и рендеринга задаются в объекте конфигурации игры.
const config = {
type: Phaser.WEBGL,
pixelArt: true,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 100 },
debug: true
}
},
scene: Example
};
- pixelArt: true включает сглаживание текстур для пиксель-арта.
- В разделе physics активируется система arcade. Здесь задана гравитация по оси Y и включен debug-режим, который отрисовывает контуры физических тел и векторы скорости, что невероятно полезно при отладке. Обратите внимание, что в данном примере гравитация действительно влияет на тела, но эффект отскока (bounce: 1) и постоянная скорость делают её влияние не столь очевидным на первый взгляд.
Что попробовать дальше
Этот пример служит отличной основой для создания динамичных сцен с множеством взаимодействующих объектов, таких как поле астероидов, стая птиц или коллекция падающих предметов. Для экспериментов попробуйте: изменить количество спрайтов и их физические параметры (массу, трение); добавить столкновения между самими спрайтами с помощью this.physics.add.collider(); реализовать взаимодействие с игроком (например, клик мышью для удаления или отталкивания объекта); или отключить гравитацию, чтобы увидеть чисто инерционное движение.
