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

В Phaser 3 физика и анимация тесно связаны. Обычно твины управляют позицией или свойствами спрайтов, но что если нужно плавно менять не позицию, а скорость физического тела? Этот пример показывает, как использовать систему твинов Phaser для анимации вектора скорости (`body.velocity`) физического тела, создавая сложные траектории движения, которые корректно взаимодействуют с другими объектами через физический движок. Этот подход полезен для создания подвижных платформ, вражеских патрулей или любых объектов, чье движение должно быть плавным, но при этом сталкиваться с игровым миром.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('block', 'assets/sprites/block.png');
        this.load.image('dude', 'assets/sprites/phaser-dude.png');
        this.load.image('ball', 'assets/sprites/blue_ball.png');
        this.load.image('sky', 'assets/skies/cavern2.png');
    }

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

        const block = this.physics.add.image(100, 500, 'block');

        block.body.immovable = true;
        block.body.allowGravity = false;

        const dude = this.physics.add.image(100, 0, 'dude');

        const balls = this.physics.add.group({
            key: 'ball',
            frameQuantity: 192,
            gridAlign: { width: 16, height: 12, cellWidth: 50, cellHeight: 50, position: Phaser.Display.Align.CENTER },
            allowGravity: false
        });

        this.tweens.chain({
            targets: block.body.velocity,
            loop: -1,
            tweens: [
                { x:    0, y: -200, duration: 2000, ease: 'Stepped' },
                { x:    0, y:    0, duration: 1000, ease: 'Stepped' },
                { x:  150, y:  100, duration: 4000, ease: 'Stepped' },
                { x:    0, y: -200, duration: 2000, ease: 'Stepped' },
                { x:    0, y:    0, duration: 1000, ease: 'Stepped' },
                { x: -150, y:  100, duration: 4000, ease: 'Stepped' }
            ]
        });

        this.physics.add.collider(block, dude);
        this.physics.add.collider(block, balls);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    physics: {
        default: 'arcade',
        arcade: {
            debug: false,
            gravity: { y: 600 }
        }
    },
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка сцены и настройка физики

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

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    physics: {
        default: 'arcade',
        arcade: {
            debug: false,
            gravity: { y: 600 }
        }
    },
    scene: Example
};

Высокое значение gravity.y: 600 заставляет все физические тела с ускорением падать вниз. Однако в коде для некоторых объектов гравитация будет отключена, что позволяет контролировать их движение вручную.

Создание и настройка физических тел

В методе create сначала добавляется фоновое изображение. Затем создаются три типа физических объектов.

1. **Неподвижный блок (block)**: Это главный объект, скорость которого мы будем анимировать. Он создается с помощью this.physics.add.image. Его тело делается неподвижным (immovable = true), что означает, что при столкновении он не будет сдвигаться другими объектами. Гравитация для него отключается (allowGravity = false).

const block = this.physics.add.image(100, 500, 'block');
block.body.immovable = true;
block.body.allowGravity = false;

2. **Персонаж (dude)**: Создается как обычное физическое изображение. На него действует гравитация, объявленная в конфиге, поэтому он начнет падать сверху.

3. **Группа шариков (balls)**: Создается 192 шара, выровненных по сетке. Для каждого члена группы также отключается гравитация. Это статичные объекты, которые будут служить препятствиями.

const balls = this.physics.add.group({
    key: 'ball',
    frameQuantity: 192,
    gridAlign: { width: 16, height: 12, cellWidth: 50, cellHeight: 50, position: Phaser.Display.Align.CENTER },
    allowGravity: false
});

Цепочка твинов для анимации скорости

Сердце примера — применение твина не к позиции спрайта, а к вектору скорости его физического тела (block.body.velocity). Твин будет плавно интерполировать значения velocity.x и velocity.y между ключевыми кадрами.

Используется this.tweens.chain(), который позволяет создать последовательность (chain) из нескольких твинов, выполняющихся один за другим. Цикл бесконечный (loop: -1).

this.tweens.chain({
    targets: block.body.velocity,
    loop: -1,
    tweens: [
        { x:    0, y: -200, duration: 2000, ease: 'Stepped' },
        { x:    0, y:    0, duration: 1000, ease: 'Stepped' },
        { x:  150, y:  100, duration: 4000, ease: 'Stepped' },
        { x:    0, y: -200, duration: 2000, ease: 'Stepped' },
        { x:    0, y:    0, duration: 1000, ease: 'Stepped' },
        { x: -150, y:  100, duration: 4000, ease: 'Stepped' }
    ]
});

Каждый твин в массиве определяет целевые значения скорости по осям X и Y и время перехода. Например, первый твин за 2 секунды меняет вертикальную скорость с текущей до -200 (движение вверх). Важно отметить использование линейной интерполяции ease: 'Stepped'. Это значит, что скорость меняется резко, без плавного разгона и торможения, создавая роботизированное, механическое движение.

Организация столкновений

Чтобы анимированный блок взаимодействовал с миром, необходимо добавить коллайдеры. Phaser Arcade Physics автоматически рассчитает столкновения между телами, если их скорости или позиции изменятся.

this.physics.add.collider(block, dude);
this.physics.add.collider(block, balls);

Первый коллайдер обеспечивает взаимодействие между блоком и падающим персонажем. Когда блок движется, он будет сталкиваться с dude, отталкивая его согласно законам физики. Второй коллайдер регистрирует столкновения между блоком и каждым шариком из группы. Так как блок immovable, при столкновении он останется на месте (точнее, продолжит движение по заданной твином траектории), а шарики будут отлетать от него. Визуально это создает эффект "бульдозера", разгребающего поле из шаров.

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

Анимация скорости через твины — мощный прием для создания нестандартного движения, которое полностью интегрировано в физическую модель игры. Вы можете экспериментировать: замените ease: 'Stepped' на 'Power2' или 'Bounce', чтобы получить плавное ускорение и замедление. Попробуйте сделать блок подвижным (immovable: false) и посмотрите, как он будет взаимодействовать с персонажем. Или привяжите твин к скорости персонажа, чтобы создавать спецэффекты, например, скольжение по льду или порывы ветра.