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

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.