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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    text;
    cursors;
    sprite;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('bullet', 'assets/games/asteroids/bullets.png');
        this.load.image('ship', 'assets/games/asteroids/ship.png');
    }

    create ()
    {
        this.sprite = this.physics.add.image(400, 300, 'ship');

        this.sprite.setDamping(true);
        this.sprite.setDrag(0.99);
        this.sprite.setMaxVelocity(200);

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

        this.text = this.add.text(10, 10, '', { font: '16px Courier', fill: '#00ff00' });
    }

    update ()
    {
        if (this.cursors.up.isDown)
        {
            this.physics.velocityFromRotation(this.sprite.rotation, 200, this.sprite.body.acceleration);
        }
        else
        {
            this.sprite.setAcceleration(0);
        }

        if (this.cursors.left.isDown)
        {
            this.sprite.setAngularVelocity(-300);
        }
        else if (this.cursors.right.isDown)
        {
            this.sprite.setAngularVelocity(300);
        }
        else
        {
            this.sprite.setAngularVelocity(0);
        }

        this.text.setText(`Speed: ${this.sprite.body.speed}`);

        this.physics.world.wrap(this.sprite, 32);
    }
}

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

const game = new Phaser.Game(config);

Настройка физического спрайта и его свойств

В методе create() создается основной спрайт корабля и сразу добавляется в физический мир Arcade. Ключевой момент — настройка свойств, имитирующих движение в безвоздушном пространстве.

this.sprite = this.physics.add.image(400, 300, 'ship');

this.sprite.setDamping(true);
this.sprite.setDrag(0.99);
this.sprite.setMaxVelocity(200);

Метод setDamping(true) включает эффект демпфирования (постепенного замедления). В паре с ним работает setDrag(0.99), который устанавливает коэффициент сопротивления движению. Значение 0.99 близко к 1, что создает эффект очень плавного, почти бесконечного скольжения в вакууме. setMaxVelocity(200) ограничивает максимальную скорость корабля, предотвращая его разгон до бесконечности.

Управление ускорением и вращением

Логика управления полностью находится в методе update(). Она обрабатывает нажатия клавиш стрелок и применяет соответствующие силы к физическому телу корабля.

if (this.cursors.up.isDown)
{
    this.physics.velocityFromRotation(this.sprite.rotation, 200, this.sprite.body.acceleration);
}
else
{
    this.sprite.setAcceleration(0);
}

Здесь используется мощная вспомогательная функция velocityFromRotation. Она вычисляет вектор ускорения (acceleration) на основе текущего угла поворота спрайта (this.sprite.rotation) и заданной величины силы (200). Это означает, что корабль всегда ускоряется туда, куда смотрит его нос, что идеально для космического полета.

if (this.cursors.left.isDown)
{
    this.sprite.setAngularVelocity(-300);
}
else if (this.cursors.right.isDown)
{
    this.sprite.setAngularVelocity(300);
}
else
{
    this.sprite.setAngularVelocity(0);
}

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

Отслеживание состояния и телепортация на границах

В каждом кадре также обновляется текстовое поле и проверяется положение корабля относительно границ игрового мира.

this.text.setText(`Speed: ${this.sprite.body.speed}`);

this.physics.world.wrap(this.sprite, 32);

Свойство this.sprite.body.speed содержит текущую модуль скорости (скалярное значение), что удобно для вывода в интерфейс.

Метод this.physics.world.wrap() — это «читерский» способ реализации бесконечного пространства. Когда спрайт полностью покидает границы мира (с учетом заданного отступа в 32 пикселя), он мгновенно появляется с противоположной стороны. Это классический прием из игр типа Asteroids, который избавляет от необходимости вручную вычислять координаты и мгновенно перемещать объект.

Конфигурация игрового экземпляра

Весь пример работает благодаря правильной конфигурации физического движка Arcade при создании экземпляра игры (Phaser.Game).

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

const game = new Phaser.Game(config);

Установка gravity: { y: 0 } отключает гравитацию, что абсолютно необходимо для симуляции невесомости. Параметр debug: false отключает отладочную визуализацию коллайдеров и векторов скорости. Для отладки сложного движения его можно временно включить.

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

Разобранный пример предоставляет готовый каркас для управления космическим кораблем с реалистичной инерцией. Экспериментируйте с параметрами: измените setDrag на 0.9 для более «тяжелого» и быстро тормозящего корабля или увеличьте setMaxVelocity для динамичного экшена. Попробуйте добавить стрельбу, создавая пули (physics.add.image) и задавая им скорость с помощью setVelocity в направлении поворота корабля. Для создания поля астероидов используйте группу физических объектов (this.physics.add.group) и метод wrap для каждого из них, чтобы они также циклически появлялись на экране.