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

При разработке игр с использованием Spine для анимаций часто возникает необходимость загрузить несколько скелетных моделей, использующих один и тот же атлас текстур. Это может быть полезно для оптимизации памяти и ускорения загрузки, когда разные персонажи или объекты разделяют части текстур. Однако, если сделать это неправильно, можно столкнуться с ошибками загрузки или некорректным отображением. В этой статье мы разберем пример из официального репозитория Phaser, который демонстрирует корректный подход к загрузке двух Spine-объектов, использующих один файл атласа (.atlas), но с разными путями к изображениям.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super({
            pack: {
                files: [
                    { type: 'scenePlugin', key: 'SpinePlugin', url: 'plugins/3.8.95/SpinePluginDebug.js', sceneKey: 'spine' }
                ]
            }
        });
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.setPath('assets/spine/3.8/coin');
        this.load.spine('coin1', 'coin-pro.json', 'coin-pro.atlas');

        this.load.setPath('assets/spine/3.8/coin2');
        this.load.spine('coin2', 'coin-pro.json', 'coin-pro.atlas');
    }

    create ()
    {
        this.add.spine(300, 300, 'coin1', 'animation', true);
        this.add.spine(600, 300, 'coin2', 'animation', true);
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и загрузка плагина Spine

Первым шагом является настройка сцены для работы с Spine-анимациями. Для этого в конструкторе класса сцены мы указываем конфигурацию для загрузки плагина SpinePlugin. Это важно, так как Spine не является частью ядра Phaser и подключается как внешний плагин.

constructor ()
{
    super({
        pack: {
            files: [
                { type: 'scenePlugin', key: 'SpinePlugin', url: 'plugins/3.8.95/SpinePluginDebug.js', sceneKey: 'spine' }
            ]
        }
    });
}

Ключевые параметры здесь: type: 'scenePlugin' указывает на тип загружаемого файла, key — это внутренний ключ плагина, url — путь к JavaScript-файлу плагина, а sceneKey: 'spine' задает ключ, под которым плагин будет доступен в сцене (например, как this.spine или через this.add.spine).

Загрузка ресурсов с разными базовыми путями

В методе preload происходит загрузка двух Spine-скелетов. Оба используют один и тот же JSON-файл с данными анимации (coin-pro.json) и один файл атласа (coin-pro.atlas). Однако, изображения для этих атласов находятся в разных директориях (assets/spine/3.8/coin и assets/spine/3.8/coin2). Для корректной загрузки необходимо временно менять базовый путь с помощью this.load.setPath.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.setPath('assets/spine/3.8/coin');
    this.load.spine('coin1', 'coin-pro.json', 'coin-pro.atlas');

    this.load.setPath('assets/spine/3.8/coin2');
    this.load.spine('coin2', 'coin-pro.json', 'coin-pro.atlas');
}

Метод this.load.setPath устанавливает относительный путь для последующих загрузок. Это позволяет загрузить один и тот же файл .atlas, но при этом Loader будет искать текстуры, на которые ссылается атлас, в указанной директории. Ключи 'coin1' и 'coin2' — это уникальные идентификаторы загруженных скелетов, которые будут использоваться при создании объектов.

Создание и отображение Spine-объектов

После загрузки ресурсов в методе create мы можем создавать экземпляры Spine-объектов. Каждый объект создается с использованием своего уникального ключа, загруженного ранее. Это позволяет иметь две независимые анимации, которые визуально могут отличаться, если текстуры в папках coin и coin2 разные.

create ()
{
    this.add.spine(300, 300, 'coin1', 'animation', true);
    this.add.spine(600, 300, 'coin2', 'animation', true);
}

Метод this.add.spine принимает координаты X и Y, ключ скелета (например, 'coin1'), имя анимации для проигрывания ('animation') и булево значение, указывающее, следует ли проигрывать анимацию в цикле (true). Оба объекта будут отображены на сцене в разных позициях.

Конфигурация и запуск игры

Финальный шаг — настройка объекта конфигурации игры и ее инстанцирование. В конфиге мы указываем использование WebGL, что необходимо для корректной работы Spine, задаем размеры холста и передаем наш класс сцены.

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

const game = new Phaser.Game(config);

Параметр type: Phaser.WEBGL обеспечивает аппаратное ускорение рендеринга. parent — это ID HTML-элемента, в который будет встроен canvas. Черный фон (backgroundColor) помогает визуально выделить анимации. Создание экземпляра Phaser.Game с этой конфигурацией запускает весь жизненный цикл игры.

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

Этот пример наглядно показывает, как правильно загружать несколько Spine-скелетов, разделяющих один атлас, но имеющих разные наборы текстур. Ключевой момент — управление путями загрузки через setPath. Для экспериментов попробуйте изменить пути на свои ассеты, использовать разные имена анимаций или настроить параметры отображения, такие как масштаб или цвет tint. Также можно поэкспериментировать с загрузкой одного скелета, но с разными атласами, чтобы создать вариативность внешнего вида персонажей.