pixfx
Pixel-art effects written once in Rust. Render them to sprite sheets, or play the same effects live in the browser, Phaser and gpui. The pixels are identical everywhere.
Click the ground to light a candle.
The same pixels on every target
Each effect is plain Rust drawing into a frame with two layers: colour, and additive light. The engine reproduces JavaScript's number rules, so a port of a pixelfx effect draws exactly what pixelfx draws.
The scene above is that engine compiled to WebAssembly. A native build writes the same frames to PNG sheets and GIFs, and a frame-hash test checks native and wasm against one set of goldens.
| Effect | Frames | Differing pixels |
|---|---|---|
fire/candle | 120 | 0 |
fire/campfire | 90 | 0 |
fire/brazier | 90 | 0 |
Effects
Every effect takes the same options: a seed, a glow strength, a palette, and options of its own. Change them here; the call below each effect updates to match.
Where it runs
Pick the layer that fits your project. All of them share one engine and one effect registry.
Write a sprite sheet (composite, colour and light PNGs plus beanfx-layout JSON), or a looping GIF.
cargo run -p pixfx-cli -- sheet fire/campfire --out out
cargo run -p pixfx-cli -- render fire/campfire --gif --scale 4 --out out
A Stage on a canvas runs its own loop and draws every effect, upscaled by a whole number with no smoothing.
import init, { Stage } from "./pkg/pixfx_canvas.js";
await init();
const stage = new Stage(document.querySelector("canvas"), 4);
stage.play("fire/campfire", 64, 112, { seed: 3 });
stage.start();
A scene plugin. Effects show up as ordinary Phaser images, a back pair and a front pair, so sprites can stand inside the fire.
import { PixfxPlugin, loadPixfx } from "pixfx-phaser";
await loadPixfx();
// plugins: { scene: [{ key: "pixfx", plugin: PixfxPlugin, mapping: "pixfx" }] }
const fire = this.pixfx.play("fire/campfire", 150, 170);
fire.follow(player, 0, -4);
Drive an effect yourself and read its pixels: RGBA bytes, or the colour and light layers separately.
import init, { Fx } from "./pkg/pixfx_wasm.js";
await init();
const fx = new Fx("fire/candle", { seed: 1 });
fx.update(1 / 60);
fx.draw("both");
ctx.putImageData(new ImageData(fx.rgba(), fx.width(), fx.height()), 0, 0);
Render frames in Rust, hash them, composite them or export them.
use pixfx_core::{Options, render_frames};
let fx = pixfx_effects::effect("fire/brazier").ok_or("not registered")?;
let rendered = render_frames(fx, &Options::default(), 0);
pixfx_export::write_gif(Path::new("brazier.gif"), &rendered, 4)?;
Paint effects inside a gpui window, light blended additively on the GPU with nearest sampling.
let mut view = FxView::new(campfire, Options::default());
view.tick(dt);
paint_fx(&mut view, Layer::Both, origin, 4, window)?;
Try it in a minute
-
Clone the repository.
rust-toolchain.tomlpins the nightly and the wasm target, so rustup fetches them on first build. -
Render the campfire to a GIF:
cargo run -p pixfx-cli -- render fire/campfire --gif --out out -
Or play it in the browser through the canvas demo:
wasm-pack build crates/pixfx-canvas --target web --out-dir demo/pkg cd crates/pixfx-canvas/demo && python3 -m http.server 8766 --bind 127.0.0.1