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

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

Версия 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.atlas('gems', 'assets/tests/columns/gems.png', 'assets/tests/columns/gems.json');
    }

    create ()
    {
        //  Define the animations first

        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 });

        //  The Sprite config
        const config = {
            key: 'gems',
            x: { randInt: [ 0, 800 ] },
            y: { randInt: [ 0, 300 ] },
            scale: { randFloat: [ 0.5, 1.5 ] },
            anims: 'ruby'
        };

        //  Make 16 sprites using the config above
        for (let i = 0; i < 16; i++)
        {
            this.make.sprite(config);
        }

        //  A more complex animation config object.
        //  This time with a call to delayedPlay that's a function.
        const config2 = {
            key: 'gems',
            frame: 'square_0000',
            x: { randInt: [ 0, 800 ] },
            y: { randInt: [ 300, 600 ] },
            scale: { randFloat: [ 0.5, 1.5 ] },
            anims: {
                key: 'square',
                repeat: -1,
                repeatDelay: { randInt: [ 1000, 4000 ] },
                delayedPlay: function ()
                {
                    return Math.random() * 6000;
                }
            }
        };

        //  Make 16 sprites using the config above
        for (let i = 0; i < 16; i++)
        {
            this.make.sprite(config2);
        }
    }
}

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

const game = new Phaser.Game(config);

Загрузка атласа и создание анимаций

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

this.load.atlas('gems', 'assets/tests/columns/gems.png', 'assets/tests/columns/gems.json');

В методе create создаются две анимации: ruby и square. Метод this.anims.generateFrameNames автоматически генерирует массив кадров на основе префикса и диапазона.

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 });

Параметр zeroPad: 4 добавляет нули к номеру кадра (например, ruby_0001), что соответствует именам файлов в атласе. repeat: -1 задает бесконечное повторение анимации.

Первый конфиг: базовое создание спрайтов

Phaser позволяет описывать свойства спрайта в виде конфигурационного объекта. Это мощный инструмент для пакетного создания объектов.

const config = {
    key: 'gems',
    x: { randInt: [ 0, 800 ] },
    y: { randInt: [ 0, 300 ] },
    scale: { randFloat: [ 0.5, 1.5 ] },
    anims: 'ruby'
};
*   `key`: Указывает ключ текстуры или атласа.
*   `x`, `y`: Используют специальный объект `{ randInt: [min, max] }` для задания случайной позиции в указанном диапазоне.
*   `scale`: Аналогично, `{ randFloat: [min, max] }` задает случайный масштаб.
*   `anims`: Просто строка `'ruby'` — ключ ранее созданной анимации, которая будет проигрываться сразу.

Создание 16 спрайтов по этому конфигу выполняется простым циклом:

for (let i = 0; i < 16; i++) {
    this.make.sprite(config);
}

Фабрика this.make.sprite принимает конфиг и создает готовый спрайт. Каждый вызов использует свежие случайные значения для `x,yиscale`.

Второй конфиг: продвинутая настройка анимации

Второй пример демонстрирует более сложный конфиг, где настройка анимации передается не строкой, а целым объектом.

const config2 = {
    key: 'gems',
    frame: 'square_0000', // Стартовый кадр
    x: { randInt: [ 0, 800 ] },
    y: { randInt: [ 300, 600 ] },
    scale: { randFloat: [ 0.5, 1.5 ] },
    anims: {
        key: 'square',
        repeat: -1,
        repeatDelay: { randInt: [ 1000, 4000 ] },
        delayedPlay: function () {
            return Math.random() * 6000;
        }
    }
};

* frame: Явно задает начальный статичный кадр (square_0000) до начала анимации. * anims: Теперь это объект, расширяющий базовую анимацию square. * repeatDelay: Задает случайную паузу (от 1 до 4 секунд) между повторениями анимации. * delayedPlay: Функция, которая возвращает задержку (до 6 секунд) перед *первым* запуском анимации. Это позволяет создать эффект "разнородности" в поведении группы объектов.

Создание спрайтов происходит аналогично:

for (let i = 0; i < 16; i++) {
    this.make.sprite(config2);
}

Каждый спрайт будет иметь уникальную задержку перед стартом и между циклами анимации.

Системный взгляд: как это работает

Ключевой компонент здесь — фабрика игровых объектов, доступная через `this.make`. Когда `this.make.sprite(config)` получает конфиг, она выполняет несколько шагов:
1.  **Разрешение значений**: Для свойств вида `{ randInt: ... }` или `{ randFloat: ... }` фабрика вычисляет итоговое значение. Функции (как `delayedPlay`) вызываются, и их результат подставляется в итоговый конфиг анимации.
2.  **Создание спрайта**: На основе итоговых значений создается экземпляр `Phaser.GameObjects.Sprite`.
3.  **Применение анимации**: Если указано свойство `anims`, к спрайту применяется анимация. В случае с конфигом `config2` создается *новая* конфигурация анимации, которая передается в `sprite.play`. Это не изменяет глобальную анимацию `square`, созданную в `this.anims.create`.

Такой подход отделяет *описание* объекта от логики его *создания*, что соответствует принципам чистой архитектуры.

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

Использование конфигурационных объектов с this.make.sprite — это мощный паттерн в Phaser для создания групп объектов с вариативными свойствами. Он идеально подходит для генерации частиц, фоновых элементов, врагов или коллекций предметов. Для экспериментов попробуйте: * Добавить в конфиг случайный угол поворота (angle или rotation). * Использовать { randPick: [array] } для выбора случайной текстуры из списка. * Комбинировать delayedPlay с разными repeatDelay, чтобы создать сложные, несинхронные паттерны движения у целой группы NPC. * Динамически менять конфиг в цикле на основе индекса `i` для создания градиентов или паттернов.