О чем этот пример
При работе с графикой в Phaser часто возникает ситуация, когда у вас есть единое изображение тайлсета, но для отображения отдельных спрайтов требуется спрайтшит — текстура с разбивкой на кадры. Обычно это решается предзагрузкой отдельного файла или использованием тайловой карты, но что если нужно сделать это динамически, прямо во время выполнения? В этой статье мы разберем официальный пример из репозитория 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('fantasy', 'assets/tilemaps/tiles/fantasy-tiles.png');
}
create ()
{
const tiles = this.textures.get('fantasy');
const base = tiles.get();
Phaser.Textures.Parsers.SpriteSheet(tiles, base.sourceIndex, base.x, base.y, base.width, base.height, {
frameWidth: 64,
frameHeight: 64
});
this.add.sprite(200, 300, 'fantasy', 6);
this.add.sprite(300, 300, 'fantasy', 24);
this.add.sprite(400, 300, 'fantasy', 28);
this.add.sprite(500, 300, 'fantasy', 31);
this.add.sprite(600, 300, 'fantasy', 49);
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Зачем это нужно? Проблема тайлсетов и спрайтов
В Phaser существует четкое различие между текстурой (объект Texture) и спрайтом (объект Sprite). Спрайт для своей работы часто использует спрайтшит — текстуру, разбитую на отдельные кадры (frames).
Тайлсет, загруженный как обычное изображение (this.load.image), по умолчанию является текстурой с одним кадром. Если попытаться создать спрайт, указав номер кадра (например, 'fantasy', 6), движок не найдет этот кадр и, в лучшем случае, отобразит весь тайлсет целиком, а в худшем — ничего.
Классическое решение — заранее подготовить JSON-конфигурацию с разметкой кадров или использовать тайловую карту (Tilemap). Но иногда требуется гибкость: например, вы загружаете тайлсет с сервера и вам нужно на лету определить его структуру кадров.
Сердце метода: парсер SpriteSheet
Ключ к решению — статический метод Phaser.Textures.Parsers.SpriteSheet. Этот внутренний парсер движка обычно используется за кулисами при загрузке спрайтшитов, но его можно вызвать вручную для любой существующей текстуры.
Метод принимает на вход текстуру и параметры разбивки, а затем модифицирует эту текстуру, добавляя в нее кадры согласно заданной сетке.
Phaser.Textures.Parsers.SpriteSheet(tiles, base.sourceIndex, base.x, base.y, base.width, base.height, {
frameWidth: 64,
frameHeight: 64
});
В этом вызове:
- tiles — объект текстуры, полученный через this.textures.get('fantasy').
- base — базовый исходный кадр текстуры (обычно это и есть всё изображение). Мы передаем его координаты (`x,y) и размеры (width,height`).
- Последний аргумент — конфигурация, где frameWidth и frameHeight задают размер одного кадра (тайла) в пикселях.
После выполнения этого кода текстура 'fantasy' перестает быть монолитным изображением и становится полноценным спрайтшитом.
Пошаговый разбор кода примера
Давайте пройдемся по коду примера от загрузки до отображения.
**1. Загрузка изображения как обычной текстуры:**
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('fantasy', 'assets/tilemaps/tiles/fantasy-tiles.png');
}
На этом этапе изображение загружено в кеш текстур под ключом 'fantasy', но оно еще не является спрайтшитом.
**2. Получение объекта текстуры и её базового кадра:**
create ()
{
const tiles = this.textures.get('fantasy');
const base = tiles.get();
this.textures.get() возвращает объект класса Texture. Метод texture.get() без аргументов возвращает её первый (и на данный момент единственный) кадр — объект Frame.
**3. Преобразование текстуры:** Код вызова парсера мы уже разобрали выше. После его выполнения текстура содержит сетку кадров 64x64 пикселя.
**4. Создание спрайтов с использованием кадров:**
this.add.sprite(200, 300, 'fantasy', 6);
this.add.sprite(300, 300, 'fantasy', 24);
// ... и так далее
Теперь, когда текстура 'fantasy' стала спрайтшитом, мы можем создавать спрайты, указывая в четвертом параметре индекс нужного кадра. Индексация кадров обычно идет слева направо, сверху вниз.
Важные нюансы и ограничения
Используя этот метод, важно помнить о нескольких моментах:
1. **Необратимость:** Парсер SpriteSheet мутирует исходную текстуру. Если она использовалась где-то еще как цельное изображение (например, как фон), это перестанет работать.
2. **Размеры изображения:** Убедитесь, что ширина и высота изображения кратны frameWidth и frameHeight. Иначе парсер создаст неполные кадры по краям.
3. **Ключ текстуры:** Вы работаете с уже существующей в кеше текстурой по её строковому ключу. После парсинга все спрайты, использующие этот ключ, будут обращаться к новому спрайтшиту.
4. **Анимации:** После создания спрайтшита вы можете создавать анимации стандартными средствами Phaser, используя индексы кадров.
// Пример создания анимации после парсинга
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('fantasy', { start: 0, end: 5 }),
frameRate: 10,
repeat: -1
});
Что попробовать дальше
Ручной вызов Phaser.Textures.Parsers.SpriteSheet — это мощный инструмент для продвинутой работы с графикой в Phaser. Он стирает грань между статическими тайлсетами и анимированными спрайтшитами, позволяя гибко управлять ассетами во время выполнения игры.
**Идеи для экспериментов:**
1. Загрузите несколько тайлсетов и скомбинируйте их в один виртуальный спрайтшит, создав новую пустую текстуру (this.textures.createCanvas) и нарисовав на ней выбранные кадры.
2. Реализуйте динамическую подгрузку графики с сервера, где конфигурация кадров (размер и, возможно, порядок) приходит отдельным JSON-файлом.
3. Используйте этот метод в редакторе уровней, чтобы позволить пользователю выбирать тайлы из любого загруженного изображения, просто указав размер сетки.
