О чем этот пример
В этой статье мы рассмотрим, как легко организовать непрерывное движение игровых объектов по сцене с помощью Phaser Actions. Часто в играх требуется, чтобы объекты, выходя за пределы экрана, появлялись с другой стороны, создавая бесконечный цикл. Механика «оборачивания» (wrapping) идеально подходит для фоновых элементов, врагов или космических кораблей, движущихся в открытом пространстве. Мы разберем практический пример, который использует встроенные методы Phaser для эффективного управления группой анимированных спрайтов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
this.wrapRect;
this.robots = [];
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('floor', 'assets/demoscene/checker-floor.png');
this.load.atlas('robot', 'assets/animations/robo.png', 'assets/animations/robo.json');
}
create ()
{
this.add.image(400, 600, 'floor').setScale(1.25).setOrigin(0.5, 1);
// Create an animation
this.anims.create({
key: 'run',
frames: this.anims.generateFrameNames('robot', { prefix: 'Running_', start: 0, end: 14, zeroPad: 3 }),
frameRate: 18,
repeat: -1
});
// Create 6 sprites, spaced out horizontally
for (let i = 0; i < 6; i++)
{
const sprite = this.add.sprite(i * 200, 380).play('run');
this.robots.push(sprite);
}
// This is our 'wrapping rectangle'
// When a sprite leaves this, it'll be wrapped around
this.wrapRect = new Phaser.Geom.Rectangle(0, 0, 800, 600);
}
update ()
{
// Move all the sprites forward 2 pixels
Phaser.Actions.IncX(this.robots, 2);
// Wrap the sprites within our wrapping rectangle, with a 200px buffer
Phaser.Actions.WrapInRectangle(this.robots, this.wrapRect, 200);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000042',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и анимации
Класс сцены инициализирует массив для хранения спрайтов и геометрический прямоугольник для определения области оборачивания.
В методе preload загружаются необходимые изображения и атлас анимации. Важно использовать setBaseURL для указания базового пути к ресурсам.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('floor', 'assets/demoscene/checker-floor.png');
this.load.atlas('robot', 'assets/animations/robo.png', 'assets/animations/robo.json');
В методе create сначала добавляется статичный фон. Затем создается анимация с ключом 'run' из кадров атласа. Метод generateFrameNames автоматически генерирует имена кадров по заданному шаблону.
this.anims.create({
key: 'run',
frames: this.anims.generateFrameNames('robot', { prefix: 'Running_', start: 0, end: 14, zeroPad: 3 }),
frameRate: 18,
repeat: -1
});
Далее в цикле создается шесть спрайтов, каждый из которых сразу проигрывает анимацию 'run', и добавляется в массив this.robots.
for (let i = 0; i < 6; i++)
{
const sprite = this.add.sprite(i * 200, 380).play('run');
this.robots.push(sprite);
}
Наконец, определяется прямоугольник this.wrapRect, который задает границы для оборачивания. Его размеры обычно совпадают с видимой областью сцены или игровым миром.
Логика обновления и перемещения объектов
Вся активная логика происходит в методе update, который вызывается на каждом кадре игры.
Первым делом все спрайты в массиве this.robots сдвигаются по оси X на 2 пикселя с помощью статического метода Phaser.Actions.IncX. Это эффективный способ применить одно изменение к целой группе объектов.
Phaser.Actions.IncX(this.robots, 2);
Без дополнительных проверок спрайты просто улетели бы за правый край экрана и исчезли из виду. Чтобы создать эффект бесконечного пространства, нам нужно их вернуть.
Механика оборачивания с помощью WrapInRectangle
Ключевой метод Phaser.Actions.WrapInRectangle решает задачу «телепортации» объектов. Он проверяет положение каждого спрайта в переданном массиве относительно заданного прямоугольника (this.wrapRect).
Phaser.Actions.WrapInRectangle(this.robots, this.wrapRect, 200);
Третий аргумент — padding (отступ), равный 200 пикселям. Это важный параметр, который определяет поведение оборачивания:
- Если спрайт покидает прямоугольник **справа** более чем на 200 пикселей, он мгновенно перемещается **влево** за пределы прямоугольника на те же 200 пикселей.
- Аналогично, если спрайт покидает прямоугольник **слева** более чем на 200 пикселей, он перемещается **вправо**.
Отступ предотвращает резкое, заметное глазу «мерцание» или скачок спрайта, когда он только-только пересек границу. Объект сначала полностью скрывается за пределами видимой зоны (на 200 пикселей), а только потом незаметно для игрока переносится на противоположную сторону. Это создает плавную и непрерывную иллюзию движения.
Настройка игры и конфигурация
Инициализация игры происходит стандартно, через объект конфигурации и создание экземпляра Phaser.Game.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000042',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Обратите внимание, что размеры сцены (width: 800, height: 600) совпадают с размерами прямоугольника оборачивания this.wrapRect, созданного в методе create. Это значит, что область оборачивания равна всей видимой области игры. Вы можете сделать wrapRect меньше или больше, чтобы контролировать, в какой именно зоне происходит «телепортация» объектов.
Что попробовать дальше
Методы Phaser.Actions.IncX и Phaser.Actions.WrapInRectangle предоставляют мощный и оптимизированный инструмент для управления движением множества объектов. Для экспериментов попробуйте: изменить значение отступа (padding) на 0 или очень большое число; применить оборачивание по вертикали с помощью Phaser.Actions.IncY; использовать другую геометрическую область, например Phaser.Geom.Circle, в сочетании с кастомной логикой оборачивания; или добавить случайную скорость каждому спрайту для более динамичной картины.
