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

Если вы разрабатываете игру для аудитории, которая использует языки с письмом справа налево (RTL), такие как арабский или иврит, вы можете столкнуться с неправильным отображением текста. Phaser предоставляет встроенную опцию `rtl` для объектов `Text`, но её использование имеет нюансы. Этот пример наглядно демонстрирует, как правильно настраивать RTL-текст и решать распространённые проблемы, связанные со смешением направлений и пунктуацией.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    create ()
    {
        const str1 = "این یک آزمایش است.";

        // Few sentences with punctuation and numerals.
        const str2 = "۱ آزمایش. 2 آزمایش، سه آزمایش & Foo آزمایش!";

        // Needs implicit bidi marks to display correctly.
        const str3 = "آزمایش برای Foo Ltd. و Bar Inc. باشد که آزموده شود.";

        // Implicit bidi marks added; "Foo Ltd.‎ و Bar Inc.‎"
        const str4 = "آزمایش برای Foo Ltd.‎ و Bar Inc.‎ باشد که آزموده شود.";

        this.add.text(10, 10, 'Normal?!');

        const t1 = this.add.text(700, 100, str1, { fontFamily: 'Arial', fontSize: 32, color: '#000000', rtl: true });
        this.add.text(700, 200, str2, { fontFamily: 'Arial', fontSize: 32, color: '#000000', rtl: true });
        this.add.text(700, 300, str3, { fontFamily: 'Arial', fontSize: 32, color: '#000000', rtl: true });
        this.add.text(700, 400, str4, { fontFamily: 'Arial', fontSize: 32, color: '#000000', rtl: true });

        let i = 0;

        this.input.on('pointerdown', () => {

            i++;

            t1.setText(`${str1} ${i}`);

        });
    }
}

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

const game = new Phaser.Game(config);

Что такое RTL и зачем нужна опция в Phaser

RTL (Right-To-Left) — это направление письма, характерное для арабского, иврита и некоторых других языков. Браузеры и движки рендеринга текста должны правильно обрабатывать порядок символов и выравнивание.

В Phaser, при создании текстового объекта с помощью this.add.text(), можно передать объект стилей. Ключевое свойство rtl: true указывает движку, что текст должен обрабатываться и отрисовываться в режиме письма справа налево. Без этой настройки текст на RTL-языке может отображаться зеркально или с неправильным порядком символов.

const t1 = this.add.text(700, 100, str1, { fontFamily: 'Arial', fontSize: 32, color: '#000000', rtl: true });

Базовое использование и демонстрация строк

В примере создаётся сцена, которая отображает четыре строки текста на персидском языке (который использует арабскую вязь и является RTL). Каждая строка иллюстрирует определённый аспект.

str1 — простая фраза "Это тест.". Она корректно отобразится с опцией rtl: true. str2 — более сложная строка, содержащая цифры (в том числе восточно-арабские "۱") и знаки препинания. Это проверка корректности обработки смешанного контента.

Все текстовые объекты позиционируются по координате X=700, что смещает их к правому краю, что логично для RTL-интерфейса, где чтение начинается справа.

const str1 = "این یک آزمایش است.";
const str2 = "۱ آزمایش. 2 آزمایش، سه آزمایش & Foo آزمایش!";
this.add.text(700, 200, str2, { fontFamily: 'Arial', fontSize: 32, color: '#000000', rtl: true });

Проблема с изоляцией и LRM-символы

Основная сложность возникает при вставке в RTL-текст фрагментов на LTR-языках (например, названий компаний на английском). В строке str3 есть "Foo Ltd. و Bar Inc.". Браузерный движок рендеринга текста может неправильно определить границы этих вставок, что приведёт к хаотичному порядку символов.

Для решения этой проблемы используются управляющие символы Unicode, явно задающие направление. В строке str4 после каждой английской аббревиатуры добавлен символ LRM (Left-to-Right Mark, `‎или‎` в HTML). Этот невидимый символ приказывает движку рендеринга трактовать последующий текст как LTR, корректно изолируя его от основного RTL-потока.

// Проблемная строка без явных меток
const str3 = "آزمایش برای Foo Ltd. و Bar Inc. باشد که آزموده شود.";
// Исправленная строка с LRM-символами (U+200E)
const str4 = "آزمایش برای Foo Ltd.‎ و Bar Inc.‎ باشد که آزموده شود.";

Динамическое обновление RTL-текста

Пример также показывает, как динамически менять текст в RTL-объекте. На сцене отслеживается событие клика (pointerdown). При каждом клике счётчик `iувеличивается, и новое значение добавляется к исходной строкеstr1в текстовом объектеt1`.

Важно: при динамической установке текста через setText() Phaser сохраняет первоначальные стили объекта, включая флаг rtl. Поэтому обновлённый текст также будет отрендерен в режиме RTL.

let i = 0;
this.input.on('pointerdown', () => {
    i++;
    t1.setText(`${str1} ${i}`);
});

Конфигурация игры и сцены

Код завершается стандартной для Phaser конфигурацией игры. Обратите внимание, что для корректного отображения примера важно использовать шрифт, поддерживающий необходимые символы (здесь — Arial). Фон сделан светлым (#efefef), чтобы чёрный текст хорошо читался.

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

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

Встроенная поддержка RTL в Phaser через опцию rtl: true решает базовую задачу отображения текста справа налево. Однако для профессиональной реализации, особенно при смешении языков, необходимо вручную управлять изоляцией направлений с помощью Unicode-символов, таких как LRM (U+200E). Для экспериментов попробуйте

  1. создать интерфейс игры с поддержкой арабского/иврита
  2. смешать в одной строке RTL-текст, LTR-цитаты и цифры
  3. реализовать динамическую подстановку имён игроков (на разных языках) в RTL-сообщения