VFX Spell Effects — Game-Quality Visual Effects for Apple Platforms
Decision Tree
What effect do you want?
|
|-- Smoke / Fog / Clouds?
| |-- Soft procedural blobs? -> Canvas noise blobs (like OCMiniOrb smoke)
| |-- Volumetric 3D fog? -> Ray marching (Metal fragment shader)
| |-- Quick particle smoke? -> SpriteKit SKEmitterNode or Vortex
| \-- Fluid dynamics? -> Metal compute (Navier-Stokes, advanced)
| -> See: references/smoke-fog.md
|
|-- Lightning / Electricity?
| |-- Single bolt? -> Midpoint displacement (most practical)
| |-- Smooth arc? -> Lissajous curves
| |-- Branching tree? -> Recursive fractal (Lichtenberg figures)
| \-- Glow on bolt? -> Additive bloom post-process
| -> See: references/lightning-electricity.md
|
|-- Particles (fire, sparks, magic)?
| |-- Quick preset? -> Vortex (SwiftUI-native)
| |-- 2D overlay? -> CAEmitterLayer or SpriteKit
| |-- Full GPU control? -> Metal compute shader pipeline
| |-- SwiftUI-only? -> Canvas + TimelineView
| -> See: references/particles.md
|
|-- Bloom / Glow / Post-processing?
| |-- SwiftUI quick glow? -> .shadow() + .blur() stacking
| |-- HDR bloom pipeline? -> Threshold -> Gaussian blur -> Composite (Metal)
| |-- Per-object glow? -> Render to texture + blur + additive blend
| -> See: references/bloom-glow.md
|
|-- Composing a full spell?
| |-- State machine? -> .idle -> .charging -> .casting -> .impact -> .dissipating
| |-- Layer ordering? -> base + particles + glow + screen shake + audio
| |-- Audio-reactive? -> Bind intensity to RMS amplitude
| -> See: references/spell-composition.md
|
\-- Performance issue?
|-- Dropped frames? -> Check particle count, blur radius, draw calls
|-- GPU bottleneck? -> Profile with Xcode GPU debugger
|-- CPU bottleneck? -> Move particle update to compute shader
-> See: Performance Budget Rules below
Quick Reference
| Effect |
Best API |
Approach |
Ref |
| Soft smoke blobs |
SwiftUI Canvas |
Noise-offset circles + alpha blend |
references/smoke-fog.md |
| Volumetric fog |
Metal fragment |
Ray march density field, 4-8 samples |
references/smoke-fog.md |
| Lightning bolt |
SwiftUI Canvas / Metal |
Midpoint displacement, 4-5 levels |
references/lightning-electricity.md |
| Branching lightning |
Canvas + recursion |
Fractal tree, angle +/-30deg |
references/lightning-electricity.md |
| Fire particles |
Vortex / SpriteKit |
Pre-built emitter, warm palette |
references/particles.md |
| Magic sparks |
Metal compute |
GPU particle pool, 500-2000 particles |
references/particles.md |
| Impact explosion |
Vortex + Canvas |
Burst emit + radial force + fade |
references/particles.md |
| Object glow |
SwiftUI .shadow |
Stacked .shadow(radius:) calls |
references/bloom-glow.md |
| HDR bloom |
Metal render pass |
Threshold + separable Gaussian + composite |
references/bloom-glow.md |
| Full spell |
Composite pattern |
State machine + layer stack + timeline |
references/spell-composition.md |
Performance Budget Rules (NON-NEGOTIABLE)
- 16ms frame budget -- Target 60fps. All VFX layers combined must complete within 16ms GPU + CPU time.
- Particle limits -- Mobile: 500-2000 active particles max. Desktop: up to 10,000 with compute shader. Never allocate mid-frame.
- Blur radius cap --
.blur(radius:) over 20pt on mobile tanks performance. Use downscaled render targets for large blurs.
- Draw call budget -- Keep under 10 draw calls for VFX overlay. Use Canvas (1 draw call per layer) or instanced rendering.
- Compute shader particles -- Mandatory for >500 particles. CPU-side
for loops over particle arrays cause frame drops on mobile.
- Ray march samples -- 4-8 samples per ray for real-time fog. More than 12 is offline-quality territory.
- Bloom passes -- 3-5 blur passes with ping-pong buffers. Downscale to half/quarter resolution for blur.
- Pool, never allocate -- Pre-allocate particle arrays. Use recycle-bin pattern (free list). Zero heap allocation during gameplay.
- Reduce motion --
@Environment(\.accessibilityReduceMotion) guards ALL effect starts. Provide static fallback.
- Profile on device -- Simulator GPU timing is meaningless. Test on oldest supported hardware.
Key Patterns
State Machine for Spell Lifecycle
enum SpellPhase: String, CaseIterable {
case idle, charging, casting, impact, dissipating
}
@Observable
class SpellState {
var phase: SpellPhase = .idle
var intensity: Float = 0.0 // 0.0-1.0
var elapsed: TimeInterval = 0.0
func advance(dt: TimeInterval) {
elapsed += dt
switch phase {
case .charging: intensity = min(1.0, Float(elapsed / 0.5))
case .casting: intensity = 1.0
case .impact: intensity = max(0.0, 1.0 - Float(elapsed / 0.3))
case .dissipating: intensity = max(0.0, 0.5 - Float(elapsed / 0.6))
case .idle: intensity = 0.0
}
}
}
Canvas-Based Effect Layer
struct EffectLayer: View {
let state: SpellState
@Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View {
if !reduceMotion && state.phase != .idle {
TimelineView(.animation(minimumInterval: 1.0 / 60.0)) { ctx in
Canvas { context, size in
// Draw effect here -- single draw call
let t = ctx.date.timeIntervalSinceReferenceDate
drawEffect(context: context, size: size, time: t, intensity: state.intensity)
}
}
}
}
}
Midpoint Displacement Lightning (Minimal)
func generateBolt(from: CGPoint, to: CGPoint, levels: Int = 5, jaggedness: CGFloat = 0.4) -> [CGPoint] {
var points = [from, to]
for level in 0..<levels {
var newPoints = [points[0]]
let scale = jaggedness * pow(0.5, CGFloat(level))
for i in 0..<(points.count - 1) {
let mid = CGPoint(
x: (points[i].x + points[i+1].x) / 2 + CGFloat.random(in: -1...1) * scale * from.distance(to: to),
y: (points[i].y + points[i+1].y) / 2 + CGFloat.random(in: -1...1) * scale * from.distance(to: to)
)
newPoints.append(mid)
newPoints.append(points[i+1])
}
points = newPoints
}
return points
}
References
| File |
Content |
references/smoke-fog.md |
Noise-based smoke, ray marching fog, Canvas blobs, Metal fluid dynamics |
references/lightning-electricity.md |
Fractal midpoint displacement, Lissajous arcs, branching trees, glow rendering |
references/particles.md |
API comparison, GPU particle architecture, emission patterns, pool pattern, Vortex presets |
references/bloom-glow.md |
HDR bloom pipeline, threshold/blur/composite, Metal + SwiftUI integration |
references/spell-composition.md |
State machine, layer stacking, audio-reactive binding, timing curves, full spell examples |
External Resources
Related Skills
- orb -- OCMiniOrb implementation (smoke layer, fractal layer, glow compositing)
- swiftui-animation -- Animation curves, springs, Metal shader integration in SwiftUI
- swiftui-performance-audit -- Runtime GPU/CPU profiling when effects cause frame drops
Technique Map
- Effect type routing — Smoke/fog, lightning, particles, bloom, full spell; because each has distinct API and approach.
- State machine for lifecycle — idle→charging→casting→impact→dissipating; because spells have phases; state drives intensity.
- 16ms frame budget — 60fps non-negotiable; because dropped frames break immersion.
- Particle limits — 500-2000 mobile; 10k desktop with compute; because CPU particle loops tank mobile.
- Canvas for overlay — Single draw call per layer; because draw call budget matters.
- Pool, never allocate — Recycle bin pattern; because mid-frame allocation causes hitches.
- Reduce motion guard — Static fallback when accessibilityReduceMotion; because necessary for compliance.
- Midpoint displacement — For lightning; 4-5 levels; because simple, effective, performant.
Technique Notes
References: smoke-fog, lightning-electricity, particles, bloom-glow, spell-composition. APIs: Vortex, SpriteKit, Metal compute, Canvas. Profile on device; simulator GPU timing meaningless. Related: orb, swiftui-animation, swiftui-performance-audit.
Prompt Architect Overlay
Role Definition: VFX spell effects builder. Game-quality smoke, lightning, particles, bloom, spell composition. 60fps performance rules. Apple platforms.
Input Contract: Accepts effect type (smoke, lightning, fire, glow, full spell), target API (Vortex, Metal, Canvas), or performance issue. Platform, particle count, style.
Output Contract: Decision tree result. Quick reference row. Code pattern (state machine, canvas layer, midpoint displacement). Performance budget rules. Reference to deep-dive. Related skills.
Edge Cases & Fallbacks: If performance issue→check particle count, blur radius, draw calls. If >500 particles→compute shader mandatory. If bloom→downscale for blur passes. If reduce motion→guard all effects; provide static fallback.
1---2name: vfx-spell-effects3description: Build game-quality spell VFX on Apple platforms with practical recipes for smoke, lightning, particles, bloom, layering, and stable 60fps performance.4triggers:5 - spell effect6 - vfx particles7 - lightning bolt effect8 - smoke fog simulation9 - bloom glow shader10 - fire effect11 - magic aura12 - Metal particle system13 - impact explosion14 - spell composition15 - procedural lightning16 - ray marching fog17 - GPU compute particles18 - Vortex preset19 - game visual effects20---2122# VFX Spell Effects — Game-Quality Visual Effects for Apple Platforms2324## Decision Tree2526```27What effect do you want?28|29|-- Smoke / Fog / Clouds?30| |-- Soft procedural blobs? -> Canvas noise blobs (like OCMiniOrb smoke)31| |-- Volumetric 3D fog? -> Ray marching (Metal fragment shader)32| |-- Quick particle smoke? -> SpriteKit SKEmitterNode or Vortex33| \-- Fluid dynamics? -> Metal compute (Navier-Stokes, advanced)34| -> See: references/smoke-fog.md35|36|-- Lightning / Electricity?37| |-- Single bolt? -> Midpoint displacement (most practical)38| |-- Smooth arc? -> Lissajous curves39| |-- Branching tree? -> Recursive fractal (Lichtenberg figures)40| \-- Glow on bolt? -> Additive bloom post-process41| -> See: references/lightning-electricity.md42|43|-- Particles (fire, sparks, magic)?44| |-- Quick preset? -> Vortex (SwiftUI-native)45| |-- 2D overlay? -> CAEmitterLayer or SpriteKit46| |-- Full GPU control? -> Metal compute shader pipeline47| |-- SwiftUI-only? -> Canvas + TimelineView48| -> See: references/particles.md49|50|-- Bloom / Glow / Post-processing?51| |-- SwiftUI quick glow? -> .shadow() + .blur() stacking52| |-- HDR bloom pipeline? -> Threshold -> Gaussian blur -> Composite (Metal)53| |-- Per-object glow? -> Render to texture + blur + additive blend54| -> See: references/bloom-glow.md55|56|-- Composing a full spell?57| |-- State machine? -> .idle -> .charging -> .casting -> .impact -> .dissipating58| |-- Layer ordering? -> base + particles + glow + screen shake + audio59| |-- Audio-reactive? -> Bind intensity to RMS amplitude60| -> See: references/spell-composition.md61|62\-- Performance issue?63 |-- Dropped frames? -> Check particle count, blur radius, draw calls64 |-- GPU bottleneck? -> Profile with Xcode GPU debugger65 |-- CPU bottleneck? -> Move particle update to compute shader66 -> See: Performance Budget Rules below67```6869## Quick Reference7071| Effect | Best API | Approach | Ref |72|--------|----------|----------|-----|73| Soft smoke blobs | SwiftUI Canvas | Noise-offset circles + alpha blend | `references/smoke-fog.md` |74| Volumetric fog | Metal fragment | Ray march density field, 4-8 samples | `references/smoke-fog.md` |75| Lightning bolt | SwiftUI Canvas / Metal | Midpoint displacement, 4-5 levels | `references/lightning-electricity.md` |76| Branching lightning | Canvas + recursion | Fractal tree, angle +/-30deg | `references/lightning-electricity.md` |77| Fire particles | Vortex / SpriteKit | Pre-built emitter, warm palette | `references/particles.md` |78| Magic sparks | Metal compute | GPU particle pool, 500-2000 particles | `references/particles.md` |79| Impact explosion | Vortex + Canvas | Burst emit + radial force + fade | `references/particles.md` |80| Object glow | SwiftUI `.shadow` | Stacked `.shadow(radius:)` calls | `references/bloom-glow.md` |81| HDR bloom | Metal render pass | Threshold + separable Gaussian + composite | `references/bloom-glow.md` |82| Full spell | Composite pattern | State machine + layer stack + timeline | `references/spell-composition.md` |8384## Performance Budget Rules (NON-NEGOTIABLE)85861. **16ms frame budget** -- Target 60fps. All VFX layers combined must complete within 16ms GPU + CPU time.872. **Particle limits** -- Mobile: 500-2000 active particles max. Desktop: up to 10,000 with compute shader. Never allocate mid-frame.883. **Blur radius cap** -- `.blur(radius:)` over 20pt on mobile tanks performance. Use downscaled render targets for large blurs.894. **Draw call budget** -- Keep under 10 draw calls for VFX overlay. Use Canvas (1 draw call per layer) or instanced rendering.905. **Compute shader particles** -- Mandatory for >500 particles. CPU-side `for` loops over particle arrays cause frame drops on mobile.916. **Ray march samples** -- 4-8 samples per ray for real-time fog. More than 12 is offline-quality territory.927. **Bloom passes** -- 3-5 blur passes with ping-pong buffers. Downscale to half/quarter resolution for blur.938. **Pool, never allocate** -- Pre-allocate particle arrays. Use recycle-bin pattern (free list). Zero heap allocation during gameplay.949. **Reduce motion** -- `@Environment(\.accessibilityReduceMotion)` guards ALL effect starts. Provide static fallback.9510. **Profile on device** -- Simulator GPU timing is meaningless. Test on oldest supported hardware.9697## Key Patterns9899### State Machine for Spell Lifecycle100101```swift102enum SpellPhase: String, CaseIterable {103 case idle, charging, casting, impact, dissipating104}105106@Observable107class SpellState {108 var phase: SpellPhase = .idle109 var intensity: Float = 0.0 // 0.0-1.0110 var elapsed: TimeInterval = 0.0111112 func advance(dt: TimeInterval) {113 elapsed += dt114 switch phase {115 case .charging: intensity = min(1.0, Float(elapsed / 0.5))116 case .casting: intensity = 1.0117 case .impact: intensity = max(0.0, 1.0 - Float(elapsed / 0.3))118 case .dissipating: intensity = max(0.0, 0.5 - Float(elapsed / 0.6))119 case .idle: intensity = 0.0120 }121 }122}123```124125### Canvas-Based Effect Layer126127```swift128struct EffectLayer: View {129 let state: SpellState130 @Environment(\.accessibilityReduceMotion) private var reduceMotion131132 var body: some View {133 if !reduceMotion && state.phase != .idle {134 TimelineView(.animation(minimumInterval: 1.0 / 60.0)) { ctx in135 Canvas { context, size in136 // Draw effect here -- single draw call137 let t = ctx.date.timeIntervalSinceReferenceDate138 drawEffect(context: context, size: size, time: t, intensity: state.intensity)139 }140 }141 }142 }143}144```145146### Midpoint Displacement Lightning (Minimal)147148```swift149func generateBolt(from: CGPoint, to: CGPoint, levels: Int = 5, jaggedness: CGFloat = 0.4) -> [CGPoint] {150 var points = [from, to]151 for level in 0..<levels {152 var newPoints = [points[0]]153 let scale = jaggedness * pow(0.5, CGFloat(level))154 for i in 0..<(points.count - 1) {155 let mid = CGPoint(156 x: (points[i].x + points[i+1].x) / 2 + CGFloat.random(in: -1...1) * scale * from.distance(to: to),157 y: (points[i].y + points[i+1].y) / 2 + CGFloat.random(in: -1...1) * scale * from.distance(to: to)158 )159 newPoints.append(mid)160 newPoints.append(points[i+1])161 }162 points = newPoints163 }164 return points165}166```167168## References169170| File | Content |171|------|---------|172| `references/smoke-fog.md` | Noise-based smoke, ray marching fog, Canvas blobs, Metal fluid dynamics |173| `references/lightning-electricity.md` | Fractal midpoint displacement, Lissajous arcs, branching trees, glow rendering |174| `references/particles.md` | API comparison, GPU particle architecture, emission patterns, pool pattern, Vortex presets |175| `references/bloom-glow.md` | HDR bloom pipeline, threshold/blur/composite, Metal + SwiftUI integration |176| `references/spell-composition.md` | State machine, layer stacking, audio-reactive binding, timing curves, full spell examples |177178## External Resources179180- [Vortex](https://github.com/twostraws/Vortex) -- SwiftUI-native particle system181- [Inferno](https://github.com/twostraws/Inferno) -- Metal shader collection for SwiftUI182- [MetalNoise](https://github.com/jmade/MetalNoise) -- Perlin/Simplex noise in Metal183- [The Book of Shaders: Noise](https://thebookofshaders.com/11/) -- Noise function fundamentals184- [Ray Marching SDFs](https://jamie-wong.com/2016/07/15/ray-marching-signed-distance-functions/) -- Ray marching primer185- [2D Lightning Effects](https://gamedevelopment.tutsplus.com/tutorials/how-to-generate-shockingly-good-2d-lightning-effects--gamedev-2681) -- Midpoint displacement tutorial186- [LearnOpenGL: Bloom](https://learnopengl.com/Advanced-Lighting/Bloom) -- HDR bloom pipeline reference187- [Realtime Fire Rendering](https://andrewkchan.dev/posts/fire.html) -- Procedural fire techniques188189## Related Skills190191- **orb** -- OCMiniOrb implementation (smoke layer, fractal layer, glow compositing)192- **swiftui-animation** -- Animation curves, springs, Metal shader integration in SwiftUI193- **swiftui-performance-audit** -- Runtime GPU/CPU profiling when effects cause frame drops194## Technique Map195196- **Effect type routing** — Smoke/fog, lightning, particles, bloom, full spell; because each has distinct API and approach.197- **State machine for lifecycle** — idle→charging→casting→impact→dissipating; because spells have phases; state drives intensity.198- **16ms frame budget** — 60fps non-negotiable; because dropped frames break immersion.199- **Particle limits** — 500-2000 mobile; 10k desktop with compute; because CPU particle loops tank mobile.200- **Canvas for overlay** — Single draw call per layer; because draw call budget matters.201- **Pool, never allocate** — Recycle bin pattern; because mid-frame allocation causes hitches.202- **Reduce motion guard** — Static fallback when accessibilityReduceMotion; because necessary for compliance.203- **Midpoint displacement** — For lightning; 4-5 levels; because simple, effective, performant.204205## Technique Notes206207References: smoke-fog, lightning-electricity, particles, bloom-glow, spell-composition. APIs: Vortex, SpriteKit, Metal compute, Canvas. Profile on device; simulator GPU timing meaningless. Related: orb, swiftui-animation, swiftui-performance-audit.208209---210211## Prompt Architect Overlay212213**Role Definition:** VFX spell effects builder. Game-quality smoke, lightning, particles, bloom, spell composition. 60fps performance rules. Apple platforms.214215**Input Contract:** Accepts effect type (smoke, lightning, fire, glow, full spell), target API (Vortex, Metal, Canvas), or performance issue. Platform, particle count, style.216217**Output Contract:** Decision tree result. Quick reference row. Code pattern (state machine, canvas layer, midpoint displacement). Performance budget rules. Reference to deep-dive. Related skills.218219**Edge Cases & Fallbacks:** If performance issue→check particle count, blur radius, draw calls. If >500 particles→compute shader mandatory. If bloom→downscale for blur passes. If reduce motion→guard all effects; provide static fallback.