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

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

Версия 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('knight', 'assets/animations/knight.png', 'assets/animations/knight.json');
        this.load.image('bg', 'assets/skies/clouds.png');
        this.load.spritesheet('tiles', 'assets/tilemaps/tiles/fantasy-tiles.png', { frameWidth: 64, frameHeight: 64 });
    }

    create() {
        //  The background and floor
        this.bg = this.add.tileSprite(0, 16, 800, 600, 'bg').setOrigin(0);
        this.ground = this.add.tileSprite(0, 536, 800, 64, 'tiles', 1).setOrigin(0);

        this.add.text(400, 8, 'Tweening the Animation.timeScale', { color: '#ffffff' }).setOrigin(0.5, 0);

        const runConfig = {
            key: 'run',
            frames: this.anims.generateFrameNames('knight', { prefix: 'run/frame', start: 0, end: 7, zeroPad: 4 }),
            frameRate: 12,
            repeat: -1
        };

        this.anims.create(runConfig);

        this.lancelot = this.add.sprite(480, 536, 'knight');

        this.lancelot.setOrigin(0.5, 1);
        this.lancelot.setScale(8);
        this.lancelot.play('run');

        this.tweens.add({
            targets: this.lancelot.anims,
            timeScale: { from: 0.5, to: 2 },
            ease: 'Sine.inOut',
            yoyo: true,
            repeat: -1,
            repeatDelay: 1000,
            hold: 1000,
            duraton: 3000
        });
    }

    update() {
        this.bg.tilePositionX += 3 * this.lancelot.anims.timeScale;
        this.ground.tilePositionX += 6 * this.lancelot.anims.timeScale;
    }
}

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

const game = new Phaser.Game(config);

Подготовка анимации для управления скоростью

Перед тем как изменять скорость, необходимо создать саму анимацию. В методе create() сцены мы определяем конфигурацию анимации бега. Ключевой параметр — frameRate, который задаёт стандартную скорость воспроизведения в кадрах в секунду.

const runConfig = {
    key: 'run',
    frames: this.anims.generateFrameNames('knight', { prefix: 'run/frame', start: 0, end: 7, zeroPad: 4 }),
    frameRate: 12,
    repeat: -1
};

После создания анимации с помощью this.anims.create(runConfig), мы добавляем спрайт рыцаря на сцену и запускаем анимацию методом play(). Обратите внимание, что спрайт масштабируется в 8 раз (setScale(8)) для наглядности. Теперь анимация готова, и её скорость можно контролировать через свойство timeScale объекта анимации, доступное по пути this.lancelot.anims.

Твининг свойства timeScale для плавных изменений

Статичное изменение скорости выглядит резко. Для плавного перехода мы используем систему твинов Phaser. Вместо того чтобы менять скорость анимации напрямую, мы анимируем её свойство timeScale.

this.tweens.add({
    targets: this.lancelot.anims,
    timeScale: { from: 0.5, to: 2 },
    ease: 'Sine.inOut',
    yoyo: true,
    repeat: -1,
    repeatDelay: 1000,
    hold: 1000,
    duration: 3000
});

Здесь targets указывает на объект анимации нашего спрайта. Значение timeScale будет плавно меняться от 0.5 (половинная скорость) до 2 (двойная скорость). Параметры yoyo: true и repeat: -1 заставляют твин повторяться бесконечно, двигаясь вперед и назад. repeatDelay и hold добавляют паузы в крайних точках анимации, создавая более интересный визуальный эффект. Важно: свойство пишется как timeScale, а не timescale.

Связывание скорости анимации с движением мира

Чтобы изменения скорости анимации ощущались целостно, можно синхронизировать с ними другие игровые процессы. В нашем примере скорость прокрутки фона и земли в методе update() зависит от текущего timeScale анимации.

update() {
    this.bg.tilePositionX += 3 * this.lancelot.anims.timeScale;
    this.ground.tilePositionX += 6 * this.lancelot.anims.timeScale;
}

Каждый кадр игры позиция тайлов фона смещается на величину базовой скорости (3), умноженную на текущий множитель timeScale. Для земли базовая скорость выше (6), создавая параллакс-эффект. Таким образом, когда анимация персонажа ускоряется, мир вокруг него также начинает двигаться быстрее, усиливая ощущение скорости.

Практические применения и нюансы

Управление timeScale — это не только визуальный эффект. Вот несколько практических идей: * **Игровая механика:** Замедление времени (timeScale < 1) как способность героя или эффект ловушки. * **Реакция на состояние:** Ускорение анимации атаки при активации баффа силы. * **Синхронизация:** Подгонка скорости циклической анимации огня или воды под ритм игрового процесса.

Важный нюанс: timeScale влияет только на скорость смены кадров анимации. Он не изменяет скорость, с которой спрайт движется по сцене через physics.velocity или положение через x, y. Эти параметры нужно контролировать отдельно, как это сделано с тайлами в примере.

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

Свойство timeScale — это простой, но крайне эффективный способ добавить динамики вашим анимациям в Phaser 3. Комбинируя его с системой твинов, вы можете создавать сложные и плавные изменения скорости, которые оживят игровой мир. Для экспериментов попробуйте: привязать изменение timeScale к количеству здоровья персонажа, создать эффект "временной пузырь", замедляющий всех врагов, или синхронизировать скорость анимации ветра с силой бури в игре.