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

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

Версия 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.image('poo', 'assets/sprites/poo.png');
        this.load.spritesheet('mummy', 'assets/animations/mummy37x45.png', { frameWidth: 37, frameHeight: 45 });
    }

    create ()
    {
        const mummyAnimation = this.anims.create({
            key: 'walk',
            frames: this.anims.generateFrameNumbers('mummy'),
            frameRate: 16
        });

        const sprite = this.add.sprite(50, 300, 'mummy').setScale(4);

        sprite.play({ key: 'walk', repeat: 7 });

        this.tweens.add({
            targets: sprite,
            x: 750,
            duration: 8800,
            ease: 'Linear'
        });

        sprite.on('animationrepeat', function () {

            const poop = this.add.image(sprite.x - 32, 300, 'poo').setScale(0.5);

            this.tweens.add({
                targets: poop,
                props: {
                    x: {
                        value: '-=64', ease: 'Power1'
                    },
                    y: {
                        value: '+=50', ease: 'Bounce.easeOut'
                    }
                },
                duration: 750
            });

        }, this);
    }
}

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

const game = new Phaser.Game(config);

Зачем нужно событие animationrepeat?

Событие animationrepeat срабатывает каждый раз, когда анимация спрайта завершает один цикл и начинает следующий. Это особенно полезно для анимаций, у которых задано свойство repeat. В отличие от событий animationstart или animationcomplete, оно позволяет выполнять код на каждом повторении, а не только в начале или конце всей последовательности.

В примере анимация 'ходьбы' мумии повторяется 7 раз. Мы можем привязать к каждому такту ходьбы создание нового объекта (например, следа), что создаёт ритмичный и синхронизированный с движением визуальный эффект.

Создание анимации и запуск с повторением

Первым делом создаётся сама анимация из спрайтшита. Ключевой момент — указание количества повторов при запуске анимации с помощью метода play().

const mummyAnimation = this.anims.create({
    key: 'walk',
    frames: this.anims.generateFrameNumbers('mummy'),
    frameRate: 16
});

const sprite = this.add.sprite(50, 300, 'mummy').setScale(4);
sprite.play({ key: 'walk', repeat: 7 });

Здесь repeat: 7 означает, что полная последовательность кадров проиграется 8 раз (1 начальный + 7 повторов). Параллельно с анимацией запускается tween, который плавно перемещает спрайт по горизонтали, создавая иллюзию ходьбы.

Подписка на событие и создание объектов

Ядро примера — подписка на событие animationrepeat у спрайта. Обработчик события будет вызываться 7 раз, по одному разу за каждый повтор после первого проигрывания.

sprite.on('animationrepeat', function () {
    const poop = this.add.image(sprite.x - 32, 300, 'poo').setScale(0.5);
    // ... tween для poop
}, this);

Важно передать контекст this (текущую сцену) третьим аргументом в on(). Это позволяет внутри функции-обработчика использовать методы сцены, такие как this.add.image и this.tweens.add. В данном случае при каждом повторе анимации позади мумии (на sprite.x - 32) создаётся уменьшенное изображение 'poo'.

Анимация вложенных объектов (tween)

Чтобы созданные объекты 'poo' не просто появлялись, а оживали, к каждому из них применяется отдельная твин-анимация. Она запускается сразу после создания объекта.

this.tweens.add({
    targets: poop,
    props: {
        x: { value: '-=64', ease: 'Power1' },
        y: { value: '+=50', ease: 'Bounce.easeOut' }
    },
    duration: 750
});

Этот твин делает две вещи: сдвигает объект на 64 пикселя влево (относительно текущей позиции) и бросает его вниз на 50 пикселей с 'пружинящим' эффектом (Bounce.easeOut). Длительность в 750 мс позволяет всем следам анимироваться почти одновременно с шагами мумии, создавая цельный эффект.

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

Событие animationrepeat — это мощный инструмент для синхронизации игровых событий с ритмом анимации. В рассмотренном примере оно использовалось для визуальных эффектов, но область применения шире: можно запускать звуки шагов, частицы, проверку столкновений или изменение состояния персонажа. **Идеи для экспериментов:** 1. Привязать к событию не создание спрайта, а эмиттер частиц для создания пыли или следов. 2. Менять в обработчике frameRate анимации, чтобы создать эффект ускорения или замедления. 3. Использовать данные из event объекта в обработчике (например, event.frame), чтобы действие выполнялось только на определённых кадрах повтора.