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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    offset;
    graphics;
    bob;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('pic', 'assets/pics/kris-jovo.jpg');
    }

    create ()
    {
        this.add.image(400, 300, 'pic').setAlpha(0.3).setFlipX(true);

        this.bob = this.add.image(400, 300, 'pic').setFlipX(true);

        this.graphics = this.add.graphics();

        const cropWidth = 200;
        const cropHeight = 100;

        this.bob.setCrop(0, 0, cropWidth, cropHeight);

        this.offset = this.bob.getTopLeft();

        this.input.on('pointermove', pointer =>
        {

            this.bob.setCrop(
                (pointer.x - this.offset.x) - cropWidth / 2,
                (pointer.y - this.offset.y) - cropHeight / 2,
                cropWidth,
                cropHeight
            );
        });
    }

    update ()
    {
        this.graphics.clear();
        this.graphics.lineStyle(1, 0x00ff00);
        this.graphics.strokeRect(this.offset.x + this.bob._crop.x, this.offset.y + this.bob._crop.y, this.bob._crop.width, this.bob._crop.height);
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    backgroundColor: '#2d2d88',
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка сцены и загрузка ассетов

Вся логика примера содержится в классе сцены Example. В методе preload мы загружаем одно изображение, которое будет использоваться как текстура. Обратите внимание на использование setBaseURL — это удобно для указания базового пути к ресурсам.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('pic', 'assets/pics/kris-jovo.jpg');
}

Создание фона и интерактивного изображения

В методе create мы создаём два экземпляра одного и того же изображения. Первый (this.add.image(400, 300, 'pic')) служит полупрозрачным фоном с отражением по горизонтали (setFlipX(true)). Второй экземпляр сохраняется в свойство this.bob — это наш основной интерактивный объект, который также сразу отражается.

create ()
{
    this.add.image(400, 300, 'pic').setAlpha(0.3).setFlipX(true);
    this.bob = this.add.image(400, 300, 'pic').setFlipX(true);
    ...
}

Затем создаётся объект Graphics (this.graphics) для последующей отрисовки зелёной рамки вокруг области кадрирования.

Настройка кадрирования и привязка к курсору

Сначала для объекта bob устанавливается исходная область кадрирования (кроп) размером 200x100 пикселей, начиная с левого верхнего угла текстуры.

const cropWidth = 200;
const cropHeight = 100;
this.bob.setCrop(0, 0, cropWidth, cropHeight);

Ключевой момент — вычисление точки отсчета (this.offset) с помощью метода getTopLeft(). Этот метод возвращает мировые координаты левого верхнего угла игрового объекта bob. Эти координаты нужны для корректного перевода позиции курсора в координаты текстуры объекта.

Затем мы подписываемся на событие движения указателя (pointermove). В обработчике мы вычисляем новые координаты для области кадрирования так, чтобы её центр совпадал с позицией курсора относительно левого верхнего угла объекта bob.

this.input.on('pointermove', pointer =>
{
    this.bob.setCrop(
        (pointer.x - this.offset.x) - cropWidth / 2,
        (pointer.y - this.offset.y) - cropHeight / 2,
        cropWidth,
        cropHeight
    );
});

Визуализация области кадрирования

Метод update вызывается на каждом кадре. Здесь мы сначала очищаем предыдущую отрисовку Graphics, задаём стиль линии и рисуем зелёный прямоугольник, который обводит текущую область кадрирования.

Важно: координаты для прямоугольника рассчитываются как сумма смещения объекта (this.offset) и внутренних координат кадрирования (this.bob._crop). Свойство _crop — это внутренний объект Phaser, содержащий текущие параметры кропа (x, y, width, height). Его использование напрямую, как в этом примере, допустимо для демонстрации, но в продакшн-коде стоит быть осторожным с приватными свойствами.

update ()
{
    this.graphics.clear();
    this.graphics.lineStyle(1, 0x00ff00);
    this.graphics.strokeRect(this.offset.x + this.bob._crop.x, this.offset.y + this.bob._crop.y, this.bob._crop.width, this.bob._crop.height);
}

Конфигурация и запуск игры

В конце файла определяется стандартная конфигурация игры Phaser (config), в которой указывается тип рендерера, элемент-контейнер, размеры холста, цвет фона и наша сцена Example. Затем создаётся экземпляр игры.

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    backgroundColor: '#2d2d88',
    scene: Example
};
const game = new Phaser.Game(config);

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

Этот пример наглядно демонстрирует мощь и гибкость работы с текстурами в Phaser. Комбинируя методы setFlipX/setFlipY, setCrop и реагируя на ввод данных, вы можете создавать сложные визуальные взаимодействия. **Идеи для экспериментов:** 1. Измените логику обработки события pointermove, чтобы кроп "прилипал" к курсору только при зажатой кнопке мыши. 2. Добавьте возможность изменять размер области кадрирования с помощью колёсика мыши. 3. Используйте несколько объектов с одной текстурой, но разными областями кадрирования, чтобы создать анимированный спрайт из texture atlas. 4. Поэкспериментируйте с setFlipY и одновременным отражением по обеим осям.