О чем этот пример
Эффекты постобработки камеры — мощный инструмент для усиления визуального воздействия игровых событий. В этом примере мы совместим их с системой частиц Phaser, чтобы создать динамичную сцену с огнём, дымом и искрами. Вы научитесь управлять несколькими камерами, применять к ним эффекты (затухание, вспышка, тряска) и синхронизировать эти эффекты с эмиттерами частиц для создания целостной и захватывающей картины.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.55.2.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
shakeCamera;
flashCamera;
fadeCamera;
spark1 = null;
spark0 = null;
whiteSmoke = null;
fire = null;
darkSmoke = null;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('dark-smoke', 'assets/particles/smoke-puff.png');
this.load.image('white-smoke', 'assets/particles/smoke0.png');
this.load.image('fire', 'assets/particles/muzzleflash3.png');
this.load.image('spark0', 'assets/particles/blue.png');
this.load.image('spark1', 'assets/particles/red.png');
}
create ()
{
this.cameras.main.setViewport(5, 5, 390, 290);
this.fadeCamera = this.cameras.add(405, 5, 390, 290);
this.flashCamera = this.cameras.add(5, 305, 390, 290);
this.shakeCamera = this.cameras.add(405, 305, 390, 290);
this.fadeCamera.fade(1000);
this.spark0 = this.add.particles('spark0').createEmitter({
x: 400,
y: 300,
speed: { min: -500, max: 500 },
angle: { min: -120, max: -60},
scale: { min: 0.05, max: 0},
alpha: { min: 1, max: 0},
gravityY: 500,
lifespan: 1
});
this.spark0.reserve(1000);
this.spark1 = this.add.particles('spark1').createEmitter({
x: 400,
y: 300,
speed: { min: -100, max: 100 },
angle: { min: -120, max: -60},
scale: { start: 0, end: 0.4},
alpha: { start: 1, end: 0},
blendMode: 'SCREEN',
gravityY: 500,
lifespan: 1000
});
this.spark1.reserve(1000);
this.fire = this.add.particles('fire').createEmitter({
x: 400,
y: 300,
speed: { min: 100, max: 200 },
angle: { min: -85, max: -95},
scale: { start: 0, end: 1},
alpha: { start: 1, end: 0},
blendMode: 'SCREEN',
lifespan: 1000
});
this.fire.reserve(1000);
this.whiteSmoke = this.add.particles('white-smoke').createEmitter({
x: 400,
y: 300,
speed: { min: 20, max: 100 },
angle: { min: 0, max: 360},
scale: { start: 1, end: 0},
alpha: { start: 0, end: 0.5},
lifespan: 2000
// active: false
});
this.whiteSmoke.reserve(1000);
this.darkSmoke = this.add.particles('dark-smoke').createEmitter({
x: 400,
y: 300,
speed: { min: 20, max: 100 },
angle: { min: 0, max: 360},
scale: { start: 1, end: 0},
alpha: { start: 0, end: 0.1},
blendMode: 'SCREEN',
lifespan: 2000
// active: false
});
this.darkSmoke.reserve(1000);
this.fire.onParticleDeath(particle =>
{
this.darkSmoke.setPosition(particle.x, particle.y);
this.whiteSmoke.setPosition(particle.x, particle.y);
this.darkSmoke.emitParticle();
this.whiteSmoke.emitParticle();
});
this.input.on('pointermove', pointer =>
{
this.darkSmoke.setPosition(pointer.x, pointer.y);
this.fire.setPosition(pointer.x, pointer.y);
});
}
update ()
{
this.spark0.x = this.fire.x;
this.spark0.y = this.fire.y;
this.spark1.x = this.fire.x;
this.spark1.y = this.fire.y;
this.flashCamera.flash(1000);
this.shakeCamera.shake(1000);
if (this.fadeCamera._fadeAlpha >= 1.0)
{
this.fadeCamera._fadeAlpha = 0.0;
this.fadeCamera.fade(1000);
}
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Настройка сцены и загрузка ресурсов
Класс Example расширяет Phaser.Scene и содержит свойства для хранения ссылок на камеры и эмиттеры частиц. В методе preload() загружаются текстуры для частиц с помощью this.load.setBaseURL() и this.load.image(). Это стандартный подход для загрузки ассетов в Phaser.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('dark-smoke', 'assets/particles/smoke-puff.png');
this.load.image('white-smoke', 'assets/particles/smoke0.png');
this.load.image('fire', 'assets/particles/muzzleflash3.png');
this.load.image('spark0', 'assets/particles/blue.png');
this.load.image('spark1', 'assets/particles/red.png');
}
Создание камер и их начальная настройка
В методе create() происходит основная инициализация. Сначала настраивается вьюпорт главной камеры с помощью this.cameras.main.setViewport(). Затем создаются три дополнительные камеры с помощью this.cameras.add(). Каждая камера занимает свой квадрант на экране, что позволяет одновременно наблюдать за разными эффектами.
На камеру fadeCamera сразу применяется эффект затухания (fade()). Это создаёт плавное появление сцены в этом квадранте.
create ()
{
this.cameras.main.setViewport(5, 5, 390, 290);
this.fadeCamera = this.cameras.add(405, 5, 390, 290);
this.flashCamera = this.cameras.add(5, 305, 390, 290);
this.shakeCamera = this.cameras.add(405, 305, 390, 290);
this.fadeCamera.fade(1000);
}
Создание эмиттеров частиц
Далее создаются пять эмиттеров частиц, представляющих разные элементы эффекта: искры (spark0, spark1), огонь (fire), белый дым (whiteSmoke) и тёмный дым (darkSmoke). Каждый эмиттер создаётся из менеджера частиц, который возвращается this.add.particles(), с последующим вызовом .createEmitter().
Конфигурация каждого эмиттера детально настраивает поведение частиц: скорость, угол разлёта, масштаб, прозрачность (альфа), гравитацию и время жизни (lifespan). Важный момент — предварительное резервирование пула частиц с помощью .reserve(1000) для оптимизации производительности.
this.spark0 = this.add.particles('spark0').createEmitter({
x: 400,
y: 300,
speed: { min: -500, max: 500 },
angle: { min: -120, max: -60},
scale: { min: 0.05, max: 0},
alpha: { min: 1, max: 0},
gravityY: 500,
lifespan: 1
});
this.spark0.reserve(1000);
Взаимодействие частиц и обработка ввода
Ключевой момент связки эффектов — обработчик события смерти частицы огня. С помощью метода onParticleDeath() эмиттера fire мы подписываемся на это событие. Когда частица огня исчезает, в её последней позиции размещаются и испускаются эмиттеры дыма (darkSmoke и whiteSmoke). Это создаёт эффект, будто огонь оставляет за собой шлейф дыма.
Одновременно обработчик события pointermove перемещает эмиттеры огня и тёмного дыма за курсором мыши. Это делает сцену интерактивной.
this.fire.onParticleDeath(particle =>
{
this.darkSmoke.setPosition(particle.x, particle.y);
this.whiteSmoke.setPosition(particle.x, particle.y);
this.darkSmoke.emitParticle();
this.whiteSmoke.emitParticle();
});
this.input.on('pointermove', pointer =>
{
this.darkSmoke.setPosition(pointer.x, pointer.y);
this.fire.setPosition(pointer.x, pointer.y);
});
Обновление состояния в реальном времени
Метод update() выполняется каждый кадр и отвечает за непрерывную анимацию. Здесь происходит несколько важных вещей:
1. Позиции эмиттеров искр (spark0 и spark1) синхронизируются с позицией эмиттера огня (fire), создавая эффект летящих вместе искр.
2. На камеру flashCamera постоянно применяется эффект вспышки (flash()), а на shakeCamera — эффект тряски (shake()).
3. Реализуется циклический эффект затухания на камере fadeCamera. Когда затухание завершается (значение внутреннего свойства _fadeAlpha достигает 1.0), оно сбрасывается и запускается заново.
update ()
{
this.spark0.x = this.fire.x;
this.spark0.y = this.fire.y;
this.spark1.x = this.fire.x;
this.spark1.y = this.fire.y;
this.flashCamera.flash(1000);
this.shakeCamera.shake(1000);
if (this.fadeCamera._fadeAlpha >= 1.0)
{
this.fadeCamera._fadeAlpha = 0.0;
this.fadeCamera.fade(1000);
}
}
Что попробовать дальше
Этот пример демонстрирует, как комбинировать мощные, но относительно простые в использовании инструменты Phaser — эффекты камеры и систему частиц — для создания сложного и динамичного визуального ряда. Вы можете экспериментировать с параметрами эмиттеров (скорость, гравитация, время жизни), пробовать другие эффекты камер (например, zoom или rotation), или привязывать запуск эффектов к конкретным игровым событиям, например, к столкновению или получению урона.
