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

Одна из ключевых задач при создании игр с большим миром — управление камерой. Если игрок может выходить за пределы одного экрана, камера должна плавно следовать за ним, чтобы игрок всегда оставался в центре внимания. В этой статье мы разберем пример из официальной документации 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('bg', 'assets/pics/the-end-by-iloe-and-made.jpg');
        this.load.image('block', 'assets/sprites/block.png');
    }

    create ()
    {
        //  Set the camera and physics bounds to be the size of 4x4 bg images
        this.cameras.main.setBounds(0, 0, 1920 * 2, 1080 * 2);
        this.physics.world.setBounds(0, 0, 1920 * 2, 1080 * 2);

        //  Mash 4 images together to create our background
        this.add.image(0, 0, 'bg').setOrigin(0);
        this.add.image(1920, 0, 'bg').setOrigin(0).setFlipX(true);
        this.add.image(0, 1080, 'bg').setOrigin(0).setFlipY(true);
        this.add.image(1920, 1080, 'bg').setOrigin(0).setFlipX(true).setFlipY(true);

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

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

        this.player.setCollideWorldBounds(true);

        this.cameras.main.setRoundPixels(true);

        this.cameras.main.startFollow(this.player, true);
        // this.cameras.main.startFollow(this.player, true, 0.05, 0.05);

        this.events.on('prerender', this.preRender, this);
    }

    preRender ()
    {
        // console.log(this.player.x, this.player.y, this.cameras.main.scrollX, this.cameras.main.scrollY);
    }

    update ()
    {
        this.player.setVelocity(0);

        if (this.cursors.left.isDown)
        {
            this.player.setVelocityX(-500);
        }
        else if (this.cursors.right.isDown)
        {
            this.player.setVelocityX(500);
        }

        if (this.cursors.up.isDown)
        {
            this.player.setVelocityY(-500);
        }
        else if (this.cursors.down.isDown)
        {
            this.player.setVelocityY(500);
        }
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    physics: {
        default: 'arcade',
    },
    scene: Example
};

const game = new Phaser.Game(config);

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

Перед тем как заставить камеру следовать за игроком, необходимо определить границы игрового мира. Это важно, чтобы камера знала, где ей можно перемещаться, а физическое тело игрока не вылетало за пределы уровня.

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

this.cameras.main.setBounds(0, 0, 1920 * 2, 1080 * 2);
this.physics.world.setBounds(0, 0, 1920 * 2, 1080 * 2);

Здесь создается мир размером 3840x2160 пикселей (удвоенное разрешение Full HD). Фон, состоящий из четырёх склеенных изображений, идеально заполняет эти границы.

Создание и настройка игрока

Игрок создается как физический спрайт с помощью фабрики this.physics.add.image. Это автоматически добавляет ему тело Arcade Physics, которое может сталкиваться с границами мира и получать скорость.

this.player = this.physics.add.image(400, 300, 'block');
this.player.setCollideWorldBounds(true);

Метод setCollideWorldBounds(true) включает столкновение спрайта с границами физического мира, которые мы установили ранее. Это не даст игроку вылететь за пределы уровня.

Управление осуществляется через курсорные клавиши. В методе update() мы каждый кадр сбрасываем скорость игрока на ноль, а затем, в зависимости от нажатых клавиш, задаем новую скорость по осям X и Y.

this.player.setVelocity(0);
if (this.cursors.left.isDown) {
    this.player.setVelocityX(-500);
}

Волшебный метод: startFollow

Основная магия происходит в одной строчке. Метод this.cameras.main.startFollow() заставляет главную камеру следовать за указанной целью — в нашем случае, за спрайтом игрока.

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

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

В закомментированной строке показаны дополнительные параметры для тонкой наки:

// this.cameras.main.startFollow(this.player, true, 0.05, 0.05);

Третий и четвертый параметры — это коэффициенты интерполяции по осям X и Y. Чем они меньше, тем сильнее запаздывание и плавнее движение камеры. Значения 0.05 создадут очень плавный, "ленивый" эффект следования.

Важные детали: округление пикселей и событие prerender

Два менее очевидных, но полезных приема также используются в примере.

this.cameras.main.setRoundPixels(true);

Метод setRoundPixels(true) включает округление координат отрисовки до целых пикселей. Это помогает избежать размытия спрайтов при их субпиксельном позиционировании, что особенно важно для пиксель-арт игр или при использовании камеры, которая часто останавливается на дробных координатах.

Также в сцену добавлен слушатель события prerender:

this.events.on('prerender', this.preRender, this);

Это событие срабатывает каждый кадр перед отрисовкой сцены. В предоставленном методе preRender() закомментирован вывод в консоль, который может быть полезен для отладки: он показывает текущие координаты игрока (this.player.x, this.player.y) и позицию прокрутки камеры (this.cameras.main.scrollX, this.cameras.main.scrollY). Вы можете раскомментировать console.log, чтобы в реальном времени наблюдать за этими значениями и лучше понимать работу камеры.

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

Настройка следящей камеры в Phaser сводится к правильной установке границ мира и вызову startFollow(). Используя параметры интерполяции, можно добиться эффекта камеры от плавного и ленивого до мгновенного и резкого, что напрямую влияет на ощущения от геймплея. Для экспериментов попробуйте: изменить коэффициенты интерполяции в startFollow() на другие значения; отключить lerp, чтобы камера телепортировалась за игроком; или привязать камеру не к самому спрайту игрока, а к точке слегка впереди него по направлению движения, чтобы камера "заглядывала" вперед.