О чем этот пример
RenderTexture — мощный инструмент Phaser для динамического рисования и композиции графики. Однако в его работе есть тонкости, которые могут привести к неожиданным визуальным артефактам. В этой статье мы разберем конкретный пример, демонстрирующий странное поведение метода `fill`, и объясним, почему так происходит и как с этим работать.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
create ()
{
const rt = this.add.renderTexture(0, 0, 200, 200).setOrigin(0);
rt.fill(0x00ff00, 1, 0, 0, 200, 200);
rt.fill(0xff0000, 1, 0, 0, 50, 50);
this.add.rectangle(0, 200, 200, 200, 0xffff00).setOrigin(0);
// rt.drawFrame('mushroom',undefined, 0, 0, 1, 0xff0000)
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что такое RenderTexture и метод fill?
RenderTexture — это специальный текстурированный объект, который работает как холст для рисования. В него можно отрисовывать другие игровые объекты, геометрические фигуры или выполнять заливку цветом.
Метод fill предназначен для заполнения прямоугольной области текстуры сплошным цветом. Его сигнатура выглядит так:
fill(tint, alpha, x, y, width, height)
Где tint — цвет в формате HEX (например, 0xff0000 для красного), alpha — значение прозрачности от 0 до 1, а `x,y,width,heightзадают область заливки относительно верхнего левого угла самойRenderTexture`.
Анализ проблемного кода
Рассмотрим исходный код примера. В нем создается RenderTexture размером 200x200 пикселей.
const rt = this.add.renderTexture(0, 0, 200, 200).setOrigin(0);
Затем происходит две последовательные заливки:
1. Сначала вся текстура заливается зеленым цветом (0x00ff00).
2. Затем в левом верхнем углу рисуется красный квадрат 50x50 (0xff0000).
rt.fill(0x00ff00, 1, 0, 0, 200, 200);
rt.fill(0xff0000, 1, 0, 0, 50, 50);
Логично ожидать, что в результате мы увидим зеленый холст с маленьким красным квадратом в углу. Однако в контексте бага (bugs/6615) визуальный результат может быть иным: красный квадрат может не отобразиться, или заливка может вести себя некорректно. Для сравнения, под текстурой добавляется статичный желтый прямоугольник.
this.add.rectangle(0, 200, 200, 200, 0xffff00).setOrigin(0);
В чем заключается проблема?
Проблема, обозначенная в баге, связана с внутренней реализацией fill в Phaser. При определенных условиях (часто связанных с WebGL-рендерингом и управлением кадровым буфером) последовательные вызовы fill на одном кадре могут конфликтовать друг с другом. Вместо того чтобы нарисовать красный квадрат поверх зеленого фона, система может "потерять" одну из операций или применить их в неверном порядке.
Это особенно заметно при работе с прозрачностью (alpha < 1) или при попытке рисовать за пределами границ текстуры, но в данном минимальном примере проблема проявляется даже с непрозрачными заливками.
Важно отметить, что закомментированная строка с методом drawFrame не является решением, а лишь показывает альтернативный API для рисования.
Практические решения и обходные пути
Пока баг не исправлен, можно использовать несколько стратегий для надежной заливки RenderTexture.
**1. Использование графики (Graphics):** Создайте временный объект Graphics, нарисуйте на нем нужные фигуры, а затем отрендерьте его в RenderTexture. Этот метод более стабилен.
create () {
const rt = this.add.renderTexture(0, 0, 200, 200).setOrigin(0);
const graphics = this.add.graphics();
// Рисуем зеленый фон
graphics.fillStyle(0x00ff00, 1);
graphics.fillRect(0, 0, 200, 200);
// Рисуем красный квадрат поверх
graphics.fillStyle(0xff0000, 1);
graphics.fillRect(0, 0, 50, 50);
// Переносим рисунок из Graphics в RenderTexture
rt.draw(graphics, 0, 0);
// Уничтожаем временный объект Graphics
graphics.destroy();
}
**2. Отрисовка в разные кадры:** Разнесите вызовы fill на разные игровые такты, используя this.time.delayedCall или события. Это может помочь, если проблема связана с состоянием рендерера.
**3. Создание новой текстуры для каждого слоя:** Вместо многократной заливки одной текстуры, создавайте отдельные текстуры для каждого цветного слоя и комбинируйте их, добавляя как дочерние объекты к сцене или другой текстуре.
Структура конфигурации игры
Пример использует стандартную конфигурацию Phaser с WEBGL-рендерером. Обратите внимание, что баг может проявляться только в режиме Phaser.WEBGL, но не в Phaser.CANVAS.
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что попробовать дальше
Метод fill у RenderTexture в Phaser, несмотря на простоту, может вести себя непредсказуемо из-за внутренних оптимизаций рендеринга. Пока проблема не решена на уровне движка, самым надежным решением является отказ от последовательных вызовов fill в пользу отрисовки через объект Graphics с последующим использованием rt.draw(). Для экспериментов попробуйте воспроизвести баг с разными значениями прозрачности (alpha), проверьте поведение в режиме CANVAS и попробуйте комбинировать fill с другими методами RenderTexture, например drawFrame.
