О чем этот пример
Создание титров, прокручивающихся новостей или повествовательного текста — частые задачи в игровом дизайне. Пример вертикального скроллера на основе `DynamicBitmapText` демонстрирует, как легко реализовать плавную, управляемую временем прокрутку большого текстового блока. Этот подход экономит ресурсы и даёт полный контроль над отображением, что полезно для визуальных новелл, кат-сцен или отображения логов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
this.scroller;
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('neuro', 'assets/pics/neuromancer.jpg');
this.load.bitmapFont('atari', 'assets/fonts/bitmap/atari-smooth.png', 'assets/fonts/bitmap/atari-smooth.xml');
this.load.bitmapFont('desyrel', 'assets/fonts/bitmap/desyrel.png', 'assets/fonts/bitmap/desyrel.xml');
}
create ()
{
const 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"
];
this.add.image(512, 0, 'neuro').setOrigin(0.5, 0);
this.scroller = this.add.dynamicBitmapText(16, 600, 'desyrel', content, 24);
this.scroller.setSize(1024, 300);
}
update (time, delta)
{
this.scroller.scrollY += 0.03 * delta;
if (this.scroller.scrollY > 2100)
{
this.scroller.scrollY = 0;
}
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
_backgroundColor: '#2d2d2d',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка ресурсов и инициализация
Класс DynamicBitmapText работает с растровыми шрифтами (bitmap fonts), которые загружаются как изображение символов и XML-файл с разметкой. Это ключевое отличие от системных шрифтов: внешний вид текста гарантированно одинаков на всех устройствах.
В методе preload мы загружаем фоновое изображение и два растровых шрифта. Обрати внимание, что второй аргумент в load.bitmapFont — это путь к текстуре, а третий — к данным (XML).
this.load.bitmapFont('desyrel', 'assets/fonts/bitmap/desyrel.png', 'assets/fonts/bitmap/desyrel.xml');
Текст для прокрутки хранится в массиве строк content. Пустые строки используются для создания начальной паузы перед появлением первого предложения.
Создание текстового объекта с границами
В методе create сначала добавляется фоновое изображение. Затем создаётся основной объект — динамический растровый текст.
this.scroller = this.add.dynamicBitmapText(16, 600, 'desyrel', content, 24);
this.scroller.setSize(1024, 300);
Первые аргументы add.dynamicBitmapText — это стартовые координаты X и Y. Ключевой параметр — массив строк content, который будет отображаться. Последний аргумент 24 задаёт размер шрифта.
Метод setSize(width, height) устанавливает область отсечения (crop area) для текста. Текст, выходящий за пределы прямоугольника размером 1024x300 пикселя, виден не будет. Это создаёт эффект «окна», через которое мы видим лишь часть длинного текста. Начальная координата Y=600 помещает нижний край этого окна близко к низу экрана, чтобы текст появлялся снизу.
Принцип анимированной прокрутки
Магия движения происходит в методе update, который вызывается на каждом кадре игры.
this.scroller.scrollY += 0.03 * delta;
Свойство scrollY объекта DynamicBitmapText контролирует вертикальное смещение *внутри* текстовой области. Увеличивая его, мы «поднимаем» текст вверх относительно окна отсечения, создавая иллюзию, что текст ползёт вниз.
Умножение на delta (время, прошедшее с предыдущего кадра) делает скорость прокрутки независимой от частоты кадров (frame-rate independent). Скорость 0.03 — это множитель, который можно регулировать для ускорения или замедления текста.
if (this.scroller.scrollY > 2100) {
this.scroller.scrollY = 0;
}
Условный оператор проверяет, не «прокрутили» ли мы весь текст. Значение 2100 — это эмпирически подобранная высота всего текстового контента. Когда прокрутка превышает её, свойство scrollY сбрасывается в ноль, и анимация начинается заново, создавая бесконечную петлю.
Настройка сцены и запуск игры
Конфигурация игры стандартна. Важно, что в scene передаётся наш класс Example. Фон игры задан тёмно-серым цветом.
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
_backgroundColor: '#2d2d2d',
scene: Example
};
const game = new Phaser.Game(config);
Инициализация экземпляра Phaser.Game с этой конфигурацией запускает жизненный цикл сцены: preload, create, а затем непрерывный вызов update.
Что попробовать дальше
DynamicBitmapText — мощный инструмент для работы с анимированным текстом в Phaser. Используя его свойства scrollX и scrollY вместе с областью отсечения, можно реализовать не только вертикальные титры, но и горизонтальную бегущую строку, эффект печатной машинки (изменяя видимую область) или сложные многоуровневые текстовые анимации. Для экспериментов попробуй изменить setSize для создания узкой колонки, привязать скорость прокрутки к вводу игрока или реализовать паузу по нажатию клавиши.
