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

Игры часто используют нестандартные форматы данных — конфиги, сейвы или, как в нашем случае, трекерную музыку. Phaser позволяет легко загружать бинарные файлы и работать с ними на низком уровне. В этой статье мы разберем, как загрузить бинарный файл, извлечь из него текстовую информацию и отобразить её на экране. Этот подход полезен для создания инструментов для моддинга, просмотра метаданных пользовательских файлов или импорта данных из специфичных форматов.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.binary('mod', 'assets/audio/protracker/elysium.mod', Uint8Array);
    }

    create ()
    {
        const buffer = this.cache.binary.get('mod');

        // var buffer = new Uint8Array(this.cache.binary.get('mod'));

        //   getString scans the binary file between the two values given, 
        //   returning the characters it finds there as a string

        const signature = this.getString(buffer, 1080, 1084);

        const text = this.add.text(32, 32, `Signature: ${signature}`, { fill: '#ffffff' });
        text.setShadow(2, 2, 'rgba(0,0,0,0.5)', 0);

        const title = this.getString(buffer, 0, 20);
        const text2 = this.add.text(32, 64, `Title: ${title}`, { fill: '#ffffff' });
        text2.setShadow(2, 2, 'rgba(0,0,0,0.5)', 0);

        //  Get the sample data
        const sampleText = [];

        for (let i = 0; i < 31; i++)
        {
            const st = 20 + i * 30;
            sampleText.push(this.getString(buffer, st, st + 22));
        }

        const text3 = this.add.text(400, 32, sampleText, { fill: '#ffffff' });
        text3.setShadow(2, 2, 'rgba(0,0,0,0.5)', 0);
    }

    getString (buffer, start, end)
    {
        let output = '';

        for (let i = start; i < end; i++)
        {
            output += String.fromCharCode(buffer[i]);
        }

        return output;
    }
}

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

const game = new Phaser.Game(config);

Загрузка бинарных данных

Phaser предоставляет удобный загрузчик для работы с бинарными данными. В методе preload мы настраиваем базовый URL и указываем загрузчику, какой файл нам нужен и в каком формате его ожидать.

Ключевой метод — this.load.binary(). Он принимает три аргумента: - Ключ (key), по которому данные будут доступны в кеше. - Путь к файлу. - Тип данных, в который будет преобразован загруженный файл. В нашем случае — Uint8Array.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.binary('mod', 'assets/audio/protracker/elysium.mod', Uint8Array);

После успешной загрузки файл будет доступен в бинарном кеше с ключом 'mod'.

Доступ к данным и извлечение строк

После загрузки сцены в методе create мы можем получить наши данные из кеша. Phaser хранит бинарные данные в this.cache.binary.

const buffer = this.cache.binary.get('mod');

Теперь в переменной buffer находится наш Uint8Array. Это массив беззнаковых 8-битных целых чисел (байтов). Чтобы прочитать из него текстовую информацию, нужна вспомогательная функция, которая преобразует коды символов в строку.

В примере используется метод getString. Он проходит по массиву от начального (start) до конечного (end) индекса, преобразуя каждый байт в символ с помощью String.fromCharCode() и собирая их в одну строку.

getString(buffer, start, end) {
    let output = '';
    for (let i = start; i < end; i++) {
        output += String.fromCharCode(buffer[i]);
    }
    return output;
}

Парсинг структуры файла

Формат файла .mod (ProTracker) имеет четкую структуру. Зная смещения (оффсеты) в файле, мы можем извлечь нужную информацию.

1. **Сигнатура файла** находится по смещению 1080 байт и имеет длину 4 символа. Это специальная метка, идентифицирующая формат. 2. **Название трека** занимает первые 20 байт файла. 3. **Список сэмплов** начинается с 20-го байта. Описание каждого сэмпла занимает 30 байт, а его название хранится в первых 22 байтах этого блока.

Код ниже демонстрирует, как, зная эти смещения, извлечь все данные за несколько строк.

// Извлечение сигнатуры
const signature = this.getString(buffer, 1080, 1084);
// Извлечение названия трека
const title = this.getString(buffer, 0, 20);
// Извлечение списка названий сэмплов (31 шт.)
const sampleText = [];
for (let i = 0; i < 31; i++) {
    const st = 20 + i * 30; // Начало блока i-го сэмпла
    sampleText.push(this.getString(buffer, st, st + 22)); // Его название
}

Отображение результата

Извлеченные данные — это обычные строки JavaScript. Phaser с помощью объекта this.add.text() позволяет легко вывести их на экран.

Метод add.text() принимает координаты X, Y, сам текст и объект стиля. Для улучшения читаемости к тексту можно добавить тень с помощью метода .setShadow().

// Создание текстового объекта для сигнатуры
const text = this.add.text(32, 32, `Signature: ${signature}`, { fill: '#ffffff' });
text.setShadow(2, 2, 'rgba(0,0,0,0.5)', 0);
// Создание текстового объекта для названия трека
const text2 = this.add.text(32, 64, `Title: ${title}`, { fill: '#ffffff' });
text2.setShadow(2, 2, 'rgba(0,0,0,0.5)', 0);
// Создание текстового объекта для списка сэмплов.
// Массив sampleText будет автоматически преобразован в строку, разделенную запятыми.
const text3 = this.add.text(400, 32, sampleText, { fill: '#ffffff' });
text3.setShadow(2, 2, 'rgba(0,0,0,0.5)', 0);

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

Загрузка и парсинг бинарных данных в Phaser — мощный инструмент для работы с пользовательскими форматами файлов. Вы можете адаптировать этот подход для загрузки конфигураций, сохранений игр, кастомных карт или ресурсов. Для экспериментов попробуйте: загрузить другой бинарный файл (например, .wav-заголовок) и прочитать его параметры (частота дискретизации, битность); создать простой инструмент для просмотра HEX-дампа файлов прямо в игре; или реализовать загрузку уровня игры из бинарного файла собственного формата.