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

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

Версия 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/fmship.png');
        this.load.tilemapTiledJSON('map', 'assets/tilemaps/maps/super-mario.json');
        this.load.image('tiles1', 'assets/tilemaps/tiles/super-mario.png');
    }

    create ()
    {
        this.cameras.main.setBounds(0, 0, 3392, 100);
        this.physics.world.setBounds(0, 0, 3392, 240);

        var map = this.make.tilemap({ key: 'map' });
        var tileset = map.addTilesetImage('SuperMarioBros-World1-1', 'tiles1');
        var layer = map.createLayer('World1', tileset, 0, 0);

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

        // this.ship = this.physics.add.image(400, 100, 'ship').setAngle(90).setCollideWorldBounds(true);
        this.ship = this.add.image(400, 100, 'ship').setAngle(90);

        // this.cameras.main.startFollow(this.ship, true, 0.08, 0.08);
        this.cameras.main.startFollow(this.ship, true);

        this.cameras.main.setZoom(4);
    }

    updatePhysics ()
    {
        this.ship.setVelocity(0);

        if (this.cursors.left.isDown)
        {
            this.ship.setAngle(-90).setVelocityX(-200);
        }
        else if (this.cursors.right.isDown)
        {
            this.ship.setAngle(90).setVelocityX(200);
        }

        if (this.cursors.up.isDown)
        {
            this.ship.setVelocityY(-200);
        }
        else if (this.cursors.down.isDown)
        {
            this.ship.setVelocityY(200);
        }
    }

    update ()
    {
        if (this.cursors.left.isDown && this.ship.x > 0)
        {
            this.ship.setAngle(-90);
            this.ship.x -= 2.5;
        }
        else if (this.cursors.right.isDown && this.ship.x < 3392)
        {
            this.ship.setAngle(90);
            this.ship.x += 2.5;
        }

        if (this.cursors.up.isDown && this.ship.y > 0)
        {
            this.ship.y -= 2.5;
        }
        else if (this.cursors.down.isDown && this.ship.y < 240)
        {
            this.ship.y += 2.5;
        }
    }

}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    pixelArt: true,
    physics: {
        default: 'arcade',
    },
    scene: Example
};

const game = new Phaser.Game(config);

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

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

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('ship', 'assets/sprites/fmship.png');
    this.load.tilemapTiledJSON('map', 'assets/tilemaps/maps/super-mario.json');
    this.load.image('tiles1', 'assets/tilemaps/tiles/super-mario.png');
}

В методе create задаются ключевые параметры мира. Границы камеры (setBounds) определяют, как далеко она может двигаться. Границы физического мира (setBounds у this.physics.world) задают область, за пределы которой физические тела не могут выйти (хотя в данном примере физика для корабля отключена).

create ()
{
    this.cameras.main.setBounds(0, 0, 3392, 100);
    this.physics.world.setBounds(0, 0, 3392, 240);
}

Создание мира: тайловая карта и объект слежения

Создание игрового мира начинается с загрузки тайловой карты. Объект map создается через this.make.tilemap. Затем добавляется набор тайлов (addTilesetImage), где указывается ключ из данных Tiled и ключ загруженного изображения. Слой создается методом createLayer.

var map = this.make.tilemap({ key: 'map' });
var tileset = map.addTilesetImage('SuperMarioBros-World1-1', 'tiles1');
var layer = map.createLayer('World1', tileset, 0, 0);

Затем создается спрайт корабля — объект, за которым будет следовать камера. В исходном коде используется обычный спрайт (this.add.image), а закомментированная строка показывает, как создать физическое тело. Также настраивается управление с клавиатуры.

this.cursors = this.input.keyboard.createCursorKeys();
this.ship = this.add.image(400, 100, 'ship').setAngle(90);

Настройка поведения камеры: слежка и зум

Сердце примера — настройка камеры. Метод startFollow заставляет основную камеру следовать за целевым объектом. Первый аргумент — объект слежения. Второй аргумент (true) включает плавное (линейное) интерполирование движения камеры. Закомментированная версия показывает, как задать коэффициенты интерполяции по осям X и Y для более резкого или плавного движения.

this.cameras.main.startFollow(this.ship, true);
// this.cameras.main.startFollow(this.ship, true, 0.08, 0.08);

Метод setZoom изменяет масштаб отображения. Значение `4` увеличивает изображение в 4 раза, что полезно для пиксель-арт игр или чтобы показать детали объекта. При зуме камера продолжает следовать за целью в рамках своих границ.

this.cameras.main.setZoom(4);

Логика перемещения объекта

В примере представлены две функции обновления. updatePhysics не используется в текущей конфигурации, но демонстрирует, как управлять объектом через физическую систему Arcade, задавая скорость (setVelocity).

updatePhysics ()
{
    this.ship.setVelocity(0);
    if (this.cursors.left.isDown)
    {
        this.ship.setAngle(-90).setVelocityX(-200);
    }
}

Активная функция update реализует простое перемещение корабля путем прямого изменения его координат (this.ship.x, this.ship.y). Проверка на границы мира (this.ship.x < 3392) предотвращает выход за пределы, заданные для камеры.

update ()
{
    if (this.cursors.left.isDown && this.ship.x > 0)
    {
        this.ship.setAngle(-90);
        this.ship.x -= 2.5;
    }
}

Конфигурация игры

Конфигурационный объект config определяет основные параметры игры. Ключевые настройки для этого примера: - pixelArt: true — включает сглаживание текстур для пиксель-арта. - physics: { default: 'arcade' } — активирует физический движок Arcade (хотя в данном примере он не используется для корабля). - scene: Example — указывает класс сцены.

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    pixelArt: true,
    physics: {
        default: 'arcade',
    },
    scene: Example
};

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

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