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

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

Версия 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.atlas('atlas', 'assets/atlas/megaset-2.png', 'assets/atlas/megaset-2.json');
    }

    create ()
    {
        this.add.image(400, 300, 'atlas', 'hello').setAlpha(0.3);

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

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

        const cropWidth = 200;
        const cropHeight = 100;

        this.bob.setCrop(20, 20, 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);

Основы загрузки и отображения текстурного атласа

Перед тем как обрезать текстуру, ее необходимо корректно загрузить. В Phaser 3 для этого используется метод this.load.atlas(), который принимает ключ атласа, путь к изображению и путь к JSON-файлу с данными о кадрах.

this.load.atlas('atlas', 'assets/atlas/megaset-2.png', 'assets/atlas/megaset-2.json');

В данном примере ключ 'atlas' будет использоваться для доступа ко всем кадрам загруженного атласа. В методе create() мы создаем два изображения с одним и тем же кадром 'hello' из атласа. Первое изображение служит полупрозрачным фоном (setAlpha(0.3)), чтобы видеть область обрезки, а второе (this.bob) — основным объектом для манипуляций.

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

Метод setCrop() объекта Image устанавливает прямоугольную область обрезки (кроп) для отображаемой текстуры. Он принимает четыре аргумента: координаты X и Y верхнего левого угла области обрезки относительно текстуры, а также ширину и высоту этой области.

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

Этот код обрежет изображение this.bob, начиная с точки (20, 20) его исходной текстуры, и покажет только прямоугольник размером 200x100 пикселей. Важно помнить, что координаты обрезки отсчитываются от внутренней системы координат текстуры кадра, а не от положения спрайта на сцене.

Связывание обрезки с движением указателя

Чтобы сделать обрезку интерактивной, в примере используется обработчик события pointermove. При каждом движении мыши или касании вычисляются новые координаты для области обрезки.

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.offset — это вектор позиции верхнего левого угла изображения this.bob на сцене, полученный через getTopLeft(). Вычитание этого смещения и половины размеров кропа из координат указателя центрирует область обрезки под курсором. Таким образом, область обрезки "следует" за указателем, показывая разные части текстуры.

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

Для наглядности в примере область обрезки обводится зеленым прямоугольником. Это делается в методе update() с помощью объекта Graphics.

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

Каждый кадр предыдущая графика очищается (clear()), задается стиль линии и рисуется прямоугольник. Координаты для strokeRect() рассчитываются как сумма позиции изображения на сцене (this.offset) и текущих координат обрезки (this.bob._crop). Обратите внимание, что свойства _crop являются внутренними (`_`), но в данном контексте используются для визуализации. В реальном проекте для подобных вычислений лучше использовать публичные методы, если они доступны.

Практическое применение и нюансы

Динамическая обрезка текстур открывает несколько практических возможностей: 1. **Создание эффекта "визуального поиска"**: когда игрок двигает курсором по затемненной картинке, открывая ее фрагменты. 2. **Простая анимация по спрайт-листу**: последовательная обрезка разных кадров одного изображения. 3. **Маскирование части интерфейса**.

Важный нюанс: обрезка работает именно с текстурой (исходным изображением). Если спрайт имеет трансформации (масштаб, поворот), это не влияет на систему координат для setCrop(). Координаты обрезки всегда в пикселях исходной текстуры.

// Установка обрезки всегда в пикселях текстуры
myImage.setCrop(texturePixelX, texturePixelY, width, height);
// Масштабирование спрайта не меняет этих координат
myImage.setScale(2);

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

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