Game Feedback¶
Snowpulse's snowpulse::fx namespace contains reusable presentation effects
for common success, reward, and transition moments. These helpers build on the
lower-level particle, renderer, and action systems while owning their temporary
scene nodes and cleanup.
The visual spawn() helpers return transient, non-owning pointers. While an
effect is active, use its pointer to call isPlaying() or cancel(). Natural
completion or cancellation queues the owned effect subtree for destruction,
after which the pointer must not be used.
ConfettiEffect¶
snowpulse::fx::ConfettiEffect creates a responsive, one-shot, screen-space
confetti celebration. Its default paper pieces use the renderer's textureless
white quad tinted by a built-in palette; callers can supply their own
SpriteRegion and tune the effect through the nested Config type.
The simplest call uses the default configuration. Choose the sort order at the call site so the celebration sits in the intended UI layer:
const int sortOrder = scene->rootUI()->baseSortOrder() - 200;
snowpulse::fx::ConfettiEffect::spawn(scene, sortOrder);
The effect uses logical screen coordinates, so its coverage follows the current viewport and is not displaced by camera movement, zoom, or shake. A spawned celebration cleans itself up after emission and all live particles finish.
Customization¶
Pass a Config as the third argument to replace the default confetti sprite or
adjust the presentation. Load replacements through the scene's AssetManager,
just like any other sprite, so they are packaged with the rest of the
application assets on desktop, web, iOS, and Android.
snowpulse::fx::ConfettiEffect::Config config;
config.sprite = scene->appContext()->assetManager()->loadSprite(
"effects/confetti.png");
config.pieceCount = 192;
config.launchSpeedMin = 680.0f;
config.launchSpeedMax = 960.0f;
config.leftCannonOffset = { -220.0f, -380.0f };
config.rightCannonOffset = { 220.0f, -380.0f };
config.palette = {
{ 0.26f, 0.82f, 1.0f, 1.0f },
{ 1.0f, 0.34f, 0.58f, 1.0f },
{ 1.0f, 0.86f, 0.18f, 1.0f },
};
snowpulse::fx::ConfettiEffect::spawn(
scene,
scene->rootUI()->baseSortOrder() - 200,
config);
Config::pieceCount is the positive total shared across both cannons;
spawn() returns nullptr for a non-positive count. The optional sprite region
is stored by value, so callers do not have to keep a separate region object
alive. leftCannonOffset and rightCannonOffset use screen-center coordinates:
{ 0, 0 } is the center, positive X points right, and positive Y points up.
Offsets and other physical values such as piece size, speed, and gravity are
logical units at Config::referenceHeight and scale uniformly with the viewport
height when the effect is spawned. Active cannon positions are re-resolved if
the logical viewport changes; already-emitted pieces keep their spawn-time
physics.
The returned effect pointer is a transient, non-owning handle. Use it only to
query isPlaying() or call cancel() while the effect is active, because the
effect destroys itself after completion.
An empty sprite value, or a region without a texture, uses the renderer's white quad tinted by the configured palette.
FireworksEffect¶
snowpulse::fx::FireworksEffect creates a one-shot, screen-space fireworks
show. Each shell rises from the lower viewport with an additive trail and then
spawns a separate radial spark burst in the upper viewport. Calls are
independent, and each controller removes its temporary nodes after the final
trail and spark have expired.
auto* fireworks = snowpulse::fx::FireworksEffect::spawn(
scene,
scene->rootUI()->baseSortOrder() - 200);
The default show launches five shells with 48 sparks each. Config can change
the shell and spark counts, normalized launch and burst regions, launch timing,
rocket and spark physics, palette, trail dimensions, and reference-height
scaling. Optional rocketSprite and sparkSprite regions replace the default
white quads. Set seed when a repeatable show is required.
snowpulse::fx::FireworksEffect::Config config;
config.shellCount = 8;
config.sparksPerShell = 64;
config.seed = 2026u;
config.burstAreaMin = { 0.12f, 0.50f };
config.burstAreaMax = { 0.88f, 0.90f };
snowpulse::fx::FireworksEffect::spawn(scene, -1200, config);
Launch and burst regions use normalized logical-screen coordinates with the
origin at the bottom-left. Other physical values are logical units at
referenceHeight and scale with the viewport height.
Both shellCount and sparksPerShell must be positive; otherwise spawn()
returns nullptr. At least one endpoint in each rocket-flight and
spark-lifetime range must also be positive and finite. The returned pointer
supports isPlaying() and immediate cancel(), and becomes invalid after the
show naturally cleans itself up.
SunburstEffect¶
snowpulse::fx::SunburstEffect draws a procedural, rotating sunburst that
covers the logical viewport. It is persistent by design and remains active
until cancelled or until its scene is destroyed.
auto* sunburst = snowpulse::fx::SunburstEffect::spawn(scene, -1050);
// Later, when the reward presentation closes:
if (sunburst && sunburst->isPlaying()) {
sunburst->cancel();
}
The default uses 18 alternating gold wedges centered on the screen and rotates
at eight degrees per second. Config controls the normalized center, wedge
count, colors, starting rotation, rotation speed, and blend mode. Odd or small
wedge counts are normalized to an even value of at least four. The mesh tracks
viewport changes and stays fixed while the camera moves, zooms, or shakes.
FlashEffect¶
snowpulse::fx::FlashEffect snaps a full-screen color curtain to its configured
opacity, optionally holds it, then fades it to transparent and cleans itself
up. It is presentation-only and does not intercept input.
snowpulse::fx::FlashEffect::Config config;
config.color = { 1.0f, 1.0f, 1.0f, 1.0f };
config.holdDurationSeconds = 0.0f;
config.fadeDurationSeconds = 0.20f;
config.fadeEase = snowpulse::EaseType::OutQuad;
snowpulse::fx::FlashEffect::spawn(scene, -1400, config);
The curtain follows logical-resolution changes and remains invariant under all
camera transforms. It participates in the normal render queue, so it can cover
scene and UI content according to its sort order, but it does not cover ImGui
or native platform overlays rendered after the scene. Keep the returned
pointer only while isPlaying() is true; cancel() removes the curtain
immediately.
ToastEffect¶
snowpulse::fx::ToastEffect displays transient MSDF text at a logical-screen
location, optionally holds it, then moves and fades it before removing all of
its temporary objects. Its position and font size are unaffected by camera
position, rotation, zoom, shake, or pump effects.
screenAnchor is normalized from the bottom-left of the logical viewport and
defaults to { 0.5, 0.5 }. position is a logical-pixel offset from that
anchor, with positive X pointing right and positive Y pointing up. Therefore,
position = { 10, 10 } places the text ten pixels right and ten pixels above
the center, even when the camera is at { 100, 100 } with zoom = 5.
snowpulse::fx::ToastEffect::Config config;
config.font = scene->appContext()->assetManager()->loadFont(
"fonts/interface.json");
config.text = "+250";
config.fontSize = 42.0f;
config.color = { 1.0f, 0.84f, 0.22f, 1.0f };
config.effects.outline.width = 3.0f;
config.effects.outline.color = { 0.08f, 0.06f, 0.02f, 1.0f };
config.sortOrder = scene->rootUI()->baseSortOrder() - 100;
config.screenAnchor = { 0.5f, 0.5f };
config.position = { 10.0f, 10.0f };
config.movement = { 0.0f, 56.0f };
config.holdDurationSeconds = 0.15f;
config.fadeDurationSeconds = 0.65f;
snowpulse::fx::ToastEffect::spawn(scene, config);
TextRendererType names the implementation choice more precisely than a
space type, because both choices are screen-positioned:
TextRendererType::UITextis the default. It creates a temporaryUITextunder the UI root and supportstextBoxSize,autoSize,wordWrap, andoverflowMode. The toast remains screen-space even if the application's UI root uses its view transform.TextRendererType::FontRenderercreates aFontRendereron the toast's scene node and sets its render space toFontRenderer::RenderSpace::Screen.
Both choices support font, fontSize, lineSpacing, letterSpacing, solid
or gradient colors, outline and shadow effects, texture filtering, horizontal
and vertical alignment, and an absolute render-queue sortOrder. Use
uiText() or fontRenderer() to inspect the selected transient text object
while isPlaying() is true. Natural completion and cancel() remove both the
text and controller; the returned pointer is invalid afterward.
PumpEffect¶
snowpulse::fx::PumpEffect is a camera-owned punch zoom. It immediately
multiplies the active view zoom, then eases the multiplier back to neutral.
The camera's public base zoom is never changed, so camera controllers can
continue updating it while the pump is active.
The default snaps to 1.08x and returns over 0.18 seconds with OutCubic.
snowpulse::fx::PumpEffect::play(scene);
snowpulse::fx::PumpEffect::Config config;
config.zoomMultiplier = 1.12f;
config.returnDurationSeconds = 0.24f;
config.returnEase = snowpulse::EaseType::OutCubic;
snowpulse::fx::PumpEffect::play(scene, config);
Retriggering replaces and restarts the active pump. cancel(scene) immediately
returns the multiplier to neutral, and isPlaying(scene) reports whether the
active camera is still returning.
ShakeEffect¶
snowpulse::fx::ShakeEffect exposes the existing camera shake through the FX
catalog without removing or deprecating Camera::shake().
The default uses intensity 8 for 0.30 seconds.
snowpulse::fx::ShakeEffect::Config config;
config.intensity = 12.0f;
config.durationSeconds = 0.35f;
snowpulse::fx::ShakeEffect::play(scene, config);
if (snowpulse::fx::ShakeEffect::isPlaying(scene)) {
snowpulse::fx::ShakeEffect::cancel(scene);
}
The newest shake replaces the previous one. Frequency and noise continue to
come from the camera's public shakeFrequency and noiseScale settings. Pump
and Shake may run at the same time; default UI and all screen-space FX remain
stationary while world content is transformed.
Choosing the right layer¶
Snowpulse uses lower sort-order values for later, foreground rendering. Derive
screen-effect ordering from UIRoot::baseSortOrder() rather than hard-coding a
global value. This keeps the effect aligned if an application changes its UI
base layer.
For custom emitters or persistent environmental effects, configure
ParticleEffects directly. See Particles and Trails.