О чем этот пример
При создании игр часто возникает задача вывода текста: диалоги, описания, интерфейс. Стандартный перенос по ширине не всегда подходит, особенно для художественных текстов или нестандартных шрифтов. Phaser позволяет взять контроль над процессом на себя, используя callback-функцию для переноса слов. В этой статье мы разберем, как с помощью параметра `wordWrap.callback` реализовать кастомную логику разбивки текста на строки. Это полезно для создания уникальных визуальных эффектов, поддержки сложных языков или просто для более точного управления версткой текста в вашей игре.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
create ()
{
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',
wordWrap: { callback: this.wordWrap, scope: this }
}
});
}
wordWrap (text, textObject)
{
// First parameter will be the string that needs to be wrapped
// Second parameter will be the Text game object that is being wrapped currently
// This wrap just puts each word on a separate line, but you could inject your own
// language-specific logic here.
const words = text.split(' ');
// You can return either an array of individual lines or a string with line breaks (e.g. \n) in
// the correct place.
return words;
}
}
const config = {
type: Phaser.CANVAS,
width: 800,
height: 600,
backgroundColor: '#0072bc',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Зачем нужен кастомный перенос?
Стандартный механизм wordWrap в Phaser отлично справляется с переносом по заданной ширине (width). Однако бывают ситуации, когда нужен особый контроль:
* **Художественное оформление:** Каждая строка — это отдельное слово для создания драматического эффекта. * **Сложные правила языка:** Например, запрет на перенос определенных сочетаний символов. * **Динамический контент:** Когда текст генерируется процедурно, и его layout нужно предсказуемо контролировать.
Именно для таких случаев в API Text Game Object есть опция callback внутри свойства wordWrap.
Анатомия callback-функции
Функция обратного вызова для переноса получает два аргумента и должна вернуть результат в определенном формате.
wordWrap (text, textObject) {
// text - строка, которую нужно разбить на строки.
// textObject - ссылка на сам объект Phaser.GameObjects.Text.
// ... ваша логика ...
return result; // Массив строк или строка с \n
}
Ключевой момент: функция может вернуть результат в одном из двух форматов:
1. **Массив строк,** где каждый элемент — это будущая строка текста.
2. **Единая строка,** в которой строки разделены символом переноса \n.
Это дает гибкость в реализации.
Разбираем пример: по слову на строку
Рассмотрим код из примера. В методе create создается объект текста. Обратите внимание на структуру стиля (style).
style: {
font: 'bold 30px Arial',
fill: 'white',
wordWrap: { callback: this.wordWrap, scope: this }
}
Здесь wordWrap задается не числом (шириной), а объектом с конфигурацией. Параметр callback указывает на метод класса, который будет выполнять перенос. Параметр scope важен для корректного контекста (this) внутри callback-функции.
Теперь посмотрим на саму функцию wordWrap. Она реализует простейший, но наглядный алгоритм.
wordWrap (text, textObject) {
const words = text.split(' ');
return words;
}
1. Исходная строка разбивается по пробелам методом split(' '). Получается массив отдельных слов.
2. Этот массив возвращается как есть. Phaser интерпретирует каждый элемент массива как отдельную строку для отрисовки. В результате каждое слово оказывается на новой линии, выровненное по центру (так как задан origin: 0.5).
Идеи для ваших экспериментов
Callback-функция открывает пространство для творчества. Вот несколько идей, что можно реализовать:
* **Перенос по слогам:** Реализуйте простой алгоритм разбиения слов на слоги для более естественного вида.
* **Эффект постепенного появления:** Возвращайте массив, где каждая новая строка добавляет по одному слову из предыдущей, создавая ощущение «наполнения».
* **Динамическая ширина:** Используйте textObject.width или другие свойства объекта внутри callback, чтобы менять логику переноса в зависимости от текущих условий.
Пример с динамическим ограничением длины строки:
wordWrap (text, textObject) {
const maxLineLength = 20;
const words = text.split(' ');
const lines = [];
let currentLine = words[0];
for (let i = 1; i < words.length; i++) {
const word = words[i];
// Проверяем, не превысит ли добавление слова лимит
if ((currentLine + ' ' + word).length <= maxLineLength) {
currentLine += ' ' + word;
} else {
// Сохраняем готовую строку и начинаем новую
lines.push(currentLine);
currentLine = word;
}
}
// Добавляем последнюю накопленную строку
lines.push(currentLine);
return lines;
}
Этот код собирает строки, стараясь не превысить заданное количество символов, что похоже на стандартный перенос, но полностью управляется вами.
Что попробовать дальше
Использование wordWrap.callback переводит работу с текстом из разряда стандартной верстки в область программируемого искусства. Вы получаете полный контроль над тем, как слова ложатся на экран, что критически важно для нарративных игр, стилизованных интерфейсов или локализации на языках со сложными правилами.
Для экспериментов попробуйте: создать эффект «дрожащего» текста, случайным образом разрывая строки; реализовать перенос, учитывающий длину следующего слова; или связать алгоритм с игровыми событиями, чтобы текст реагировал на действия игрока.
