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

Работа с угловым движением — ключевой навык для создания динамичных и отзывчивых игр. Phaser Arcade Physics предоставляет простой, но мощный API для управления вращением объектов. Эта статья на практическом примере покажет, как использовать угловое ускорение (`angularAcceleration`), сопротивление (`angularDrag`) и скорость (`angularVelocity`) для создания интуитивного управления вращением спрайта. Вы научитесь рассчитывать угол поворота за кадр и визуализировать физические параметры на экране.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    cursors;
    graphics;
    text;
    wheel;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('wheel', 'assets/sprites/blade.png');
    }

    create ()
    {
        this.wheel = this.physics.add.image(400, 300, 'wheel')
            .setAngularDrag(0)
            .setAngularVelocity(360);

        this.graphics = this.add.graphics({ fillStyle: { color: 0xffff00, alpha: 0.5 } });

        this.text = this.add.text(0, 0, '', {
            fixedWidth: 350,
            fixedHeight: 150,
            fill: 'aqua',
            backgroundColor: '#000c'
        });

        this.cursors = this.input.keyboard.createCursorKeys();
    }

    update ()
    {
        const { left, right, down } = this.cursors;

        this.wheel.setAngularAcceleration(0).setAngularDrag(0);

        if (left.isDown)
        {
            this.wheel.setAngularAcceleration(-360);
        }
        else if (right.isDown)
        {
            this.wheel.setAngularAcceleration(360);
        }

        if (down.isDown)
        {
            this.wheel.setAngularDrag(360);
        }

        const deltaZ = this.wheel.body.deltaZ();

        this.graphics
            .clear()
            .slice(
                this.wheel.x,
                this.wheel.y,
                0.5 * this.wheel.width,
                0,
                Phaser.Math.DegToRad(deltaZ),
                deltaZ < 0
            )
            .fillPath();

        const { angularAcceleration, angularDrag, angularVelocity } = this.wheel.body;

        this.text.setText(`
Accelerate with LEFT and RIGHT keys.
Drag with DOWN key.

Angular Acceleration: ${angularAcceleration.toFixed(1)} deg/s²
Angular Drag:         ${angularDrag.toFixed(1)} deg/s²
Angular Velocity:     ${angularVelocity.toFixed(1)} deg/s
Delta Z:              ${deltaZ.toFixed(1)} deg/step`
        );
    }
}

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

const game = new Phaser.Game(config);

Инициализация сцены и загрузка ресурсов

Класс сцены наследуется от Phaser.Scene. В методе preload() загружается единственный спрайт — изображение лезвия, которое будет выступать в роли вращающегося колеса. Базовый URL задаётся для удобства загрузки из репозитория с примерами.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('wheel', 'assets/sprites/blade.png');
}

Создание физического тела и элементов интерфейса

В методе create() происходит основная настройка. Спрайт 'wheel' добавляется в физический мир через this.physics.add.image. Это автоматически создаёт для него Arcade Physics тело.

Ключевой момент — сразу после создания задаются начальные физические свойства: угловое сопротивление (angularDrag) обнуляется, а угловая скорость (angularVelocity) устанавливается в 360 градусов в секунду. Это заставляет колесо вращаться сразу после старта сцены.

Также создаются графический объект graphics для визуализации и текстовое поле text для вывода статистики.

create ()
{
    this.wheel = this.physics.add.image(400, 300, 'wheel')
        .setAngularDrag(0)
        .setAngularVelocity(360);

    this.graphics = this.add.graphics({ fillStyle: { color: 0xffff00, alpha: 0.5 } });
    this.text = this.add.text(0, 0, '', {
        fixedWidth: 350,
        fixedHeight: 150,
        fill: 'aqua',
        backgroundColor: '#000c'
    });

    this.cursors = this.input.keyboard.createCursorKeys();
}

Обработка ввода и применение физических сил

Метод update() вызывается каждый кадр. В нём сбрасываются угловое ускорение и сопротивление тела до нуля. Это важно делать на каждом кадре, потому что логика управления задаёт эти значения условно, только при нажатых клавишах. Без сброса ускорение или сопротивление, применённые в предыдущем кадре, остались бы активными.

Затем проверяется состояние клавиш-стрелок. Стрелки ВЛЕВО и ВПРАВО задают отрицательное и положительное угловое ускорение соответственно. Стрелка ВНИЗ включает угловое сопротивление (angularDrag), которое замедляет вращение.

update ()
{
    const { left, right, down } = this.cursors;

    this.wheel.setAngularAcceleration(0).setAngularDrag(0);

    if (left.isDown)
    {
        this.wheel.setAngularAcceleration(-360);
    }
    else if (right.isDown)
    {
        this.wheel.setAngularAcceleration(360);
    }

    if (down.isDown)
    {
        this.wheel.setAngularDrag(360);
    }

Визуализация и вывод отладочной информации

Одна из самых полезных функций для работы с вращением — deltaZ(). Она возвращает изменение угла поворота тела (в градусах) за последний шаг физического расчета. Это значение используется для отрисовки сектора (slice) с помощью Graphics API, который наглядно показывает, насколько и в какую сторону повернулось тело за кадр.

В текстовом блоке выводятся текущие физические параметры тела, полученные напрямую из this.wheel.body. Это отличный способ отладки и демонстрации работы физики.

const deltaZ = this.wheel.body.deltaZ();

    this.graphics
        .clear()
        .slice(
            this.wheel.x,
            this.wheel.y,
            0.5 * this.wheel.width,
            0,
            Phaser.Math.DegToRad(deltaZ),
            deltaZ < 0
        )
        .fillPath();

    const { angularAcceleration, angularDrag, angularVelocity } = this.wheel.body;

    this.text.setText(`
Accelerate with LEFT and RIGHT keys.
Drag with DOWN key.

Angular Acceleration: ${angularAcceleration.toFixed(1)} deg/s²
Angular Drag:         ${angularDrag.toFixed(1)} deg/s²
Angular Velocity:     ${angularVelocity.toFixed(1)} deg/s
Delta Z:              ${deltaZ.toFixed(1)} deg/step`
    );
}

Конфигурация игры и запуск

Объект конфигурации определяет основные параметры игры: тип рендерера, размер холста, контейнер для вставки и, что самое важное, настройки физики. В данном примере используется система 'arcade' с отключённым режимом отладки (debug: false). После создания экземпляра Phaser.Game с этой конфигурацией игра запускается.

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

const game = new Phaser.Game(config);

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

Пример наглядно демонстрирует принцип управления вращением через ускорение и сопротивление в Arcade Physics. setAngularAcceleration изменяет скорость вращения, а setAngularDrag создаёт эффект торможения. Для экспериментов попробуйте изменить начальную угловую скорость, поиграть со значениями ускорения и сопротивления, или привязать эти параметры к состоянию другого игрового объекта (например, скорости персонажа). Также можно использовать deltaZ() для создания визуальных эффектов, зависящих от скорости вращения (следы, частицы).