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

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

Версия 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.image('logo', 'assets/sprites/phaser2.png');

        this.load.setPath('assets/spine/3.8/demos/');

        this.load.spine('set1', 'demos.json', [ 'atlas1.atlas' ], true);
    }

    create ()
    {
        this.add.image(32, 32, 'logo').setOrigin(0);

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

            this.make.spine({
                x: 600,
                y: 600,
                key: 'set1.spineboy'
            }, true);

        });

        window.setTimeout(() => {

            this.make.spine({
                x: 400,
                y: 600,
                key: 'set1.spineboy'
            }, false);

        }, 1000);
    }
}

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

const game = new Phaser.Game(config);

Настройка сцены и загрузка плагина

В конструкторе сцены происходит предварительная настройка. Ключевой момент — указание, что для этой сцены нужно загрузить и использовать плагин SpinePlugin. Это делается через конфигурационный объект, передаваемый в super().

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

Параметр pack определяет файлы, которые нужно загрузить до вызова preload(). Здесь мы указываем загрузку Scene Plugin (type: 'scenePlugin'). Плагин будет доступен в сцене по ключу sceneKey: 'spine'. Использование отладочной версии (SpinePluginDebug.js) может быть полезно для диагностики.

Загрузка ресурсов: изображение и данные Spine

Метод preload() отвечает за загрузку всех необходимых для сцены ресурсов. Важно правильно задавать пути и использовать специфичные для Spine методы загрузки.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('logo', 'assets/sprites/phaser2.png');

    this.load.setPath('assets/spine/3.8/demos/');

    this.load.spine('set1', 'demos.json', [ 'atlas1.atlas' ], true);
}

Сначала задаётся базовый URL для всех последующих загрузок. Затем загружается обычное изображение-логотип. Далее метод this.load.setPath() меняет текущий путь на папку с демо-данными Spine. Ключевой вызов — this.load.spine(). Он загружает данные Spine: - 'set1' — ключ, по которому набор данных будет доступен в кеше. - 'demos.json' — файл скелета (скелет, кости, анимации). - [ 'atlas1.atlas' ] — массив файлов атласов текстур. Именно здесь часто возникает ошибка, если указать неверный путь или имя файла. - true — флаг, указывающий на необходимость загрузки связанных с атласом PNG-изображений текстур. Если false, текстуры нужно загружать отдельно.

Создание Spine-объектов: немедленно и с задержкой

В методе create() мы создаём объекты. Пример наглядно показывает разницу между созданием объекта по клику мыши и созданием с задержкой.

create ()
{
    this.add.image(32, 32, 'logo').setOrigin(0);

    this.input.once('pointerdown', () => {
        this.make.spine({
            x: 600,
            y: 600,
            key: 'set1.spineboy'
        }, true);
    });

    window.setTimeout(() => {
        this.make.spine({
            x: 400,
            y: 600,
            key: 'set1.spineboy'
        }, false);
    }, 1000);
}

Сначала добавляется статичное лого. Затем на событие однократного клика (pointerdown) назначается создание Spine-объекта через фабрику this.make.spine(). Объект создаётся в точке (600, 600). Ключ 'set1.spineboy' состоит из двух частей: 'set1' — ключ загруженного набора данных, 'spineboy' — имя конкретного скелета внутри этого JSON-файла. Второй аргумент true означает, что анимация начнёт проигрываться сразу после создания.

Далее, с помощью setTimeout, через секунду создаётся второй такой же объект, но с аргументом false. Это значит, что его анимация будет остановлена в начальном состоянии (T-pose).

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

Код завершается созданием экземпляра игры Phaser.Game с необходимой конфигурацией.

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

const game = new Phaser.Game(config);

Конфиг задаёт использование WebGL-рендерера (type: Phaser.WEBGL), что рекомендуется для Spine из-за его производительности. Указывается ID родительского HTML-элемента (parent), размеры холста и цвет фона. Сама сцена Example передаётся в свойстве scene. После этого создание объекта new Phaser.Game(config) запускает весь жизненный цикл.

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

Корректная загрузка Spine-анимаций в Phaser сводится к точному указанию путей к JSON-файлу скелета и файлам атласов в методе this.load.spine(). Фабрика this.make.spine() позволяет удобно создавать экземпляры моделей. Для экспериментов попробуйте

  1. изменить второй параметр в this.make.spine() на false для первого объекта и на true для второго, чтобы наблюдать разницу в старте анимации
  2. загрузить другой набор данных Spine и создать из него объект
  3. добавить обработку событий анимации (start, complete) для созданных spine-объектов через их свойство .on