О чем этот пример
В разработке игр на Phaser каждая мелочь может привести к неожиданным багам. Этот пример демонстрирует коварную проблему, связанную с рендерингом текста при использовании флага `rtl`. Вы узнаете, как один разрушенный текстовый объект с поддержкой письма справа налево может "заразить" контекст рендеринга и скрыть последующие тексты в браузерах на движке Chromium. Понимание этой особенности сэкономит часы отладки и поможет избежать критических визуальных ошибок в вашем проекте.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class MainScene extends Phaser.Scene
{
constructor()
{
super({ key: "MainScene" });
}
create ()
{
const rtlText1 = this.add.text(200, 100, 'Text 1', { rtl: true });
rtlText1.destroy();
const regularText2 = this.add.text(200, 200, 'Text 2', { rtl: false }); // In Chrome / Edge, Text 2 would not be shown!
const regularText3 = this.add.text(200, 300, 'Text 3', { rtl: false }); // In Chrome / Edge, Text 2 would not be shown!
const regularText4 = this.add.text(200, 400, 'Text 4', { rtl: false }); // In Chrome / Edge, Text 2 would not be shown!
const regularText5 = this.add.text(200, 500, 'Text 5', { rtl: true }); // In Chrome / Edge, Text 2 would not be shown!
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
pixelArt: true,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
},
scene: [ MainScene ]
};
const game = new Phaser.Game(config);
Суть проблемы: контекст Canvas и RTL
Phaser для рендеринга текста использует Canvas API. Флаг rtl в стиле текста влияет на состояние контекста рисования (CanvasRenderingContext2D), в частности, на свойство direction. Проблема возникает, когда текстовый объект с rtl: true уничтожается (destroy()). Внутренние механизмы Phaser пытаются очистить контекст, но в некоторых браузерах (Chrome, Edge) это может оставить контекст в "испорченном" состоянии для последующих операций рисования.
Ключевой момент: состояние контекста (например, direction: rtl) является общим для всех операций на этом Canvas. Если оно некорректно сброшено после удаления RTL-текста, последующие тексты, даже с rtl: false, могут быть отрисованы за пределами видимой области или не отрисованы вовсе.
Разбор проблемного кода
Давайте построчно пройдемся по коду примера, чтобы увидеть цепочку событий, ведущую к багу.
Сначала создается и тут же уничтожается текстовый объект с включенным RTL. Это триггерит проблему.
const rtlText1 = this.add.text(200, 100, 'Text 1', { rtl: true });
rtlText1.destroy();
Затем создается серия текстовых объектов. В исходном комментарии указано, что Text 2 не отображается, но на самом деле проблема может затронуть все последующие объекты, в зависимости от версии браузера и Phaser.
const regularText2 = this.add.text(200, 200, 'Text 2', { rtl: false });
const regularText3 = this.add.text(200, 300, 'Text 3', { rtl: false });
const regularText4 = this.add.text(200, 400, 'Text 4', { rtl: false });
Интересно, что создание нового объекта с rtl: true в конце также может не сработать корректно, так как контекст уже в неопределенном состоянии.
const regularText5 = this.add.text(200, 500, 'Text 5', { rtl: true });
Почему это происходит?
Основная причина кроется во внутреннем методе Text.destroy. При уничтожении текстового объекта Phaser пытается очистить контекст Canvas, который использовался для рендеринга этого текста. Для текста с rtl: true контекст был переключен в режим отрисовки справа налево (устанавливается свойство direction).
В идеальном сценарии, после вызова destroy(), Phaser должен полностью сбросить состояние контекста для этого текстового объекта, включая сброс direction обратно в 'ltr'. Однако, из-за особенностей реализации или специфики браузера, этот сброс может не произойти. В результате контекст, который переиспользуется для следующих вызовов this.add.text(), остается с direction: rtl.
Когда вы создаете новый текст с rtl: false, движок устанавливает позицию X (в данном случае 200), но контекст с direction: rtl интерпретирует эту координату как позицию *правого* края текста, а не левого. Если ширина текста меньше 200 пикселей, он будет отрисован левее заданной точки X и может легко выйти за пределы видимой области или канваса, создавая иллюзию его отсутствия.
Как избежать проблемы: практические решения
Есть несколько стратегий, чтобы обойти эту ошибку в ваших проектах.
**1. Избегайте уничтожения отдельных RTL-текстовых объектов.** Если нужно скрыть текст, используйте setVisible(false). Если объект действительно больше не нужен, убедитесь, что после его уничтожения не происходит немедленное создание нового текста.
**2. Явно сбрасывайте контекст.** Можно попробовать принудительно отрисовать что-то с rtl: false после уничтожения RTL-текста. Создайте временный невидимый текстовый объект.
// После уничтожения проблемного текста
const resetText = this.add.text(-1000, -1000, '', { rtl: false });
resetText.destroy();
**3. Используйте отдельные Canvas-слои.** Для сложных сцен с混合 RTL и LTR текстом рассмотрите возможность рендеринга текста на отдельный Canvas или использование контейнеров, которые можно целиком уничтожать и создавать заново.
**4. Обновляйте Phaser.** Эта проблема (баг #7077) могла быть исправлена в более новых версиях Phaser 3. Всегда проверяйте, используете ли вы актуальную стабильную сборку.
Что попробовать дальше
Баг с RTL-текстом — наглядный пример того, как внутреннее состояние графического контекста может повлиять на стабильность рендеринга. Главный вывод: операции с Canvas требуют аккуратного управления состоянием, особенно при работе с экзотическими настройками вроде направления текста.
Для экспериментов попробуйте воспроизвести баг в разных браузерах (Chrome, Firefox, Safari). Исследуйте, влияет ли на проблему использование pixelArt: true или определенного scale.mode. Также можно поэкспериментировать с отрисовкой простых примитивов (например, this.add.rectangle) между созданием текстовых объектов, чтобы проверить, затрагивает ли "испорченное" состояние только текст или весь рендеринг на Canvas.
