agentleFS
IceMasterTIceMasterT/Unity-6.3_SKILLS/unity-vfx-particles/SKILL.md

unity-vfx-particles

Skill0 starsChanged 3 months agoMITView on GitHub

Use when creating particle/visual effects in Unity — choosing between the Shuriken Particle System and VFX Graph. Grounds answers in the local Unity 6.3 docs over pretrained knowledge.

The file

Files
SKILL.md

Unity VFX & Particles

Retrieval Sources

SourcePathUse for
Choosing your particle systemManual/ChoosingYourParticleSystem.htmlShuriken vs. VFX Graph comparison table (render pipeline support, feasible particle counts, physics interaction, script interaction, frame buffer access)
Particle Systems overviewManual/ParticleSystems.htmlShuriken component landing page, general concepts, links into every sub-topic
Particle System modules referenceManual/ParticleSystemModules.htmlFull module list (Emission, Shape, Velocity over Lifetime, Color over Lifetime, Size over Lifetime, Collision, Sub Emitters, Trails, Lights, Noise, Triggers, Texture Sheet Animation)
ParticleSystem componentManual/class-ParticleSystem.htmlComponent properties, Inspector layout, Main module settings
Particle emissions and emittersManual/particle-emissions-emitters.htmlEmission module (Rate over Time vs. Rate over Distance, bursts) and Shape module (emission volume/surface, start-velocity direction)
Particle colorManual/particle-color.htmlColor over Lifetime and Color by Speed modules
Particle sizeManual/particle-size.htmlSize over Lifetime and Size by Speed modules
Particle rotationManual/particle-rotation.htmlRotation over Lifetime and Rotation by Speed modules
Particle velocityManual/particle-velocity.htmlVelocity over Lifetime, Limit Velocity over Lifetime, Inherit Velocity
Particle physics/forcesManual/particle-physics-forces.htmlForce over Lifetime, External Forces module, gravity modifier
Particle collisionsManual/particle-collisions.htmlCollision module — World mode (real Colliders) vs. Planes mode (lower overhead, no Collider needed), Collides With layer mask, sendCollisionMessages
Particle triggersManual/particle-triggers.htmlTrigger module — script callbacks (OnParticleTrigger) when particles enter/exit defined colliders, without full collision physics
Particle lights and trailsManual/particle-lights-trails.htmlLights module (real-time lights per particle, Maximum Lights cap) and Trails module (per-particle trail ribbons, shares properties with Trail Renderer)
Varying properties over timeManual/varying-particle-system-properties-over-time.htmlCurve/random-between-curves authoring pattern used across most modules
Particle System + C# Job SystemManual/particle-system-job-system-integration.htmlIJobParticleSystem, IJobParticleSystemParallelFor, IJobParticleSystemParallelForBatch — Burst-compiled per-particle access, faster than GetParticles()/SetParticles()
Particle System optimization (Built-in RP)Manual/particle-system-optimization.htmlGPU instancing for particles (render Meshes instead of billboards) in the Built-in Render Pipeline
VFX Graph overviewManual/VFXGraph.htmlGPU-simulated node-graph VFX package, requirements, links to VFX asset/component/Property Binder reference
VFX Project SettingsManual/class-VFXManager.htmlProject-wide VFX tab settings (only visible once the VFX Graph package is installed)
ParticleSystem scripting APIScriptReference/ParticleSystem.htmlRuntime control (Play/Stop/Pause/Emit/Clear, particleCount, module struct accessors)
ParticleSystem.MainModuleScriptReference/ParticleSystem.MainModule.htmlduration, loop, startLifetime, startSpeed, startSize, simulationSpace, maxParticles
ParticleSystem.EmissionModuleScriptReference/ParticleSystem.EmissionModule.htmlrateOverTime, rateOverDistance, SetBursts/GetBursts
ParticleSystem.ShapeModuleScriptReference/ParticleSystem.ShapeModule.htmlshapeType, radius, arc, randomDirectionAmount
ParticleSystem.VelocityOverLifetimeModuleScriptReference/ParticleSystem.VelocityOverLifetimeModule.htmlPer-axis velocity curves, space (local/world)
ParticleSystem.ColorOverLifetimeModuleScriptReference/ParticleSystem.ColorOverLifetimeModule.htmlcolor (MinMaxGradient) applied across normalized lifetime
ParticleSystem.SizeOverLifetimeModuleScriptReference/ParticleSystem.SizeOverLifetimeModule.htmlsize/sizeMultiplier curve, separateAxes for non-uniform scale
ParticleSystem.CollisionModuleScriptReference/ParticleSystem.CollisionModule.htmltype (Planes/World), mode, quality, AddPlane/SetPlane, bounce, dampen, lifetimeLoss
ParticleSystem.SubEmittersModuleScriptReference/ParticleSystem.SubEmittersModule.htmlSub-emitter slots keyed to Birth/Collision/Death/Trigger/Manual events
ParticleSystem.TrailModuleScriptReference/ParticleSystem.TrailModule.htmlratio, lifetime, minVertexDistance, textureMode for per-particle trails
VisualEffect component APIScriptReference/VFX.VisualEffect.htmlRuntime control of VFX Graph instances — Play/Stop/SendEvent, SetFloat/SetVector3/SetTexture/SetGraphicsBuffer, pause, playRate
VFXManager (VFX Graph global settings)ScriptReference/VFX.VFXManager.htmlfixedTimeStep, maxDeltaTime, GetBatchedEffectInfos (GPU/CPU memory and instance-batching stats)

Key Guidelines

Shuriken Particle System Modules

The Shuriken system is the ParticleSystem component: a fixed pipeline of toggleable modules, each simulated on the CPU (with optional Burst/Job System acceleration — see below) and configured either in the Inspector or through the matching module struct in script. The Main module (always on) sets system-wide defaults — duration, loop, startLifetime, startSpeed, startSize, simulationSpace, maxParticles. Emission controls spawn rate (rateOverTime for continuous emission independent of motion, rateOverDistance for motion-driven emission like tire dust) plus discrete bursts. Shape defines the emission volume/surface (cone, sphere, box, mesh, edge, etc.) and therefore the initial position and direction of new particles. From there, "over Lifetime" and "by Speed" modules (Color, Size, Rotation, Velocity, Limit Velocity, Force, Noise) apply curves or gradients evaluated against a particle's normalized age or current speed — the "varying properties over time" authoring pattern (curve, random-between-two-curves, or random-between-two-constants) is shared across nearly all of them. Every module is independently toggled per system, so a system pays simulation cost only for the modules it has enabled.

using UnityEngine;

public class ConfigureBurstEffect : MonoBehaviour
{
    [SerializeField] private ParticleSystem ps;

    void Start()
    {
        var main = ps.main;
        main.startLifetime = 2f;
        main.startSpeed = 5f;
        main.maxParticles = 500;

        var emission = ps.emission;
        emission.rateOverTime = 0f; // no continuous emission
        emission.SetBursts(new[]
        {
            new ParticleSystem.Burst(0f, 30, 50, cycleCount: 1, repeatInterval: 0f)
        });

        var shape = ps.shape;
        shape.shapeType = ParticleSystemShapeType.Sphere;
        shape.radius = 0.5f;

        var colorOverLifetime = ps.colorOverLifetime;
        colorOverLifetime.enabled = true;
        var grad = new Gradient();
        grad.SetKeys(
            new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.red, 1f) },
            new[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(0f, 1f) });
        colorOverLifetime.color = new ParticleSystem.MinMaxGradient(grad);

        ps.Play();
    }
}

Sub-Emitters & Bursts

Bursts (part of the Emission module) fire a batch of particles at a specific time — or, with cycleCount/repeatInterval, repeatedly — making them the tool for punctual effects like muzzle flashes or explosions rather than driving them through a sustained rateOverTime. Sub Emitters attach a second ParticleSystem to specific lifecycle events of the parent's particles — Birth, Collision, Death, Trigger, or Manual (fired from script) — so a single system can spawn a layered secondary effect, e.g. a smoke trail on birth and a burst of sparks on death. Sub-emitters inherit spatial context from the parent particle they're attached to but are configured as independent ParticleSystem assets/children, so their own modules (Emission, Shape, etc.) apply normally once triggered.

using UnityEngine;

public class ConfigureSubEmitter : MonoBehaviour
{
    [SerializeField] private ParticleSystem parentPs;
    [SerializeField] private ParticleSystem deathBurstPs;

    void Start()
    {
        var subEmitters = parentPs.subEmitters;
        subEmitters.enabled = true;
        // Attach deathBurstPs to fire when parent particles die.
        subEmitters.AddSubEmitter(deathBurstPs, ParticleSystemSubEmitterType.Death,
            ParticleSystemSubEmitterProperties.InheritEverything);
    }
}

Particle Collision & Triggers

The Collision module has two distinct modes with different cost profiles. World mode collides particles against real Colliders in the Scene — flexible (any layer, dynamic obstacles) but the most expensive option, with a quality setting (High/Medium/Low) trading accuracy for CPU cost. Planes mode instead collides against a manually authored list of infinite planes (defined by empty-GameObject transforms) that need no Collider component at all — this is explicitly documented as lower processor overhead and is the right default for simple floors/walls. sendCollisionMessages opts into OnParticleCollision callbacks on scripts for gameplay reactions (spawning decals, damage, sound). The separate Trigger module is cheaper still: it defines a set of collider regions and fires OnParticleTrigger when particles enter/exit/inside/outside them, without running full collision physics (bounce, dampen, lifetime loss) — use it purely to detect particle presence in a volume, not to make particles physically bounce.

using UnityEngine;

public class ParticleCollisionHandler : MonoBehaviour
{
    private ParticleSystem ps;
    private readonly System.Collections.Generic.List<ParticleCollisionEvent> collisionEvents = new();

    void Awake()
    {
        ps = GetComponent<ParticleSystem>();
        var collision = ps.collision;
        collision.enabled = true;
        collision.type = ParticleSystemCollisionType.World;
        collision.mode = ParticleSystemCollisionMode.Collision3D;
        collision.quality = ParticleSystemCollisionQuality.Medium;
        collision.bounce = 0.3f;
        collision.sendCollisionMessages = true;
    }

    void OnParticleCollision(GameObject other)
    {
        int count = ps.GetCollisionEvents(other, collisionEvents);
        for (int i = 0; i < count; i++)
        {
            // e.g. spawn a decal/impact effect at collisionEvents[i].intersection
        }
    }
}

VFX Graph (GPU-Simulated)

VFX Graph is a separate package built around the VisualEffectAsset (a node-graph you author in a dedicated graph editor) and the VisualEffect component that plays an instance of it in a scene. Unlike Shuriken's fixed module pipeline, simulation logic itself is authored as a graph and runs on the GPU via compute shaders, which is what lets it scale to millions of particles versus Shuriken's thousands (per the docs' own comparison table). It only supports the Universal and High Definition Render Pipelines (not the Built-in Render Pipeline), and in HDRP it additionally exposes camera color/depth buffer sampling that Shuriken has no equivalent for. Script interaction is event-driven rather than direct-particle-access: you expose named properties on the graph and drive them from C# via SetFloat/SetVector3/SetTexture/SetGraphicsBuffer/SetGradient, and send named events (with optional attached VFXEventAttribute payload data) via SendEvent instead of reading/writing individual particles.

using UnityEngine;
using UnityEngine.VFX;

public class DriveVfxGraph : MonoBehaviour
{
    [SerializeField] private VisualEffect vfx;
    private static readonly int SpawnRateID = Shader.PropertyToID("SpawnRate");
    private static readonly int ColorID = Shader.PropertyToID("Color");

    void Start()
    {
        vfx.SetFloat(SpawnRateID, 200f);
        vfx.SetVector4(ColorID, Color.cyan);
        vfx.playRate = 1f;
        vfx.Play();
    }

    public void TriggerImpact(Vector3 worldPos)
    {
        var attr = vfx.CreateVFXEventAttribute();
        attr.SetVector3("position", worldPos);
        vfx.SendEvent("OnImpact", attr);
    }
}

Choosing Between Them

The docs' own comparison table (ChoosingYourParticleSystem.html) is the canonical decision reference: Shuriken supports Built-in/URP/HDRP and full C# read/write access to every particle plus physics-engine collision, and is authored via Inspector modules; VFX Graph supports only URP/HDRP, scales to millions of particles via GPU simulation, and is authored via a visual node graph with an event/exposed-property scripting surface rather than per-particle access. Note that on platforms that support compute shaders, Unity explicitly allows using both solutions simultaneously in one project — this isn't an either/or choice at the project level, only per-effect. Default to Shuriken for gameplay-driven effects that need per-particle script logic (collision response, custom job-system behaviors), for projects still on the Built-in Render Pipeline, or for moderate particle counts. Reach for VFX Graph when an effect needs particle counts CPU simulation can't sustain, when you need HDRP camera-buffer sampling (refraction, depth-aware effects), or when the effect is authored primarily by a technical artist working in a graph rather than by a programmer.

Performance / Overdraw

Shuriken's CPU cost scales with maxParticles, active module count, and the Collision module's quality/type setting — profile with the Profiler's CPU timeline, not guesswork. The C# Job System integration (IJobParticleSystem, IJobParticleSystemParallelFor, IJobParticleSystemParallelForBatch) lets custom per-particle behavior run Burst-compiled across worker threads, which the docs call out as strictly faster than the main-thread GetParticles()/SetParticles() pair — prefer it for any per-frame custom particle logic at scale. Independently of CPU sim cost, GPU instancing (Manual/particle-system-optimization.html, Built-in RP) renders particles as instanced Meshes instead of billboards, which is a rendering-side optimization orthogonal to simulation cost. The Lights module is called out explicitly in the docs as high performance cost — "especially in Forward Rendering mode," worse still with shadow-casting lights — which is why it has a dedicated Maximum Lights safety cap to prevent an emission-rate tweak from spawning thousands of real-time lights. VFX Graph cost shows up as GPU time (compute simulation + fill-rate for translucent particle rendering) rather than CPU time, so profile it with the GPU frame-timing tools/Frame Debugger, not the CPU profiler.

using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.ParticleSystemJobs;

public struct FadeAndDriftJob : IJobParticleSystemParallelFor
{
    public float deltaTime;

    public void Execute(ParticleSystemJobData jobData, int index)
    {
        var velocities = jobData.velocities;
        velocities[index] = new Vector3(
            velocities[index].x,
            velocities[index].y - 0.5f * deltaTime, // extra gravity, Burst-compiled
            velocities[index].z);
    }
}

public class JobDrivenParticles : MonoBehaviour
{
    [SerializeField] private ParticleSystem ps;

    void LateUpdate()
    {
        new FadeAndDriftJob { deltaTime = Time.deltaTime }
            .Schedule(ps)
            .Complete();
    }
}

Common Mistakes

MistakeWhy it happens / fix
Using Shuriken for hundreds of thousands+ particlesThe docs' own comparison rates Shuriken's feasible count as "thousands" vs. VFX Graph's "millions"; CPU simulation bottlenecks well before GPU-simulated VFX Graph would — move large-scale effects to VFX Graph
VFX Graph effect renders on desktop but not on a target devicePlatform/API lacks compute shader support, or the project is on the Built-in Render Pipeline (VFX Graph only supports URP/HDRP) — check both before authoring in VFX Graph
Leftover particles on pooled/reused GameObjectsStop()/Clear() isn't called before reuse; call ParticleSystem.Clear() (and Stop() with ParticleSystemStopBehavior.StopEmittingAndClear if needed) when returning an object to a pool
Assuming VFX Graph is a drop-in replacement for every Shuriken effectThe authoring model differs fundamentally (graph/GPU/event-driven vs. modules/CPU/direct-particle-access); simple gameplay-feedback effects with per-particle script logic are often cheaper to build and run in Shuriken
Enabling World-mode Collision when Planes mode would doWorld mode queries real Colliders and costs more CPU; the docs call out Planes mode as explicitly lower overhead for simple floors/walls that don't need a real Collider
Using the Collision module just to detect particle presence in a regionCollision module runs full physics response (bounce, dampen, lifetime loss) at collision cost; the Trigger module does cheap enter/exit/inside/outside detection via OnParticleTrigger without simulating a bounce
Driving per-particle custom behavior through GetParticles()/SetParticles() every frameThese run on the main thread and can't use the Burst Compiler; IJobParticleSystem/IJobParticleSystemParallelFor/IJobParticleSystemParallelForBatch distribute the same work across worker threads with Burst compilation
Over-using the Lights module for per-particle real-time lightsExplicitly flagged in the docs as high performance cost, worse in Forward Rendering and with shadows; uncapped Maximum Lights after an emission-rate tweak can silently spawn thousands of lights — always set a sane cap
Reading/writing VisualEffect particle data like a Shuriken systemVFX Graph has no per-particle C# read/write API; interact only through exposed properties (SetFloat/SetVector3/etc.) and events (SendEvent), never direct particle access
Forgetting subEmitters.enabled = true after calling AddSubEmitterThe module (like every Shuriken module) has its own enabled flag separate from having sub-emitter slots populated; a configured-but-disabled module does nothing
Assuming particle systems must pick either Shuriken or VFX Graph project-wideThe docs state platforms with compute shader support can use both simultaneously — the choice is per-effect, not a project-wide constraint
Ignoring GPU instancing for Mesh-based Shuriken particles in the Built-in RPparticle-system-optimization.html documents applying GPU instancing to render Mesh particles instead of billboards for a straightforward performance win, but it must be explicitly activated
Treating rateOverDistance and rateOverTime as interchangeablerateOverDistance only emits as the emitter's transform moves (e.g. tire dust); a stationary object with only rateOverDistance configured will emit nothing — use rateOverTime for motion-independent continuous emission
Not setting cycleCount/repeatInterval correctly on BurstsA default Burst fires once; repeated punctual effects (e.g. a chimney puffing every few seconds) need explicit cycleCount (or -1 semantics per the scripting reference) and repeatInterval, not a rateOverTime workaround
Blaming VFX Graph GPU cost on the wrong subsystemGPU cost surfaces as compute-shader simulation time plus translucent-particle fill-rate/overdraw; profiling with the CPU Profiler alone hides the real bottleneck — use GPU frame timing/Frame Debugger

Quick Reference

ConceptPurpose
Main moduleSystem-wide defaults: duration, loop, startLifetime, startSpeed, startSize, simulationSpace, maxParticles
Emission moduleControls spawn rate (rateOverTime, rateOverDistance) and bursts
Shape moduleDefines the volume/surface particles emit from and initial velocity direction
Velocity over LifetimeAdds velocity that changes across a particle's life, in local or world space
Limit Velocity over LifetimeClamps/dampens particle speed over lifetime (e.g. air drag)
Inherit VelocityParticles inherit some or all of the emitter Transform's velocity
Force over Lifetime / External ForcesApplies scripted or scene wind-zone forces to particles over time
Color over Lifetime / Color by SpeedAnimates color/alpha across a particle's normalized life or current speed
Size over Lifetime / Size by SpeedAnimates uniform or per-axis size across life or speed
Rotation over Lifetime / Rotation by SpeedAnimates particle rotation across life or speed
Collision modulePhysical particle interaction with Colliders (World mode) or authored infinite planes (Planes mode, lower overhead)
Trigger moduleCheap enter/exit/inside/outside detection against collider regions via OnParticleTrigger, no physics response
Sub Emitters moduleSpawns a secondary ParticleSystem on Birth/Collision/Death/Trigger/Manual events
Trails moduleAdds per-particle trail ribbons; shares properties with the Trail Renderer component
Lights moduleAttaches real-time lights to particles; high performance cost, guarded by Maximum Lights
Noise modulePerturbs particle motion with procedural turbulence
Texture Sheet Animation modulePlays a flipbook of frames from a texture sheet across a particle's life
IJobParticleSystem / …ParallelFor / …ParallelForBatchBurst-compiled, worker-thread particle access; faster than main-thread GetParticles()/SetParticles()
VisualEffectAssetThe VFX Graph asset compiled into a GPU simulation (URP/HDRP only)
VisualEffect componentRuntime instance/controller for a VisualEffectAsset — Play/Stop/SendEvent/exposed-property setters
VFXManagerProject-wide VFX Graph settings (fixedTimeStep, maxDeltaTime) and batching diagnostics (GetBatchedEffectInfos)
GPU instancing (Built-in RP)Renders Mesh particles instead of billboards for a rendering-side performance win

Advanced Notes

Mobile GPUs are bandwidth- and fill-rate-constrained far more than desktop/console GPUs, which makes particle-heavy effects one of the fastest ways to tank frame time on mobile even when CPU simulation cost looks cheap in the Profiler. Most particle materials are alpha-blended and unlit-additive, which means every overlapping particle quad is a full-screen-resolution shader invocation stacked on top of whatever's already been drawn — large, overlapping, screen-filling particle systems (explosions, smoke columns, weather) create severe overdraw where the same pixel is shaded many times per frame. On tile-based mobile GPUs this is punished especially hard because blended geometry can't use early-Z/hidden-surface removal the way opaque geometry can, so overdraw cost is close to linear in the number of overlapping translucent layers.

Practical mitigations, in rough order of impact: keep individual particle quads small relative to the screen and cap maxParticles aggressively for mobile-targeted systems, since a handful of huge, screen-filling particles cost far more fill-rate than many small ones covering the same visual area; prefer fewer, larger bursts of short-lived particles over sustained high rateOverTime emission so the number of simultaneously alive (and therefore simultaneously overdrawing) particles stays low; avoid stacking multiple semi-transparent particle systems in the same screen-space region (e.g. layering separate smoke, embers, and glow systems on one explosion) — collapse layers into fewer systems or a single custom shader where possible; disable or heavily cap the Lights module on mobile, since real-time per-particle lights are explicitly documented as high-cost, worse in Forward Rendering, and worst with shadows enabled, all of which are common mobile constraints already; use the World/Planes Collision-mode distinction to avoid physics-query overhead on lower-end CPUs, defaulting to Planes wherever the interaction is against static level geometry; and use the Texture Sheet Animation module or lower-resolution/simpler particle textures rather than driving visual complexity purely through overlapping particle count. For VFX Graph specifically, remember it requires compute shader support — verify target mobile hardware/API actually supports it before committing an effect to it, since a compute-shader-driven graph that silently fails to render on a subset of Android/iOS devices is a common, hard-to-catch regression. Regardless of which system you use, validate overdraw hotspots empirically with the Rendering Debugger's overdraw view (or a platform-specific GPU profiler/frame capture on device) rather than assuming from particle count alone — actual fill-rate cost depends on final on-screen size and blend-layer depth, not raw particle count.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.