О чем этот пример
При создании игр часто возникает задача динамической генерации текстур или спрайтов из частей других изображений. Пример демонстрирует мощь `RenderTexture` и его метода `saveTexture()` для создания новой текстуры в рантайме, которую затем можно использовать в любом игровом объекте. Этот подход полезен для создания пользовательских интерфейсов, анимаций из атласов или procedural контента, когда заранее подготовить все ресурсы невозможно.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
const USE_WEBGL = true; // Change to `true` to see bugs
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('mario', 'assets/bugs/mario4x4.png');
this.load.spritesheet('marioss', 'assets/bugs/mario4x4.png', { frameWidth: 128, frameHeight: 128 });
}
create ()
{
const origin = this.add.renderTexture(0, 0, 256, 256).setIsSpriteTexture(true);
origin.draw('mario', 0, 0, 1, 0xffffff, true);
origin.saveTexture('test-texture');
const sqSize = 128;
const f1 = origin.texture.add('square-1', 0, 0, 0, sqSize, sqSize);
const f2 = origin.texture.add('square-2', 0, sqSize, 0, sqSize, sqSize);
const f3 = origin.texture.add('square-3', 0, 0, sqSize, sqSize, sqSize);
const f4 = origin.texture.add('square-4', 0, sqSize, sqSize, sqSize, sqSize);
this.add.text(0, sqSize * 2, 'Original (RT)', {color: '#000', fontSize: 24}).setOrigin(0,0);
// ----------- Test cases ------------------
let offset = 256 + 10;
// `Image`
this.add.text(offset, sqSize * 2, 'Image', {color: '#000', fontSize: 24}).setOrigin(0,0);
[
['square-1', 0, 0],
['square-2', sqSize+1, 0],
['square-3', 0, sqSize+1],
['square-4', sqSize+1, sqSize+1]
].forEach(([frame, x, y])=>{
this.add.image(offset + x, y, 'test-texture', frame).setOrigin(0,0);
});
offset = (256 * 2) + (10 * 2);
// `Sprite`
this.add.text(offset, sqSize * 2, 'Sprite', {color: '#000', fontSize: 24}).setOrigin(0,0);
[
['square-1', 0, 0],
['square-2', sqSize+1, 0],
['square-3', 0, sqSize+1],
['square-4', sqSize+1, sqSize+1]
].forEach(([frame, x, y])=>{
this.add.sprite(offset + x, y, 'test-texture', frame).setOrigin(0,0);
});
offset = (256 * 3) + (10 * 3);
// `Blitter`
this.add.text(offset, sqSize * 2, 'Blitter', {color: '#000', fontSize: 24}).setOrigin(0,0);
const blitter = this.add.blitter(0, 0, 'test-texture');
[
['square-1', 0, 0],
['square-2', sqSize+1, 0],
['square-3', 0, sqSize+1],
['square-4', sqSize+1, sqSize+1]
].forEach(([frame, x, y])=>{
blitter.create(offset + x, y, frame);
});
offset = (256 * 4) + (10 * 4);
// `RenderTexture`
this.add.text(offset, sqSize * 2, 'RT.drawFrame', {color: '#000', fontSize: 24}).setOrigin(0,0);
const target = this.add.renderTexture(offset, 0, 512, 512).setIsSpriteTexture(true);
[
['square-1', 0, 0],
['square-2', sqSize+1, 0],
['square-3', 0, sqSize+1],
['square-4', sqSize+1, sqSize+1]
].forEach(([frame, x, y])=>{
target.drawFrame('test-texture', frame, x, y);
});
}
}
new Phaser.Game({
width: 1350,
height: 524,
type: USE_WEBGL ? Phaser.WEBGL : Phaser.CANVAS,
backgroundColor: 0xFFFFFF,
parent: 'phaser-example',
scene: Example
});
Подготовка сцены и загрузка ресурсов
В методе preload() загружаются два ресурса: обычное изображение и спрайтшит из одного и того же файла. Ключевой момент — использование одного файла mario4x4.png разными способами.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('mario', 'assets/bugs/mario4x4.png');
this.load.spritesheet('marioss', 'assets/bugs/mario4x4.png', { frameWidth: 128, frameHeight: 128 });
}
Создание RenderTexture и сохранение текстуры
В create() создаётся объект RenderTexture размером 256x256 пикселей. Метод setIsSpriteTexture(true) позволяет использовать эту текстуру для создания спрайтов. Затем на эту текстуру рисуется исходное изображение 'mario' с помощью draw().
Самый важный шаг — вызов saveTexture('test-texture'). Этот метод регистрирует текущее содержимое RenderTexture в кэше текстур игры под ключом 'test-texture'. Теперь к этой текстуре можно обращаться по имени, как к любой загруженной.
const origin = this.add.renderTexture(0, 0, 256, 256).setIsSpriteTexture(true);
origin.draw('mario', 0, 0, 1, 0xffffff, true);
origin.saveTexture('test-texture');
Нарезка текстуры на кадры (фреймы)
После сохранения текстуры мы можем работать с её объектом Texture. Метод texture.add() добавляет в текстуру новые фреймы. Он принимает имя фрейма, индекс (0), координаты X, Y, ширину и высоту области.
В примере исходное изображение 256x256 нарезается на четыре квадрата 128x128 пикселей, каждому присваивается уникальное имя: 'square-1', 'square-2' и т.д.
const sqSize = 128;
const f1 = origin.texture.add('square-1', 0, 0, 0, sqSize, sqSize);
const f2 = origin.texture.add('square-2', 0, sqSize, 0, sqSize, sqSize);
const f3 = origin.texture.add('square-3', 0, 0, sqSize, sqSize, sqSize);
const f4 = origin.texture.add('square-4', 0, sqSize, sqSize, sqSize, sqSize);
Использование созданной текстуры в различных объектах
Далее код демонстрирует, как созданные фреймы можно использовать с разными типами игровых объектов Phaser. Для каждого типа создаётся подпись и четыре экземпляра, отображающие разные фреймы.
- `Image`: Статичное изображение. Создаётся через `this.add.image()`, где третьим аргументом передаётся имя текстуры ('test-texture'), а четвёртым — имя конкретного фрейма.
- `Sprite`: Анимируемый объект. Создаётся аналогично через `this.add.sprite()`.
- `Blitter`: Высокопроизводительный объект для отрисовки множества одинаковых спрайтов. Сначала создаётся сам `blitter`, затем для каждого фрейма вызывается `blitter.create()`.
- `RenderTexture` с `drawFrame()`: Позволяет отрисовать конкретный фрейм текстуры в другую `RenderTexture`. Используется метод `target.drawFrame('test-texture', frame, x, y)`.
Код для Image (остальные аналогичны по структуре):
this.add.text(offset, sqSize * 2, 'Image', {color: '#000', fontSize: 24}).setOrigin(0,0);
[
['square-1', 0, 0],
['square-2', sqSize+1, 0],
['square-3', 0, sqSize+1],
['square-4', sqSize+1, sqSize+1]
].forEach(([frame, x, y])=>{
this.add.image(offset + x, y, 'test-texture', frame).setOrigin(0,0);
});
Конфигурация игры и переключение рендерера
В конце создаётся экземпляр игры Phaser.Game. Обратите внимание на переменную USE_WEBGL в самом начале файла. Она управляет типом рендерера: Phaser.WEBGL или Phaser.CANVAS. Этот пример был создан для демонстрации специфичных багов при использовании WebGL, поэтому переключение позволяет увидеть разницу в отображении.
new Phaser.Game({
width: 1350,
height: 524,
type: USE_WEBGL ? Phaser.WEBGL : Phaser.CANVAS,
backgroundColor: 0xFFFFFF,
parent: 'phaser-example',
scene: Example
});
Что попробовать дальше
Метод saveTexture() объекта RenderTexture открывает мощные возможности для runtime-генерации контента. Вы можете комбинировать изображения, рисовать фигуры, добавлять текст, а затем сохранять результат как новую текстуру для повторного использования. Для экспериментов попробуйте
- Нарисовать на
RenderTextureнесколько разных изображений перед сохранением - Динамически создавать фреймы разного размера и формы
- Использовать полученную текстуру для создания частиц (
ParticleEmitter) - Менять параметры
draw()(масштаб, tint) для получения различных визуальных эффектов
