О чем этот пример
Визуальные эффекты — ключ к созданию атмосферы в играх. Одним из самых впечатляющих приёмов является эффект свечения (bloom), который делает яркие объекты по-настоящему сияющими. В этой статье мы разберем, как реализовать динамический bloom-эффект для взаимодействия объектов в Phaser, используя пример с выстрелом по планете. Вы научитесь комбинировать фильтры, управлять их параметрами в реальном времени и создавать цепляющую визуальную обратную связь для игрока.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
// Bloom effect by combining filters
function AddBloomTo (gameObject)
{
// This has no effect if filters are already enabled.
gameObject.enableFilters();
const parallelFilters = gameObject.filters.external.addParallelFilters();
parallelFilters.top.addThreshold(0.5, 1);
const blur = parallelFilters.top.addBlur();
parallelFilters.blend.blendMode = Phaser.BlendModes.ADD;
parallelFilters.blend.amount = 0;
return {
get amount () {
return parallelFilters.blend.amount;
},
set amount (value) {
parallelFilters.blend.amount = value;
},
get blurStrength () {
return blur.strength;
},
set blurStrength (value) {
blur.strength = value;
}
};
}
// Bullet class - fires from ship and "destroys" planet
class Bullet extends Phaser.GameObjects.Image
{
speed;
flame;
constructor(scene, x, y) {
super(scene, x, y, "bullet");
this.speed = Phaser.Math.GetSpeed(450, 1);
this.name = "bullet";
Phaser.Actions.AddEffectBloom(this,
{
blendAmount: 1.2,
blurStrength: 2
}
);
}
fire (x, y)
{
this.setPosition(x, y);
this.setActive(true);
this.setVisible(true);
}
destroyBullet ()
{
if (this.flame === undefined) {
// Create particles for flame
this.flame = this.scene.add.particles(this.x, this.y, 'flares',
{
frame: 'white',
color: [0xfacc22, 0xf89800, 0xf83600, 0x9f0404],
colorEase: 'quad.out',
lifespan: 500,
scale: { start: 0.70, end: 0, ease: 'sine.out' },
speed: 200,
advance: 500,
frequency: 50,
blendMode: 'ADD',
duration: 1000,
});
this.flame.setDepth(1);
// When particles are complete, destroy them
this.flame.once("complete", () => {
this.flame.destroy();
})
}
// Destroy bullet after 50ms (helps to enter inside of planet)
this.scene.time.addEvent({
delay: 50,
callback: () => {
this.setActive(false);
this.setVisible(false);
this.destroy();
}
});
}
// Update bullet position and destroy if it goes off screen
update (time, delta)
{
this.x += this.speed * delta;
if (this.x > this.scene.sys.canvas.width) {
this.setActive(false);
this.setVisible(false);
this.destroy();
}
}
}
// Logic game
class Example extends Phaser.Scene
{
ship;
bullets;
// Control for firing bullets
spacebar;
constructor ()
{
super({
key: 'MainScene'
});
}
init ()
{
// Description text for fire bullet
this.add.text(10, 10, 'Press "space" to fire bullet', { font: '16px Courier', fill: '#ffffff' }).setDepth(100);
// Fade in camera
this.cameras.main.fadeIn(800);
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.setPath("assets/");
this.load.image("bullet", "sprites/bullets/bullet6.png");
this.load.image("ship", "sprites/x2kship.png");
this.load.image("bg", "tests/space/nebula.jpg");
this.load.image("planet", "tests/space/blue-planet.png");
this.load.atlas('flares', '/particles/flares.png', '/particles/flares.json');
}
create ()
{
// Just stars background
const bg = this.add.image(0, 0, "bg")
.setOrigin(0, 0)
.setTint(0x333333);
const planet = this.physics.add.image(this.sys.scale.width - 100, this.sys.scale.height / 2, "planet")
.setScale(.2);
planet.flipX = true;
// Tween to rotate slow planet
this.tweens.add({
targets: planet,
duration: 5000000,
rotation: 360,
repeat: -1
});
// Bloom effect for the planet
const { blur } = Phaser.Actions.AddEffectBloom(planet,
{
blendAmount: 1.2,
blurStrength: 0
}
);
this.ship = this.add.image(100, this.sys.scale.height / 2, 'ship')
.setDepth(2);
this.bullets = this.physics.add.group({
classType: Bullet,
maxSize: 30,
runChildUpdate: true,
});
this.spacebar = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
// Effect for planet bloom
const planetFXTween = this.tweens.add({
targets: blur,
strength: 2,
yoyo: true,
duration: 100,
paused: true,
onComplete: () => {
planetFXTween.restart();
planetFXTween.pause();
}
});
this.physics.add.overlap(this.bullets, planet, (planet, bullet) => {
// If bullet hits planet, destroy the bullet and play the effect
bullet.destroyBullet();
if (!planetFXTween.isPlaying()) {
planetFXTween.restart();
planetFXTween.play();
}
});
}
// Bullet fire
update() {
if (this.spacebar)
{
if (Phaser.Input.Keyboard.JustDown(this.spacebar))
{
const bullet = this.bullets.get();
if (bullet) {
bullet.fire(this.ship.x, this.ship.y);
}
}
}
}
}
const config = {
type: Phaser.WEBGL,
width: 700,
height: 500,
physics: {
default: 'arcade'
},
backgroundColor: '#2f3640',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Суть эффекта Bloom
Bloom — это постобработка, имитирующая пересвет камеры, когда яркие области "растекаются" на соседние пиксели. В Phaser 3 этот эффект можно собрать из нескольких базовых фильтров.
Основная функция AddBloomTo создаёт свечение для любого игрового объекта. Она использует систему параллельных фильтров (external.addParallelFilters()), которая позволяет применять несколько эффектов одновременно и гибко их смешивать.
const parallelFilters = gameObject.filters.external.addParallelFilters();
parallelFilters.top.addThreshold(0.5, 1);
const blur = parallelFilters.top.addBlur();
parallelFilters.blend.blendMode = Phaser.BlendModes.ADD;
parallelFilters.blend.amount = 0;
Сначала фильтр Threshold (пороговый) выделяет только самые яркие части спрайта (те, чья яркость выше 0.5). Затем к этому выделению применяется размытие по Гауссу (Blur). Результат смешивается с исходным изображением в режиме ADD (сложение), что и даёт эффект свечения. Параметр parallelFilters.blend.amount контролирует интенсивность этого наложения.
Готовое решение Phaser.Actions.AddEffectBloom
В примере используется удобная обёртка Phaser.Actions.AddEffectBloom, которая внутри вызывает рассмотренную логику. Она сразу применяет bloom к объекту и возвращает геттеры/сеттеры для управления параметрами.
const { blur } = Phaser.Actions.AddEffectBloom(planet,
{
blendAmount: 1.2,
blurStrength: 0
}
);
Функция принимает объект и конфигурацию. blendAmount задаёт начальную интенсивность свечения, а blurStrength — силу размытия. Возвращаемый объект blur позволяет анимировать силу размытия, что используется для создания вспышки при попадании.
Динамическая реакция на событие
Свечение планеты становится интерактивным: при попадании пули сила размытия (blurStrength) анимируется, создавая вспышку. Для этого используется Tween.
const planetFXTween = this.tweens.add({
targets: blur,
strength: 2,
yoyo: true,
duration: 100,
paused: true,
onComplete: () => {
planetFXTween.restart();
planetFXTween.pause();
}
});
Твин анимирует свойство strength объекта blur от текущего значения до 2 и обратно (за счёт yoyo: true). Изначально он поставлен на паузу (paused: true). При коллизии пули с планетой твин перезапускается и проигрывается.
this.physics.add.overlap(this.bullets, planet, (planet, bullet) => {
bullet.destroyBullet();
if (!planetFXTween.isPlaying()) {
planetFXTween.restart();
planetFXTween.play();
}
});
Визуализация выстрела и попадания
Для пули также применяется bloom-эффект, чтобы она сама светилась. При её уничтожении создаётся система частиц, имитирующая взрыв.
this.flame = this.scene.add.particles(this.x, this.y, 'flares',
{
frame: 'white',
color: [0xfacc22, 0xf89800, 0xf83600, 0x9f0404],
colorEase: 'quad.out',
lifespan: 500,
scale: { start: 0.70, end: 0, ease: 'sine.out' },
speed: 200,
advance: 500,
frequency: 50,
blendMode: 'ADD',
duration: 1000,
});
Ключевые параметры: color задаёт градиент от жёлтого к тёмно-красному, blendMode: 'ADD' обеспечивает яркое, аддитивное наложение частиц, а duration: 1000 ограничивает время жизни эмиттера. Частицы уничтожаются после завершения анимации (once('complete', ...)).
Что попробовать дальше
Комбинация фильтров bloom и частиц позволяет создавать сочные, отзывчивые визуальные эффекты с минимальным кодом. Для экспериментов попробуйте: изменить параметры threshold и blurStrength для получения разного характера свечения; анимировать не только силу размытия, но и blendAmount; применить bloom-эффект к фону или UI-элементам; комбинировать bloom с другими фильтрами, например, цветовой коррекцией.
