О чем этот пример
Работая над игровым интерфейсом или спецэффектами, часто нужно отобразить изображение не в простом прямоугольнике, а внутри произвольной фигуры — например, в круге, шестиугольнике или изометрическом боксе. В Phaser для этого есть элегантное решение: Bitmap Mask, созданная из графического объекта. В этой статье разберем, как с помощью нескольких строк кода наложить маску на спрайт, используя встроенные геометрические примитивы.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Scene extends Phaser.Scene
{
constructor()
{
super();
}
preload()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('pic', 'assets/pics/barbarian-loading.png');
}
create()
{
// const shape = this.add.rectangle(400, 300, 300, 200, 0xff0000);
// const shape = this.add.circle(400, 300, 120, 0xff0000);
const shape = this.add.isobox(400, 300, 80, 80, 0x00b9f2, 0x016fce, 0x028fdf);
const mask = shape.createBitmapMask();
const pic = this.add.image(400, 300, 'pic');
pic.mask = mask;
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Scene
}
new Phaser.Game(config);
Что такое Bitmap Mask и зачем она нужна
Маска в Phaser — это механизм, который определяет видимую область другого игрового объекта. Области, где маска имеет непрозрачные пиксели, будут отображаться; прозрачные части маски скроют содержимое. Это похоже на трафарет.
Bitmap Mask создается из текстуры или, как в нашем примере, из графического объекта (Graphics). Главное преимущество такого подхода — возможность использовать любую фигуру, которую можно нарисовать с помощью API Phaser, в качестве маски. Это открывает путь для динамического создания сложных обтравок во время выполнения игры.
В исходном примере мы видим, что изображение с рыцарем (pic) отображается только внутри синего изометрического бокса (shape). Все, что находится за его границами, становится невидимым.
Создание графической фигуры-основы
Первый шаг — создать графический объект, который определит форму нашей маски. Phaser предоставляет несколько удобных методов для рисования в объекте this.add.graphics(), но для простых фигур можно использовать готовые фабричные методы.
В коде примера закомментированы варианты с прямоугольником и кругом, а активна строка с созданием изобокса. Давайте посмотрим на синтаксис:
const shape = this.add.isobox(400, 300, 80, 80, 0x00b9f2, 0x016fce, 0x028fdf);
Эта строка создает изометрический куб (isobox) в центре сцены (координаты 400, 300). Первые два аргумента после размеров — это цвета для граней. Важно: сам цвет фигуры в данном контексте не важен для конечного результата маски. Маска будет использовать не цвет, а альфа-канал (непрозрачность) фигуры. Поскольку мы создаем сплошную фигуру без прозрачности, вся ее область станет видимой зоной для маскированного изображения.
Можно легко заменить isobox на rectangle или circle — принцип работы маски от этого не изменится.
Превращение фигуры в маску и её применение
Следующий ключевой этап — создание объекта маски из нашей фигуры и назначение его целевому изображению.
Создание маски выполняется методом createBitmapMask():
const mask = shape.createBitmapMask();
Этот метод доступен для любого объекта, который является экземпляром Phaser.GameObjects.Graphics (как наш isobox). Он генерирует новую Bitmap Mask, используя текстуру отрисованной фигуры.
Теперь нужно взять изображение, которое мы хотим обтравить, и присвоить ему созданную маску. В примере это делается через прямое присваивание свойству .mask:
const pic = this.add.image(400, 300, 'pic');
pic.mask = mask;
Обратите внимание на два важных момента:
1. Изображение pic добавлено на те же координаты (400, 300), что и фигура shape. Это обеспечивает их идеальное совмещение. Если сместить изображение, видимая часть будет соответствовать положению маски, а не исходному положению картинки.
2. Свойство mask — это именно присваивание (`=`), а не вызов метода. После этого присваивания рендеринг изображения автоматически ограничивается областью, заданной маской.
Как это работает под капотом
Когда вы присваиваете pic.mask = mask, Phaser начинает использовать маску во время рендеринга кадра. Для каждого пикселя изображения pic движок проверяет соответствующий пиксель в текстуре маски mask. Если альфа-канал (прозрачность) пикселя маски больше нуля, пиксель изображения отрисовывается. Если альфа равна нулю (полная прозрачность), пиксель изображения игнорируется.
Поскольку наша фигура shape нарисована сплошным цветом без прозрачности, вся ее внутренняя область имеет альфа > 0, а все, что снаружи — альфа = 0. Именно поэтому мы видим изображение строго внутри контуров бокса.
Важное ограничение, которое следует из примера: маска создается из текущего состояния графического объекта. Если вы после создания маски измените фигуру shape (например, передвинете или перерисуете), эти изменения **не** отразятся на уже существующей маске mask. Маска — это снимок (snapshot) фигуры на момент вызова createBitmapMask().
Что попробовать дальше
Использование createBitmapMask() на графических объектах — это мощный и простой способ наложить нестандартную обтравку на изображения, спрайты или даже частицы в Phaser. Вы можете экспериментировать: попробуйте создать маску из составной фигуры, нарисованной вручную через this.add.graphics(), или анимируйте само изображение под маской, перемещая его. Для более сложных сценариев, где маска должна меняться динамически, потребуется пересоздавать её на каждом кадре, но для статичных или редко меняющихся форм этот подход идеален по производительности и простоте кода.
