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

Работа с изображениями — основа визуальной части игры. Часто требуется показать не всю текстуру целиком, а лишь её часть. Метод `setCrop()`, доступный для игровых объектов типа `Image`, позволяет динамически обрезать текстуру, создавая эффекты видоискателя, масок или плавного раскрытия картинки. В этой статье мы разберем, как работает этот метод на практическом примере, где область обрезки следует за курсором мыши.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    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);

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

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

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

В методе preload() загружаем текстуру из внешнего источника. Важно: setBaseURL() задаёт базовый путь для всех последующих загрузок, что удобно для организации ассетов.

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

В методе create() создаются основные объекты. Первое изображение добавляется на сцену с прозрачностью 0.3 и служит фоновой подсказкой, показывая полную текстуру. Второе изображение this.bob — это основной объект, к которому будет применяться обрезка. Также создаётся объект Graphics для отрисовки зелёной рамки, визуализирующей область crop.

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

Принцип работы метода setCrop

Метод setCrop(x, y, width, height) обрезает текстуру игрового объекта Image. Параметры `xиyзадают координаты верхнего левого угла области обрезки **относительно текстуры самого изображения**, аwidthиheight` — её размеры.

Ключевой момент: координаты crop отсчитываются не от позиции изображения на сцене, а от его внутренней текстуры. Поэтому для корректного позиционирования области обрезки относительно мировой системы координат нужны дополнительные вычисления.

В примере мы сразу задаём начальную область обрезки размером 200x100 пикселей, начиная с левого верхнего угла текстуры.

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

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

Динамическое изменение crop по движению мыши

Чтобы область обрезки следовала за курсором, мы подписываемся на событие pointermove. В обработчике события вычисляются новые координаты `xиyдляsetCrop`.

Алгоритм:
1.  От координат курсора (`pointer.x`, `pointer.y`) отнимаем мировые координаты левого верхнего угла изображения (`this.offset.x`, `this.offset.y`). Это переводит мировые координаты в локальную систему координат текстуры.
2.  Чтобы центр области обрезки находился под курсором, от полученных локальных координат отнимаем половину ширины и высоты области (`cropWidth / 2`, `cropHeight / 2`).
3.  Вызываем `this.bob.setCrop()` с вычисленными координатами.
this.input.on('pointermove', pointer =>
{
    this.bob.setCrop(
        (pointer.x - this.offset.x) - cropWidth / 2,
        (pointer.y - this.offset.y) - cropHeight / 2,
        cropWidth,
        cropHeight
    );
});

Таким образом, при каждом движении мыши текстура объекта this.bob перерисовывается, показывая только ту её часть, которая находится под виртуальным "окном".

Визуализация области обрезки с помощью Graphics

Чтобы визуально выделить текущую область crop на экране, в методе update() каждый кадр рисуется зелёная рамка. Сначала предыдущая графика очищается вызовом this.graphics.clear().

Координаты для отрисовки рамки рассчитываются следующим образом:
1.  Берутся внутренние, "сырые" координаты области обрезки из свойств `this.bob._crop.x` и `this.bob._crop.y`. Обратите внимание, это внутреннее свойство объекта (`_crop`), а не публичный API.
2.  К ним прибавляются мировые координаты верхнего левого угла изображения (`this.offset.x`, `this.offset.y`). Это переводит координаты crop из системы координат текстуры обратно в мировую систему координат сцены.
3.  Метод `strokeRect()` рисует прямоугольник по полученным мировым координатам.
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
    );
}

В результате зелёная рамка точно обрамляет видимую часть изображения на сцене.

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

Метод setCrop() — мощный инструмент для работы с текстурами в Phaser 3, позволяющий создавать динамические эффекты без подготовки отдельных изображений. Для экспериментов попробуйте: изменить форму области обрезки (например, на круг, используя маску), анимировать размеры cropWidth и cropHeight для эффекта "приближения", или привязать область обрезки не к курсору, а к движущемуся спрайту, создав эффект фонарика в тёмном уровне.