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

Загрузка ресурсов — критичный этап для игрового опыта. Долгое ожидание без визуальной обратной связи может заставить игрока закрыть игру. Этот пример демонстрирует мощный подход Phaser: использование `Asset Pack` для предзагрузки критически важных ресурсов (например, анимации) прямо в конструкторе сцены. Это позволяет запустить анимацию или индикатор загрузки практически мгновенно, пока в фоне загружается основной контент игры. Вы научитесь разделять ресурсы на "немедленно необходимые" и "фоновые", создавая более плавный и профессиональный пользовательский опыт.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    logo;
    constructor ()
    {
        super({
            pack: {
                files: [
                    {
                        type: 'spritesheet',
                        key: 'muybridge',
                        url: 'assets/animations/muybridge01.png',
                        frameConfig: { frameWidth: 119, frameHeight: 228 }
                    }
                ]
            }
        });
    }

    init ()
    {
        var config = {
            key: 'run',
            frames: 'muybridge',
            frameRate: 15,
            repeat: -1
        };

        this.anims.create(config);
        this.add.sprite(400, 300, 'muybridge').play('run');
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.setPath('assets/sprites/');

        this.load.image('atari130xe');
        this.load.image('atari400');
        this.load.image('atari800');
        this.load.image('atari800xl');
        this.load.image('128x128');
        this.load.image('128x128-v2');
        this.load.image('a');
        this.load.image('advanced_wars_land');
        this.load.image('advanced_wars_tank');
        this.load.image('amiga-cursor');
        this.load.image('aqua_ball');
        this.load.image('arrow');
        this.load.image('arrows');
        this.load.image('asteroids_ship');
        this.load.image('asteroids_ship_white');
        this.load.image('asuna_by_vali233');
        this.load.image('atari1200xl');
        this.load.image('b');
        this.load.image('baddie_cat_1');
        this.load.image('balls');
        this.load.image('beball1');
        this.load.image('bikkuriman');
        this.load.image('block');
        this.load.image('blue_ball');
        this.load.image('bluebar');
        this.load.image('bluemetal_32x32x4');
        this.load.image('bobs-by-cleathley');
        this.load.image('bsquadron1');
        this.load.image('bsquadron2');
        this.load.image('bsquadron3');
        this.load.image('budbrain_chick');
        this.load.image('bullet');
        this.load.image('bunny');
        this.load.image('cakewalk');
        this.load.image('car');
        this.load.image('carrot');
        this.load.image('centroid');
        this.load.image('chain');
        this.load.image('checkerboard');
        this.load.image('chick');
        this.load.image('chunk');
        this.load.image('clown');
        this.load.image('coin');
        this.load.image('cokecan');
        this.load.image('columns-blue');
        this.load.image('columns-orange');
        this.load.image('columns-red');
        this.load.image('copy-that-floppy');
        this.load.image('crate');
        this.load.image('crate32');
        this.load.image('cursor-rotate');
        this.load.image('darkwing_crazy');
        this.load.image('default');
        this.load.image('diamond');
        this.load.image('dragcircle');
        this.load.image('drawcursor');
        this.load.image('dude');
        this.load.image('eggplant');
        this.load.image('elephant');
        this.load.image('enemy-bullet');
        this.load.image('exocet_spaceman');
        this.load.image('explosion');
        this.load.image('eyes');
        this.load.image('firstaid');
        this.load.image('fish-136x80');
        this.load.image('flectrum');
        this.load.image('flectrum2');
        this.load.image('fork');
        this.load.image('fruitnveg32wh37');
        this.load.image('fruitnveg64wh37');
        this.load.image('fuji');
        this.load.image('gameboy_seize_color_40x60');
        this.load.image('gem');
        this.load.image('gem-blue-16x16x4');
        this.load.image('gem-green-16x16x4');
        this.load.image('gem-red-16x16x4');
        this.load.image('ghost');
        this.load.image('green_ball');
        this.load.image('healthbar');
        this.load.image('helix');
        this.load.image('hello');
        this.load.image('hotdog');
        this.load.image('humstar');
        this.load.image('ilkke');
        this.load.image('interference_ball_48x48');
        this.load.image('interference_tunnel');
        this.load.image('jets');
        this.load.image('kirito_by_vali233');
        this.load.image('lemming');
        this.load.image('longarrow');
        this.load.image('longarrow-down');
        this.load.image('longarrow-white');
        this.load.image('longarrow2');
        this.load.image('loop');
        this.load.image('maggot');
        this.load.image('magnify-glass-inside');
        this.load.image('magnify-glass-outside');
        this.load.image('mask1');
        this.load.image('mask2');
        this.load.image('master');
        this.load.image('melon');
        this.load.image('metalface78x92');
        this.load.image('metalslug_monster39x40');
        this.load.image('metalslug_mummy37x45');
        this.load.image('mine');
        this.load.image('mouse_jim_sachs');
        this.load.image('mushroom');
        this.load.image('mushroom2');
        this.load.image('onion');
        this.load.image('orange-cat1');
        this.load.image('orange-cat2');
        this.load.image('orb-blue');
        this.load.image('orb-green');
        this.load.image('orb-red');
        this.load.image('oz_pov_melting_disk');
        this.load.image('pacman_by_oz_28x28');
        this.load.image('palm-tree-left');
        this.load.image('palm-tree-right');
        this.load.image('pangball');
        this.load.image('parsec');
        this.load.image('particle1');
        this.load.image('pepper');
        this.load.image('phaser');
        this.load.image('phaser-dude');
        this.load.image('phaser-large');
        this.load.image('phaser-ship');
        this.load.image('phaser_tiny');
        this.load.image('phaser1');
        this.load.image('phaser2');
        this.load.image('phaser3-logo-alpha');
        this.load.image('pineapple');
        this.load.image('plane');
        this.load.image('platform');
        this.load.image('player');
        this.load.image('purple_ball');
        this.load.image('ra_dont_crack_under_pressure');
        this.load.image('rain');
        this.load.image('red_ball');
        this.load.image('rgblaser');
        this.load.image('saw');
        this.load.image('shinyball');
        this.load.image('ship');
        this.load.image('shmup-baddie');
        this.load.image('shmup-baddie-bullet');
        this.load.image('shmup-baddie2');
        this.load.image('shmup-baddie3');
        this.load.image('shmup-boom');
        this.load.image('shmup-bullet');
        this.load.image('shmup-ship');
        this.load.image('shmup-ship2');
        this.load.image('skull');
        this.load.image('slime');
        this.load.image('slimeeyes');
        this.load.image('snake');
        this.load.image('snowflake-pixel');
        this.load.image('snowflakes');
        this.load.image('snowflakes_large');
        this.load.image('sonic');
        this.load.image('sonic_havok_sanity');
        this.load.image('soundtracker');
        this.load.image('space-baddie');
        this.load.image('space-baddie-purple');
        this.load.image('spaceman');
        this.load.image('speakers');
        this.load.image('spikedball');
        this.load.image('spinObj_01');
        this.load.image('spinObj_02');
        this.load.image('spinObj_03');
        this.load.image('spinObj_04');
        this.load.image('spinObj_05');
        this.load.image('spinObj_06');
        this.load.image('spinObj_07');
        this.load.image('spinObj_08');
        this.load.image('splat');
        this.load.image('steelbox');
        this.load.image('stormlord-dragon96x64');
        this.load.image('strip1');
        this.load.image('strip2');
        this.load.image('tetrisblock1');
        this.load.image('tetrisblock2');
        this.load.image('tetrisblock3');
        this.load.image('thrust_ship');
        this.load.image('thrust_ship2');
        this.load.image('tinycar');
        this.load.image('tomato');
        this.load.image('treasure_trap');
        this.load.image('tree-european');
        this.load.image('ufo');
        this.load.image('ukko-fujilogy-texture-64x64');
        this.load.image('vu');
        this.load.image('wabbit');
        this.load.image('wasp');
        this.load.image('wizball');
        this.load.image('x2kship');
        this.load.image('xenon2_bomb');
        this.load.image('xenon2_ship');
        this.load.image('yellow_ball');
        this.load.image('zelda-hearts');
        this.load.image('zelda-life');
    }

    create ()
    {
        var keys = this.textures.getTextureKeys();

        for (var i = 0; i < keys.length; i++)
        {
            var x = Phaser.Math.Between(0, 800);
            var y = Phaser.Math.Between(0, 600);

            this.add.image(x, y, keys[i]);
        }

        this.logo = this.add.image(400, 300, 'phaser3-logo-alpha');
    }

    update ()
    {
        this.logo.rotation += 0.01;
    }
}

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

const game = new Phaser.Game(config);

Asset Pack: предзагрузка ключевых ресурсов

Phaser позволяет определить набор ресурсов (Asset Pack) прямо в конструкторе сцены. Эти ресурсы загружаются движком до вызова стандартных методов жизненного цикла сцены, таких как preload(). Это идеально для ассетов, которые должны быть доступны немедленно — например, для анимации индикатора загрузки.

В примере в конструктор передается объект конфигурации с полем pack. Внутри определяется массив файлов. В данном случае загружается один спрайтшит, но можно указать и изображения, и аудио.

constructor ()
{
    super({
        pack: {
            files: [
                {
                    type: 'spritesheet',
                    key: 'muybridge',
                    url: 'assets/animations/muybridge01.png',
                    frameConfig: { frameWidth: 119, frameHeight: 228 }
                }
            ]
        }
    });
}

Создание анимации до начала загрузки

Метод init() выполняется сразу после конструктора, но до preload(). К этому моменту ресурсы из Asset Pack уже загружены и доступны в кэше. Это позволяет немедленно создать и запустить анимацию, используя предзагруженный спрайтшит.

Здесь мы: 1. Создаем конфигурацию анимации run с помощью this.anims.create(). 2. Создаем спрайт и сразу запускаем на нем эту анимацию методом .play().

init ()
{
    var config = {
        key: 'run',
        frames: 'muybridge',
        frameRate: 15,
        repeat: -1
    };
    this.anims.create(config);
    this.add.sprite(400, 300, 'muybridge').play('run');
}

В результате, как только сцена инициализируется, в центре экрана сразу начнет проигрываться цикличная анимация бегущего человека, создавая визуальную активность.

Параллельная фоновая загрузка основного контента

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

Методы setBaseURL() и setPath() задают базовый путь для всех последующих загрузок, что упрощает указание путей.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.setPath('assets/sprites/');
    this.load.image('atari130xe');
    // ... загружается очень много других изображений
    this.load.image('zelda-life');
}

Использование загруженных ресурсов и интерактивность

Когда все ресурсы из preload() загружены, Phaser автоматически вызывает метод create(). Здесь демонстрируется, как можно работать с только что загруженными текстурами.

1.  `this.textures.getTextureKeys()` возвращает массив ключей всех доступных текстур (включая загруженные в `preload` и `pack`).
2.  Для каждого ключа создается изображение в случайной позиции на экране, используя `this.add.image()`.
3.  Отдельно создается и сохраняется в свойстве `this.logo` логотип Phaser.
create ()
{
    var keys = this.textures.getTextureKeys();
    for (var i = 0; i < keys.length; i++)
    {
        var x = Phaser.Math.Between(0, 800);
        var y = Phaser.Math.Between(0, 600);
        this.add.image(x, y, keys[i]);
    }
    this.logo = this.add.image(400, 300, 'phaser3-logo-alpha');
}

Добавление простой интерактивности

Метод update() вызывается на каждом кадре игры. В нем реализуется простая анимация — постоянное вращение логотипа Phaser. Это показывает, как легко добавить динамику к любому игровому объекту после его создания.

update ()
{
    this.logo.rotation += 0.01;
}

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

Этот паттерн — разделение ресурсов на критичные (в Asset Pack) и фоновые (в preload) — мощный инструмент для улучшения восприятия загрузки игры. Анимация или индикатор прогресса, запущенные мгновенно, сообщают игроку, что игра работает и контент скоро будет готов. Для экспериментов попробуйте

  1. Добавить в pack звук и проиграть его в init
  2. Создать в init полноценную сцену-заставку с прогресс-баром, который будет обновляться по событиям загрузчика (load.progress)
  3. Загружать через pack не спрайтшит, а atlas JSON-формата