О чем этот пример
Работа с текстом — частая задача в играх. Phaser предлагает `BitmapText` для производительного вывода текста с кастомными шрифтами. Ключевой метод `setMaxWidth()` автоматически переносит строки, но в его работе есть тонкости и баги, которые могут испортить верстку интерфейса. Разберемся, как работает перенос, и на что обратить внимание, чтобы избежать неожиданных наложений строк и проблем с пробелами.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
const text = `
the end is never | the end is never | the end is never A
the end is never | the end is never | the end is never B
the end is never | the end is never | the end is never C
the end is never | the end is never | the end is never D`
const text2 = `first line overlaps with itself
second line ends with a space
third line not wrapped as it should be`
const text3 = `first line overlaps with itself
second line ends with a space \n sssssssssssssssssssssssssssssssssssssssssssssssss
third line not wrapped as it should be`;
class Example extends Phaser.Scene {
preload() {
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.bitmapFont('shortStack', 'assets/fonts/bitmap/shortStack.png', 'assets/fonts/bitmap/shortStack.xml');
this.load.bitmapFont("atari", "assets/fonts/bitmap/atari-smooth.png", "assets/fonts/bitmap/atari-smooth.xml");
}
create() {
this.add.bitmapText(0, 300, 'shortStack', text, 16).setMaxWidth(400);
this.add.bitmapText(50, 50, 'atari', text3, 20).setMaxWidth(300);
const rect = this.add.rectangle(50, 50, 300, 200).setOrigin(0);
rect.setStrokeStyle(2, 0x1a65ac);
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example,
};
const game = new Phaser.Game(config);
Что такое BitmapText и зачем ему setMaxWidth?
Класс BitmapText в Phaser использует растровые шрифты (bitmap fonts), которые состоят из набора готовых изображений-глифов для каждого символа. Это быстрее, чем отрисовывать векторные шрифты, особенно для нестандартных дисплеев.
Однако у такого текста изначально нет понятия «перенос по словам». Если строка не помещается в заданную область, она просто обрезается или выходит за границы. Метод setMaxWidth() решает эту проблему: он заставляет текст автоматически переноситься на новую строку, когда его ширина достигает заданного лимита.
this.add.bitmapText(50, 50, 'atari', 'Очень длинная строка', 20).setMaxWidth(300);
Анализ бага: Наложение строк и лишние пробелы
В примере показаны три проблемных случая. Первый текст (text3) демонстрирует основную проблему: когда строка заканчивается пробелом, а следующая начинается с длинного слова, логика переноса может дать сбой.
const text3 = `first line overlaps with itself
second line ends with a space \n sssssssssssssssssssssssssssssssssssssssssssssssss
third line not wrapped as it should be`;
Здесь вторая строка заканчивается пробелом, а после символа новой строки (\n) идет очень длинное слово из букв 's'. Ожидается, что это слово будет перенесено, так как задан setMaxWidth(300). Однако в некоторых версиях движка (как в данном примере на баг #6860) перенос может не сработать, и строка «выедет» за пределы ограничивающего прямоугольника, накладываясь на другие элементы.
Второй текст (text2) показывает другую сторону проблемы: строка, начинающаяся с пробела, может быть некорректно обработана при расчете ширины, что также ведет к неправильному позиционированию.
Практические выводы и обходные пути
Исходя из анализа кода, можно сформулировать правила для стабильной работы с BitmapText:
1. **Избегайте завершающих пробелов в строках.** Особенно это критично перед ручным переносом (\n) или в конце строки внутри одного блока текста.
2. **Проверяйте визуально.** Всегда добавляйте отладочную графику (как прямоугольник в примере), чтобы видеть фактические границы области для текста.
const rect = this.add.rectangle(50, 50, 300, 200).setOrigin(0);
rect.setStrokeStyle(2, 0x1a65ac); // Синяя рамка для визуализации
3. **Разбивайте текст вручную.** Если автоматический перенос ведет себя непредсказуемо, контролируйте разрывы строк самостоятельно, используя символ \n в подготавливаемых строках.
4. **Тестируйте с разными шрифтами.** Проблема может проявляться по-разному в зависимости от файла bitmap-шрифта (как 'shortStack' и 'atari' в примере).
Сравнение с DynamicText (Text)
Альтернативой является класс Text (Dynamic Text), который использует системные или веб-шрифты. У него также есть метод setWordWrapWidth(), но его механизм переноса обычно более надежен, так как делегирован браузеру или системе рендеринга.
Однако BitmapText незаменим, когда нужен пиксель-перфект контроль, стилизация или поддержка нестандартных символов. Выбор между ними — это компромисс между производительностью (BitmapText), надежностью переноса (Text) и гибкостью стилизации.
Что попробовать дальше
Метод setMaxWidth() для BitmapText — мощный, но требующий аккуратности инструмент. Его основная проблема в текущей реализации — чувствительность к пробелам в конце и начале строк, что может ломать расчеты ширины. Для экспериментов попробуйте: создать свой простой bitmap-шрифт и протестировать перенос; написать функцию-препроцессор, которая удаляет лишние пробелы и вставляет \n по необходимости; сравнить рендеринг одного и того же текста в BitmapText и Text с одинаковыми параметрами ширины.
