О чем этот пример
При создании игр часто возникает необходимость расположить множество объектов по сетке: инвентарь, элементы интерфейса, игровое поле. Ручной расчет координат для каждого объекта — утомительная и подверженная ошибкам задача. В этом примере мы рассмотрим, как в Phaser загрузить большое количество текстур и автоматически расположить созданные из них спрайты в аккуратный сеточный массив. Это не только экономит время разработки, но и делает код чище и проще для поддержки.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('veg01', 'assets/tests/fruit/veg01.png');
this.load.image('veg02', 'assets/tests/fruit/veg02.png');
this.load.image('veg03', 'assets/tests/fruit/veg03.png');
this.load.image('veg04', 'assets/tests/fruit/veg04.png');
this.load.image('veg05', 'assets/tests/fruit/veg05.png');
this.load.image('veg06', 'assets/tests/fruit/veg06.png');
this.load.image('veg07', 'assets/tests/fruit/veg07.png');
this.load.image('veg08', 'assets/tests/fruit/veg08.png');
this.load.image('veg09', 'assets/tests/fruit/veg09.png');
this.load.image('veg10', 'assets/tests/fruit/veg10.png');
this.load.image('veg11', 'assets/tests/fruit/veg11.png');
this.load.image('veg12', 'assets/tests/fruit/veg12.png');
this.load.image('veg13', 'assets/tests/fruit/veg13.png');
this.load.image('veg14', 'assets/tests/fruit/veg14.png');
this.load.image('veg15', 'assets/tests/fruit/veg15.png');
this.load.image('veg16', 'assets/tests/fruit/veg16.png');
this.load.image('veg17', 'assets/tests/fruit/veg17.png');
this.load.image('veg18', 'assets/tests/fruit/veg18.png');
this.load.image('veg19', 'assets/tests/fruit/veg19.png');
this.load.image('veg20', 'assets/tests/fruit/veg20.png');
this.load.image('veg21', 'assets/tests/fruit/veg21.png');
this.load.image('veg22', 'assets/tests/fruit/veg22.png');
this.load.image('veg23', 'assets/tests/fruit/veg23.png');
this.load.image('veg24', 'assets/tests/fruit/veg24.png');
this.load.image('veg25', 'assets/tests/fruit/veg25.png');
this.load.image('veg26', 'assets/tests/fruit/veg26.png');
this.load.image('veg27', 'assets/tests/fruit/veg27.png');
this.load.image('veg28', 'assets/tests/fruit/veg28.png');
this.load.image('veg29', 'assets/tests/fruit/veg29.png');
this.load.image('veg30', 'assets/tests/fruit/veg30.png');
this.load.image('veg31', 'assets/tests/fruit/veg31.png');
this.load.image('veg32', 'assets/tests/fruit/veg32.png');
this.load.image('veg33', 'assets/tests/fruit/veg33.png');
this.load.image('veg34', 'assets/tests/fruit/veg34.png');
this.load.image('veg35', 'assets/tests/fruit/veg35.png');
this.load.image('veg36', 'assets/tests/fruit/veg36.png');
this.load.image('veg37', 'assets/tests/fruit/veg37.png');
}
create ()
{
const fruit = [];
const test1 = 8;
const test2 = 16;
const test3 = 32;
const test4 = 37;
const test5 = 17;
const test6 = 31;
for (let i = 1; i < test3 + 1; i++)
{
fruit.push(this.add.sprite(0, 0, `veg${Phaser.Utils.String.Pad(i, 2, '0', 1)}`));
}
Phaser.Actions.GridAlign(fruit, {
width: 8,
height: 8,
cellWidth: 64,
cellHeight: 64,
x: 100,
y: 100
});
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Загрузка текстур в Phaser
Первый шаг — загрузка ресурсов. В методе preload() класса сцены (Scene) используется загрузчик (Loader).
Здесь мы загружаем 37 изображений с фруктами и овощами. Ключевой момент — структура их ключей (идентификаторов). Все они имеют формат vegXX, где XX — двузначный номер. Это важно для последующего создания спрайтов в цикле.
Метод this.load.setBaseURL() задает базовый URL, от которого будут строиться пути к файлам. Это удобно, если все ресурсы лежат в одной папке.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('veg01', 'assets/tests/fruit/veg01.png');
// ... загрузка остальных изображений вплоть до veg37
Создание и хранение спрайтов
В методе create() происходит инициализация игровых объектов. Сначала создается пустой массив fruit, который будет хранить все наши спрайты.
Затем объявляются несколько констант (test1, test2 и т.д.). В данном примере активно используется только test3, равная 32. Она определяет, сколько спрайтов мы создадим.
Цикл for создает спрайты и помещает их в массив. Ключевая деталь — формирование ключа текстуры. Поскольку в ключах используются двузначные числа (01, 02, ...), а наш счетчик `i— обычное число, нам нужно добавить ведущий ноль. Для этого используется утилитаPhaser.Utils.String.Pad()`.
const fruit = [];
const test3 = 32;
for (let i = 1; i < test3 + 1; i++)
{
fruit.push(this.add.sprite(0, 0, `veg${Phaser.Utils.String.Pad(i, 2, '0', 1)}`));
}
Метод this.add.sprite() создает новый спрайт. Первые два аргумента (0, 0) — это временные координаты, которые будут переопределены на следующем шаге.
Автоматическое выравнивание по сетке
После того как все спрайты созданы и собраны в массив, их нужно красиво расположить. Phaser предоставляет для этого мощный метод Phaser.Actions.GridAlign().
Этот метод принимает массив игровых объектов (в нашем случае — fruit) и объект конфигурации. Он автоматически расставляет объекты по сетке, обновляя их свойства `xиy`.
Разберем параметры конфигурации:
* width и height — количество столбцов и строк в сетке.
* cellWidth и cellHeight — размер каждой ячейки в пикселях.
* `xиy` — координаты верхнего левого угла всей сетки.
Phaser.Actions.GridAlign(fruit, {
width: 8,
height: 8,
cellWidth: 64,
cellHeight: 64,
x: 100,
y: 100
});
В результате 32 спрайта будут расположены в сетку 8x4 (так как 32 / 8 = 4 строки) с отступом в 100 пикселей от левого и верхнего края окна.
Настройка игры и запуск
Финальная часть кода — создание экземпляра игры (Game). Конфигурационный объект config определяет основные параметры:
* type: Phaser.WEBGL указывает на использование WebGL-рендерера (можно также использовать Phaser.CANVAS).
* parent: ID HTML-элемента, в который будет встроен canvas игры.
* width и height: размеры области отрисовки.
* scene: класс сцены, которая будет запущена первой.
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
После создания экземпляра Phaser.Game автоматически запускается жизненный цикл сцены: preload(), create(), а затем update() на каждом кадре.
Что попробовать дальше
Пример демонстрирует два важных паттерна в Phaser: пакетную загрузку ресурсов с предсказуемыми ключами и использование действий (Actions) для управления группами объектов. Это фундамент для создания сложных интерфейсов и уровней.
Для экспериментов попробуйте:
1. Изменить параметры width и height в GridAlign, чтобы получить другую форму сетки.
2. Использовать не все 37 загруженных текстур, а, например, только 17 (test5) или 31 (test6), изменив условие в цикле.
3. Добавить спрайтам физические тела и сделать их интерактивными по клику.
