О чем этот пример

Создание группы одинаковых спрайтов — частая задача в разработке игр. Но что, если каждый спрайт должен выглядеть чуть иначе, не прибегая к загрузке множества текстур? Phaser предлагает элегантное решение: использование одного спрайтшита и случайных кадров для каждого объекта в группе. Это мощный приём для генерации разнообразных визуальных элементов, таких как толпа, звёзды на небе или коллекция предметов, без ручного размещения каждого варианта. В статье разберём, как работает `randomFrame` в `this.make.group`, и как с его помощью наполнить сцену сотнями уникальных спрайтов в несколько строк кода.

Версия 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.spritesheet('bobs', 'assets/sprites/bobs-by-cleathley.png', { frameWidth: 32, frameHeight: 32 });
    }

    create ()
    {
        //  This will create a Group with 400 children.
        //  Each child will use the 'bobs' texture and a random frame number between 0 and 399 (inclusive)
        //  Change 'randomFrame' to false to see the difference it makes

        const group = this.make.group({
            key: 'bobs',
            frame: Phaser.Utils.Array.NumberArray(0, 399),
            randomFrame: true,
            gridAlign: {
                x: 16,
                y: 16,
                width: 25,
                height: 25,
                cellWidth: 32,
                cellHeight: 32
            }
        });
    }
}

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка: загрузка спрайтшита

Перед созданием группы необходимо загрузить текстуру. В данном примере используется спрайтшит — изображение, содержащее множество отдельных кадров (спрайтов).

this.load.spritesheet('bobs', 'assets/sprites/bobs-by-cleathley.png', { frameWidth: 32, frameHeight: 32 });

Метод load.spritesheet загружает изображение и нарезает его на кадры размером 32x32 пикселя. Ключ 'bobs' будет использоваться для доступа к этому ресурсу. Спрайтшит в примере содержит 400 последовательных кадров, что идеально подходит для демонстрации.

Создание группы с `randomFrame`

Основная логика находится в методе create. Здесь создаётся группа (Group) — специальный контейнер Phaser для управления множеством игровых объектов.

const group = this.make.group({
    key: 'bobs',
    frame: Phaser.Utils.Array.NumberArray(0, 399),
    randomFrame: true,
    gridAlign: {
        x: 16,
        y: 16,
        width: 25,
        height: 25,
        cellWidth: 32,
        cellHeight: 32
    }
});

Рассмотрим ключевые параметры: - key: Указывает на загруженный спрайтшит 'bobs'. - frame: Задаёт массив номеров кадров, которые могут быть использованы. Phaser.Utils.Array.NumberArray(0, 399) создаёт массив чисел от 0 до 399 включительно. - randomFrame: Самый важный параметр. Если установлен в true, каждый создаваемый в группе спрайт получит случайный кадр из массива frame. Если false, все спрайты будут использовать первый кадр (или кадр по умолчанию). - gridAlign: Автоматически выравнивает спрайты в сетке 25x16 (400 объектов), начиная с координат (16, 16) и с шагом (cellWidth, cellHeight).

Как работает выбор кадра?

Когда randomFrame равен true, фаза создания каждого спрайта в группе включает дополнительный шаг: выбор случайного элемента из предоставленного массива frame. Это происходит "под капотом" метода make.group.

// Псевдокод логики внутри Phaser при randomFrame: true
for (let i = 0; i < totalObjects; i++) {
    const randomFrameIndex = Phaser.Math.Between(0, frameArray.length - 1);
    const spriteFrame = frameArray[randomFrameIndex];
    // Создаётся спрайт с этим кадром
}

Именно это приводит к тому, что все 400 бобов на сцене выглядят по-разному, хотя используют одну текстуру. Без randomFrame (или с randomFrame: false) все спрайты получили бы кадр номер 0, создав однородную сетку.

Практическое применение и вариации

Этот приём выходит за рамки декоративных бобов. Вот несколько идей для ваших игр:

1. **Создание окружения:** Используйте спрайтшит с 10-20 вариантами травы или камней, чтобы сгенерировать натурально выглядящий ландшафт без паттернов. 2. **Толпа или армия:** Несколько типов анимации "ожидания" (idle) для юнитов в стратегической игре, чтобы они не двигались синхронно. 3. **Инвентарь или коллекции:** Для отображения стопки монет или груды самоцветов, где каждый элемент имеет небольшие визуальные отличия.

Вы можете контролировать пул кадров, изменяя массив frame.

// Только каждый пятый кадр (0, 5, 10, ...)
frame: Phaser.Utils.Array.NumberArrayStep(0, 399, 5),
// Конкретный набор кадров
frame: [0, 15, 100, 255, 399],

Изменяя gridAlign или используя setXY после создания, можно расположить объекты в любом порядке.

Что попробовать дальше

Параметр randomFrame в this.make.group — это небольшой, но мощный инструмент для добавления визуального разнообразия в вашу игру. Он позволяет создавать сложные на вид композиции из минимального количества ресурсов, разгружая художника и упрощая код. Для экспериментов попробуйте изменить логику выбора кадров: например, задать веса для разных кадров (чтобы одни встречались чаще других) или привязать выбор кадра к позиции спрайта в сетке, создавая плавные градиенты.