DEV Community

Hiroshi TK
Hiroshi TK

Posted on

NixieFX tutorial: trigger a PixiJS combo flare only when the meter crosses its threshold

A combo-meter flare should mark the moment a player earns a new state. If it runs on every update while the meter is full, the signal becomes background noise and repeated effects can overlap unnecessarily.

This NixieFX tutorial separates two responsibilities: plain JavaScript decides when the threshold is crossed, and the PixiJS scene renders a short effect. Reduced-motion mode keeps a persistent “Combo ready” message while omitting the decorative burst.

Define the trigger before authoring the effect

For this example, the threshold is 100. Moving from 99 to 100 should trigger once. Moving from 100 to 105 should not trigger again. Dropping below 100 rearms the cue. These are proposed gameplay rules; change them if your meter uses a different reward model.

Keep the rule independent of the renderer so it can be checked without a canvas:

export function createThresholdGate(threshold = 100, initial = 0) {
  if (!Number.isFinite(threshold) || !Number.isFinite(initial)) {
    throw new TypeError('Use finite meter values');
  }
  let previous = initial;
  return (next) => {
    if (!Number.isFinite(next)) throw new TypeError('Invalid meter value');
    const crossed = previous < threshold && next >= threshold;
    previous = next;
    return crossed;
  };
}
Enter fullscreen mode Exit fullscreen mode

Initializing the gate at an already-full value deliberately does not play an entrance flare. A saved game opening with a full meter should still display the ready state. Treat “ready on load” and “became ready during play” as separate events.

Author a short, readable flare

Create a Pixi-oriented effect named “Combo Flare” in a local NixieFX project. Start with one unlit billboard emitter, a single burst of 12 particles, no continuous emission, and a lifetime of 0.35 seconds. Use a soft texture, modest outward movement, and an alpha fade to zero. These values are design starting points, not measured performance recommendations.

Place the origin beside the meter rather than over the number. Preview against the actual HUD background and reduce the burst if it obscures useful information. Keep prepared 3D meshes, mesh-surface emission, lit shading, and GPU-depth-dependent behavior out of this Pixi recipe.

The NixieFX PixiJS integration guide covers exporting the effect and loading it into the Pixi adapter. Follow that setup first, then use the event wiring below in the initialized scene. Confirm that the exported effect ID is combo-flare.

Connect the gate to the existing scene

The following integration fragment assumes app, vfx, and the validated bundle already exist. It also assumes a real HTML status element named combo-status beside the canvas. The runnable lab and embedded source supply a complete isolated fixture. Configure PixiVfxRenderer with createPixiVfx2dProjection({ yAxis: "down" }) when passing screen coordinates, and rotate the circle emitter 90 degrees around X into the XY plane.

const crossedThreshold = createThresholdGate(100, 0);
const status = document.querySelector('#combo-status');
const motion = window.matchMedia('(prefers-reduced-motion: reduce)');
const definition = bundle.effectsById.get('combo-flare');
if (!status || !definition) throw new Error('Missing status or effect');
const live = new Set();
let ready = false;

export function setCombo(next, x, y) {
  const crossed = crossedThreshold(next);
  const nextReady = next >= 100;
  if (nextReady !== ready) {
    ready = nextReady;
    status.textContent = ready ? 'Combo ready' : 'Combo charging';
  }
  if (crossed && !motion.matches) {
    live.add(vfx.createEffect(definition, { position: [x, y, 0], seed: 42 }));
  }
}

function clearFlares() {
  for (const fx of live) vfx.removeEffect(fx, true);
  live.clear();
}
function onMotionChange() { if (motion.matches) clearFlares(); }
motion.addEventListener('change', onMotionChange);

// Use this as this scene's single VFX update path.
function tick(ticker) {
  vfx.update(ticker.deltaMS / 1000);
  for (const fx of live) {
    if (!fx.isActive) {
      vfx.removeEffect(fx, true);
      live.delete(fx);
    }
  }
}
app.ticker.add(tick);

export function disposeComboCue() {
  app.ticker.remove(tick);
  motion.removeEventListener('change', onMotionChange);
  clearFlares();
}
Enter fullscreen mode Exit fullscreen mode

Use coordinates in the VFX parent’s local space. If the meter belongs to a scaled or translated container, convert its position before calling setCombo. Do not add a second VFX update loop if the scene already owns one. Initialize the HTML status as “Combo charging” and use role="status"; update it on state changes rather than every frame.

Reproduce the important cases

Feed the sequence 0, 99, 100, 105, 80, 100 into the gate. The expected crossing count is two. Repeat with reduced motion enabled: the threshold logic is unchanged, but no decorative flare should be created. Toggle reduced motion while a flare is active and confirm it clears.

PixiJS combo meter at 100 with the ready message and a small flare beside the meter, frozen at 0.10 seconds.

Also check repeated values, an initial value above the threshold, a scene exit, and missing assets. The gate does not prevent legitimate rapid recrossings; add a game-specific cooldown or hysteresis only if that matches the intended rules.

Observed runtime result

The fixture uses NixieFX 0.1.17 and PixiJS 8.22.0. The sequence produced two threshold events, two created effect instances, and zero remaining instances after completion. Reduced motion preserved two threshold events while creating zero instances. At the frozen 0.10-second capture, the renderer reported twelve active particles. The export validated for pixi2d and three3d with no errors. These are functional observations from an isolated fixture, not a performance benchmark.

Limits

The JavaScript gate can be verified independently. Visual quality, exported-effect compatibility, and device performance require the real PixiJS build. Reduced motion is implemented by the host application here; it is not an automatic property of the authored effect. Do not infer a frame-rate guarantee from the small particle count.

Top comments (0)