О чем этот пример
Визуальные эффекты — ключ к захватывающему геймплею. Один из мощных, но неочевидных приёмов — обрезка (кроп) анимированных спрайтов. Эта техника позволяет создавать динамические маски, эффекты "взгляда через подзорную трубу" или интерактивное раскрытие контента, когда игрок управляет видимой областью. В статье разберём пример, где анимированный скат отображается не целиком, а лишь в области, следующей за курсором мыши, и поймём, как управлять этим механизмом.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
cropHeight;
cropWidth;
py;
px;
offset;
graphics;
bob;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('sea', 'assets/animations/seacreatures_json.png', 'assets/animations/seacreatures_json.json');
}
create ()
{
this.anims.create({ key: 'stingray', frames: this.anims.generateFrameNames('sea', { prefix: 'stingray', end: 23, zeroPad: 4 }), repeat: -1 });
this.add.sprite(400, 300, 'sea').setAlpha(0.3).play('stingray');
this.bob = this.add.sprite(400, 300, 'sea').play('stingray');
this.graphics = this.add.graphics();
this.cropWidth = 64;
this.cropHeight = 64;
this.px = 0;
this.py = 0;
this.bob.setCrop(0, 0, this.cropWidth, this.cropHeight);
this.offset = this.bob.getTopLeft();
this.input.on('pointermove', pointer =>
{
this.px = pointer.x - this.offset.x;
this.py = pointer.y - this.offset.y;
});
}
update ()
{
this.bob.setCrop(
this.px - this.cropWidth / 2,
this.py - this.cropHeight / 2,
this.cropWidth,
this.cropHeight
);
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.CANVAS,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#2d2d88',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка анимации и спрайтов
В методе preload загружается атлас анимации с морскими существами. В create создаётся сама анимация stingray на основе кадров из атласа.
Затем добавляются два спрайта с одной и той же текстурой 'sea' и анимацией 'stingray'. Первый спрайт служит фоновым, полупрозрачным (setAlpha(0.3)) ориентиром, чтобы видеть полную анимацию. Второй, this.bob, — это основной спрайт, к которому будет применён кроп. Также создаётся объект Graphics для отладки — он будет рисовать зелёную рамку вокруг области кропа.
this.anims.create({ key: 'stingray', frames: this.anims.generateFrameNames('sea', { prefix: 'stingray', end: 23, zeroPad: 4 }), repeat: -1 });
this.add.sprite(400, 300, 'sea').setAlpha(0.3).play('stingray');
this.bob = this.add.sprite(400, 300, 'sea').play('stingray');
this.graphics = this.add.graphics();
Инициализация кропа и отслеживание курсора
Задаются начальные размеры области кропа — 64x64 пикселя. Изначально кроп применяется к левому верхнему углу спрайта с помощью метода setCrop. Важно получить точку offset — это мировые координаты верхнего левого угла спрайта this.bob. Они понадобятся для корректного перевода координат курсора.
Обработчик события pointermove обновляет переменные this.px и this.py. В них записываются координаты указателя мыши, но не мировые, а относительно верхнего левого угла самого спрайта this.bob. Это достигается вычитанием this.offset.x и this.offset.y из координат курсора.
this.cropWidth = 64;
this.cropHeight = 64;
this.bob.setCrop(0, 0, this.cropWidth, this.cropHeight);
this.offset = this.bob.getTopLeft();
this.input.on('pointermove', pointer => {
this.px = pointer.x - this.offset.x;
this.py = pointer.y - this.offset.y;
});
Динамическое обновление кропа и отладка
В методе update, который вызывается каждый кадр, происходит магия. Позиция области кропа пересчитывается так, чтобы её центр совпадал с координатами курсора относительно спрайта. Метод setCrop принимает координаты X и Y левого верхнего угла области кропа (относительно текстуры спрайта), а также её ширину и высоту. Вычитание половины размера из this.px и this.py как раз центрирует область на курсоре.
Для наглядности поверх спрайта рисуется зелёная рамка, точно соответствующая области кропа. Координаты для рисования рассчитываются как сумма мирового смещения спрайта (this.offset) и внутренних координат кропа, которые после применения setCrop хранятся в свойстве this.bob._crop.
this.bob.setCrop(
this.px - this.cropWidth / 2,
this.py - this.cropHeight / 2,
this.cropWidth,
this.cropHeight
);
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.type указан Phaser.CANVAS. Это важно, потому что некоторые эффекты, связанные с отрисовкой, могут по-разному работать в режиме WebGL. Сцена Example передаётся в конфиг, после чего создаётся экземпляр игры.
const config = {
type: Phaser.CANVAS,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#2d2d88',
scene: Example
};
const game = new Phaser.Game(config);
Что попробовать дальше
Метод setCrop — это гибкий инструмент для управления отображением текстуры спрайта. Он работает даже с анимированными спрайтами, обрезая текущий отображаемый кадр. Для экспериментов попробуйте: изменить размер области кропа в зависимости от скорости движения мыши, привязать кроп не к курсору, а к другому игровому объекту, создать эффект постепенного «проявления» спрайта, увеличивая размер кропа, или использовать несколько областей кропа на одном спрайте для сложных эффектов разбиения.
