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

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

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

Живой запуск

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

Исходный код



class Example extends Phaser.Scene
{
    stars = [];
    cameraRotation = 0;
    camera0;
    camera1;
    camera2;
    camera3;

    create() {
        const radius = 20;
        const radius2 = radius * 2;
        const maxWidth = (400 / radius2)|0;

        for (let i = 0; i < 80; ++i)
        {
            const graphics = this.add.graphics({x: radius + (i % maxWidth) * radius2, y: radius + ((i / maxWidth)|0) * radius2});
            this.drawStar(graphics, 0, 0, 5, radius, radius / 2, 0xff0000, 0xFFFF00);
            graphics.fillStyle(0xFFFF00 + (i % 0xFF), 1.0);
            this.stars.push(graphics);
            this.stars[i].rotation += 0.1;
        }

        this.cameras.main.setSize(400, 300);

        this.camera0 = this.cameras.main;
        this.camera1 = this.cameras.add(400, 0, 400, 300);
        this.camera2 = this.cameras.add(0, 300, 400, 300);
        this.camera3 = this.cameras.add(400, 300, 400, 300);
    }

    update() {
        this.camera0.scrollX = Math.cos(this.cameraRotation) * 100;
        this.camera0.scrollY = Math.sin(this.cameraRotation) * 100;
        this.camera1.shake(100, 0.01);
        this.camera2.flash(2000);
        this.camera3.fade(2000);
        this.camera3.rotation = Math.sin(this.cameraRotation);
        this.camera3.zoom = 0.5 + Math.abs(Math.sin(this.cameraRotation));
        if (this.camera3._fadeAlpha >= 1.0)
        {
            this.camera3._fadeAlpha = 0.0;
            this.camera3.fade(1000);
        }
        for (let i = 0; i < this.stars.length; ++i)
        {
            const star = this.stars[i];
            star.rotation += 0.01;
            star.scaleX = 0.5 + Math.abs(Math.sin(star.rotation));
            star.scaleY = 0.5 + Math.abs(Math.sin(star.rotation));
        }
        this.cameraRotation += 0.01;
    }

    drawStar (graphics, cx, cy, spikes, outerRadius, innerRadius, color, lineColor) {
        let rot = Math.PI / 2 * 3;
        let x = cx;
        let y = cy;
        const step = Math.PI / spikes;
        graphics.lineStyle(1, lineColor, 1.0);
        graphics.fillStyle(color, 1.0);
        graphics.beginPath();
        graphics.moveTo(cx, cy - outerRadius);
        for (let i = 0; i < spikes; i++) {
            x = cx + Math.cos(rot) * outerRadius;
            y = cy + Math.sin(rot) * outerRadius;
            graphics.lineTo(x, y);
            rot += step;

            x = cx + Math.cos(rot) * innerRadius;
            y = cy + Math.sin(rot) * innerRadius;
            graphics.lineTo(x, y);
            rot += step;
        }
        graphics.lineTo(cx, cy - outerRadius);
        graphics.closePath();
        graphics.fillPath();
        graphics.strokePath();
    }
}

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


const game = new Phaser.Game(config);


Создание массива графических объектов

В методе create() создается сетка из 80 звезд. Каждая звезда — это отдельный объект Graphics, которому задается позиция на экране. Ключевой момент: отрисовка фигуры делегируется вспомогательному методу drawStar(), что делает код создания каждого объекта чистым и повторяемым.

for (let i = 0; i < 80; ++i)
{
    const graphics = this.add.graphics({x: radius + (i % maxWidth) * radius2, y: radius + ((i / maxWidth)|0) * radius2});
    this.drawStar(graphics, 0, 0, 5, radius, radius / 2, 0xff0000, 0xFFFF00);
    graphics.fillStyle(0xFFFF00 + (i % 0xFF), 1.0);
    this.stars.push(graphics);
    this.stars[i].rotation += 0.1;
}

Здесь this.add.graphics() создает объект для векторной графики. Позиция `xиyрассчитывается так, чтобы звезды расположились в аккуратную сетку. После отрисовки звезды черезdrawStar(), ей задается уникальный цвет заливки, используя индексi, чтобы каждая звезда немного отличалась. Объект сохраняется в массивthis.starsдля последующей анимации вupdate()`.

Настройка основной и дополнительных камер

Phaser позволяет работать не с одной, а с несколькими камерами одновременно. В этом примере экран делится на четыре равные части, каждая со своей камерой. Основная камера (this.cameras.main) уменьшается в размерах, чтобы освободить место для остальных.

this.cameras.main.setSize(400, 300);

this.camera0 = this.cameras.main;
this.camera1 = this.cameras.add(400, 0, 400, 300);
this.camera2 = this.cameras.add(0, 300, 400, 300);
this.camera3 = this.cameras.add(400, 300, 400, 300);

Метод this.cameras.main.setSize(400, 300) изменяет размер и область видимости основной камеры. Теперь она занимает только верхний левый квадрант экрана 800x600. Метод this.cameras.add(x, y, width, height) создает новую камеру с указанными координатами (относительно всего холста игры) и размерами. Таким образом, camera1 — правый верхний квадрант, camera2 — левый нижний, camera3 — правый нижний. Все четыре камеры смотрят на одну и ту же сцену со звездами, но могут управляться независимо.

Анимация объектов на сцене

В методе update() происходит анимация самих графических объектов — звезд. Это демонстрирует, что логика объектов сцены существует отдельно от логики камер.

for (let i = 0; i < this.stars.length; ++i)
{
    const star = this.stars[i];
    star.rotation += 0.01;
    star.scaleX = 0.5 + Math.abs(Math.sin(star.rotation));
    star.scaleY = 0.5 + Math.abs(Math.sin(star.rotation));
}

Каждый кадр к вращению каждой звезды прибавляется 0.01. Масштаб по осям X и Y (scaleX, scaleY) привязывается к синусу от текущего вращения. Использование Math.abs() гарантирует, что масштаб всегда будет положительным (от 0.5 до 1.5), создавая эффект пульсации. Это простая, но эффективная формула для создания живой, органической анимации.

Управление камерами и встроенные эффекты

Сердце примера — применение разных эффектов к каждой камере в update(). Это показывает богатый API объекта Camera в Phaser.

this.camera0.scrollX = Math.cos(this.cameraRotation) * 100;
this.camera0.scrollY = Math.sin(this.cameraRotation) * 100;
this.camera1.shake(100, 0.01);
this.camera2.flash(2000);
this.camera3.fade(2000);
this.camera3.rotation = Math.sin(this.cameraRotation);
this.camera3.zoom = 0.5 + Math.abs(Math.sin(this.cameraRotation));

1. **Камера 0 (движение):** Свойства scrollX и scrollY заставляют камеру плавно двигаться по кругу, создавая эффект орбиты вокруг сцены. 2. **Камера 1 (дрожание):** Метод shake(duration, intensity) вызывает эффект тряски, как от удара или землетрясения. Он вызывается каждый кадр, поэтому тряска непрерывная. 3. **Камера 2 (вспышка):** Метод flash(duration, color, force) создает короткую белую вспышку на весь вид камеры. Длительность в 2000 мс делает ее плавной. 4. **Камера 3 (комплексные эффекты):** Здесь применяется сразу несколько трансформаций. fade() создает эффект постепенного затемнения до черного. rotation и zoom анимируются по синусоиде, заставляя камеру поворачиваться и плавно увеличивать/уменьшать масштаб. Условный блок с _fadeAlpha сбрасывает эффект затухания, когда он завершается, и запускает его снова, создавая цикл.

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

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