О чем этот пример
Работа с текстом — неотъемлемая часть разработки игр. Phaser предлагает мощную, но не всегда очевидную систему переноса строк (word wrap), которая позволяет гибко управлять отображением текста в интерфейсах, диалогах и описаниях. Эта статья разбирает исходный код тестового примера, демонстрируя все аспекты API: базовый и продвинутый перенос по ширине, а также использование кастомных функций-колбэков для полного контроля над разбивкой текста. Вы научитесь точно управлять тем, как текст ложится на экран вашей игры.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
testsPassed = 0;
totalTests = 0;
create ()
{
const text = this.make.text({
x: 400,
y: 300,
text: ' The sky above the port was the color of television, tuned to a dead channel.',
origin: 0.5,
style: {
font: 'bold 30px Arial',
fill: 'white'
}
});
// --- DEFAULTS ---
this.assert('Word wrap width should be null', text.style.wordWrapWidth === null);
this.assert('Advanced word wrap should be false', text.style.wordWrapUseAdvanced === false);
this.assert('Word wrap callback should be null', text.style.wordWrapCallback === null);
this.assert('Word wrap callback scope should be null', text.style.wordWrapCallbackScope === null);
this.assert('Wrapped text should be one line', text.getWrappedText().length === 1);
{
// --- BASIC WIDTH WRAPPING ---
text.setWordWrapWidth(200, false);
let lines = text.getWrappedText();
this.assert('Wrapped text should be multiple lines', lines.length > 1);
this.assert('First line should not be trimmed', lines[0].startsWith(' '));
this.assert('First line not have whitespace collapsed', lines[0].includes('The sky'));
text.setWordWrapWidth(null);
}
{
// --- DISABLING WRAPPING AFTER ENABLING IT ---
text.setWordWrapWidth(200, false);
text.setWordWrapWidth(null);
this.assert('Wrapped text should be one line', text.getWrappedText().length === 1);
}
{
// --- ADVANCED WIDTH WRAPPING ---
text.setWordWrapWidth(200, true);
let lines = text.getWrappedText();
this.assert('Wrapped text should be multiple lines', lines.length > 1);
this.assert('First line should be trimmed', !lines[0].startsWith(' '));
this.assert('First line have whitespace collapsed', lines[0].includes('The sky'));
text.setWordWrapWidth(null);
}
{
// --- WRAPPING CALLBACK, RETURNING ARRAY ---
text.setWordWrapCallback(function (string, textObject)
{
this.assert('Second argument should be the text gameobject', text === textObject);
this.assert('Scope should match the given scope object', this.testObject === true);
return [ '1', '2' ];
}, { testObject: true, assert: this.assert });
let lines = text.getWrappedText();
this.assert('Wrapped text should be exactly two lines', lines.length === 2);
this.assert('Wrapped text should be ["1", "2"]', lines[0] === '1' && lines[1] === '2');
text.setWordWrapCallback(null);
}
{
// --- WRAPPING CALLBACK, RETURNING STRING ---
text.setWordWrapCallback(() => '1\n2');
let lines = text.getWrappedText();
this.assert('Wrapped text should be exactly two lines', lines.length === 2);
this.assert('Wrapped text should be ["1", "2"]', lines[0] === '1' && lines[1] === '2');
text.setWordWrapCallback(null);
}
{
// --- DISABLING WRAPPING CALLBACK AFTER ENABLING IT ---
text.setWordWrapCallback(text => text, { testObject: true });
text.setWordWrapCallback(null);
let lines = text.getWrappedText();
this.assert('Wrapped text should be one line', lines.length === 1);
}
console.log(`${this.testsPassed} / ${this.totalTests} tests passed`);
}
assert (message, condition)
{
this.totalTests++;
if (condition) { this.testsPassed++; }
console.assert(condition, message);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#0072bc',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Создание текстового объекта
Всё начинается с создания объекта Text через фабрику this.make.text(). В примере задаются базовые свойства: позиция, исходная строка, точка привязки и стиль.
const text = this.make.text({
x: 400,
y: 300,
text: ' The sky above the port was the color of television, tuned to a dead channel.',
origin: 0.5,
style: {
font: 'bold 30px Arial',
fill: 'white'
}
});
По умолчанию перенос строк не активен. Проверить это можно через свойства стиля text.style и метод text.getWrappedText(), который возвращает массив строк. Изначально это будет массив из одного элемента — всей исходной строки.
Базовый перенос по ширине
Самый простой способ включить перенос — задать максимальную ширину строки в пикселях с помощью метода setWordWrapWidth(width, useAdvancedWrap). Если второй аргумент useAdvancedWrap равен false, включается базовый режим.
text.setWordWrapWidth(200, false);
let lines = text.getWrappedText();
В этом режиме текст разбивается строго по достижении заданной ширины, но без какой-либо предварительной обработки строки. Пробелы в начале строки и множественные пробелы внутри не удаляются. Чтобы отключить перенос, нужно передать null в качестве ширины.
Продвинутый перенос по ширине
Если передать в setWordWrapWidth вторым аргументом true, активируется продвинутый режим.
text.setWordWrapWidth(200, true);
let lines = text.getWrappedText();
В этом режиме Phaser проводит дополнительную обработку текста перед переносом: удаляет пробелы в начале строки и «схлопывает» множественные пробелы внутри строки в один. Это полезно для аккуратного отображения текста, полученного из внешних источников.
Кастомный перенос через колбэк
Для максимального контроля можно задать свою функцию для переноса с помощью setWordWrapCallback(callback, scope). Функция-колбэк получает исходную строку и сам текстовый объект. Она может вернуть результат в двух форматах.
Первый вариант — вернуть массив строк, где каждый элемент станет новой строкой:
text.setWordWrapCallback(function (string, textObject) {
return [ '1', '2' ];
}, scopeObject);
Второй вариант — вернуть единую строку, где строки разделены символом \n:
text.setWordWrapCallback(() => '1\n2');
Объект scope (второй аргумент) задаёт контекст (this), в котором будет вызван колбэк. Это позволяет получить доступ к нужным данным или методам внутри функции. Колбэк отключается передачей null.
Проверка результата
Ключевой метод для работы с переносом — getWrappedText(). Он всегда возвращает актуальный массив строк, учитывая текущие настройки ширины и колбэка. В примере он используется после каждой операции для проверки утверждений (assert).
let lines = text.getWrappedText();
this.assert('Wrapped text should be exactly two lines', lines.length === 2);
Этот метод — главный инструмент для отладки и построения логики, зависящей от разбитого текста.
Что попробовать дальше
Phaser предоставляет три уровня контроля над переносом текста: автоматический по ширине (базовый и продвинутый) и полностью ручной через колбэк. Для UI подойдёт продвинутый режим (setWordWrapWidth(width, true)), а для диалогов со сложными правилами — кастомный колбэк. Поэкспериментируйте: создайте колбэк, который переносит текст только по точкам или добавляет тире в месте разрыва слова, или динамически меняйте ширину переноса в зависимости от размера игрового окна.
