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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    cursors;
    ship;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('ship', 'assets/sprites/x2kship.png');
        this.load.atlas('space', 'assets/tests/space/space.png', 'assets/tests/space/space.json');
    }

    create ()
    {
        const emitter = this.add.particles(0, 0, 'space', {
            frame: 'blue',
            speed: {
                onEmit: (particle, key, t, value) => this.ship.body.speed
            },
            lifespan: {
                onEmit: (particle, key, t, value) => Phaser.Math.Percent(this.ship.body.speed, 0, 300) * 200000
            },
            alpha: {
                onEmit: (particle, key, t, value) => Phaser.Math.Percent(this.ship.body.speed, 0, 300) * 1000
            },
            scale: { start: 1.0, end: 0 },
            blendMode: 'ADD'
        });

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

        this.ship.setFixedRotation();
        this.ship.setAngle(270);
        this.ship.setFrictionAir(0.05);
        this.ship.setMass(30);

        emitter.startFollow(this.ship);

        this.matter.world.setBounds(0, 0, 800, 600);

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

    update ()
    {
        if (this.cursors.left.isDown)
        {
            this.ship.thrustLeft(0.1);
        }
        else if (this.cursors.right.isDown)
        {
            this.ship.thrustRight(0.1);
        }

        if (this.cursors.up.isDown)
        {
            this.ship.thrust(0.1);
        }
        else if (this.cursors.down.isDown)
        {
            this.ship.thrustBack(0.1);
        }
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#1b1464',
    parent: 'phaser-example',
    physics: {
        default: 'matter',
        matter: {
            gravity: {
                x: 0,
                y: 0
            }
        }
    },
    scene: Example
};

const game = new Phaser.Game(config);

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

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

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#1b1464',
    parent: 'phaser-example',
    physics: {
        default: 'matter',
        matter: {
            gravity: {
                x: 0,
                y: 0
            }
        }
    },
    scene: Example
};

В методе preload загружаются необходимые изображения: спрайт корабля и атлас текстур для системы частиц. Важно использовать setBaseURL, чтобы указать корректный путь к удалённым ресурсам из репозитория примеров.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('ship', 'assets/sprites/x2kship.png');
    this.load.atlas('space', 'assets/tests/space/space.png', 'assets/tests/space/space.json');
}

Создание физического тела корабля

В методе create происходит основная настройка. Корабль создаётся не как обычный спрайт, а как физическое тело с помощью this.matter.add.image. Это автоматически наделяет его свойствами твёрдого тела.

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

Далее тело корабля настраивается с помощью методов Matter.js: - setFixedRotation() предотвращает вращение тела от воздействия сил, что даёт нам полный контроль над углом поворота. - setAngle(270) изначально разворачивает корабль носом вверх (так как в Phaser нулевой угол смотрит вправо). - setFrictionAir(0.05) задаёт сопротивление воздуха (или, в нашем случае, космической среды). Небольшое значение создаёт эффект плавного скольжения и постепенной остановки. - setMass(30) устанавливает массу тела, которая влияет на его инерцию.

this.ship.setFixedRotation();
this.ship.setAngle(270);
this.ship.setFrictionAir(0.05);
this.ship.setMass(30);

Также важно установить границы мира, чтобы тело корабля не могло их покинуть: this.matter.world.setBounds(0, 0, 800, 600).

Динамическая система частиц для струи

Эффект реактивной струи создаётся с помощью системы частиц (this.add.particles), которая следует за кораблём (emitter.startFollow(this.ship)). Особенность этой системы в том, что её параметры динамически зависят от скорости корабля.

const emitter = this.add.particles(0, 0, 'space', {
    frame: 'blue',
    speed: {
        onEmit: (particle, key, t, value) => this.ship.body.speed
    },
    lifespan: {
        onEmit: (particle, key, t, value) => Phaser.Math.Percent(this.ship.body.speed, 0, 300) * 200000
    },
    alpha: {
        onEmit: (particle, key, t, value) => Phaser.Math.Percent(this.ship.body.speed, 0, 300) * 1000
    },
    scale: { start: 1.0, end: 0 },
    blendMode: 'ADD'
});
Ключевые моменты:
- `onEmit`: Это функции обратного вызова, которые вычисляют значение параметра в момент создания каждой новой частицы.
- `this.ship.body.speed`: Текущее значение скорости тела корабля.
- `Phaser.Math.Percent(this.ship.body.speed, 0, 300)`: Преобразует скорость (в диапазоне от 0 до 300) в процентное значение от 0 до 1. Это позволяет связать время жизни (`lifespan`) и прозрачность (`alpha`) частицы со скоростью корабля. Чем быстрее летит корабль, тем длиннее и ярче струя.
- `blendMode: 'ADD'`: Режим наложения 'ADD' делает частицы яркими и светящимися, что идеально подходит для эффекта огня или плазмы.

Управление и применение сил (Thrust)

Логика управления вынесена в метод update, который вызывается каждый кадр. Здесь проверяется состояние клавиш-стрелок и применяются соответствующие силы к телу корабля.

if (this.cursors.left.isDown)
{
    this.ship.thrustLeft(0.1);
}
else if (this.cursors.right.isDown)
{
    this.ship.thrustRight(0.1);
}

if (this.cursors.up.isDown)
{
    this.ship.thrust(0.1);
}
else if (this.cursors.down.isDown)
{
    this.ship.thrustBack(0.1);
}

Важно понимать, что методы thrust, thrustBack, thrustLeft и thrustRight применяют силу **относительно текущего угла поворота тела**. - thrust(0.1): Применяет силу вперёд (в направлении, куда «смотрит» спрайт). - thrustBack(0.1): Применяет силу назад. - thrustLeft(0.1) и thrustRight(0.1): Применяют силу влево и вправо относительно направления корабля, что и создаёт эффект поворота на месте или бокового манёвра.

Аргумент 0.1 — это величина силы. Поскольку эти силы применяются каждый кадр, пока зажата клавиша, корабль плавно разгоняется. А благодаря настроенному трению (setFrictionAir) он так же плавно замедляется, когда игрок отпускает клавиши.

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

Этот пример — отличная основа для космического симулятора или аркадного шутера. Вы создали не просто управляемый спрайт, а физическое тело, которое ведёт себя предсказуемо и реалистично. Для экспериментов попробуйте: изменить массу корабля и трение, чтобы получить другое ощущение управления; добавить инерционное вращение, убрав setFixedRotation(); создать разные системы частиц для различных типов двигателей или повреждений; или реализовать стрельбу, где снаряды также будут физическими телами, создаваемыми с помощью this.matter.add.image.