Linked particle stages: sub-emitters
Emitter links, event modes, capacity and chain lifecycle.
A sub-emitter link sends particle births from one GPU system into another existing GPU system. It does not create a child Entity or a new particle-system Entity for each parent particle. The two emitter Entities can be siblings in the scene tree.
Configure the link and the event
- Create both GpuParticles3D Entities with their own process material and draw mesh in the intended World3D.
- Assign the primary emitter's Sub Emitter path to the target emitter. Resolve the path from the primary, not from the scene root.
- On the primary ParticleProcessMaterial, choose a Sub Emitter Mode other than Disabled and configure the corresponding count/frequency.
- Leave the target's autonomous Emitting disabled; its incoming births are driven by the primary. Do not repeatedly restart the target as if it were an ordinary looping emitter.
- Trigger the primary and inspect both systems' bounds/materials/capacity. A valid path alone does not enable a disabled material mode.
Create/attach both Entities before assigning a runtime path. If you reparent or replace the target, resolve the link again; a cached EntityPath is not an automatically repaired reference to every future hierarchy.
This workflow requires the supported GPU rendering path. The current material implementation warns that non-disabled sub-emitter modes are unsupported in Compatibility. The executed example uses Windows Forward+/Vulkan; it is not proof of every target device.
Run an end-triggered pair in C#
using System;
using Reckfell;
public static class ParticleChain
{
private static GpuParticles3D Emitter(string name, Color color)
{
var surface = new StandardMaterial3D
{
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
AlbedoColor = color,
};
return new GpuParticles3D
{
Name = name, Emitting = false, Amount = 32, Lifetime = 1.2,
LocalCoords = false, UseFixedSeed = true, Seed = 42,
DrawPass1 = new QuadMesh { Size = new Vector2(0.12f, 0.12f), Material = surface },
VisibilityAabb = new Aabb(new Vector3(-3, -3, -3), new Vector3(6, 6, 6)),
};
}
public static (GpuParticles3D primary, GpuParticles3D secondary) Create(Entity root)
{
if (!root.IsInsideTree()) throw new ArgumentException("Add the root to the tree first.");
var primary = Emitter("Primary", Colors.Red);
primary.Amount = 4; primary.OneShot = true; primary.Lifetime = 0.3;
primary.Explosiveness = 1;
primary.ProcessMaterial = new ParticleProcessMaterial
{
Direction = Vector3.Right, Spread = 0, Gravity = Vector3.Zero,
InitialVelocityMin = 0.6f, InitialVelocityMax = 0.6f,
SubEmitterMode = ParticleProcessMaterial.SubEmitterModeEnum.AtEnd,
SubEmitterAmountAtEnd = 2,
};
var secondary = Emitter("Secondary", Colors.Blue);
secondary.ProcessMaterial = new ParticleProcessMaterial
{
Direction = Vector3.Up, Spread = 0, Gravity = Vector3.Zero,
InitialVelocityMin = 0.4f, InitialVelocityMax = 0.4f,
};
root.AddChild(primary);
root.AddChild(secondary);
// Both Entities exist before resolving the relative target path.
primary.SubEmitter = primary.GetPathTo(secondary);
return (primary, secondary);
}
}From an Entity already in the tree, call var (primary, secondary) = ParticleChain.Create(this); and primary.Restart();. Keep an active camera covering the effect. Red primary particles trigger births into the blue target at the end of their life. The helper is functional test geometry; replace its draw assets/materials for a finished effect.
The native test confirms that the target does not appear immediately in AtEnd, then renders after the primary stage. AtStart renders it near the initial stage instead. The Entity count remains two. Disabling the material mode or clearing the link prevents subsequent target births.
Choose a mode deliberately
| Mode | Trigger / setting |
|---|---|
| Disabled | No generated sub-emission from this material. |
| AtStart | Birth of a primary particle; SubEmitterAmountAtStart. |
| AtEnd | End of a primary particle's life; SubEmitterAmountAtEnd. |
| AtCollision | A qualifying particle collision; SubEmitterAmountAtCollision. Requires working GPU collision setup. |
| Constant | Repeated emission while the primary particle is active; SubEmitterFrequency in Hz. |
Counts are per triggering primary particle, not a single global burst count. Constant frequency must be finite and positive; the setter converts Hz to an interval internally. Simulation-step limits mean it is not a promise to produce an exact arbitrary high-frequency stream regardless of step size.
SubEmitterKeepVelocity controls forwarding the primary velocity. In the generated path, a forwarded velocity can replace the target's initial-velocity initialization; do not assume it simply adds to every target velocity/force setting. This comparison is source-reviewed, not part of the current pixel tests.
Budget capacity and lifecycle
The target's Amount is still its capacity. More requested births do not guarantee all of them fit or become separately visible. Estimate primary count × events × requested children and account for target lifetime, overlap and any additional stages. Chaining is not free or safely unbounded.
Avoid direct self-links and indirect cycles. These are not supported recipes for recursive effects. Use an explicit finite stage design instead of relying on recursive births to terminate themselves.
A primary Finished signal does not prove the target has expired. The target has its own particles and lifetime, and a target driven by sub-emission does not provide one complete-chain event automatically. Do not immediately recycle/free the whole pair from the primary signal alone. Also account for the documented SpeedScale/Finished limitation.
If you work with instantiated prefabs, editor Editable Children affects what can be edited in that instance. It is not a general runtime permission required by path resolution. Inspect the actual runtime hierarchy and link after the relevant Entities exist.
Diagnose missing secondary particles
| Symptom | Check |
|---|---|
| Primary visible, target absent | Relative path, target existence, primary material mode and target draw resources. |
| Only AtCollision fails | Validate collision mode, GPU colliders and masks before debugging sub-emission. |
| Too few target particles | Target Amount, lifetime, event counts and overlapping geometry. |
| Target disappears when primary finishes | Lifetime/pooling code may free the pair too soon. |
| Retargeting a prefab fails | Resolve from the primary after runtime instantiation/reparenting. |
| A material change seems delayed | Allow its update/shader preparation and restart a controlled comparison; do not judge from one partially updated frame. |
Evidence covers relative linking, AtEnd/AtStart/Disabled, removal of a link and the unchanged Entity hierarchy. Constant, AtCollision, velocity inheritance comparisons, cycles, complex pooling, other renderers/exports and performance are not claimed tested by this example.