О чем этот пример
Работая со сложными Spine-анимациями в Phaser, разработчики часто сталкиваются с необходимостью ограничить область их отображения — например, для создания эффекта взгляда через подзорную трубу, оконного проёма или UI-элементов неправильной формы. Стандартный контейнер `SpineContainer` позволяет группировать анимации, но как наложить на него маску? В этой статье мы разберем реальный пример из официального репозитория Phaser, который демонстрирует неочевидный, но эффективный способ применения геометрической маски к контейнеру со Spine-объектами. Вы узнаете, как создать контейнер, добавить в него анимации и обрезать их видимую область с помощью `GeometryMask`, а также как отлаживать положение и размер маски.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
var config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#2d2d66',
scene: {
preload: preload,
create: create,
update: update,
pack: {
files: [
{ type: 'scenePlugin', key: 'SpinePlugin', url: 'plugins/3.8.95/SpinePluginDebug.js', sceneKey: 'spine' }
]
}
}
};
var controls;
var game = new Phaser.Game(config);
function preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('logo', 'assets/sprites/phaser.png');
this.load.setPath('assets/spine/3.8/spineboy');
this.load.spine('boy', 'spineboy-pro.json', 'spineboy-pro.atlas', true);
this.load.setPath('assets/spine/3.8/coin');
this.load.spine('coin', 'coin-pro.json', 'coin-pro.atlas');
}
function create ()
{
const spineContainer = this.add.spineContainer(200, 400);
const spineBoy = this.add.spine(0, 50, 'boy', 'walk', true).setScale(0.5);
spineContainer.add(spineBoy)
//add mask to spineContainer
spineContainer.maskShape = this.add.graphics()
.fillStyle(0x7fff00, 0.2) //for debugging purposes
.setVisible(true) //set true for debugging purposes
.setDepth(5)
.fillRect(
Math.ceil(100),
Math.ceil(100),
Math.ceil(300),
Math.ceil(300)
)
const mask = new Phaser.Display.Masks.GeometryMask(this, spineContainer.maskShape)
spineContainer.setMask(mask)
// this.add.image(0, 0, 'logo');
const coin = this.add.spine(200, 300, 'coin', 'rotate', true).setScale(0.3);
var cursors = this.input.keyboard.createCursorKeys();
var controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
acceleration: 0.06,
drag: 0.0005,
maxSpeed: 1.0
};
controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
}
function update (time, delta)
{
controls.update(delta);
}
Подготовка сцены и загрузка ресурсов
В примере используется конфигурация под WEBGL, так как плагин SpinePlugin требует именно этот рендерер. В секции pack сцены подключается сам плагин для работы со Spine.
var config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#2d2d66',
scene: {
preload: preload,
create: create,
update: update,
pack: {
files: [
{ type: 'scenePlugin', key: 'SpinePlugin', url: 'plugins/3.8.95/SpinePluginDebug.js', sceneKey: 'spine' }
]
}
}
};
В функции preload загружаются необходимые анимации. Обратите внимание на использование setBaseURL и setPath для организации путей к ресурсам. Spine-анимации загружаются с помощью метода this.load.spine.
function preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('logo', 'assets/sprites/phaser.png');
this.load.setPath('assets/spine/3.8/spineboy');
this.load.spine('boy', 'spineboy-pro.json', 'spineboy-pro.atlas', true);
this.load.setPath('assets/spine/3.8/coin');
this.load.spine('coin', 'coin-pro.json', 'coin-pro.atlas');
}
Создание контейнера и добавление Spine-объектов
Ключевой объект для маскирования — SpineContainer. Он действует как группа для Spine-объектов, позволяя применять к ним трансформации и эффекты совместно.
В функции create мы создаем контейнер в точке (200, 400) и добавляем в него анимацию Spineboy. Объект spineBoy создается с анимацией 'walk' в режиме повтора и масштабируется.
const spineContainer = this.add.spineContainer(200, 400);
const spineBoy = this.add.spine(0, 50, 'boy', 'walk', true).setScale(0.5);
spineContainer.add(spineBoy)
Отдельно, вне контейнера, создается анимация монетки. Это демонстрирует, что маска, применяемая к контейнеру, не затрагивает другие объекты на сцене.
const coin = this.add.spine(200, 300, 'coin', 'rotate', true).setScale(0.3);
Создание и настройка геометрической маски
Маска в Phaser — это объект, который определяет видимую область другого объекта. Для создания маски на основе графики используется класс Phaser.Display.Masks.GeometryMask.
Сначала создается графический объект (Graphics), который будет формой маски. В примере это зеленый прямоугольник с полупрозрачной заливкой. Параметры fillStyle и setVisible(true) оставлены для отладки, чтобы видеть, где именно находится область маски.
spineContainer.maskShape = this.add.graphics()
.fillStyle(0x7fff00, 0.2)
.setVisible(true)
.setDepth(5)
.fillRect(
Math.ceil(100),
Math.ceil(100),
Math.ceil(300),
Math.ceil(300)
);
Затем эта графика передается в конструктор GeometryMask. Важно: маска создается для конкретной сцены (контекста this) и формы.
const mask = new Phaser.Display.Masks.GeometryMask(this, spineContainer.maskShape);
Наконец, маска применяется к контейнеру с помощью метода setMask. Теперь видимыми будут только те части анимации Spineboy, которые попадают в границы зеленого прямоугольника.
spineContainer.setMask(mask);
Управление камерой для проверки результата
Чтобы можно было перемещать камеру и с разных ракурсов проверить работу маски, в примере реализовано управление с клавиатуры. Используется Phaser.Cameras.Controls.SmoothedKeyControl для плавного перемещения.
var cursors = this.input.keyboard.createCursorKeys();
var controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
acceleration: 0.06,
drag: 0.0005,
maxSpeed: 1.0
};
controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
В функции update вызывается метод controls.update(delta) для обработки ввода и обновления положения камеры.
function update (time, delta)
{
controls.update(delta);
}
Что попробовать дальше
Применение GeometryMask к SpineContainer — мощный прием для контроля отображения сложных анимаций. Этот метод позволяет создавать динамические эффекты обрезки, которые остаются производительными благодаря работе на уровне контейнера.
Для экспериментов попробуйте:
1. Заменить прямоугольник fillRect на более сложную форму, используя методы графики Graphics.
2. Анимировать положение или размер графического объекта maskShape в update, чтобы маска двигалась или меняла форму.
3. Использовать в качестве маски не Graphics, а спрайт с текстурой, создав BitmapMask.
4. Применить маску не к контейнеру, а к отдельному Spine-объекту и сравнить разницу в отображении.
