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

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

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

Живой запуск

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

Исходный код


class WorldMap extends Phaser.Scene
{
    bg;
    player;
    cursors;

    currentlyOver = false;

    constructor ()
    {
        super({ key: "world" });
    }

    create ()
    {
        this.bg = this.add.tileSprite(0, 0, 800, 600, 'bg').setOrigin(0).setScrollFactor(0);

        //  Some planets to fly into
        const planet1 = this.physics.add.image(100, 100, 'space', 'blue-planet').setScale(0.15).setSize(360, 360).setOffset(400, 400);
        const planet2 = this.physics.add.image(600, 0, 'space', 'brown-planet').setScale(0.2).setSize(360, 360).setOffset(400, 500);
        const planet3 = this.physics.add.image(300, 700, 'space', 'gas-giant').setScale(0.2).setSize(360, 360).setOffset(450, 500);
        const planet4 = this.physics.add.image(1200, 500, 'space', 'purple-planet').setScale(0.3).setSize(360, 360).setOffset(400, 400);

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

        this.player.setDamping(true);
        this.player.setDrag(0.99);
        this.player.setAngularDrag(400);

        this.physics.add.overlap(this.player, [ planet1, planet2, planet3, planet4 ], this.playerHitPlanet, null, this);

        this.cameras.main.startFollow(this.player);

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

        this.add.text(10, 10, 'Cursors to move. Fly into the planets.', { font: '16px Courier', fill: '#00ff00' }).setScrollFactor(0);
    }

    update ()
    {
        const cursors = this.cursors;
        const sprite = this.player;

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

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

        this.bg.tilePositionX += sprite.body.deltaX() * 0.5;
        this.bg.tilePositionY += sprite.body.deltaY() * 0.5;
    }

    playerHitPlanet (player, planet)
    {
        if (this.currentlyOver !== planet)
        {
            this.currentlyOver = planet;

            this.registry.set('planet', planet.frame.name);

            this.scene.switch('subgame');
        }
    }
}

Подготовка сцены и создание мира

В методе create() происходит инициализация игрового мира. Первым делом создается фон в виде TileSprite, который будет прокручиваться, создавая эффект бесконечного пространства. Ключевой момент — использование .setScrollFactor(0), чтобы фон оставался на месте относительно камеры, а не игрока. Затем создаются физические объекты планет. Важно настроить их размеры и смещение коллайдера с помощью setSize() и setOffset(), так как текстуры планет могут быть больше нужной области взаимодействия.

const planet1 = this.physics.add.image(100, 100, 'space', 'blue-planet').setScale(0.15).setSize(360, 360).setOffset(400, 400);

Игрок создается как физический спрайт с ключом 'ship'. Для него сразу настраиваются параметры движения: демпфирование (setDamping(true)), линейное (setDrag(0.99)) и угловое (setAngularDrag(400)) сопротивление. Это придает движению корабля инерционность и плавность.

Организация физики и перехода между сценами

Для обнаружения столкновений корабля с любой из планет используется метод this.physics.add.overlap(). В качестве коллбэка передается функция this.playerHitPlanet.

this.physics.add.overlap(this.player, [ planet1, planet2, planet3, planet4 ], this.playerHitPlanet, null, this);

Коллбэк playerHitPlanet проверяет, что столкновение происходит с новой планетой (чтобы избежать множественных срабатываний), сохраняет имя кадра планеты в реестр игровых данных (registry) и переключает сцену.

this.registry.set('planet', planet.frame.name);
this.scene.switch('subgame');

Реестр (registry) — это глобальное хранилище данных, доступное из любой сцены, что позволяет передать информацию о выбранной планете в подгружаемую сцену 'subgame'.

Управление кораблем и следящая камера

Камера начинает следовать за игроком сразу после его создания, что создает классический вид 'от третьего лица'.

this.cameras.main.startFollow(this.player);

В методе update() обрабатывается ввод с клавиатуры. Корабль ускоряется вперед по направлению своего вращения с помощью physics.velocityFromRotation(). Это удобный метод для преобразования угла в вектор скорости.

this.physics.velocityFromRotation(sprite.rotation, 600, sprite.body.acceleration);

Поворот корабля осуществляется через setAngularVelocity(). Если клавиши не нажаты, ускорение и угловая скорость сбрасываются в ноль.

Создание эффекта параллакса для фона

Один из самых изящных приемов в коде — это создание параллакс-эффекта для фона, который усиливает ощущение движения. В каждом кадре обновления позиция текстуры фона (tilePosition) смещается на часть от пройденного игроком расстояния.

this.bg.tilePositionX += sprite.body.deltaX() * 0.5;
this.bg.tilePositionY += sprite.body.deltaY() * 0.5;

Метод body.deltaX() и body.deltaY() возвращают изменение координат тела физического объекта за последний шаг. Умножая эту дельту на коэффициент 0.5, мы заставляем фон двигаться медленнее, чем игрок, создавая иллюзию глубины.

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

Данный паттерн создания карты мира с физическим движением, следящей камерой и мгновенными переходами между сценами является мощным фундаментом для многих жанров. Для экспериментов попробуйте: добавить на карту вражеские корабли с простым ИИ, реализовать систему варп-туннелей между удаленными секторами или создать многослойный параллакс с несколькими фонами, движущимися с разной скоростью.