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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.atlas('soldier', 'assets/animations/soldier.png', 'assets/animations/soldier.json');
        this.load.image('bg', 'assets/pics/town-wreck.jpg');
    }

    create ()
    {
        this.add.image(400, 300, 'bg');

        const rambo = this.add.sprite(500, 500, 'soldier');

        //  The following animation is created directly on the 'rambo' Sprite.

        //  It cannot be used by any other sprite, and the key ('walk') is never added to
        //  the global Animation Manager, as it's kept local to this Sprite.
        rambo.anims.create({
            key: 'walk',
            frames: this.anims.generateFrameNames('soldier', { prefix: 'soldier_3_walk_', start: 1, end: 8 }),
            frameRate: 12,
            repeat: -1
        });

        //  Now let's create a new 'walk' animation that is stored in the global Animation Manager:
        this.anims.create({
            key: 'walk',
            frames: this.anims.generateFrameNames('soldier', { prefix: 'Soldier_2_walk_', start: 1, end: 8 }),
            frameRate: 12,
            repeat: -1
        });

        //  Because the rambo Sprite has its own 'walk' animation, it will play it:
        rambo.play('walk');

        //  However, this Sprite will play the global 'walk' animation, because it doesn't have its own:
        this.add.sprite(200, 500, 'soldier')
            .play('walk');
    }
}

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

const game = new Phaser.Game(config);

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

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

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('soldier', 'assets/animations/soldier.png', 'assets/animations/soldier.json');
this.load.image('bg', 'assets/pics/town-wreck.jpg');

В create сначала добавляется фон, а затем создаётся спрайт rambo с ключом текстуры 'soldier'. Этот спрайт пока что не имеет анимации.

this.add.image(400, 300, 'bg');
const rambo = this.add.sprite(500, 500, 'soldier');

Создание локальной анимации на спрайте

Локальная анимация принадлежит конкретному экземпляру спрайта. Она создаётся через его свойство anims. Такую анимацию нельзя использовать для других объектов, и её ключ не регистрируется в глобальном менеджере анимаций сцены (this.anims).

rambo.anims.create({
    key: 'walk',
    frames: this.anims.generateFrameNames('soldier', { prefix: 'soldier_3_walk_', start: 1, end: 8 }),
    frameRate: 12,
    repeat: -1
});

Метод this.anims.generateFrameNames генерирует массив кадров из атласа 'soldier', используя шаблон имени 'soldier_3_walk_' и номера от 1 до 8. Параметр repeat: -1 задаёт бесконечное повторение.

Создание глобальной анимации

Глобальная анимация создаётся через менеджер анимаций сцены this.anims и становится доступной для всех спрайтов, использующих тот же ключ текстуры ('soldier'). Это эффективно для одинаковых анимаций у множества объектов.

this.anims.create({
    key: 'walk',
    frames: this.anims.generateFrameNames('soldier', { prefix: 'Soldier_2_walk_', start: 1, end: 8 }),
    frameRate: 12,
    repeat: -1
});

Обратите внимание, что префикс кадров ('Soldier_2_walk_') отличается от префикса в локальной анимации, что позволяет использовать другой набор спрайтов для ходьбы.

Приоритет анимаций и вызов play()

При вызове метода play() спрайт сначала ищет анимацию по ключу среди своих локальных анимаций. Если она найдена, он играет её. В противном случае он обращается к глобальному менеджеру анимаций.

rambo.play('walk');

Поскольку у rambo есть собственная локальная анимация 'walk', он проиграет именно её (с префиксом 'soldier_3_walk_').

this.add.sprite(200, 500, 'soldier')
    .play('walk');

Второй спрайт, созданный напрямую, не имеет локальных анимаций. Поэтому при вызове play('walk') он использует глобальную анимацию из this.anims (с префиксом 'Soldier_2_walk_'). На экране будут два солдата, шагающих с разными анимациями.

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

Используйте локальные анимации для уникальных способностей или состояний конкретного персонажа (например, раненый солдат). Глобальные анимации идеальны для типовых действий (ходьба, прыжок), общих для многих объектов. Экспериментируйте: создайте глобальную анимацию 'shoot' и локальную 'reload' только для главного героя, чтобы управлять его уникальными особенностями.