О чем этот пример

Использование нестандартных шрифтов — мощный инструмент для создания уникальной атмосферы в вашей игре. Однако их корректная загрузка в Phaser требует понимания особенностей веб-платформы. В этой статье мы разберем два практических подхода: динамическое добавление шрифтов через CSS и использование библиотеки Google WebFont Loader. Вы научитесь надежно подключать любые шрифты формата OTF/TTF, избегая проблем с их отображением на этапе рендеринга сцены.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    init ()
    {
        //  Inject our CSS
        const element = document.createElement('style');

        document.head.appendChild(element);

        const sheet = element.sheet;

        let styles = '@font-face { font-family: "troika"; src: url("assets/fonts/ttf/troika.otf") format("opentype"); }\n';

        sheet.insertRule(styles, 0);

        styles = '@font-face { font-family: "Caroni"; src: url("assets/fonts/ttf/caroni.otf") format("opentype"); }';

        sheet.insertRule(styles, 0);
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.script('webfont', 'https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js');
    }

    create ()
    {
        const add = this.add;
        const input = this.input;

        WebFont.load({
            custom: {
                families: [ 'troika', 'Caroni' ]
            },
            active: function ()
            {
                add.text(32, 32, 'The face of the\nmoon was in\nshadow.', { fontFamily: 'troika', fontSize: 80, color: '#ff0000' }).setShadow(2, 2, '#333333', 2, false, true);

                add.text(150, 350, 'Waves flung themselves\nat the blue evening.', { fontFamily: 'Caroni', fontSize: 64, color: '#5656ee' });
            }
        });
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    backgroundColor: '#efefef',
    scene: Example
};

const game = new Phaser.Game(config);

Проблема загрузки шрифтов в Phaser

В отличие от обычных ресурсов (изображений, аудио), шрифты требуют предварительной регистрации в браузере через CSS-правило @font-face перед тем, как их можно будет использовать в Canvas. Если попытаться создать текст со шрифтом, который еще не загружен или не объявлен, браузер просто проигнорирует указанное семейство.

Phaser сам по себе не управляет процессом объявления шрифтов. Это ответственность разработчика. Поэтому нам нужно убедиться, что правило @font-face добавлено в документ до вызова методов создания текста, таких как this.add.text().

Способ 1: Динамическое добавление CSS через JavaScript

В методе init() сцены происходит программное создание и внедрение CSS-стилей в документ. Это гарантирует, что шрифты будут объявлены на самом раннем этапе жизненного цикла сцены.

init ()
{
    // Создаем элемент <style>
    const element = document.createElement('style');
    document.head.appendChild(element);
    const sheet = element.sheet;
    // Определяем первое правило @font-face
    let styles = '@font-face { font-family: \"troika\"; src: url(\"assets/fonts/ttf/troika.otf\") format(\"opentype\"); }\n';
    sheet.insertRule(styles, 0);
    // Определяем второе правило @font-face
    styles = '@font-face { font-family: \"Caroni\"; src: url(\"assets/fonts/ttf/caroni.otf\") format(\"opentype\"); }';
    sheet.insertRule(styles, 0);
}

Ключевые моменты: 1. Мы получаем доступ к таблице стилей (sheet) созданного элемента. 2. Метод sheet.insertRule() добавляет CSS-правило. Второй аргумент `0` указывает на позицию вставки (в начало). 3. Обратите внимание на экранирование кавычек внутри строки JavaScript (\"). Это необходимо для корректного формирования итогового CSS.

Способ 2: Использование Google WebFont Loader

Второй подход использует популярную библиотеку WebFont Loader, которая предоставляет удобный API и события для контроля загрузки шрифтов. Сначала мы загружаем саму библиотеку как внешний скрипт.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.script('webfont', 'https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js');
}

Метод this.load.script() загружает JavaScript-файл и добавляет его в глобальную область видимости. После загрузки становится доступен объект WebFont.

Затем, в методе create(), мы настраиваем и запускаем загрузчик. Создание текста оборачивается в коллбэк active, который выполнится только после успешной загрузки всех указанных шрифтов.

create ()
{
    const add = this.add;
    WebFont.load({
        custom: {
            families: [ 'troika', 'Caroni' ] // Указываем семейства, объявленные ранее в CSS
        },
        active: function ()
        {
            // Этот код выполнится после загрузки шрифтов
            add.text(32, 32, 'The face...', { fontFamily: 'troika', fontSize: 80, color: '#ff0000' }).setShadow(2, 2, '#333333', 2, false, true);
            add.text(150, 350, 'Waves flung...', { fontFamily: 'Caroni', fontSize: 64, color: '#5656ee' });
        }
    });
}

Важно: Этот подход полагается на то, что правило @font-face уже существует в CSS (мы добавили его в init()). Библиотека лишь управляет процессом загрузки файла шрифта и вызовом событий.

Объединение подходов и порядок работы

Представленный пример демонстрирует гибридный подход, который является наиболее надежным: 1. **Фаза init**: Шрифты объявляются через CSS. Браузер узнает об их существовании. 2. **Фаза preload**: Загружается скрипт-менеджер WebFont Loader. 3. **Фаза create**: Запускается загрузчик, который отслеживает готовность файлов шрифтов. Текст создается строго внутри коллбэка active.

Такой порядок гарантирует, что к моменту рендеринга текст будет использовать именно те файлы шрифтов, которые мы указали, а не fallback-шрифт браузера.

Настройка объекта текста

Метод this.add.text() принимает объект конфигурации. Для работы со шрифтами ключевыми являются свойства fontFamily и fontSize.

{ fontFamily: 'troika', fontSize: 80, color: '#ff0000' }

Значение fontFamily должно в точности соответствовать имени, объявленному в правиле @font-face. После создания объект текста возвращается, и к нему можно применять методы, такие как setShadow().

.setShadow(2, 2, '#333333', 2, false, true)

Этот вызов устанавливает тень со смещением по X и Y на 2 пикселя, цветом #333333, размытием в 2 пикселя. Последние два аргумента false, true означают: не использовать тень для заполнения (stroke) и использовать ее для самого текстового блока соответственно.

Что попробовать дальше

Для корректного использования кастомных шрифтов в Phaser необходимо предварительно объявить их через CSS @font-face и дождаться их загрузки. Гибридный подход из примера — отличная основа. Для экспериментов попробуйте: 1. Загружать шрифты с удаленного CDN, указав полный URL в src. 2. Использовать другие форматы (например, format('woff2')). 3. Создать систему предзагрузки, которая показывает лоадер до срабатывания события active от WebFont Loader. 4. Динамически менять fontFamily у уже созданного текстового объекта, если оба шрифта уже загружены.