From d1f93bc6eaa391e40a2239882d5de811181fe652 Mon Sep 17 00:00:00 2001 From: Jamie Wells Date: Mon, 2 Nov 2026 06:02:03 +0000 Subject: [PATCH 1/2] Document scene effects authoring, assets and runtime controls --- content/user/systems.md | 1 + docs/SCENE_EFFECTS.md | 179 ++++++++++ docs/SCENE_EFFECTS_REFERENCE.md | 517 ++++++++++++++++++++++++++++ docs/Scripting/API/effects.md | 5 + docs/Scripting/API/scene-effects.md | 86 +++++ lib/api-docs.ts | 2 +- lib/docs-catalog.ts | 3 + 7 files changed, 792 insertions(+), 1 deletion(-) create mode 100644 docs/SCENE_EFFECTS.md create mode 100644 docs/SCENE_EFFECTS_REFERENCE.md create mode 100644 docs/Scripting/API/scene-effects.md diff --git a/content/user/systems.md b/content/user/systems.md index 113e077..7e79009 100644 --- a/content/user/systems.md +++ b/content/user/systems.md @@ -5,6 +5,7 @@ Use the [status and compatibility reference](/docs/reference/compatibility) to d | System | Start here | API reference | | --- | --- | --- | | Rendering / scene lighting | [Sky and atmosphere](/docs/guides/scene-environment), [Rendering System](/docs/open-source/engine/rendering-system) | [Environment](/docs/api/environment) | +| Scene effects / VFX | [Scene effects and presets](/docs/guides/scene-effects) | [Typed configuration](/docs/guides/scene-effects/configuration) | | Primitive physics | [Basic physics](/docs/guides/physics) | [Queries and forces](/docs/api/physics-actions) | | Animation | [Imported clips](/docs/guides/gameplay-actions/imported-animation), [Layering](/docs/guides/gameplay-actions/animation-layers) | [Animation](/docs/api/animation) | | Tweening / easing | [Animate properties](/docs/guides/gameplay-actions/properties) | [Tween](/docs/api/tween), [Ease](/docs/api/ease) | diff --git a/docs/SCENE_EFFECTS.md b/docs/SCENE_EFFECTS.md new file mode 100644 index 0000000..573aa7a --- /dev/null +++ b/docs/SCENE_EFFECTS.md @@ -0,0 +1,179 @@ +# Scene Effects and VFX + +Scene effects are authored entity components. They use the existing hierarchy, transforms, scene persistence and undo history. An entity has at most one effect component; use child entities to combine effects. Configuration is saved; live particles and playback clocks are transient. + +This guide describes the native editor and native Play path. CPU simulation is the supported particle path. The compatibility section identifies renderer features that remain unavailable. + +## Add and preview an effect + +1. Open a project and scene in the editor. +2. Use hierarchy **+ Add → Effects** to create an effect entity, or select an entity and open **Properties → Scene effects → + Add / replace effect**. +3. Select the new effect entity. Expand **Common** for playback, seed, duration and visibility, then the effect-specific section for simulation and appearance. +4. Open **Properties → Effect preview** to play, pause, resume, stop, restart, reset or step by 1/60 second. Preview speed ranges from 0 to 4; time scrubbing replays up to 30 seconds, and bounds/metrics are available. Move its entity with the normal transform gizmo. An emitting trail needs movement before it creates a ribbon. Preview runs a scratch SceneWorld through the same simulation path, including registered child-asset sub-emitters. Stopping preview clears transient child groups. +5. Save the scene, close it and reopen it. The configuration returns; playback starts from the configured initial state. + +Open **Effects presets & assets**, search the built-in library and click a preset to instantiate a group under the selection. Drag a preset onto the viewport or hierarchy to place it. Use **Load .effect** or **Save selection .effect** for custom assets. The preset is editable. For a selected single-effect component, **Assign single-effect asset…** changes its source. Composite assets are instantiated through the asset browser. Configure entity references such as a light pulse's target or a beam's endpoint; effects requiring targets cannot infer your intended object. + +## Common properties + +Distances use scene world units, times use seconds, rotations use radians, and rates use units per second unless stated otherwise. Entity transforms provide position, rotation, scale and parenting. Optional entity/asset references are stable UUIDs; do not substitute OS file paths for an asset identifier. + +| Property | Type | Default | Meaning | +| --- | --- | --- | --- | +| enabled / visible | boolean | true / true | Enable simulation and render visibility | +| auto_play | boolean | true | Start a newly encountered component automatically | +| looping | boolean | true | Repeat the duration | +| duration / delay | number | 5 / 0 | Positive cycle duration and initial delay, seconds | +| time_scale | number | 1 | Nonnegative effect clock multiplier | +| simulation_space | enum | World | World or Local particle coordinates | +| attachment_target | entity or null | null | Follow a scene entity | +| visibility_layers | unsigned integer | 4294967295 | Effect layer mask | +| render_distance | number | 1000 | Maximum camera distance | +| quality | enum | Medium | Low, Medium or High | +| random_seed | unsigned integer | 1 | Deterministic simulation seed | +| priority | integer | 0 | Ascending modifier order; higher priority blends last | +| parameters | typed map | empty | Named per-instance parameter bindings | +| bounds | optional bounds | null | Explicit culling region | + +Simulation remains cosmetic. Do not use particle collision events for authoritative damage or gameplay physics. Replaying the same seed requires the same transforms and time steps; a seed alone does not make arbitrary frame partitions identical. + +Named `common.parameters` bindings accept `Scalar`, `Vector`, `Color` or `Boolean` variants. Supported names include enabled, visible, emission_rate, intensity, color, speed, size, gravity, force, density, progress, width and radius on compatible effects. Unknown or incompatible names do not invent behavior; use a documented binding appropriate to the effect kind. Explicit bounds use a local center and positive half extents for camera culling. + +## Curves and gradients + +`Curve` stores `keys` with normalized `time` and scalar `value`. `Gradient` stores `keys` with normalized `time` and RGBA `color`. Evaluation interpolates linearly between surrounding keys and holds the nearest endpoint outside their range. Keep times between zero and one and use distinct times for predictable interpolation. The Inspector edits key times and values with native numeric controls, plus native color pickers for gradients. Key lists support add, duplicate, reorder and remove. The interactive curve/gradient graph supports dragging keys, double-clicking to add an interpolated key and right-clicking a key to remove it. The scalar parameter type also supports constants, ranges and a random interpolation between two curves. Color parameters can interpolate randomly between two gradients; vector curves keep separate normalized X/Y/Z channels. + +Appearance configuration is shared by particles, trails and other geometry effects: + +| Property | Default | Meaning | +| --- | --- | --- | +| color | opaque white gradient | Color over normalized lifetime | +| opacity | linear 1 to 0 | Alpha multiplier over lifetime | +| size | constant 1 | Size/width multiplier over lifetime | +| material / texture | null | Optional stable asset references | +| emission | 1 | Emissive strength | +| blend | Alpha | Alpha, Additive or Multiply | +| uv_scale | [1, 1] | Texture coordinate scaling | +| uv_scroll | [0, 0] | Texture movement per second | + +## Particle Emitter tutorial + +Create a **Particle Emitter**, keep auto-play enabled and expand the emitter settings. The Inspector groups its settings under Emission, Initialization, Movement, Appearance, Collision, Sub-emitters, and Capacity and simulation. For MeshSurface emission, first assign a mesh under Appearance; the editor extracts source triangles before validating the shape. Start with a rate of 30, maximum 1000, lifetime [1, 2] and speed [1, 3]. Cone emission, upward direction [0, 1, 0] and gravity [0, -1, 0] create a small fountain. Change the appearance gradient to orange and use Additive blending for sparks. For smoke, use Alpha blending, larger sizes and a longer lifetime. + +A burst contains `time`, `count`, `probability`, `cycles` and `interval`; a zero continuous rate plus a time-zero burst creates an impulse. Disable looping to play it once. Particle collision uses explicit collision volumes, with layer masks, bounce/friction and Bounce, Stick or Kill response. A force field and a wind zone can affect multiple compatible emitters without allocating rigid bodies per particle. + +For a looping emitter, `prewarm` simulates `prewarm_time` (bounded to ten seconds) before its first visible update. It uses 1/60-second steps and suppresses warmup event delivery. + +Select the Rain, Snow or Leaves preset for weather. Weather reuses the particle emitter; its intensity multiplies emission. Move a local weather volume to the desired region rather than creating an unbounded scene population. + +## Trail Renderer tutorial + +Add a **Trail Renderer** to a moving entity or create a child of a moving object. Set lifetime to 1 second, width to 0.2, minimum vertex distance to 0.05 and maximum points to 256. Move the object in Play or while previewing. The renderer records separated points, removes expired points and builds an indexed ribbon. Changing the appearance size curve changes width along the ribbon; the opacity curve fades the tail. + +A teleport farther than `teleport_distance` clears the previous strip so it does not connect unrelated positions. Stop emitting to let existing points expire. Use separate trail entities for left and right wheels. The generated showcase includes a Lua orbit behavior to exercise a moving trail. + +## Effect asset and composite tutorial + +An `.effect` file contains `EffectAsset { version, name, effects }`, with optional `entity_ids` and column-major `transforms` arrays for authored child identities and transforms relative to the group. Instantiation allocates fresh child IDs and remaps references between children, preserving their relative transforms. Legacy assets without those arrays instantiate at the group origin. References to scene objects outside the asset remain external references; assign them for the destination scene. Version 1 stores an ordered list of fully typed child configurations. `common.delay` and `common.duration` give each child its position in the composite timeline. The asset reader rejects unsupported versions, empty groups, groups above 128 children and files above 4 MiB. + +`presets()` returns 35 independent built-in assets; `instantiate()` clones a group's configuration. `instantiate_from(asset_id)` additionally retains the source UUID and child index; `restore(instance)` resets that child to its shared asset defaults. Editing one instance does not mutate another instance or the source asset. Explosion combines a time-zero burst, delayed smoke, an expanding shockwave and a camera impulse. The showcase generator writes all built-in assets under `assets/effects`. + +A composite's children are ordinary scene effects. Parenting preserves transform inheritance and standard duplication/deletion behavior. Reset individual instance values by reapplying the asset. Select the group and open **Scene effects → Composite timeline (seconds)** to change child enabled state, start and duration, select a child to edit, duplicate, move earlier or delete it. Select a source child for **Restore asset defaults** and **Update shared asset**. **Update shared asset** refreshes matching instances in the currently open authoring scene while preserving fields that differ from the old defaults; arrays are treated as complete overrides. That scene refresh is one undo transaction. The shared asset file write is a separate asset edit. When an asset is registered during scene loading, persisted source baselines merge current asset defaults while preserving per-field overrides; arrays remain atomic overrides. Legacy instances without a baseline retain embedded configuration until restored. + +## All effect types + +| # | Kind key | Intended behavior and setup | +| --- | --- | --- | +| 1 | Trail | Moving-object ribbon with bounded lifetime and point count | +| 2 | Particle | CPU particle emitter with emission, initialization, movement, appearance and collisions | +| 3 | Beam | Segmented beam/lightning between positions or scene targets | +| 4 | Shockwave | Expanding annulus aligned by its entity transform | +| 5 | MeshParticle | Particle simulation carrying a mesh asset for geometry rendering | +| 6 | Ribbon | Control-point ribbon or tube with spline subdivision | +| 7 | ForceField | Directional, attraction, repulsion, vortex or turbulence influence volume | +| 8 | CollisionVolume | Particle sphere, box, plane or cylinder interaction volume | +| 9 | ProjectileTracer | Velocity-driven visible streak | +| 10 | Fog | Localized depth-aware fog volume | +| 11 | Decal | Surface projection region with appearance configuration | +| 12 | Weather | Rain, Snow, Hail, Dust, Sand or Leaves using the particle infrastructure | +| 13 | WaterRipple | Bounded water disturbances, displaced grid/target surface, normals and wake foam | +| 14 | Wind | Global or local sampled wind influence | +| 15 | HeatHaze | Actual resolved-framebuffer UV distortion | +| 16 | LightShaft | Light scattering input linked to a light source | +| 17 | FoliageScatter | Deterministic decorative geometry distribution | +| 18 | Dissolve | Temporary target surface threshold and emissive edge modifier | +| 19 | AnimatedSurface | Temporary UV movement, color and emission modifier | +| 20 | LensFlare | Camera-projected flare ghosts and streak | +| 21 | LightPulse | Temporary light color/intensity animation; assign a light target | +| 22 | PostProcess | Priority-weighted global or local camera grading settings | +| 23 | Outline | Target object highlight; assign target entities | +| 24 | CameraShake | Additive render-camera translation and rotation impulse | +| 25 | ScreenFlash | Fading camera color overlay with vignette | +| 26 | ReflectionProbe | Authored local reflection influence and cubemap reference; see compatibility limits | + +The [configuration reference](/docs/guides/scene-effects/configuration) lists the exact typed fields. A registered component does not establish support for every advanced rendering option: consult the limitations below before choosing an effect for production. + +## Playback and scripting + +Attach a Lua Object Component Script to an entity through **Programming → New Script**, then attach it in the Inspector. The `Effects` global extends the existing fade/flash/shake/pulse tween helpers. It does not replace them. + +```lua +return { + Start = function() + Effects.create(nil, "Particle") + Effects.configure(nil, { + common = {looping = false, duration = 2.0}, + kind = {Particle = {rate = 0, bursts = { + {time = 0, count = 50, probability = 1, cycles = 1, interval = 1} + }}} + }) + Effects.restart(nil) + end +} +``` + +Run this on an entity that does not already have an effect. For a previously authored effect, omit `create`. `nil` means the script owner. Passing a real entity reference targets another object. Public signatures: + +| Call | Result | +| --- | --- | +| Effects.types() | Array of supported, case-sensitive kind keys | +| Effects.create(entity, kind) | Creates the one effect component and returns its configuration | +| Effects.get(entity) | Configuration or null/nil | +| Effects.configure(entity, settings) | Validated nested configuration patch; returns new configuration | +| Effects.play/pause/resume/stop/restart/reset(entity) | Playback operation | +| Effects.dispose(entity) | Removes runtime state and the component | +| Effects.state(entity) | Playback state string or null/nil | +| Effects.instantiate(asset, parent, position) | Instantiated composite root entity UUID | +| Effects.spawn(asset, position, lifetime, parent) | Temporary group; positive explicit lifetime schedules group destruction | + +JavaScript uses the same names and objects, with `null` instead of `nil`. Python also exposes the same `Effects` wrappers. Other generic-query adapters can use the existing `rustic.query` boundary, for example `{op:'effect_types'}`. Effect requests include `effect_create`, `effect_get`, `effect_configure`, `effect_control` and `effect_state`. Entity arguments in raw protocol requests must be stable UUIDs. Configuration uses the typed variant shape `kind: {Particle: {...}}`; unknown fields, unknown kinds and invalid values return errors before committing a scene mutation. + +Runtime edits affect the live Play world. Stop Play to return to the authored scene. Use the Inspector and Save for persistent authoring. `dispose` removes the effect component, not its entity. Completion and particle birth/collision/death records are available in Rust `EffectFrame.events` and the existing Events service. Subscribe with `Events.connect(entity, "EffectCompleted", callback)` or `EffectBirth`, `EffectCollision` and `EffectDeath`; event arguments contain the world position vector. A composite root emits EffectCompleted once when its descendants complete; restarting or resetting begins a new completion cycle. `Effects.sampleWind(position, time, layers)` samples compatible wind at a position, using the supplied time in seconds for gust phase. + +## Renderer compatibility and color management + +The native renderer supports up to eight fog volumes, four shafts and four flares per frame. Fog raymarching uses 8–64 steps. Shafts use radial framebuffer sampling with depth occlusion and remain available with fog because the current fog model does not already produce equivalent directional shafts. Fog has height, edge and procedural noise density; anisotropic phase functions and dynamic-light scattering are unavailable. Depth of field uses a 25-tap depth-dependent blur focused at the center of the image. + +The native wgpu renderer batches depth-tested transparent geometry and particles. It supports Alpha, Additive and Multiply effects without a separate draw call for every billboard. Empty texture slots use procedural sprites. Geometry-based effects should use the existing mesh/material conventions when selecting assets. Mesh particles use batched source geometry through the PBR path with a 16-step color palette; a missing source mesh is omitted. Custom particle sprite atlases support up to 63 textures per frame, resampled to 256-pixel layers, with flipbook UVs and velocity stretching. Decal projection clips receiver triangles and supports albedo/normal asset textures plus scalar roughness/metallicity; packed ORM, decal surface-layer filtering and explicit material-asset overrides are unavailable. Outline color and thickness use depth-occluded stencil silhouettes; through-wall outlines are unavailable. + +The existing material shader tone-maps before the resolved frame is post-processed. Effects grading decodes display colors for its calculations and re-encodes them. Bloom is a thresholded display-range filter; this is **not an HDR bloom pipeline**. Heat haze changes actual sampled framebuffer coordinates. GPU particle simulation is not implemented or advertised. CPU simulation is the supported path. + +Unsupported advanced options must not be inferred from a configuration field alone. Current boundaries include GPU particle simulation, runtime cubemap capture, six-face cubemap file layouts, temporal volumetric history, and motion-vector motion blur. Baked local probes use a 2:1 equirectangular HDR/PNG/JPEG asset registered through its `.rmeta` ID; their bounds blend local reflection input and support box parallax correction. Overlap chooses the highest-priority probe per mesh rather than a weighted sum of probes. `cubemap` names that panorama asset. The showcase supplies a generated HDR panorama and sidecar. Water uses the reusable Rust `WaterSurface::sample(world_position)` interface, returning displacement, normal and foam. `RippleWaterSurface` combines a bounded queue of disturbances; `water_surface_mesh` builds a displaced grid. Configure target/source velocity, wavelength, damping, wake angle, foam and normal strength. This is an analytical cosmetic surface model, not a fluid solver. Browser export must be checked independently; the native effect renderer does not establish browser support. + +## Performance and cleanup + +Use bounded particle/point/instance counts, shorter lifetimes and local volumes. Prefer one emitter containing many particles over thousands of effect entities. Keep transparent overdraw low: overlapping large smoke sprites and weather volumes can dominate frame time even with batching. Low quality tiers reduce cosmetic density; never use these tiers to gate gameplay damage or authoritative collisions. + +World effect state is keyed by stable entity IDs. Destroying entities or unloading scenes must release their simulation state. Playback does not permanently overwrite the authored camera or light configuration; frame modifiers are applied during rendering. Reuse configured entities for repeated bursts when practical. Asset instantiation clones authored data; it is not a GPU resource duplication guarantee. + +## Showcase and troubleshooting + +From the engine's `Engine/` directory: + +```sh +cargo run -p engine-project --example scene_effects_showcase -- /tmp/rustic-effects-showcase +``` + +The generator creates a normal project with a checksummed scene, all 26 component types, moving trail behavior and all 35 preset assets. Open that project in Project Manager. Scene entities have numbered names corresponding to the table above. Select a section and change its configuration, save and reopen to check persistence. Replay a one-shot effect with Restart. + +If no particles appear, check enabled, visible, auto-play, rate/bursts, lifetime, camera distance and the layer mask. If a trail is empty, move the emitting entity farther than its minimum vertex distance. If a target effect has no result, choose a valid target of the appropriate component type. If a preset cannot load, inspect its version and validation error; do not remove a scene checksum to force-load corrupted content. If a graphical backend is unavailable, CPU/configuration tests cannot replace visual verification. diff --git a/docs/SCENE_EFFECTS_REFERENCE.md b/docs/SCENE_EFFECTS_REFERENCE.md new file mode 100644 index 0000000..66a5143 --- /dev/null +++ b/docs/SCENE_EFFECTS_REFERENCE.md @@ -0,0 +1,517 @@ +# Effect configuration reference + +The table names match the typed scene configuration and scripting patches exactly. See [Scene Effects](/docs/guides/scene-effects) for attachment, tutorials, units, renderer support and limitations. Optional references default to `None` (null in JavaScript); vectors and colors use arrays. `Default::default()` refers to the shared default configuration described in that section. Values below are Rust default expressions; they are not JSON request syntax. + +Schema presence does not promise an advanced backend capability. Use the compatibility guide before relying on GPU simulation, water solvers, probe capture or temporal features. + +## Shared lifecycle + +Configuration type: `EffectCommon`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `parameters` | `std::collections::BTreeMap` | `Default::default()` | parameters | +| `bounds` | `Option` | `None` | bounds | +| `enabled` | `bool` | `true` | enabled | +| `visible` | `bool` | `true` | visible | +| `auto_play` | `bool` | `true` | auto play | +| `looping` | `bool` | `true` | looping | +| `duration` | `f32` | `5.0` | seconds | +| `delay` | `f32` | `0.0` | seconds | +| `time_scale` | `f32` | `1.0` | clock multiplier | +| `simulation_space` | `SimulationSpace` | `SimulationSpace::World` | simulation space | +| `attachment_target` | `Option` | `None` | attachment target | +| `visibility_layers` | `u32` | `u32::MAX` | unsigned bit mask | +| `render_distance` | `f32` | `1000.0` | scene units | +| `quality` | `QualityLevel` | `QualityLevel::Medium` | quality | +| `random_seed` | `u64` | `1` | deterministic unsigned seed | +| `priority` | `i32` | `0` | priority | + +## Shared appearance + +Configuration type: `EffectAppearance`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `color` | `Gradient` | `Gradient::default()` | RGBA linear color | +| `opacity` | `Curve` | `Curve::linear(1.0, 0.0)` | opacity | +| `size` | `Curve` | `Curve::constant(1.0)` | size | +| `material` | `Option` | `None` | material | +| `texture` | `Option` | `None` | texture | +| `emission` | `f32` | `1.0` | emission | +| `blend` | `BlendMode` | `BlendMode::Alpha` | blend | +| `uv_scale` | `[f32; 2]` | `[1.0; 2]` | uv scale | +| `uv_scroll` | `[f32; 2]` | `[0.0; 2]` | UV units/second | + +## Burst + +Configuration type: `Burst`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `time` | `f32` | `0.0` | time | +| `count` | `u32` | `20` | count | +| `probability` | `f32` | `1.0` | probability | +| `cycles` | `u32` | `1` | cycles | +| `interval` | `f32` | `1.0` | interval | + +## 1. Trail + +Configuration type: `TrailRenderer`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `emitting` | `bool` | `true` | emitting | +| `lifetime` | `f32` | `1.0` | seconds or [minimum, maximum] seconds | +| `width` | `f32` | `0.2` | scene units | +| `min_vertex_distance` | `f32` | `0.05` | min vertex distance | +| `max_points` | `u32` | `256` | max points | +| `teleport_distance` | `f32` | `20.0` | teleport distance | +| `camera_facing` | `bool` | `true` | camera facing | +| `appearance` | `EffectAppearance` | `EffectAppearance::default()` | appearance | + +## 2. Particle + +Configuration type: `ParticleEmitter`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `prewarm` | `bool` | `false` | prewarm | +| `prewarm_time` | `f32` | `5.0` | prewarm time | +| `max_particles` | `u32` | `1000` | max particles | +| `rate` | `f32` | `30.0` | particles/second | +| `rate_over_distance` | `f32` | `0.0` | particles/scene unit | +| `bursts` | `Vec` | `vec![]` | bursts | +| `shape` | `SpawnShape` | `SpawnShape::Cone` | shape | +| `dimensions` | `[f32; 3]` | `[1.0; 3]` | scene units, XYZ | +| `surface_only` | `bool` | `false` | surface only | +| `lifetime` | `[f32; 2]` | `[1.0, 2.0]` | seconds or [minimum, maximum] seconds | +| `speed` | `[f32; 2]` | `[1.0, 3.0]` | scene units/second | +| `direction` | `[f32; 3]` | `[0.0, 1.0, 0.0]` | direction | +| `start_size` | `[f32; 2]` | `[0.1, 0.25]` | start size | +| `gravity` | `[f32; 3]` | `[0.0, -1.0, 0.0]` | gravity | +| `force` | `[f32; 3]` | `[0.0; 3]` | force | +| `drag` | `f32` | `0.1` | drag | +| `wind_influence` | `f32` | `1.0` | wind influence | +| `noise` | `f32` | `0.0` | noise | +| `angular_velocity` | `f32` | `0.0` | angular velocity | +| `inherit_velocity` | `f32` | `0.0` | inherit velocity | +| `velocity_limit` | `f32` | `1000.0` | velocity limit | +| `appearance` | `EffectAppearance` | `EffectAppearance::default()` | appearance | +| `mesh` | `Option` | `None` | mesh | +| `mesh_spawn_triangles` | `Vec<[[f32; 3]; 3]>` | `Vec::new()` | mesh spawn triangles | +| `collision` | `bool` | `false` | collision | +| `collision_layers` | `u32` | `u32::MAX` | unsigned bit mask | +| `bounce` | `f32` | `0.5` | bounce | +| `friction` | `f32` | `0.1` | friction | +| `collision_response` | `CollisionResponse` | `CollisionResponse::Bounce` | collision response | +| `sub_emitter` | `Option` | `None` | sub emitter | +| `sub_emitter_probability` | `f32` | `1.0` | sub emitter probability | +| `max_generations` | `u8` | `3` | max generations | +| `flipbook` | `[u32; 2]` | `[1, 1]` | flipbook | +| `flipbook_fps` | `f32` | `0.0` | flipbook fps | +| `stretch` | `f32` | `0.0` | stretch | + +## 3. Beam + +Configuration type: `BeamRenderer`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `start_target` | `Option` | `None` | start target | +| `end_target` | `Option` | `None` | end target | +| `start` | `[f32; 3]` | `[0.0; 3]` | start | +| `end` | `[f32; 3]` | `[0.0, 3.0, 0.0]` | end | +| `width` | `f32` | `0.1` | scene units | +| `segments` | `u32` | `24` | segments | +| `noise_amplitude` | `f32` | `0.2` | noise amplitude | +| `noise_frequency` | `f32` | `3.0` | noise frequency | +| `noise_speed` | `f32` | `5.0` | noise speed | +| `branch_count` | `u32` | `0` | branch count | +| `appearance` | `EffectAppearance` | `EffectAppearance::default()` | appearance | + +## 4. Shockwave + +Configuration type: `RingEffect`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `target` | `Option` | `None` | target | +| `source_velocity` | `[f32; 3]` | `[0.0; 3]` | source velocity | +| `wake_angle` | `f32` | `0.6` | wake angle | +| `foam_intensity` | `f32` | `0.5` | foam intensity | +| `foam_lifetime` | `f32` | `1.0` | foam lifetime | +| `normal_strength` | `f32` | `1.0` | normal strength | +| `max_disturbances` | `u32` | `32` | max disturbances | +| `initial_radius` | `f32` | `0.1` | initial radius | +| `final_radius` | `f32` | `4.0` | final radius | +| `thickness` | `f32` | `0.2` | thickness | +| `vertical_offset` | `f32` | `0.01` | vertical offset | +| `amplitude` | `f32` | `0.1` | amplitude | +| `wavelength` | `f32` | `1.0` | wavelength | +| `damping` | `f32` | `1.0` | damping | +| `appearance` | `EffectAppearance` | `EffectAppearance::default()` | appearance | + +## 5. MeshParticle + +Configuration type: `ParticleEmitter`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `prewarm` | `bool` | `false` | prewarm | +| `prewarm_time` | `f32` | `5.0` | prewarm time | +| `max_particles` | `u32` | `1000` | max particles | +| `rate` | `f32` | `30.0` | particles/second | +| `rate_over_distance` | `f32` | `0.0` | particles/scene unit | +| `bursts` | `Vec` | `vec![]` | bursts | +| `shape` | `SpawnShape` | `SpawnShape::Cone` | shape | +| `dimensions` | `[f32; 3]` | `[1.0; 3]` | scene units, XYZ | +| `surface_only` | `bool` | `false` | surface only | +| `lifetime` | `[f32; 2]` | `[1.0, 2.0]` | seconds or [minimum, maximum] seconds | +| `speed` | `[f32; 2]` | `[1.0, 3.0]` | scene units/second | +| `direction` | `[f32; 3]` | `[0.0, 1.0, 0.0]` | direction | +| `start_size` | `[f32; 2]` | `[0.1, 0.25]` | start size | +| `gravity` | `[f32; 3]` | `[0.0, -1.0, 0.0]` | gravity | +| `force` | `[f32; 3]` | `[0.0; 3]` | force | +| `drag` | `f32` | `0.1` | drag | +| `wind_influence` | `f32` | `1.0` | wind influence | +| `noise` | `f32` | `0.0` | noise | +| `angular_velocity` | `f32` | `0.0` | angular velocity | +| `inherit_velocity` | `f32` | `0.0` | inherit velocity | +| `velocity_limit` | `f32` | `1000.0` | velocity limit | +| `appearance` | `EffectAppearance` | `EffectAppearance::default()` | appearance | +| `mesh` | `Option` | `None` | mesh | +| `mesh_spawn_triangles` | `Vec<[[f32; 3]; 3]>` | `Vec::new()` | mesh spawn triangles | +| `collision` | `bool` | `false` | collision | +| `collision_layers` | `u32` | `u32::MAX` | unsigned bit mask | +| `bounce` | `f32` | `0.5` | bounce | +| `friction` | `f32` | `0.1` | friction | +| `collision_response` | `CollisionResponse` | `CollisionResponse::Bounce` | collision response | +| `sub_emitter` | `Option` | `None` | sub emitter | +| `sub_emitter_probability` | `f32` | `1.0` | sub emitter probability | +| `max_generations` | `u8` | `3` | max generations | +| `flipbook` | `[u32; 2]` | `[1, 1]` | flipbook | +| `flipbook_fps` | `f32` | `0.0` | flipbook fps | +| `stretch` | `f32` | `0.0` | stretch | + +## 6. Ribbon + +Configuration type: `RibbonRenderer`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `points` | `Vec<[f32; 3]>` | `vec![[0.0; 3], [1.0, 1.0, 0.0], [2.0, 0.0, 0.0]]` | points | +| `width` | `f32` | `0.2` | scene units | +| `tube` | `bool` | `false` | tube | +| `radial_segments` | `u32` | `8` | radial segments | +| `subdivisions` | `u32` | `8` | subdivisions | +| `twist` | `f32` | `0.0` | twist | +| `appearance` | `EffectAppearance` | `EffectAppearance::default()` | appearance | + +## 7. ForceField + +Configuration type: `ForceField`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `shape` | `VolumeShape` | `VolumeShape::Sphere` | shape | +| `dimensions` | `[f32; 3]` | `[5.0; 3]` | scene units, XYZ | +| `force_type` | `ForceType` | `ForceType::Directional` | force type | +| `direction` | `[f32; 3]` | `[1.0, 0.0, 0.0]` | direction | +| `strength` | `f32` | `1.0` | strength | +| `falloff` | `f32` | `1.0` | falloff | +| `turbulence` | `f32` | `0.0` | turbulence | +| `frequency` | `f32` | `1.0` | cycles/second | +| `layers` | `u32` | `u32::MAX` | unsigned bit mask | +| `global` | `bool` | `false` | global | + +## 8. CollisionVolume + +Configuration type: `CollisionVolume`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `shape` | `VolumeShape` | `VolumeShape::Plane` | shape | +| `dimensions` | `[f32; 3]` | `[10.0; 3]` | scene units, XYZ | +| `layers` | `u32` | `u32::MAX` | unsigned bit mask | +| `bounce` | `f32` | `0.5` | bounce | +| `friction` | `f32` | `0.1` | friction | +| `response` | `CollisionResponse` | `CollisionResponse::Bounce` | response | +| `impact_effect` | `Option` | `None` | impact effect | + +## 9. ProjectileTracer + +Configuration type: `ProjectileTracer`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `velocity` | `[f32; 3]` | `[0.0, 0.0, 10.0]` | velocity | +| `length` | `f32` | `2.0` | length | +| `width` | `f32` | `0.05` | scene units | +| `appearance` | `EffectAppearance` | `EffectAppearance::default()` | appearance | + +## 10. Fog + +Configuration type: `FogVolume`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `shape` | `VolumeShape` | `VolumeShape::Box` | shape | +| `dimensions` | `[f32; 3]` | `[10.0, 3.0, 10.0]` | scene units, XYZ | +| `density` | `f32` | `0.1` | density | +| `color` | `[f32; 3]` | `[0.6, 0.7, 0.8]` | RGBA linear color | +| `height_falloff` | `f32` | `0.5` | height falloff | +| `edge_falloff` | `f32` | `1.0` | edge falloff | +| `noise_scale` | `f32` | `1.0` | noise scale | +| `noise_speed` | `f32` | `0.1` | noise speed | +| `anisotropy` | `f32` | `0.0` | anisotropy | +| `steps` | `u32` | `32` | steps | + +## 11. Decal + +Configuration type: `DecalProjector`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `dimensions` | `[f32; 3]` | `[1.0, 1.0, 1.0]` | scene units, XYZ | +| `appearance` | `EffectAppearance` | `EffectAppearance::default()` | appearance | +| `normal_map` | `Option` | `None` | normal map | +| `orm_map` | `Option` | `None` | orm map | +| `roughness` | `f32` | `0.8` | roughness | +| `metallic` | `f32` | `0.0` | metallic | +| `edge_fade` | `f32` | `0.1` | edge fade | +| `angle_fade` | `f32` | `0.5` | angle fade | +| `layers` | `u32` | `u32::MAX` | unsigned bit mask | + +## 12. Weather + +Configuration type: `WeatherVolume`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `weather` | `WeatherType` | `WeatherType::Rain` | weather | +| `emitter` | `ParticleEmitter` | `Box, [20,10,20], downward [8,10], rate 200, lifetime [2,3]` | emitter | +| `intensity` | `f32` | `1.0` | intensity | +| `camera_follow` | `bool` | `false` | camera follow | + +## 13. WaterRipple + +Configuration type: `RingEffect`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `target` | `Option` | `None` | target | +| `source_velocity` | `[f32; 3]` | `[0.0; 3]` | source velocity | +| `wake_angle` | `f32` | `0.6` | wake angle | +| `foam_intensity` | `f32` | `0.5` | foam intensity | +| `foam_lifetime` | `f32` | `1.0` | foam lifetime | +| `normal_strength` | `f32` | `1.0` | normal strength | +| `max_disturbances` | `u32` | `32` | max disturbances | +| `initial_radius` | `f32` | `0.1` | initial radius | +| `final_radius` | `f32` | `4.0` | final radius | +| `thickness` | `f32` | `0.2` | thickness | +| `vertical_offset` | `f32` | `0.01` | vertical offset | +| `amplitude` | `f32` | `0.1` | amplitude | +| `wavelength` | `f32` | `1.0` | wavelength | +| `damping` | `f32` | `1.0` | damping | +| `appearance` | `EffectAppearance` | `EffectAppearance::default()` | appearance | + +## 14. Wind + +Configuration type: `ForceField`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `shape` | `VolumeShape` | `VolumeShape::Sphere` | shape | +| `dimensions` | `[f32; 3]` | `[5.0; 3]` | scene units, XYZ | +| `force_type` | `ForceType` | `ForceType::Directional` | force type | +| `direction` | `[f32; 3]` | `[1.0, 0.0, 0.0]` | direction | +| `strength` | `f32` | `1.0` | strength | +| `falloff` | `f32` | `1.0` | falloff | +| `turbulence` | `f32` | `0.0` | turbulence | +| `frequency` | `f32` | `1.0` | cycles/second | +| `layers` | `u32` | `u32::MAX` | unsigned bit mask | +| `global` | `bool` | `false` | global | + +## 15. HeatHaze + +Configuration type: `DistortionEffect`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `strength` | `f32` | `0.01` | strength | +| `noise_scale` | `f32` | `10.0` | noise scale | +| `speed` | `f32` | `1.0` | scene units/second | +| `radius` | `f32` | `3.0` | scene units | +| `edge_falloff` | `f32` | `1.0` | edge falloff | + +## 16. LightShaft + +Configuration type: `LightShaft`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `light` | `Option` | `None` | light | +| `intensity` | `f32` | `0.5` | intensity | +| `color` | `[f32; 3]` | `[1.0, 0.9, 0.6]` | RGBA linear color | +| `density` | `f32` | `0.1` | density | +| `anisotropy` | `f32` | `0.5` | anisotropy | +| `max_distance` | `f32` | `100.0` | max distance | +| `samples` | `u32` | `32` | samples | + +## 17. FoliageScatter + +Configuration type: `FoliageScatter`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `meshes` | `Vec` | `vec![]` | meshes | +| `density` | `f32` | `1.0` | density | +| `dimensions` | `[f32; 3]` | `[10.0, 1.0, 10.0]` | scene units, XYZ | +| `scale` | `[f32; 2]` | `[0.5, 1.5]` | scale | +| `max_instances` | `u32` | `1000` | max instances | +| `wind_response` | `f32` | `1.0` | wind response | +| `appearance` | `EffectAppearance` | `EffectAppearance::default()` | appearance | + +## 18. Dissolve + +Configuration type: `DissolveEffect`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `target` | `Option` | `None` | target | +| `progress` | `f32` | `0.0` | progress | +| `edge_width` | `f32` | `0.05` | edge width | +| `edge_color` | `[f32; 3]` | `[1.0, 0.4, 0.0]` | edge color | +| `edge_emission` | `f32` | `3.0` | edge emission | +| `noise_scale` | `f32` | `10.0` | noise scale | +| `curve` | `Curve` | `Curve::linear(0.0, 1.0)` | curve | + +## 19. AnimatedSurface + +Configuration type: `AnimatedSurface`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `target` | `Option` | `None` | target | +| `uv_scroll` | `[f32; 2]` | `[0.2, 0.0]` | UV units/second | +| `uv_scale` | `[f32; 2]` | `[1.0; 2]` | uv scale | +| `uv_rotation` | `f32` | `0.0` | uv rotation | +| `color` | `Gradient` | `Gradient::default()` | RGBA linear color | +| `emission` | `f32` | `1.0` | emission | +| `speed` | `f32` | `1.0` | scene units/second | + +## 20. LensFlare + +Configuration type: `LensFlare`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `light` | `Option` | `None` | light | +| `intensity` | `f32` | `1.0` | intensity | +| `scale` | `f32` | `0.1` | scale | +| `color` | `[f32; 3]` | `[1.0, 0.8, 0.5]` | RGBA linear color | +| `ghosts` | `u32` | `4` | ghosts | +| `ghost_spacing` | `f32` | `0.2` | ghost spacing | +| `streak_length` | `f32` | `0.3` | streak length | +| `fade_speed` | `f32` | `5.0` | fade speed | +| `texture` | `Option` | `None` | texture | + +## 21. LightPulse + +Configuration type: `LightPulse`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `target` | `Option` | `None` | target | +| `intensity` | `Curve` | `Curve::linear(0.0, 1.0)` | intensity | +| `color` | `Gradient` | `Gradient::default()` | RGBA linear color | +| `frequency` | `f32` | `1.0` | cycles/second | +| `randomness` | `f32` | `0.0` | randomness | +| `phase` | `f32` | `0.0` | phase | + +## 22. PostProcess + +Configuration type: `PostProcessVolume`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `global` | `bool` | `true` | global | +| `dimensions` | `[f32; 3]` | `[10.0; 3]` | scene units, XYZ | +| `blend_distance` | `f32` | `1.0` | blend distance | +| `weight` | `f32` | `1.0` | weight | +| `exposure` | `f32` | `0.0` | exposure | +| `saturation` | `f32` | `1.0` | saturation | +| `contrast` | `f32` | `1.0` | contrast | +| `brightness` | `f32` | `0.0` | brightness | +| `tint` | `[f32; 3]` | `[1.0; 3]` | tint | +| `bloom` | `f32` | `0.0` | bloom | +| `vignette` | `f32` | `0.0` | vignette | +| `chromatic_aberration` | `f32` | `0.0` | chromatic aberration | +| `grain` | `f32` | `0.0` | grain | +| `depth_of_field` | `f32` | `0.0` | depth of field | + +## 23. Outline + +Configuration type: `OutlineEffect`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `targets` | `Vec` | `vec![]` | targets | +| `color` | `[f32; 4]` | `[1.0, 0.6, 0.0, 1.0]` | RGBA linear color | +| `thickness` | `f32` | `0.03` | thickness | +| `pulse_speed` | `f32` | `0.0` | pulse speed | +| `through_walls` | `bool` | `false` | through walls | + +## 24. CameraShake + +Configuration type: `CameraShake`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `target` | `Option` | `None` | target | +| `position_amplitude` | `[f32; 3]` | `[0.1; 3]` | scene units, XYZ | +| `rotation_amplitude` | `[f32; 3]` | `[0.02; 3]` | radians, XYZ | +| `frequency` | `f32` | `20.0` | cycles/second | +| `falloff` | `Curve` | `Curve::linear(1.0, 0.0)` | falloff | +| `radius` | `f32` | `20.0` | scene units | + +## 25. ScreenFlash + +Configuration type: `ScreenFlash`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `target` | `Option` | `None` | target | +| `color` | `[f32; 4]` | `[1.0, 0.0, 0.0, 1.0]` | RGBA linear color | +| `opacity` | `f32` | `0.5` | opacity | +| `fade_in` | `f32` | `0.05` | fade in | +| `fade_out` | `f32` | `0.4` | fade out | +| `vignette` | `f32` | `0.5` | vignette | +| `blend` | `BlendMode` | `BlendMode::Alpha` | blend | + +## 26. ReflectionProbe + +Configuration type: `ReflectionProbe`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `dimensions` | `[f32; 3]` | `[10.0; 3]` | scene units, XYZ | +| `cubemap` | `Option` | `None` | cubemap | +| `resolution` | `u32` | `128` | resolution | +| `refresh` | `ProbeRefresh` | `ProbeRefresh::Baked` | refresh | +| `blend_distance` | `f32` | `1.0` | blend distance | +| `update_interval` | `f32` | `1.0` | update interval | +| `parallax_correction` | `bool` | `false` | parallax correction | + +## Source asset identity + +`EffectComponent.source` optionally stores `{asset, child}`: the existing stable asset UUID and zero-based child index. Embedded configuration is the instance override. Restore resolves the source through its adjacent `.rmeta` identity and reapplies the selected child defaults. Keep the sidecar when moving or renaming an asset; replacing the UUID breaks existing references. Runtime imports use importer `rustic.effect`, version 1. + +## Validated limits + +Effect assets support version 1, one to 128 children, and at most 4 MiB. Duration must be positive; delay, speed and distance must be finite and nonnegative. Particle capacity is bounded at 100,000 and burst arrays at 1024. Particle lifetimes must be positive and ordered. Trail capacity is 2 to 65,536 points; beam segments are 1 to 4096. Keep curve and gradient values finite. The runtime applies additional safety bounds to generated geometry, simulation and event recursion. + +## Composite child metadata + +EffectAsset version 1 optionally stores `entity_ids` and `transforms`, each empty or the same length as `effects`. IDs must be unique; transforms are column-major finite nonsingular TRS matrices relative to the group. `instances_with_ids(asset_id)` creates independent child snapshots, remaps internal targets to fresh IDs and records the remapped source baseline. Missing metadata preserves legacy identity transforms. diff --git a/docs/Scripting/API/effects.md b/docs/Scripting/API/effects.md index 41b04f6..6159a57 100644 --- a/docs/Scripting/API/effects.md +++ b/docs/Scripting/API/effects.md @@ -41,3 +41,8 @@ globalThis.behavior = { - Shake uses deterministic decaying offsets. Avoid overlapping actions or physics that write the same property. Camera.shake uses the same implementation. - Check script attachment, enabled state and the Console when an operation fails. Asynchronous errors also appear in operation state. Callback exceptions disable the owner and clean up its actions. - Owner destruction, disable, successful reload or Stop cleans up owned actions and subscriptions. Failed reload retains the previous behavior's operations. See [Operation handles](/docs/api/operation-handles) for controls and lifetime details. + + +## Typed scene effects + +The same `Effects` service also creates and controls typed particles, trails, weather and camera effects. See [Scene Effects API](/docs/api/scene-effects) for component controls, configuration patches and backend limitations. The tween helpers above retain their existing behavior. diff --git a/docs/Scripting/API/scene-effects.md b/docs/Scripting/API/scene-effects.md new file mode 100644 index 0000000..df096aa --- /dev/null +++ b/docs/Scripting/API/scene-effects.md @@ -0,0 +1,86 @@ +# Scene Effects API + +Use `Effects` to control one typed effect component on an existing scene entity. The same global retains the existing `fade`, `flash`, `shake` and `pulse` tween helpers. Read the [authoring guide](/docs/guides/scene-effects) for editor steps, all 26 kind names, presets, composites and backend limitations. + +## Setup and attachment + +Create a Lua or JavaScript **Object Component Script** through Programming, attach it to an entity in the Inspector, and press Play. Python has equivalent `Effects` wrappers in the engine-provided SDK; import `Effects` from `rustic`. Other SDKs use the existing generic `rustic.query` boundary with the request operations below. Do not install a third-party package named rustic. + +## Signatures + +| Call | Result | +| --- | --- | +| `Effects.types()` | Array of the 26 case-sensitive kind keys | +| `Effects.create(entity, kind)` | Creates an effect and returns its configuration; existing effect is an error | +| `Effects.get(entity)` | Configuration, or null/nil when no component exists | +| `Effects.configure(entity, settings)` | Validated recursive patch and resulting configuration | +| `Effects.play(entity)` | Begins playback | +| `Effects.pause(entity)` | Freezes playback | +| `Effects.resume(entity)` | Resumes playback | +| `Effects.stop(entity)` | Stops playback | +| `Effects.restart(entity)` | Clears previous simulation and starts again | +| `Effects.reset(entity)` | Clears simulation and restores the initial playback state | +| `Effects.dispose(entity)` | Removes effect component and runtime state, preserving the entity | +| `Effects.state(entity)` | Playback state string, or null/nil | +| `Effects.instantiate(asset, parent, position)` | Composite root UUID, using registered asset UUID, optional parent and default zero position | +| `Effects.spawn(asset, position, lifetime, parent)` | Temporary root UUID; explicit positive lifetime schedules whole group destruction | +| `Effects.sampleWind(position, time, layers)` | XYZ wind vector; time in seconds; layers defaults to all bits | + +Entity arguments accept the existing resolved entity forms. Lua `nil` or JavaScript `null` chooses the script owner. UI/global script owners may be synthetic; pass a real scene entity for those scopes. Raw protocol requests require stable entity UUID strings. Playback states are `Stopped`, `Playing`, `Paused`, `Completed` and `Disposed`. Disposal removes the component, so subsequent component state reads return null/nil. + +## Complete Lua example + +This example requires an entity without an existing effect. For an authored emitter, remove `Effects.create`. + +```lua +return { + Start = function() + Effects.create(nil, "Particle") + Effects.configure(nil, { + common = {duration = 2, looping = false}, + kind = {Particle = { + rate = 40, + lifetime = {0.5, 1.0}, + gravity = {0, -2, 0} + }} + }) + Effects.restart(nil) + end +} +``` + +Expected result: an upward emitter runs for its configured duration. Restart repeats it from cleared simulation state. Changing settings is a live Play mutation; saving the authored scene requires Inspector changes outside Play. + +## JavaScript and Python + +```javascript +globalThis.behavior = { + Start() { + Effects.create(null, "Weather"); + Effects.configure(null, {kind: {Weather: {intensity: 0.3}}}); + Effects.restart(null); + } +}; +``` + +```python +from rustic import Effects + +def on_start(): + Effects.create(None, "Wind") + Effects.configure(None, {"kind": {"Wind": {"strength": 3.0}}}) +``` + +Use the generated Python starter's callback registration pattern if it differs from this callback body. The wind example changes compatible particles; a wind volume has no standalone visible mesh. + +## Events and generic requests + +The existing Events service carries `EffectBirth`, `EffectCollision`, `EffectDeath` and `EffectCompleted`. Subscribe with `Events.connect(entity, name, callback)`; payload includes a world-position vector. Composite roots emit `EffectCompleted` once per completion cycle after child effects complete. Restart/reset starts a new cycle. Keep callbacks small and avoid recursively creating unbounded effects. Sub-emitter recursion is separately bounded by the runtime generation limit. + +Generic operations are `effect_types`, `effect_create`, `effect_get`, `effect_configure`, `effect_control`, `effect_state` `effect_wind`, `effect_instantiate` and `effect_spawn`. For example, `rustic.query({op:'effect_create',entity:id,kind:'Particle'})` is equivalent to creation through the wrapper. Configuration requests use `settings`; controls use an `action` string such as `pause`. `effect_instantiate` accepts asset UUID, optional parent and position; `effect_spawn` requires position and a positive lifetime so a missing lifetime cannot silently create a persistent instance. Use the native query syntax for your language; do not construct private transport messages yourself. + +## Validation and lifetime + +Configuration has the shape `{source, common, kind}`. Kind settings are nested under the variant key, for example `{kind:{Particle:{rate:50}}}`. Unknown fields and invalid values return an error before the world transaction is committed. Use the [typed reference](/docs/guides/scene-effects/configuration) for exact fields, defaults and limits. + +Effect configuration survives scene serialization when authored. Runtime clocks, live particles, script mutations and event queues do not become permanent scene edits. Entity destruction and scene unload release effect state. One component is allowed per entity; composite assets instantiate children as ordinary scene effects. Controls and state can target the composite group root; disposal removes descendant effect components and preserves the entity hierarchy. This API does not expose GPU particle simulation or automatically make cosmetic particle collisions authoritative gameplay physics. diff --git a/lib/api-docs.ts b/lib/api-docs.ts index de65a21..ad905fe 100644 --- a/lib/api-docs.ts +++ b/lib/api-docs.ts @@ -296,7 +296,7 @@ export function buildApiMarkdown(id: string) { } function codeLanguage(lang: string) { return ({ "Lua 5.4": "lua", JavaScript: "javascript", Python: "python", "C++": "cpp", "C#": "csharp", Luau: "lua", C: "c", Java: "java", PHP: "php", "HTML / inline JS": "javascript" } as Record)[lang]; } -function overview() { return `# Rustic scripting API\n\nThe reference is organized by what game code needs to do. Core operation pages include purpose, timing, variables, return behavior, and equivalent calls for all ten supported script types. The Gameplay APIs navigation section provides dedicated references for actions, easing, animation, composition, physics, audio, events and the scene clock. API 1.1 gameplay actions have an explicit language support matrix in [Use gameplay actions](/docs/guides/gameplay-actions).\n\n## Execution model\n\nGlobal scripts run before Scene scripts, then Object Component scripts. Mutation timing depends on the adapter: Lua host writes are immediate; JavaScript/Web core writes are deferred until callback completion or a synchronous query flush; canonical scene-target resolution and external SDK queries also flush earlier commands. Read [Scopes, lifecycle and execution](/docs/guides/scripting#when-mutations-take-effect) for the exact distinctions. Every gameplay language calls built-in functions. Lua, JavaScript, and Web use embedded bindings; external languages use engine-supplied SDKs with private transport.\n\n## Language support\n\n| Languages | Integration |\n| --- | --- |\n| Lua 5.4, JavaScript, HTML / inline JS | bundled embedded API |\n| Python, C#, C++, Luau, C, Java, PHP | engine-owned SDK and persistent external session |\n\n## Safety limits\n\nSource and responses are limited to 1 MiB. External callbacks have a three-second deadline. Boundary values are booleans, integers, finite numbers, strings, vectors, and optional stable entity IDs.`; } +function overview() { return `# Rustic scripting API\n\nFor typed particle, trail, weather, camera and surface effects, see the [Scene Effects API](/docs/api/scene-effects) and [authoring guide](/docs/guides/scene-effects).\n\nThe reference is organized by what game code needs to do. Core operation pages include purpose, timing, variables, return behavior, and equivalent calls for all ten supported script types. The Gameplay APIs navigation section provides dedicated references for actions, easing, animation, composition, physics, audio, events and the scene clock. API 1.1 gameplay actions have an explicit language support matrix in [Use gameplay actions](/docs/guides/gameplay-actions).\n\n## Execution model\n\nGlobal scripts run before Scene scripts, then Object Component scripts. Mutation timing depends on the adapter: Lua host writes are immediate; JavaScript/Web core writes are deferred until callback completion or a synchronous query flush; canonical scene-target resolution and external SDK queries also flush earlier commands. Read [Scopes, lifecycle and execution](/docs/guides/scripting#when-mutations-take-effect) for the exact distinctions. Every gameplay language calls built-in functions. Lua, JavaScript, and Web use embedded bindings; external languages use engine-supplied SDKs with private transport.\n\n## Language support\n\n| Languages | Integration |\n| --- | --- |\n| Lua 5.4, JavaScript, HTML / inline JS | bundled embedded API |\n| Python, C#, C++, Luau, C, Java, PHP | engine-owned SDK and persistent external session |\n\n## Safety limits\n\nSource and responses are limited to 1 MiB. External callbacks have a three-second deadline. Boundary values are booleans, integers, finite numbers, strings, vectors, and optional stable entity IDs.`; } function callbacks() { const names = ["OnCreate", "Start", "OnEnable", "FixedUpdate", "Update", "OnDisable", "OnDestroy", "OnStop"]; const nativeNames = ["on_create", "on_start", "on_enable", "fixed_update", "update", "on_disable", "on_destroy", "on_stop"]; diff --git a/lib/docs-catalog.ts b/lib/docs-catalog.ts index a8913f2..ea477e8 100644 --- a/lib/docs-catalog.ts +++ b/lib/docs-catalog.ts @@ -12,6 +12,8 @@ const categoryGroups: DocGroup[] = [ { slug: "callbacks", title: "Lifecycle callbacks", description: "Choose the correct callback for setup, frames, physics, and teardown.", source: "api:callbacks" }, ] }, { label: "Editor guides", docs: [ + { slug: "guides/scene-effects", title: "Scene Effects and VFX", description: "Author, preview, script and save scene effects, presets and composites; renderer compatibility and limitations.", source: "docs/SCENE_EFFECTS.md" }, + { slug: "guides/scene-effects/configuration", title: "Effect configuration reference", description: "Typed fields and defaults for all 26 scene effects and their shared appearance configuration.", source: "docs/SCENE_EFFECTS_REFERENCE.md", parent: "guides/scene-effects" }, { slug: "guides/editor/surface-textures", title: "Surface textures and PBR maps", description: "Browse optional surface maps, drop HDR/PBR images onto meshes and attach Texture children.", source: "docs/SURFACE_TEXTURES.md", parent: "guides/editor" }, { slug: "guides/editor/build-project", title: "Build and distribute a project", description: "Package Windows games, design installers, export playable websites and build Android apps.", source: "docs/PROJECT_BUILDS.md", parent: "guides/editor" }, { slug: "guides/editor", title: "Editor overview", description: "Open and save projects, navigate panels and choose an editor workflow.", source: "content/editor.md" }, @@ -80,6 +82,7 @@ const categoryGroups: DocGroup[] = [ { slug: "api/physics-actions", title: "Physics queries and forces", description: "Query live primitive colliders and change simulated body velocity.", source: "docs/Scripting/API/physics-actions.md" }, { slug: "api/physics-actions/queries", title: "Physics queries", description: "Find primitive colliders with raycasts, sphere casts and overlaps.", source: "docs/Scripting/API/physics-actions/queries.md", parent: "api/physics-actions" }, { slug: "api/physics-actions/forces", title: "Physics forces", description: "Apply forces, impulses and velocity changes to simulated bodies.", source: "docs/Scripting/API/physics-actions/forces.md", parent: "api/physics-actions" }, + { slug: "api/scene-effects", title: "Scene Effects API", description: "Create and control typed effect components, configure simulation, sample wind and subscribe to particle events.", source: "docs/Scripting/API/scene-effects.md" }, { slug: "api/effects", title: "Effects", description: "Fade, flash, shake and pulse entity properties with shared easing.", source: "docs/Scripting/API/effects.md" }, { slug: "api/audio", title: "Audio", description: "Record audio, read microphone PCM and load reusable audio in every gameplay language; select Windows audio devices and play and fade WAV, MP3, AAC, FLAC, and OGG voices.", source: "docs/Scripting/API/audio.md" }, { slug: "api/events", title: "Events and signals", description: "Communicate through queued global events and object-scoped signals across scripting languages.", source: "docs/Scripting/API/events.md" }, From 850298fc9087cd75423967c4549df7f45d8eb011 Mon Sep 17 00:00:00 2001 From: Jamie Wells Date: Mon, 2 Nov 2026 06:22:07 +0000 Subject: [PATCH 2/2] Clarify bounded sub-emitter generation behavior --- docs/SCENE_EFFECTS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/SCENE_EFFECTS.md b/docs/SCENE_EFFECTS.md index 573aa7a..b71b2de 100644 --- a/docs/SCENE_EFFECTS.md +++ b/docs/SCENE_EFFECTS.md @@ -166,6 +166,8 @@ Use bounded particle/point/instance counts, shorter lifetimes and local volumes. World effect state is keyed by stable entity IDs. Destroying entities or unloading scenes must release their simulation state. Playback does not permanently overwrite the authored camera or light configuration; frame modifiers are applied during rendering. Reuse configured entities for repeated bursts when practical. Asset instantiation clones authored data; it is not a GPU resource duplication guarantee. +Sub-emitter `max_generations` is compared with the current absolute generation at each emitter, so a child may use a different limit from its source; the entire chain is capped at eight generations, 128 child spawns per tick and 1,024 active temporary child effects. + ## Showcase and troubleshooting From the engine's `Engine/` directory: