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

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

Версия 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('map', 'assets/tests/camera/earthbound-scarab.png');
    }

    create ()
    {
        this.cameras.main.setBounds(0, 0, 1024, 2048);

        this.add.image(0, 0, 'map').setOrigin(0);

        this.cameras.main.setZoom(1);
        this.cameras.main.centerOn(0, 0);

        this.text = this.add.text(304, 230)
            .setText('Click to move')
            .setScrollFactor(0);
            this.text.setShadow(1, 1, '#000000', 2);

        let pos = 0;
        this.input.on('pointerdown', function () {

            const cam = this.cameras.main;

            if (pos === 0)
            {
                cam.alpha = 0.5;
                cam.pan(767, 1096, 2000, 'Power2');
                cam.zoomTo(4, 3000);
                pos++;
            }
            else if (pos === 1)
            {
                cam.alpha = 1;
                cam.pan(703, 1621, 2000, 'Elastic');
                cam.zoomTo(2, 3000);
                pos++;
            }
            else if (pos === 2)
            {
                cam.alpha = 0.2;
                cam.pan(256, 623, 2000, 'Sine.easeInOut');
                cam.zoomTo(1, 3000);
                pos++;
            }
            else if (pos === 3)
            {
                cam.alpha = 0.9;
                cam.pan(166, 304, 2000);
                cam.zoomTo(4, 1500);
                pos++;
            }
            else if (pos === 4)
            {
                cam.alpha = 0.1;
                cam.pan(624, 158, 2000);
                cam.zoomTo(0.5, 3000);
                pos++;
            }
            else if (pos === 5)
            {
                cam.alpha = 0.6;
                cam.pan(680, 330, 2000);
                pos++;
            }
            else if (pos === 6)
            {
                cam.alpha = 1;
                cam.pan(748, 488, 2000);
                pos++;
            }
            else if (pos === 7)
            {
                cam.pan(1003, 1719, 2000);
                pos = 0;
            }

        }, this);
    }

    update ()
    {
        const cam = this.cameras.main;
        this.text.setText(['Click to move', 'x: ' + cam.scrollX, 'y: ' + cam.scrollY ]);
    }
}

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 загружается изображение большой карты, которое будет служить игровым миром. Важно задать корректный базовый URL для загрузчика.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('map', 'assets/tests/camera/earthbound-scarab.png');
}

В create мы выполняем первоначальную настройку. Сначала устанавливаем границы (setBounds) для основной камеры this.cameras.main. Это определяет область мира, которую камера может просматривать. Затем изображение карты добавляется в начало координат мира (0,0).

create ()
{
    this.cameras.main.setBounds(0, 0, 1024, 2048);
    this.add.image(0, 0, 'map').setOrigin(0);
    this.cameras.main.setZoom(1);
    this.cameras.main.centerOn(0, 0);
    // ... создание текста и обработчика клика
}

Отслеживание кликов и управление состоянием

Для переключения между разными эффектами используется простая переменная-счетчик pos. Обработчик события pointerdown на игровом поле (this.input) содержит логику, которая выполняется при каждом клике.

let pos = 0;
this.input.on('pointerdown', function () {
    const cam = this.cameras.main;
    // Логика в зависимости от значения pos
}, this);

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

Магия камеры: Прозрачность, панорамирование и зум

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

* **Прозрачность (Alpha)**: Свойству cam.alpha присваивается значение от 0 (полная прозрачность) до 1 (полная непрозрачность). Это позволяет создавать эффекты затухания. * **Панорамирование (Pan)**: Метод cam.pan(x, y, duration, ease) плавно перемещает центр камеры к заданным мировым координатам за указанное время (в миллисекундах). Необязательный параметр ease задает функцию плавности анимации (например, 'Power2', 'Elastic'). * **Приближение/Отдаление (ZoomTo)**: Метод cam.zoomTo(zoomLevel, duration) анимирует изменение масштаба камеры.

// Пример первого перехода
if (pos === 0)
{
    cam.alpha = 0.5; // Полупрозрачность
    cam.pan(767, 1096, 2000, 'Power2'); // Плавное движение к точке (767,1096)
    cam.zoomTo(4, 3000); // Увеличить масштаб в 4 раза
    pos++;
}

Каждый блок кода меняет эти параметры по-разному, создавая разнообразные визуальные переходы. Обратите внимание, как меняется ease-функция, влияя на характер движения.

Динамический UI с текстом

Чтобы визуализировать текущее положение камеры, в сцене создается текстовый объект. Ключевой момент — вызов .setScrollFactor(0). Это фиксирует текст относительно экрана, а не игрового мира, поэтому он не двигается вместе с камерой.

this.text = this.add.text(304, 230)
    .setText('Click to move')
    .setScrollFactor(0); // Текст привязан к экрану
this.text.setShadow(1, 1, '#000000', 2);

В методе update, который вызывается каждый кадр, текст обновляется, отображая текущие координаты прокрутки камеры (cam.scrollX и cam.scrollY). Это полезно для отладки и понимания логики работы камеры.

update ()
{
    const cam = this.cameras.main;
    this.text.setText(['Click to move', 'x: ' + cam.scrollX, 'y: ' + cam.scrollY ]);
}

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

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

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);

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

Этот пример — отличная отправная точка для освоения выразительных возможностей камеры в Phaser. Комбинируя alpha, pan и zoomTo, вы можете создавать сложные сцены без необходимости анимировать сами игровые объекты. Для экспериментов попробуйте: изменить последовательность и параметры переходов; привязать эффекты не к клику, а к событиям игры (например, получению урона или открытию сундука); использовать несколько камер (this.cameras.add) с разными свойствами и переключаться между ними для эффекта разделенного экрана или картины в картине.