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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    controls;
    text2;
    text;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('eye', 'assets/pics/lance-overdose-loader-eye.png');
    }

    create ()
    {
        for (let i = 0; i < 32; i++)
        {
            const x = Phaser.Math.Between(0, 2000);
            const y = Phaser.Math.Between(0, 2000);

            this.add.sprite(x, y, 'eye').setInteractive();
        }

        this.input.on('gameobjectover', (pointer, gameObject) =>
        {

            gameObject.setTint(0xff0000);

        });

        this.input.on('gameobjectout', (pointer, gameObject) =>
        {

            gameObject.clearTint();

        });

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

        const controlConfig = {
            camera: this.cameras.main,
            left: cursors.left,
            right: cursors.right,
            up: cursors.up,
            down: cursors.down,
            zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
            zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
            acceleration: 0.06,
            drag: 0.0005,
            maxSpeed: 1.0
        };

        this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);

        // this.cameras.main.setBackgroundColor('rgba(255, 0, 0, 0.5)');
        // this.cameras.main.setZoom(0.8);
        // this.cameras.main.setRotation(Phaser.Math.DegToRad(10));

        this.text = this.add.text(100, 200, 'x: 0 y: 0', { font: '18px Courier', fill: '#00ff00' }).setScrollFactor(0);
        this.text2 = this.add.text(100, 400, '', { font: '18px Courier', fill: '#00ff00' }).setScrollFactor(0);

        this.input.keyboard.on('keydown_Z', function (event)
        {

            this.cameras.main.setRotation(this.cameras.main.rotation + 0.01);

        }, this);

        this.input.keyboard.on('keydown_X', function (event)
        {

            this.cameras.main.setRotation(this.cameras.main.rotation - 0.01);

        }, this);
    }

    update (time, delta)
    {
        this.controls.update(delta);

        const cam = this.cameras.main;

        //  Take a coordinate from screen space and convert it into World space within the Camera
        // var p = cam.screenToCamera({ x: this.input.x, y: this.input.y });

        const p = this.input.activePointer.positionToCamera(cam);

        this.text.setText([
            `cx: ${cam.scrollX}`,
            `cy: ${cam.scrollY}`,
            '',
            `sx: ${this.input.x}`,
            `sy: ${this.input.y}`,
            '',
            `px: ${p.x}`,
            `py: ${p.y}`
        ]);

        this.text2.setText([
            `a: ${cam.matrix.matrix[0]}`,
            `b: ${cam.matrix.matrix[1]}`,
            `c: ${cam.matrix.matrix[2]}`,
            `d: ${cam.matrix.matrix[3]}`,
            `tx: ${cam.matrix.matrix[4]}`,
            `ty: ${cam.matrix.matrix[5]}`
        ]);
    }
}

const config = {
    type: Phaser.WEBGL,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Создание интерактивного игрового мира

В методе create мы создаем основу для примера: множество спрайтов, которые будут реагировать на курсор, и систему управления камерой.

Цикл создает 32 спрайта с изображением глаза в случайных позициях на большой карте размером 2000x2000 пикселей. Ключевой момент — вызов метода setInteractive() для каждого спрайта. Без этого спрайт будет просто картинкой, и события ввода на него не сработают.

for (let i = 0; i < 32; i++)
{
    const x = Phaser.Math.Between(0, 2000);
    const y = Phaser.Math.Between(0, 2000);
    this.add.sprite(x, y, 'eye').setInteractive();
}

Далее настраиваются обработчики событий. Событие gameobjectover срабатывает, когда указатель мыши (или касание) попадает на интерактивный игровой объект. В ответ объект подкрашивается красным оттенком с помощью setTint. Событие gameobjectout срабатывает при уходе указателя и очищает этот оттенок.

this.input.on('gameobjectover', (pointer, gameObject) => {
    gameObject.setTint(0xff0000);
});

this.input.on('gameobjectout', (pointer, gameObject) => {
    gameObject.clearTint();
});

Настройка плавного управления камерой

Чтобы исследовать большой мир, камерой нужно управлять. Phaser предоставляет удобный класс SmoothedKeyControl для плавного перемещения и зума камеры с клавиатуры.

Сначала создается объект конфигурации controlConfig. В нем указывается, какой камерой управлять (this.cameras.main), какие клавиши отвечают за движение (стрелки) и зум (Q и E). Параметры acceleration, drag и maxSpeed настраивают физику движения: ускорение, замедление и максимальную скорость прокрутки.

const controlConfig = {
    camera: this.cameras.main,
    left: cursors.left,
    right: cursors.right,
    up: cursors.up,
    down: cursors.down,
    zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
    zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
    acceleration: 0.06,
    drag: 0.0005,
    maxSpeed: 1.0
};

this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);

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

update (time, delta)
{
    this.controls.update(delta);
    // ... остальной код
}

Преобразование координат: экран vs мир

Одна из частых задач — понять, на какой объект в мировых координатах указывает курсор, особенно когда камера сдвинута, повернута или увеличена. Исходный код демонстрирует два подхода.

Первый, закомментированный, использует метод камеры screenToCamera. Второй, активный, использует метод указателя positionToCamera. Оба выполняют одну функцию: переводят координаты с экрана (относительно окна браузера) в координаты внутри видимой области камеры (с учетом ее трансформаций).

// Старый подход (закомментирован)
// var p = cam.screenToCamera({ x: this.input.x, y: this.input.y });

// Актуальный подход
const p = this.input.activePointer.positionToCamera(cam);

Полученные координаты p.x и p.y — это уже координаты в мировом пространстве, которые можно использовать для проверки столкновений, размещения объектов или, как в нашем случае, для отладки. В текстовом поле this.text выводятся оба набора координат для наглядности: позиция камеры (scrollX, scrollY), позиция курсора на экране и его позиция в мире.

Эксперименты с трансформацией камеры

Камера в Phaser — это не просто "окно". Ей можно задавать дополнительные трансформации: поворот, масштаб (зум) и даже цветную маску (через setBackgroundColor). В примере показано, как это делать.

Закомментированные строки в create демонстрируют базовую настройку. Можно раскомментировать их, чтобы сразу увидеть эффект.

// this.cameras.main.setBackgroundColor('rgba(255, 0, 0, 0.5)');
// this.cameras.main.setZoom(0.8);
// this.cameras.main.setRotation(Phaser.Math.DegToRad(10));

Более интересно динамическое управление. В примере на клавиши Z и X назначено изменение угла поворота камеры на лету. Это отлично показывает, как трансформации камеры влияют на отрисовку всего мира и на логику преобразования координат курсора.

this.input.keyboard.on('keydown_Z', function (event) {
    this.cameras.main.setRotation(this.cameras.main.rotation + 0.01);
}, this);

Для глубокого понимания внутреннего устройства в текстовом поле this.text2 выводится матрица трансформации текущей камеры (cam.matrix). Это 2D-матрица 3x3, хранящаяся в виде массива из 9 элементов, которая кодирует все преобразования (поворот, масштаб, сдвиг).

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

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

  1. заменить спрайты на тайловую карту
  2. реализовать привязку камеры к персонажу с границами
  3. использовать преобразование координат для реализации системы перетаскивания объектов по миру или построения путей. Понимание этих механизмов открывает путь к созданию сложных и динамичных игровых сцен