О чем этот пример
Создание интерактивного космического корабля с реалистичной физикой и динамическими визуальными эффектами — частый запрос в играх. В этой статье мы разберем пример из официальной галереи 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.image('blue', 'assets/particles/blue.png');
}
create ()
{
const emitter = this.add.particles(0, 0, 'blue', {
speed: {
onEmit: (particle, key, t, value) => this.ship.body.speed * 10
},
lifespan: {
onEmit: (particle, key, t, value) => Phaser.Math.Percent(this.ship.body.speed, 0, 300) * 40000
},
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.setFrictionAir(0.1);
this.ship.setMass(30);
this.ship.setFixedRotation();
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.setAngularVelocity(-0.1);
}
else if (this.cursors.right.isDown)
{
this.ship.setAngularVelocity(0.1);
}
if (this.cursors.up.isDown)
{
this.ship.thrust(0.08);
}
}
}
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);
Инициализация сцены и загрузка ассетов
В методе preload мы загружаем два изображения: спрайт корабля (ship) и текстуру для частиц (blue). Важно использовать setBaseURL, чтобы указать базовый путь для загрузки.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ship', 'assets/sprites/x2kship.png');
this.load.image('blue', 'assets/particles/blue.png');
}
Этот код выполняется один раз при старте сцены. Корабль и текстура частиц будут готовы к использованию в методе create.
Создание физического тела корабля
В методе create мы создаем физическое тело корабля с помощью движка Matter.js. Ключевой метод — this.matter.add.image, который создает объект с физическим телом.
this.ship = this.matter.add.image(400, 300, 'ship');
this.ship.setFrictionAir(0.1);
this.ship.setMass(30);
this.ship.setFixedRotation();
* setFrictionAir(0.1) устанавливает сопротивление воздуха, что создает эффект инерции и плавного замедления в вакууме.
* setMass(30) задает массу тела, влияющую на его движение при приложении силы.
* setFixedRotation() фиксирует вращение тела, предотвращая его нежелательное вращение от столкновений. Мы будем управлять им вручную с помощью клавиш.
Также здесь задаются границы игрового мира, чтобы корабль не улетел за его пределы: this.matter.world.setBounds(0, 0, 800, 600).
Управление кораблем с клавиатуры
Логика управления вынесена в метод update, который вызывается на каждом кадре игры. Мы проверяем состояние клавиш-стрелок.
if (this.cursors.left.isDown)
{
this.ship.setAngularVelocity(-0.1);
}
else if (this.cursors.right.isDown)
{
this.ship.setAngularVelocity(0.1);
}
if (this.cursors.up.isDown)
{
this.ship.thrust(0.08);
}
* setAngularVelocity задает угловую скорость вращения тела. Левая и правая стрелки вращают корабль.
* thrust — это специфичный для Matter.js метод, который применяет силу вперед относительно текущего угла поворота тела. Стрелка вверх разгоняет корабль.
Клавиши создаются в create: this.cursors = this.input.keyboard.createCursorKeys().
Динамическая система частиц
Один из самых интересных элементов примера — система частиц выхлопа, параметры которой динамически зависят от скорости корабля.
const emitter = this.add.particles(0, 0, 'blue', {
speed: {
onEmit: (particle, key, t, value) => this.ship.body.speed * 10
},
lifespan: {
onEmit: (particle, key, t, value) => Phaser.Math.Percent(this.ship.body.speed, 0, 300) * 40000
},
alpha: {
onEmit: (particle, key, t, value) => Phaser.Math.Percent(this.ship.body.speed, 0, 300) * 1000
},
scale: { start: 1.0, end: 0 },
blendMode: 'ADD'
});
emitter.startFollow(this.ship);
* onEmit — это специальные коллбэки, которые вызываются в момент создания каждой новой частицы. Они позволяют вычислить значение параметра на основе текущего состояния игры.
* this.ship.body.speed — текущая линейная скорость тела корабля.
* Phaser.Math.Percent вычисляет процент, который представляет первое значение (скорость) в диапазоне от 0 до 300.
* Таким образом, чем быстрее летит корабль, тем быстрее (speed), дольше (lifespan) и заметнее (alpha) будут частицы его выхлопа. blendMode: 'ADD' создает эффект яркого свечения.
* startFollow заставляет эмиттер следовать за кораблем.
Конфигурация игры и физики
Вся игра инициализируется объектом конфигурации. Ключевой момент — настройка физического движка Matter.js.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#1b1464',
parent: 'phaser-example',
physics: {
default: 'matter', // Указываем Matter.js как движок по умолчанию
matter: {
gravity: {
x: 0,
y: 0 // Отключаем гравитацию для космического симулятора
}
}
},
scene: Example
};
Игра создается с этой конфигурацией: const game = new Phaser.Game(config);.
Что попробовать дальше
Этот пример демонстрирует мощную синергию между физикой Matter.js и системой частиц Phaser. Вы можете экспериментировать: изменить массу корабля и силу трения для другого "чувства" управления, добавить стрельбу, создавая снаряды через this.matter.add.image, или сделать частицы выхлопа разноцветными, используя несколько эмиттеров с разными текстурами. Попробуйте заменить setFixedRotation() на setDensity и посмотрите, как изменится поведение при столкновениях.
