О чем этот пример
При работе с текстом в играх часто нужно знать его точные размеры на экране — для выравнивания, проверки столкновений или динамической компоновки интерфейса. В Phaser у любого игрового объекта, включая текстовые, есть метод `getBounds()`, возвращающий его геометрические границы в мировых координатах. Эта статья на конкретном примере покажет, как визуализировать эти границы и почему реальная занимаемая область текста может сильно отличаться в зависимости от стиля шрифта, размера и эффектов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
c = 0;
i = 0;
content = [
'The sky above the port was the color of television, tuned to a dead channel.',
'`It\'s not like I\'m using,\' Case heard someone say, as he shouldered his way ',
'through the crowd around the door of the Chat. `It\'s like my body\'s developed',
'this massive drug deficiency.\' It was a Sprawl voice and a Sprawl joke.',
'The Chatsubo was a bar for professional expatriates; you could drink there for',
'a week and never hear two words in Japanese.',
'',
'Ratz was tending bar, his prosthetic arm jerking monotonously as he filled a tray',
'of glasses with draft Kirin. He saw Case and smiled, his teeth a webwork of',
'East European steel and brown decay. Case found a place at the bar, between the',
'unlikely tan on one of Lonny Zone\'s whores and the crisp naval uniform of a tall',
'African whose cheekbones were ridged with precise rows of tribal scars. `Wage was',
'in here early, with two joeboys,\' Ratz said, shoving a draft across the bar with',
'his good hand. `Maybe some business with you, Case?\'',
'',
'Case shrugged. The girl to his right giggled and nudged him.',
'The bartender\'s smile widened. His ugliness was the stuff of legend. In an age of',
'affordable beauty, there was something heraldic about his lack of it. The antique',
'arm whined as he reached for another mug.',
'',
'From Neuromancer by William Gibson',
''
];
line = this.content[0].split(' ');
graphics;
text3;
text2;
text1;
create ()
{
this.text1 = this.add.text(100, 100, 'The');
this.text2 = this.add.text(100, 200, 'The', { fontFamily: 'Arial', fontSize: 64, color: '#00ff00' });
this.text3 = this.add.text(100, 400, 'The', { fontFamily: 'Arial Black', fontSize: 74, color: '#c51b7d' });
this.text3.setStroke('#de77ae', 16).setShadow(2, 2, '#333333', 2, true, true);
this.graphics = this.add.graphics();
this.time.addEvent({
delay: 500, callback: function ()
{
const word = this.getWord();
this.text1.setText(word);
this.text2.setText(word);
this.text3.setText(word);
}, callbackScope: this, loop: true
});
}
update ()
{
this.graphics.clear();
this.graphics.lineStyle(1, 0xff0000, 1);
this.graphics.strokeRectShape(this.text1.getBounds());
this.graphics.strokeRectShape(this.text2.getBounds());
this.graphics.strokeRectShape(this.text3.getBounds());
}
getWord ()
{
this.c++;
if (this.c === this.line.length)
{
this.c = 0;
this.i++;
if (this.i === this.content.length)
{
this.i = 0;
}
this.line = this.content[this.i].split(' ');
}
return this.line[this.c];
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-example',
backgroundColor: '#8d8d8d',
scene: Example
};
const game = new Phaser.Game(config);
Сцена и данные: подготовка контента
В примере создается сцена, которая будет поочередно отображать слова из литературного отрывка. Текст разбит на массив строк content, а первая строка дополнительно разделена на отдельные слова для пошагового вывода.
class Example extends Phaser.Scene
{
c = 0;
i = 0;
content = [
'The sky above the port was the color of television, tuned to a dead channel.',
// ... остальные строки
];
line = this.content[0].split(' ');
Переменные `cиi— это счетчики:cотслеживает текущее слово в строке, аi— текущую строку в массивеcontent`. Это позволяет циклически перебирать весь текст по словам.
Создание текстовых объектов с разными стилями
В методе create() создаются три текстовых объекта с одним и тем же начальным словом "The", но с совершенно разным визуальным оформлением. Это ключевой момент для демонстрации различий в границах.
create ()
{
this.text1 = this.add.text(100, 100, 'The');
this.text2 = this.add.text(100, 200, 'The', { fontFamily: 'Arial', fontSize: 64, color: '#00ff00' });
this.text3 = this.add.text(100, 400, 'The', { fontFamily: 'Arial Black', fontSize: 74, color: '#c51b7d' });
this.text3.setStroke('#de77ae', 16).setShadow(2, 2, '#333333', 2, true, true);
}
text1 использует стиль по умолчанию. text2 — крупный зеленый шрифт Arial. text3 — самый крупный и сложный: жирный шрифт Arial Black с толстой обводкой (setStroke) и внутренней тенью (setShadow). Обводка и тень увеличивают визуальный размер объекта, что позже отразится на его границах.
Динамическое обновление текста
Чтобы текст не был статичным, в сцене настроено повторяющееся событие таймера. Каждые 500 миллисекунд оно вызывает функцию, которая меняет слово во всех трех текстовых объектах одновременно.
this.time.addEvent({
delay: 500,
callback: function ()
{
const word = this.getWord();
this.text1.setText(word);
this.text2.setText(word);
this.text3.setText(word);
},
callbackScope: this,
loop: true
});
Метод getWord() управляет логикой перебора, обновляя счетчики и возвращая следующее слово из подготовленного массива. Когда слова в строке заканчиваются, он переходит к следующей строке, а по достижении конца массива — начинает сначала.
Визуализация границ в реальном времени
Самый важный метод — update(). Он выполняется на каждом кадре игры. Здесь созданный ранее графический объект graphics очищается, и для каждого текстового объекта рисуется красная рамка по его границам.
update ()
{
this.graphics.clear();
this.graphics.lineStyle(1, 0xff0000, 1);
this.graphics.strokeRectShape(this.text1.getBounds());
this.graphics.strokeRectShape(this.text2.getBounds());
this.graphics.strokeRectShape(this.text3.getBounds());
}
Метод getBounds() возвращает объект типа Phaser.Geom.Rectangle, который содержит координаты левого верхнего угла, ширину и высоту реальной занимаемой области текста на экране. strokeRectShape() использует этот прямоугольник для отрисовки рамки. Очистка (clear()) нужна, чтобы рамки предыдущего кадра не накладывались на новые, так как текст постоянно меняется.
Что показывают границы на практике
Запустив пример, вы сразу увидите разницу:
1. **text1 (стандартный)**: Рамка плотно прилегает к символам.
2. **text2 (крупный)**: Рамка значительно больше из-за увеличенного fontSize.
3. **text3 (с эффектами)**: Рамка самая большая, потому что метод getBounds() учитывает не только размер шрифта, но и добавленную обводку (stroke) и тень (shadow). Это и есть реальная область, которую объект занимает на игровом поле.
Это наглядно демонстрирует, что визуальное представление текста и его математические границы — разные вещи. Для точного позиционирования или проверки пересечений всегда опирайтесь на getBounds().
Что попробовать дальше
Метод getBounds() — это надежный способ получить точные габариты текстового объекта в Phaser, учитывая все примененные к нему стили и эффекты. Используйте эту технику для точного выравнивания интерфейса, реализации сложных переходов или даже для создания нестандартных коллизий, основанных на тексте. Поэкспериментируйте: попробуйте выровнять несколько текстовых объектов по центру, используя их границы, или создайте интерактивную кнопку, где область нажатия определяется границами текстовой метки.
