О чем этот пример
Создание плавной анимации для большого количества объектов — классическая задача в игровой разработке. Обычно для этого используют циклы, но они могут нагружать процессор. В этой статье мы разберем пример, где 128 самолетов парят по экрану с помощью всего двух вызовов API. Вы узнаете, как использовать `Phaser.Actions` для эффективного управления группой спрайтов, что особенно полезно для создания фоновых эффектов, частиц или вражеских волн.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
this.planes = [];
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('bg', 'assets/skies/deepblue.png');
this.load.image('plane', 'assets/sprites/ww2plane.png');
}
create ()
{
this.add.image(400, 300, 'bg');
this.cameras.main.setBounds(0, 0, 800, 600);
for (let i = 0; i < 128; i++)
{
const x = Phaser.Math.Between(0, 800);
const y = Phaser.Math.Between(0, 600);
this.planes.push(this.add.image(x, y, 'plane'));
}
}
update ()
{
Phaser.Actions.IncY(this.planes, -1, -0.025);
Phaser.Actions.WrapInRectangle(this.planes, this.cameras.main.getBounds(), 128);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и загрузка ресурсов
Код начинается с создания класса сцены, наследующего от Phaser.Scene. В конструкторе инициализируется пустой массив this.planes, который будет хранить все спрайты самолетов.
class Example extends Phaser.Scene
{
constructor ()
{
super();
this.planes = [];
}
В методе preload загружаются два изображения: фон неба (bg) и спрайт самолета (plane). Базовый URL указывает на репозиторий с примерами Phaser.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('bg', 'assets/skies/deepblue.png');
this.load.image('plane', 'assets/sprites/ww2plane.png');
}
Создание игровых объектов и настройка камеры
В методе create сначала добавляется фоновое изображение. Затем устанавливаются границы камеры с помощью this.cameras.main.setBounds. Это определяет виртуальное пространство, в котором будут двигаться объекты.
create ()
{
this.add.image(400, 300, 'bg');
this.cameras.main.setBounds(0, 0, 800, 600);
Далее в цикле создаются 128 спрайтов самолета. Их начальные координаты задаются случайным образом в пределах границ камеры (от 0 до 800 по X и от 0 до 600 по Y) с помощью Phaser.Math.Between. Каждый созданный спрайт добавляется в массив this.planes.
for (let i = 0; i < 128; i++)
{
const x = Phaser.Math.Between(0, 800);
const y = Phaser.Math.Between(0, 600);
this.planes.push(this.add.image(x, y, 'plane'));
}
}
Анимация через Phaser.Actions: эффективное обновление
Сердце анимации находится в методе update, который вызывается на каждом кадре. Вместо ручного перебора массива this.planes в цикле, используются две мощные функции из модуля Phaser.Actions.
Первая функция — Phaser.Actions.IncY. Она увеличивает (или уменьшает) свойство `y` для каждого спрайта в переданном массиве. В данном случае все самолеты плавно поднимаются вверх.
update ()
{
Phaser.Actions.IncY(this.planes, -1, -0.025);
Параметры функции:
1. this.planes — массив объектов для изменения.
2. -1 — базовое значение, на которое изменяется координата Y.
3. -0.025 — случайное отклонение от базового значения (step). Итоговое изменение для каждого объекта вычисляется как value + (Math.random() * step). Это придает движению небольшой разброс и выглядит более естественно.
Обеспечение непрерывности движения с помощью WrapInRectangle
Если бы мы только двигали самолеты вверх, они бы вскоре улетели за верхнюю границу экрана и исчезли. Функция Phaser.Actions.WrapInRectangle решает эту проблему. Она проверяет положение каждого спрайта в массиве относительно заданного прямоугольника (границ камеры) и, если спрайт его покидает, "перебрасывает" его на противоположную сторону.
Phaser.Actions.WrapInRectangle(this.planes, this.cameras.main.getBounds(), 128);
}
}
Параметры функции:
1. `this.planes` — массив объектов для проверки.
2. `this.cameras.main.getBounds()` — прямоугольник (границы камеры), внутри которого объекты должны оставаться.
3. `128` — отступ (padding). Объект считается покинувшим зону только когда уйдет за ее границу более чем на 128 пикселей. Это создает плавный эффект: самолеты начинают появляться снизу еще до того, как последние скроются наверху, формируя бесконечный поток.
Конфигурация и запуск игры
Завершает пример стандартная конфигурация игры Phaser 3. В объекте config задается автоматический выбор рендерера, размеры холста, цвет фона, ID родительского HTML-элемента и класс основной сцены. После этого создается экземпляр игры new Phaser.Game(config), который запускает весь описанный цикл.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что попробовать дальше
Использование Phaser.Actions позволяет управлять сотнями объектов с минимальными затратами производительности, заменяя громоздкие циклы на одну строку кода. Для экспериментов попробуйте изменить параметры в IncY (например, двигать объекты по оси X с IncX), использовать другие действия из модуля (например, Rotate для вращения или SetTint для изменения цвета) или применить WrapInRectangle к объектам, которые движутся по диагонали, создавая более сложные паттерны движения.
