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

Работа с тайловыми спрайтами (Tile Sprite) в Phaser открывает отличные возможности для создания фонов, анимаций и текстурных эффектов. Однако их потенциал раскрывается полностью, когда мы можем динамически менять не только положение тайла, но и сам отображаемый кадр (frame) из атласа. Этот приём позволяет создавать сложные визуальные композиции, где элементы фона или декораций могут меняться в реальном времени по действию игрока, что идеально подходит для интерактивных сцен, головоломок или визуальных новелл. В этой статье мы разберём пример, который демонстрирует, как создать ряд тайловых спрайтов и по клику мыши менять их текстуру на случайную из загруженного атласа. Это практичный паттерн для реализации систем смены сезонов, времени суток, разрушаемых фонов или просто добавления элемента неожиданности в геймплей.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    iter = 0;
    images = [];

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.atlas('atlas', 'assets/atlas/megaset-2.png', 'assets/atlas/megaset-2.json');
    }

    create ()
    {
        const atlasTexture = this.textures.get('atlas');

        const frames = atlasTexture.getFrameNames();

        const startingFrames = [ 'atari400', 'bunny', 'cokecan', 'copy-that-floppy', 'hotdog' ];

        for (let i = 0; i < 5; i++)
        {
            this.images[i] = this.add.tileSprite(i * 160, 0, 160, 600, 'atlas', startingFrames[i]).setOrigin(0);
        }

        this.add.text(10, 10, 'Click to change frame', { font: '16px Courier', fill: '#ffffff' });

        this.input.on('pointerdown', () =>
        {

            this.images[0].setFrame(Phaser.Utils.Array.GetRandom(frames));
            this.images[1].setFrame(Phaser.Utils.Array.GetRandom(frames));
            this.images[2].setFrame(Phaser.Utils.Array.GetRandom(frames));
            this.images[3].setFrame(Phaser.Utils.Array.GetRandom(frames));
            this.images[4].setFrame(Phaser.Utils.Array.GetRandom(frames));

        }, this);
    }

    update ()
    {
        let x = 1;

        for (let i = 0; i < 5; i++)
        {
            this.images[i].tilePositionX += x;
            this.images[i].tilePositionY += x / 2;

            x *= -1;
        }

        this.iter += 0.01;
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка ресурсов и создание спрайтов

Всё начинается с загрузки атласа текстур в методе preload. Атлас — это один большой изображение и JSON-файл, описывающий координаты отдельных кадров (спрайтов) внутри него. Использование атласов оптимизирует загрузку и отрисовку.

В методе create мы получаем ссылку на загруженную текстуру и извлекаем из неё массив со всеми именами доступных кадров. Далее создаётся массив startingFrames с именами пяти конкретных кадров, которые будут использованы как начальные для каждого тайлового спрайта.

const atlasTexture = this.textures.get('atlas');
const frames = atlasTexture.getFrameNames();
const startingFrames = [ 'atari400', 'bunny', 'cokecan', 'copy-that-floppy', 'hotdog' ];

В цикле создаются пять объектов TileSprite. Ключевые параметры конструктора: позиция X, позиция Y, ширина, высота, ключ текстуры ('atlas') и имя стартового кадра из массива startingFrames. Метод .setOrigin(0) устанавливает точку начала координат спрайта в его левый верхний угол, что упрощает позиционирование в строку.

for (let i = 0; i < 5; i++)
{
    this.images[i] = this.add.tileSprite(i * 160, 0, 160, 600, 'atlas', startingFrames[i]).setOrigin(0);
}

Обработка клика и смена кадра

Интерактивность добавляется через слушатель события pointerdown. По клику мыши в любом месте игрового поля вызывается функция, которая для каждого из пяти тайловых спрайтов в массиве this.images меняет его текущий кадр.

Для выбора нового кадра используется утилита Phaser.Utils.Array.GetRandom, которая возвращает случайный элемент из переданного массива. В нашем случае массив frames содержит все имена кадров из атласа.

this.input.on('pointerdown', () =>
{
    this.images[0].setFrame(Phaser.Utils.Array.GetRandom(frames));
    this.images[1].setFrame(Phaser.Utils.Array.GetRandom(frames));
    this.images[2].setFrame(Phaser.Utils.Array.GetRandom(frames));
    this.images[3].setFrame(Phaser.Utils.Array.GetRandom(frames));
    this.images[4].setFrame(Phaser.Utils.Array.GetRandom(frames));
}, this);

Метод setFrame объекта TileSprite — это главный инструмент для смены отображаемого изображения. Он принимает строку с именем кадра и мгновенно обновляет внешний вид спрайта, не затрагивая его тайловые свойства (позицию тайла, масштаб).

Анимация движения тайла

Статичная картинка — скучно. Чтобы продемонстрировать, что смена кадра не ломает тайловую природу спрайта, в методе update реализована простая анимация прокрутки.

Для каждого спрайта в массиве изменяются свойства tilePositionX и tilePositionY. Эти свойства отвечают за смещение внутренней текстуры тайла относительно его контейнера. Изменяя их, мы создаём эффект бесконечного скроллинга или движения текстуры.

for (let i = 0; i < 5; i++)
{
    this.images[i].tilePositionX += x;
    this.images[i].tilePositionY += x / 2;
    x *= -1;
}

Переменная `xна каждой итерации цикла меняет знак на противоположный (x *= -1`). Это создаёт зигзагообразный, встречный паттерн движения для соседних спрайтов: первый сдвигается вправо-вниз, второй — влево-вверх, и так далее. Движение продолжается непрерывно, независимо от смены кадров по клику.

Конфигурация игры и запуск

Код завершается стандартной для Phaser 3 конфигурацией и созданием экземпляра игры. В конфиге важно указать наш класс сцены Example.

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: Example
};
const game = new Phaser.Game(config);

Эта конфигурация создаёт игровое поле размером 800x600 пикселей с тёмно-серым фоном и размещает его внутри HTML-элемента с id phaser-example. Рендерер (type) выбран автоматически (Phaser.AUTO), что позволяет Phaser самому выбрать между WebGL и Canvas в зависимости от поддержки браузером.

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

Сочетание тайловых спрайтов и динамической смены их кадров — мощный инструмент в арсенале геймдев-разработчика на Phaser. Вы научились загружать атлас, создавать из него тайлспрайты, менять их текстуру по действию игрока и анимировать фоновое движение. Этот паттерн можно развивать в разных направлениях: попробуйте менять кадры не случайно, а по определённой логике (цикл времён года, прогресс разрушения стены). Экспериментируйте с анимацией tilePosition и tileScale для создания эффектов волн, ряби или параллакса. Или привяжите смену кадра не к клику, а к таймеру, столкновению или достижению игроком определённой локации, чтобы создать живой, реагирующий мир.