О чем этот пример
Один из ключевых элементов в создании захватывающих 2D-игр — это большой, интересный мир и плавное управление камерой. В этой статье мы разберем пример, где игрок на космическом корабле летает по процедурно сгенерированному ландшафту, а камера плавно следует за ним. Вы научитесь настраивать границы мира, создавать динамическую графику и привязывать камеру к физическому объекту.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ship', 'assets/sprites/shmup-ship2.png');
}
create ()
{
// The world is 3200 x 600 in size
this.matter.world.setBounds(0, 0, 3200, 600);
this.cameras.main.setBounds(0, 0, 3200, 600);
this.createLandscape();
// Add a player ship and camera follow
this.player = this.matter.add.sprite(1600, 200, 'ship')
.setFixedRotation()
.setFrictionAir(0.05)
.setMass(30);
this.cameras.main.startFollow(this.player, false, 0.2, 0.2);
this.cursors = this.input.keyboard.createCursorKeys();
}
update ()
{
if (this.cursors.left.isDown)
{
this.player.thrustBack(0.1);
this.player.flipX = true;
}
else if (this.cursors.right.isDown)
{
this.player.thrust(0.1);
this.player.flipX = false;
}
if (this.cursors.up.isDown)
{
this.player.thrustLeft(0.1);
}
else if (this.cursors.down.isDown)
{
this.player.thrustRight(0.1);
}
}
createLandscape ()
{
// Draw a random 'landscape'
const landscape = this.add.graphics();
landscape.fillStyle(0x008800, 1);
landscape.lineStyle(2, 0x00ff00, 1);
landscape.beginPath();
const maxY = 550;
const minY = 400;
let x = 0;
let y = maxY;
let range = 0;
let up = true;
landscape.moveTo(0, 600);
landscape.lineTo(0, 550);
do
{
// How large is this 'side' of the mountain?
range = Phaser.Math.Between(20, 100);
if (up)
{
y = Phaser.Math.Between(y, minY);
up = false;
}
else
{
y = Phaser.Math.Between(y, maxY);
up = true;
}
landscape.lineTo(x + range, y);
x += range;
} while (x < 3100);
landscape.lineTo(3200, maxY);
landscape.lineTo(3200, 600);
landscape.closePath();
landscape.strokePath();
landscape.fillPath();
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
parent: 'phaser-example',
pixelArt: true,
physics: {
default: 'matter',
matter: {
gravity: {
x: 0,
y: 0
},
enableSleeping: true
}
},
scene: Example
};
const game = new Phaser.Game(config);
Настройка мира и камеры
Первым делом необходимо определить границы игрового мира, которые будут больше размеров экрана. Это позволяет игроку исследовать пространство за пределами одного экрана. Камера также должна знать об этих границах, чтобы не выходить за их пределы.
this.matter.world.setBounds(0, 0, 3200, 600);
this.cameras.main.setBounds(0, 0, 3200, 600);
Здесь this.matter.world.setBounds задает границы для физического движка Matter.js, а this.cameras.main.setBounds — для основной камеры. Оба метода принимают координаты левого верхнего угла (x, y) и размеры (width, height).
Создание процедурного ландшафта
Вместо использования спрайтов ландшафт рисуется с помощью графического объекта Graphics. Метод createLandscape создает случайную гористую местность.
const landscape = this.add.graphics();
landscape.fillStyle(0x008800, 1);
landscape.lineStyle(2, 0x00ff00, 1);
landscape.beginPath();
Сначала создается объект Graphics. fillStyle задает цвет заливки (темно-зеленый), а lineStyle — цвет и толщину обводки (ярко-зеленый). beginPath начинает отрисовку нового контура.
Далее в цикле do...while рисуется ломаная линия, имитирующая горный хребет. Высота каждой следующей точки (`y`) выбирается случайно между минимальным и максимальным значением, а направление (вверх или вниз) чередуется.
y = Phaser.Math.Between(y, minY); // Движение вверх
...
y = Phaser.Math.Between(y, maxY); // Движение вниз
После завершения цикла контур замыкается и отрисовывается с помощью strokePath() (обводка) и fillPath() (заливка).
Добавление игрока и настройка следования камеры
Игрок создается как физический спрайт с помощью Matter.js. Ему задаются важные физические свойства.
this.player = this.matter.add.sprite(1600, 200, 'ship')
.setFixedRotation()
.setFrictionAir(0.05)
.setMass(30);
setFixedRotation() предотвращает вращение корабля под действием сил. setFrictionAir(0.05) устанавливает небольшое сопротивление воздуха для более плавного движения. setMass(30) задает массу объекта.
Чтобы камера следила за игроком, используется метод startFollow.
this.cameras.main.startFollow(this.player, false, 0.2, 0.2);
Первый аргумент — цель слежения. Второй (false) означает, что камера будет плавно наводиться на цель, а не телепортироваться к ней мгновенно. Последние два аргумента (0.2, 0.2) — это коэффициенты линейной интерполяции (LERP) по осям X и Y, отвечающие за плавность движения камеры.
Управление кораблем с помощью клавиатуры
Управление реализовано в методе update(), который вызывается на каждом кадре. Состояние клавиш считывается из объекта this.cursors.
if (this.cursors.left.isDown) {
this.player.thrustBack(0.1);
this.player.flipX = true;
}
Методы thrust, thrustBack, thrustLeft и thrustRight объекта Matter.js применяют импульс (толчок) в соответствующем направлении. Аргумент определяет силу импульса. Свойство flipX используется для отражения спрайта корабля по горизонтали, когда он летит влево.
Обратите внимание, что гравитация в конфигурации игры отключена (gravity: { x: 0, y: 0 }). Это позволяет кораблю двигаться по инерции в пространстве, как в невесомости.
Что попробовать дальше
Мы создали простой, но эффективный прототип: большой мир с процедурным ландшафтом, физический корабль и камеру, которая плавно следует за игроком. Этот фундамент можно развивать: добавить препятствия, врагов, собираемые предметы или сделать ландшафт коллайдером для столкновений. Поэкспериментируйте с параметрами следования камеры, силами толчка и алгоритмом генерации ландшафта, чтобы добиться нужного игрового ощущения.
