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

Многие игровые ресурсы, такие как старые аудиотреки или пользовательские форматы данных, хранятся в бинарном виде. Phaser позволяет не только загружать такие файлы, но и извлекать из них полезную информацию. Этот пример показывает, как загрузить файл в формате MOD (старый аудиоформат) и распарсить его заголовок, чтобы получить название и данные семплов. Этот подход пригодится для чтения конфигов, пользовательских карт или любых нестандартных ресурсов.

Версия 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/act_of_impulse.mod');
    }

    create ()
    {
        const 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 осуществляется через Loader API. В методе preload() сцена настраивает базовый URL для загрузки и вызывает метод load.binary(). Этот метод регистрирует файл в загрузочной очереди под заданным ключом.

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

После завершения загрузки файл сохраняется в бинарном кэше игры под ключом 'mod'. Phaser не пытается интерпретировать содержимое файла, он просто предоставляет доступ к исходным байтам.

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

Чтобы начать работу с данными, их нужно получить из кэша. Phaser хранит бинарные данные в виде объекта ArrayBuffer. Для удобства работы с байтами этот буфер оборачивается в типизированный массив Uint8Array. Каждый элемент такого массива — это число от 0 до 255, представляющее один байт данных.

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

Теперь buffer — это массив байтов, с которым можно работать в цикле, используя индексы. Это основа для парсинга любого бинарного формата.

Парсинг строк из бинарных данных

Бинарные форматы часто содержат текстовые строки, записанные как последовательности байтов (обычно в кодировке ASCII). Чтобы извлечь такую строку, нужно взять диапазон байтов и преобразовать каждый байт в символ. В примере для этого создана вспомогательная функция getString.

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

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

Извлечение конкретных данных из формата MOD

Зная структуру формата MOD, можно извлечь из файла конкретные данные. В этом примере парсятся сигнатура, название трека и список семплов. Смещения (оффсеты) для этих данных жестко заданы в спецификации формата.

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

Полученные строки затем отображаются на экране с помощью текстовых объектов Phaser.

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

Phaser предоставляет простой и эффективный способ загрузки и низкоуровневой обработки бинарных файлов. Этот механизм открывает двери для работы с пользовательскими форматами данных, будь то карты уровней, сейвы игроков или старые медиафайлы. Для экспериментов попробуйте загрузить другой бинарный файл (например, .sav файл) и написать парсер для его заголовка, или используйте этот подход для динамической загрузки конфигурационных данных вашей игры.