Animation, timelines, particles, and effects
Move from Story transforms to awaitable timelines, native transitions, and packaged GPU effects.
1. Begin with a simple move
Display a character before manipulating it:
[show guide expression=smile position=center]
[move guide x=80 duration=400 wait=true]
@guide
I moved a little to the side.Story move duration uses milliseconds. Slot switching and x/y interpolation are different operations. For a smooth transform, supply x/y/scale explicitly and inspect the result at your logical resolution.
2. A complete camera timeline
Create Animations/camera-pulse.pxtimeline:
{
"format": "PrismatiXTimeline",
"schemaRevision": 2,
"id": "tutorial.camera-pulse",
"name": "Camera pulse",
"duration": 2,
"tracks": [
{
"id": "camera-zoom",
"binding": {"kind": "camera", "target": "main", "property": "zoom"},
"keyframes": [
{"time": 0, "value": 1, "easing": "linear"},
{"time": 1, "value": 1.05, "easing": "easeInOut"},
{"time": 2, "value": 1, "easing": "easeInOut"}
]
}
],
"markers": [],
"nestedClips": []
}Append its descriptor to the project's assets:
{
"id": "71000000-0000-4000-8000-000000000002",
"name": "Camera pulse",
"kind": "timeline",
"source": "Animations/camera-pulse.pxtimeline"
}Use it in a Story passage with visible background and character content:
[timeline Animations/camera-pulse.pxtimeline]Timeline duration and keyframe time use seconds. This example changes camera zoom from 1 to 1.05 and back over two seconds. Track binding kinds include stage, ui, camera, text, audio, and shader. Targets and properties must still identify runtime-supported bindings; a schema accepting strings does not make every arbitrary name meaningful.
Keyframe easing accepts step, linear, easeIn, easeOut, easeInOut, and backOut. This is not the same complete name set as native Story-move easing. Markers carry a payload at a specified time, and nestedClips compose other clips. Verify a single track before adding complexity.
3. Explicit waiting and playback control
Inside an extension command with runtime/animation capability and appropriate await: true metadata:
const resource = ctx.animation.load('Animations/camera-pulse.pxtimeline');
const handle = ctx.animation.play(resource, { speed: 1 });
await ctx.animation.wait(handle);The API also provides pause/resume/seek/finish/cancel/status/error. Seek uses positionSeconds; 500 milliseconds is not a position of 500 seconds. Use engine-owned handles rather than a blocking loop. Story's timeline command passes a resource reference; it is not shorthand for every playback API option.
4. Stage, camera, and particles
With the appropriate extension capabilities:
ctx.stage.camera({ x: -24, y: 10, zoom: 1.08 });
ctx.stage.particles('weather.snow', 'snow', {
seed: 42,
rate: 60,
maxParticles: 300,
});
// At the end of the effect's lifecycle:
// ctx.stage.clearParticles('weather.snow');Particle presets include rain, snow, sakura, dust, light, and motes. Supply a fixed seed and let the native particle system own deterministic sampling instead of generating uncontrolled JavaScript randomness every frame.
Stage hierarchy APIs include group/parent/transform/order/visible. Watch opacity units: StageNodeTransform.opacity is 0–1, while some lower-level layer/renderer alpha values are 0–255. Similar names do not imply interchangeable units.
5. Transitions and screen effects
For menu navigation, start with built-in UI transitions such as crossfade. To define a portable custom full-screen transition, register a native effect plan:
ctx.effects.register('tutorial.tiles', (screen) => ({
operator: 'tiles',
columns: 10,
rows: 6,
stagger: 0.42,
order: 'row-major',
outgoing: screen.outgoing,
incoming: screen.incoming,
progress: screen.progress,
viewport: screen.viewport,
}));These are symbolic screen inputs and a native compositing plan, not GPU textures handed to JavaScript for per-frame rendering. During the appropriate navigation/screen lifecycle, ctx.effects.play returns a handle supporting wait/stop/cancel/status.
Basic-tier built-ins include flash and fade. Blur, vignette, color-grade, and custom shaders require gpu-effects. Do not promise identical rendering across WASM and software paths for every effect.
6. Packaged custom GPU effects
Set the project's graphics tier and register an effect descriptor:
{
"graphicsTier": "gpu-effects",
"effects": [{"id": "dream-tone", "source": "Effects/dream-tone.pxeffect"}]
}The descriptor has this complete shape, but packaging also requires an HLSL source implementing the fixed interface:
{
"format": "PrismatiXEffect",
"schemaRevision": 3,
"id": "dream-tone",
"targetLayer": "stage",
"shader": "Effects/dream-tone.frag.hlsl",
"uniforms": [
{"name": "amount", "type": "number", "slot": 0,
"default": 0.5, "minimum": 0, "maximum": 1}
]
}Follow the upstream effect contract and fixed HLSL interface. Renaming an arbitrary ShaderToy or OpenGL shader is not a conversion. Revision 3 supports stage, node, and transition targets. Up to eight uniforms use unique names and slots; types are number, vec2, and color, with color values in 0–1.
The Packager compiles offline, checks the resource layout, and produces fingerprinted native shader artifacts. The Player ships neither HLSL source nor a shader compiler. Output formats depend on the build backend; not every host emits the same format set. WASM Preview currently has no custom GPU-effect backend and explicitly rejects that tier instead of silently substituting different rendering.
7. Restore semantics
Timelines, Stage state, and managed seeds participate in save/seek/rollback. A save is not a copy of every GPU frame. In-flight full-screen transitions are transient; restored authoritative presentation wins on load. Test saving during animation, rollback, cancellation, scene changes, and capability failures on weaker targets.
Sources: Timeline schema, Track schema, Camera timeline sample, Runtime SDK.