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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    controls;
    cards;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.atlas('cards', 'assets/atlas/cards.png', 'assets/atlas/cards.json');
    }

    create ()
    {
        //  Create a stack of random cards
        this.cards = this.add.group();

        const frames = this.textures.get('cards').getFrameNames();

        for (let i = 0; i < 200; i++)
        {
            const x = Phaser.Math.Between(0, 2048);
            const y = Phaser.Math.Between(0, 1200);

            const image = this.add.image(x, y, 'cards', Phaser.Math.RND.pick(frames));

            image.setInteractive();

            image.setScale(Phaser.Math.FloatBetween(0.25, 1.0));

            image.setScrollFactor(image.scaleX);

            image.setDepth(image.scrollFactorX);

            image.setAngle(Phaser.Math.Between(0, 359));

            this.input.setDraggable(image);

            this.cards.add(image);
        }

        this.input.on('drag', (pointer, gameObject, dragX, dragY) =>
        {

            gameObject.x = dragX;
            gameObject.y = dragY;

        });

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

        const controlConfig = {
            camera: this.cameras.main,
            left: cursors.left,
            right: cursors.right,
            up: cursors.up,
            down: cursors.down,
            acceleration: 0.04,
            drag: 0.0005,
            maxSpeed: 0.7
        };

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

        this.cameras.main.setBounds(0, 0, 2048, 1200);
    }

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

        Phaser.Actions.Rotate(this.cards.getChildren(), 0.01);
    }
}

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

const game = new Phaser.Game(config);

Подготовка и создание объектов

В методе preload загружается атлас спрайтов с картами. В create создаётся группа this.cards для удобного управления множеством объектов.

Затем в цикле генерируется 200 карт со случайными параметрами: позиция, кадр из атласа, масштаб, угол поворота. Ключевые настройки — setScrollFactor и setDepth.

image.setScale(Phaser.Math.FloatBetween(0.25, 1.0));
image.setScrollFactor(image.scaleX);
image.setDepth(image.scrollFactorX);

setScrollFactor определяет, как объект движется относительно камеры. Значение, равное масштабу, создаёт параллакс-эффект: крупные карты (масштаб ~1.0) движутся почти с камерой, мелкие (масштаб ~0.25) — медленнее, создавая ощущение глубины. setDepth использует тот же фактор для корректного порядка отрисовки.

Каждая карта делается интерактивной и перетаскиваемой с помощью setInteractive и setDraggable.

Механика перетаскивания объектов

Для обработки перетаскивания используется событие drag объекта this.input. Оно срабатывает при перемещении зажатого указателя (мыши или касания).

this.input.on('drag', (pointer, gameObject, dragX, dragY) => {
    gameObject.x = dragX;
    gameObject.y = dragY;
});

В колбэке gameObject — это перетаскиваемый объект (карта), а dragX и dragY — его новые мировые координаты, автоматически рассчитанные движком с учётом камеры. Просто присваивая эти значения, мы перемещаем карту. Это базовая, но мощная механика для взаимодействия с игровыми объектами.

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

Управление камерой реализовано через класс Phaser.Cameras.Controls.SmoothedKeyControl. Сначала создаётся объект конфигурации, куда передаются ключи-стрелки и настройки камеры.

const controlConfig = {
    camera: this.cameras.main,
    left: cursors.left,
    right: cursors.right,
    up: cursors.up,
    down: cursors.down,
    acceleration: 0.04,
    drag: 0.0005,
    maxSpeed: 0.7
};
this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);

Здесь acceleration — ускорение камеры при нажатии клавиши, drag — сила замедления при отпускании, а maxSpeed — ограничение максимальной скорости. Такой подход даёт инерционное, плавное движение, а не мгновенные рывки.

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

this.cameras.main.setBounds(0, 0, 2048, 1200);

Обновление состояния в update

В методе update происходит две вещи: обновление управления камерой и анимация карт.

this.controls.update(delta);
Phaser.Actions.Rotate(this.cards.getChildren(), 0.01);

Метод this.controls.update(delta) необходимо вызывать каждый кадр, передавая дельту времени для плавного и независимого от частоты кадров движения камеры.

Phaser.Actions.Rotate — это хелпер для групповых операций. Он поворачивает все карты в группе на 0.01 радиана за кадр, создавая постоянное, едва заметное вращение, которое оживляет сцену. Вращение происходит в мировых координатах, независимо от камеры и параллакса.

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

Пример объединяет несколько важных техник: плавное инерционное управление камерой, параллакс-эффект через scrollFactor, перетаскивание объектов и фоновую анимацию. Для экспериментов попробуйте изменить параметры acceleration и drag у камеры, чтобы добиться другого 'ощущения' управления. Добавьте зум камеры через this.cameras.main.zoom или реализуйте сортировку глубины при перетаскивании, меняя depth у активной карты. Можно также заменить вращение карт на их покачивание или пульсацию, используя Phaser.Actions.