Use when you have a written implementation plan to execute in a separate session with review checkpoints
npx skills add pluginagentmarketplace/custom-plugin-game-developer --skill "particle-systems"
Install specific skill from multi-skill repository
# Description
|
# SKILL.md
name: particle-systems
version: "2.0.0"
description: |
Creating visual effects using particle systems, physics simulation,
and post-processing for polished, dynamic game graphics.
sasmp_version: "1.3.0"
bonded_agent: 03-graphics-rendering
bond_type: PRIMARY_BOND
parameters:
- name: effect_type
type: string
required: false
validation:
enum: [explosion, fire, smoke, magic, weather, impact]
- name: platform
type: string
required: false
validation:
enum: [pc, console, mobile, vr]
retry_policy:
enabled: true
max_attempts: 3
backoff: exponential
observability:
log_events: [start, complete, error]
metrics: [particle_count, draw_calls, gpu_time_ms]
Particle Systems
Particle System Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PARTICLE LIFECYCLE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β EMISSION β
β ββ Spawn Rate (particles/second) β
β ββ Burst (instant spawn count) β
β ββ Shape (point, sphere, cone, mesh) β
β β β
β SIMULATION β
β ββ Velocity (initial + over lifetime) β
β ββ Forces (gravity, wind, turbulence) β
β ββ Collision (world, depth buffer) β
β ββ Noise (procedural movement) β
β β β
β RENDERING β
β ββ Billboard (camera-facing quads) β
β ββ Mesh (3D geometry per particle) β
β ββ Trail (ribbon following path) β
β ββ GPU Instancing (batched draw) β
β β β
β DEATH β
β ββ Lifetime expired β recycle or destroy β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Common Effect Recipes
EXPLOSION EFFECT:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β LAYERS: β
β 1. Flash (instant, bright, 0.1s) β
β 2. Core fireball (expanding sphere, 0.3s) β
β 3. Debris (physics-enabled chunks, 1-2s) β
β 4. Smoke (slow rising, fade out, 2-3s) β
β 5. Sparks (fast, gravity-affected, 0.5-1s) β
β 6. Shockwave (expanding ring, 0.2s) β
β β
β SETTINGS: β
β β’ Emission: Burst only (no rate) β
β β’ Start speed: 5-20 (varies by layer) β
β β’ Gravity: -9.8 for debris, 0 for smoke β
β β’ Color: OrangeβRedβBlack over lifetime β
β β’ Size: Start large, shrink (fireball) or grow (smoke) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
FIRE EFFECT:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β LAYERS: β
β 1. Core flame (upward, orange-yellow, looping) β
β 2. Ember particles (small, floating up) β
β 3. Smoke (dark, rises above flame) β
β 4. Light source (flickering point light) β
β β
β SETTINGS: β
β β’ Emission: Continuous (50-100/sec) β
β β’ Velocity: Upward (2-5 units/sec) β
β β’ Noise: Turbulence for natural movement β
β β’ Color: WhiteβYellowβOrangeβRed over lifetime β
β β’ Size: Start small, grow, then shrink β
β β’ Blend: Additive for glow effect β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
MAGIC SPELL EFFECT:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β LAYERS: β
β 1. Core glow (pulsing, bright center) β
β 2. Orbiting particles (circle around core) β
β 3. Trail particles (follow movement path) β
β 4. Impact burst (on hit/destination) β
β 5. Residual sparkles (lingering after effect) β
β β
β SETTINGS: β
β β’ Emission: Rate + burst on cast/impact β
β β’ Velocity: Custom curves for orbiting β
β β’ Trails: Enable for mystical streaks β
β β’ Color: Themed to element (blue=ice, red=fire) β
β β’ Blend: Additive for ethereal glow β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Unity Particle System Setup
// β
Production-Ready: Particle Effect Controller
public class ParticleEffectController : MonoBehaviour
{
[Header("Effect Settings")]
[SerializeField] private ParticleSystem mainEffect;
[SerializeField] private ParticleSystem[] subEffects;
[SerializeField] private AudioSource audioSource;
[SerializeField] private Light effectLight;
[Header("Pooling")]
[SerializeField] private bool usePooling = true;
[SerializeField] private float autoReturnDelay = 3f;
private ParticleSystem.MainModule _mainModule;
private float _originalLightIntensity;
public event Action OnEffectComplete;
private void Awake()
{
_mainModule = mainEffect.main;
if (effectLight != null)
_originalLightIntensity = effectLight.intensity;
}
public void Play(Vector3 position, Quaternion rotation)
{
transform.SetPositionAndRotation(position, rotation);
// Play all particle systems
mainEffect.Play();
foreach (var effect in subEffects)
{
effect.Play();
}
// Play audio
if (audioSource != null)
audioSource.Play();
// Animate light
if (effectLight != null)
StartCoroutine(AnimateLight());
// Auto-return to pool
if (usePooling)
StartCoroutine(ReturnToPoolAfterDelay());
}
public void Stop()
{
mainEffect.Stop(true, ParticleSystemStopBehavior.StopEmitting);
foreach (var effect in subEffects)
{
effect.Stop(true, ParticleSystemStopBehavior.StopEmitting);
}
}
private IEnumerator AnimateLight()
{
effectLight.intensity = _originalLightIntensity;
effectLight.enabled = true;
float duration = _mainModule.duration;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;
effectLight.intensity = Mathf.Lerp(_originalLightIntensity, 0f, t);
yield return null;
}
effectLight.enabled = false;
}
private IEnumerator ReturnToPoolAfterDelay()
{
yield return new WaitForSeconds(autoReturnDelay);
OnEffectComplete?.Invoke();
// Reset for reuse
Stop();
if (effectLight != null)
{
effectLight.enabled = false;
effectLight.intensity = _originalLightIntensity;
}
}
public void SetColor(Color color)
{
var startColor = _mainModule.startColor;
startColor.color = color;
_mainModule.startColor = startColor;
if (effectLight != null)
effectLight.color = color;
}
public void SetScale(float scale)
{
transform.localScale = Vector3.one * scale;
}
}
GPU Particles (Compute Shader)
// β
Production-Ready: GPU Particle Compute Shader
#pragma kernel UpdateParticles
struct Particle
{
float3 position;
float3 velocity;
float4 color;
float size;
float lifetime;
float maxLifetime;
};
RWStructuredBuffer<Particle> particles;
float deltaTime;
float3 gravity;
float3 windDirection;
float windStrength;
float turbulenceStrength;
float time;
float noise3D(float3 p)
{
// Simple 3D noise for turbulence
return frac(sin(dot(p, float3(12.9898, 78.233, 45.164))) * 43758.5453);
}
[numthreads(256, 1, 1)]
void UpdateParticles(uint3 id : SV_DispatchThreadID)
{
Particle p = particles[id.x];
if (p.lifetime <= 0)
return;
// Apply forces
float3 acceleration = gravity;
// Wind
acceleration += windDirection * windStrength;
// Turbulence
float3 turbulence = float3(
noise3D(p.position + time) - 0.5,
noise3D(p.position + time + 100) - 0.5,
noise3D(p.position + time + 200) - 0.5
) * turbulenceStrength;
acceleration += turbulence;
// Update velocity and position
p.velocity += acceleration * deltaTime;
p.position += p.velocity * deltaTime;
// Update lifetime
p.lifetime -= deltaTime;
// Fade out
float lifetimeRatio = p.lifetime / p.maxLifetime;
p.color.a = lifetimeRatio;
p.size *= (0.5 + 0.5 * lifetimeRatio);
particles[id.x] = p;
}
Performance Optimization
PARTICLE BUDGET GUIDELINES:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PLATFORM β MAX PARTICLES β MAX DRAW CALLS β NOTES β
ββββββββββββββββββΌββββββββββββββββΌβββββββββββββββββΌβββββββββββ€
β Mobile Low β 500 β 5 β Simple β
β Mobile High β 2,000 β 10 β Moderate β
β Console β 50,000 β 50 β Complex β
β PC Low β 10,000 β 20 β Moderate β
β PC High β 1,000,000+ β 100+ β GPU sim β
β VR β 5,000 β 15 β 90 FPS! β
ββββββββββββββββββ΄ββββββββββββββββ΄βββββββββββββββββ΄βββββββββββ
OPTIMIZATION TECHNIQUES:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β REDUCE COUNT: β
β β’ LOD system (fewer particles at distance) β
β β’ Cull off-screen emitters β
β β’ Limit max particles per system β
β β
β REDUCE OVERDRAW: β
β β’ Use smaller particle sizes β
β β’ Reduce transparency layers β
β β’ Use cutout instead of transparent β
β β
β BATCH DRAWS: β
β β’ Share materials between systems β
β β’ Use texture atlases β
β β’ Enable GPU instancing β
β β
β GPU SIMULATION: β
β β’ Use compute shaders for >10K particles β
β β’ Move physics to GPU β
β β’ VFX Graph (Unity) / Niagara (Unreal) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Effect Pooling System
// β
Production-Ready: VFX Pool Manager
public class VFXPoolManager : MonoBehaviour
{
public static VFXPoolManager Instance { get; private set; }
[System.Serializable]
public class EffectPool
{
public string effectName;
public ParticleEffectController prefab;
public int initialSize = 5;
public int maxSize = 20;
}
[SerializeField] private EffectPool[] effectPools;
private Dictionary<string, Queue<ParticleEffectController>> _pools;
private Dictionary<string, EffectPool> _poolConfigs;
private void Awake()
{
Instance = this;
InitializePools();
}
private void InitializePools()
{
_pools = new Dictionary<string, Queue<ParticleEffectController>>();
_poolConfigs = new Dictionary<string, EffectPool>();
foreach (var pool in effectPools)
{
_pools[pool.effectName] = new Queue<ParticleEffectController>();
_poolConfigs[pool.effectName] = pool;
// Pre-warm pool
for (int i = 0; i < pool.initialSize; i++)
{
var effect = CreateEffect(pool);
_pools[pool.effectName].Enqueue(effect);
}
}
}
private ParticleEffectController CreateEffect(EffectPool pool)
{
var effect = Instantiate(pool.prefab, transform);
effect.gameObject.SetActive(false);
effect.OnEffectComplete += () => ReturnToPool(pool.effectName, effect);
return effect;
}
public ParticleEffectController SpawnEffect(
string effectName,
Vector3 position,
Quaternion rotation)
{
if (!_pools.ContainsKey(effectName))
{
Debug.LogError($"Effect pool '{effectName}' not found!");
return null;
}
var pool = _pools[effectName];
ParticleEffectController effect;
if (pool.Count > 0)
{
effect = pool.Dequeue();
}
else if (_poolConfigs[effectName].maxSize > pool.Count)
{
effect = CreateEffect(_poolConfigs[effectName]);
}
else
{
Debug.LogWarning($"Pool '{effectName}' exhausted!");
return null;
}
effect.gameObject.SetActive(true);
effect.Play(position, rotation);
return effect;
}
private void ReturnToPool(string effectName, ParticleEffectController effect)
{
effect.gameObject.SetActive(false);
_pools[effectName].Enqueue(effect);
}
}
// Usage
public class WeaponController : MonoBehaviour
{
public void OnHit(Vector3 hitPoint, Vector3 hitNormal)
{
VFXPoolManager.Instance.SpawnEffect(
"ImpactSparks",
hitPoint,
Quaternion.LookRotation(hitNormal));
}
}
π§ Troubleshooting
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PROBLEM: Particles popping in/out β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SOLUTIONS: β
β β Add fade-in at birth (size/alpha over lifetime) β
β β Increase soft particle distance β
β β Check culling settings β
β β Extend lifetime with fade-out β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PROBLEM: Low frame rate with particles β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SOLUTIONS: β
β β Reduce max particle count β
β β Use LOD (fewer particles at distance) β
β β Switch to GPU simulation β
β β Reduce overdraw (smaller/fewer particles) β
β β Use simpler shaders β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PROBLEM: Particles clipping through geometry β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SOLUTIONS: β
β β Enable collision module β
β β Use depth fade/soft particles β
β β Adjust near clip plane β
β β Position emitter away from surfaces β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PROBLEM: Effect looks different in build β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SOLUTIONS: β
β β Check quality settings (particle raycast budget) β
β β Verify shader compatibility β
β β Test on target hardware β
β β Check for editor-only components β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Engine-Specific Tools
| Engine | System | GPU Support | Visual Editor |
|---|---|---|---|
| Unity | Shuriken | VFX Graph | Yes (VFX Graph) |
| Unity | VFX Graph | Native | Yes |
| Unreal | Cascade | Limited | Yes |
| Unreal | Niagara | Native | Yes |
| Godot | GPUParticles | Yes | Inspector |
Use this skill: When creating visual effects, polishing gameplay, or optimizing particle performance.
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.