Lua for Game Development β€” Chapter 10: Audio, Particles, VFX & Feedback Systems

A full production-grade guide to audio, SFX, BGM, mixing, particles, VFX, hit feedback, screen effects, and timings using Lua.

Great gameplay is not only logic, animation, and UI.

It is FEEL.

Games become satisfying through:

  • impactful audio
  • juicy hit feedback
  • particles & sparks
  • camera shake
  • screen flashes
  • slow motion
  • dynamic music

Lua excels at orchestrating these effects.

This chapter will show how to design a complete feedback system using Lua.

1. Audio Architecture

Audio usually comes in three categories:

Sound Effects (SFX)

Attacks, footsteps, explosions.

Background Music (BGM)

Looping tracks.

Environmental Audio

Wind, rain, ambience layers.

Dynamic Music

Boss phases, intensity-driven music.

1.1 Audio Manager

local Audio = {}
Audio.__index = Audio

function Audio.new()
  return setmetatable({
    sfx = {},
    bgm = nil,
    volume = 1.0
  }, Audio)
end

function Audio:play_sfx(name, vol)
  engine_play_sound(name, (vol or 1) * self.volume)
end

function Audio:play_bgm(name, volume)
  self.bgm = name
  engine_play_music(name, volume or 1)
end

function Audio:set_volume(v)
  self.volume = v
end

return Audio

This abstracts away the engine-specific functions.

1.2 Dynamic Music System

Dynamic intensity-driven music:

local function update_music_intensity(game)
  local hp_ratio = game.player.hp / game.player.max_hp
  local enemies = #game.level.enemies

  local intensity = 0
  if hp_ratio < 0.3 then intensity = intensity + 0.4 end
  if enemies > 5 then intensity = intensity + 0.6 end

  engine_set_music_parameter("intensity", intensity)
end

Useful for:

  • boss fights
  • stealth detection
  • stressful chase scenes

2. Particle Systems (Lua-Controlled)

Particles make hits, magic, fire, smoke, dust, and explosions more exciting.

2.1 Particle Object

local Particle = {}
Particle.__index = Particle

function Particle.new(x, y, vx, vy, life)
  return setmetatable({
    x=x, y=y,
    vx=vx, vy=vy,
    life=life,
    age=0
  }, Particle)
end

function Particle:update(dt)
  self.age = self.age + dt
  self.x = self.x + self.vx * dt
  self.y = self.y + self.vy * dt
  return self.age >= self.life
end

function Particle:draw()
  draw_sprite("particle", self.x, self.y, 1 - self.age / self.life)
end

return Particle

2.2 Particle System

local ParticleSystem = {}
ParticleSystem.__index = ParticleSystem

function ParticleSystem.new()
  return setmetatable({list={}}, ParticleSystem)
end

function ParticleSystem:spawn(x, y, n)
  for i=1,n do
    local vx = (math.random() - 0.5) * 100
    local vy = (math.random() - 0.5) * 100
    table.insert(self.list, Particle.new(x, y, vx, vy, 0.4))
  end
end

function ParticleSystem:update(dt)
  for i=#self.list,1,-1 do
    if self.list[i]:update(dt) then
      table.remove(self.list, i)
    end
  end
end

function ParticleSystem:draw()
  for _, p in ipairs(self.list) do
    p:draw()
  end
end

return ParticleSystem

3. VFX (Visual Effects)

VFX systems combine:

  • particles
  • flashes
  • shader effects
  • screen overlays
  • camera shake
  • timeline sequence

Lua orchestrates these easily.

3.1 VFX Manager

local VFX = {}
VFX.__index = VFX

function VFX.new()
  return setmetatable({
    flashes = {},
    screenshake = nil,
    particles = ParticleSystem.new()
  }, VFX)
end

function VFX:flash(color, duration)
  table.insert(self.flashes, {color=color, dur=duration, t=0})
end

function VFX:shake(mag, dur)
  camera:shake(mag, dur)
end

function VFX:spark(x, y)
  self.particles:spawn(x, y, 12)
end

function VFX:update(dt)
  for i=#self.flashes,1,-1 do
    local f = self.flashes[i]
    f.t = f.t + dt
    if f.t >= f.dur then table.remove(self.flashes, i) end
  end
  self.particles:update(dt)
end

function VFX:draw()
  for _, f in ipairs(self.flashes) do
    draw_rect(0,0,800,600, f.color.r, f.color.g, f.color.b, 1 - f.t/f.dur)
  end
  self.particles:draw()
end

return VFX

4. Hit Feedback (Game Feel)

β€œGame feel” is what makes combat impactful.

Components:

  • Hit pause (freeze frames)
  • Enemy knockback
  • Camera shake
  • Loud SFX
  • Flash overlay
  • Particles/sparks
  • Screen jolt
  • Slow motion

4.1 Hit Pause (Freeze Frames)

function hit_pause(dur)
  local t = 0
  while t < dur do
    t = t + (1/60)
    coroutine.yield()
  end
end

4.2 Knockback

function apply_knockback(entity, dir, force)
  entity.vx = entity.vx + dir.x * force
  entity.vy = entity.vy + dir.y * force
end

4.3 Combined Hit Event

local function on_hit(attacker, target, dmg)
  Audio:play_sfx("hit")
  VFX:spark(target.x, target.y)
  VFX:flash({r=1,g=1,b=1}, 0.1)
  VFX:shake(8, 0.2)

  timeline:add(function()
    hit_pause(0.03)
  end)

  apply_knockback(target, attacker.dir, 200)
end

This integrates SFX, particles, camera shake, hit pause, and knockback.

5. Slow Motion Effects

Slow-motion is used in:

  • finishing moves
  • boss intros
  • dramatic kills
  • parries
  • last-hit sequences

5.1 Global Time Scale

local Time = {scale=1}

function Time:set_scale(s)
  self.scale = s
end

function Time:update(dt)
  return dt * self.scale
end

return Time

5.2 Slow Motion Timeline

timeline:add(function()
  Time:set_scale(0.2)
  wait(0.2)
  Time:set_scale(1)
end)

6. Event-Driven Audio & VFX

Use event bus for feedback.

event:on("enemy_killed", function(pos)
  VFX:spark(pos.x, pos.y)
  Audio:play_sfx("enemy_die")
end)

7. Background Effects (Weather, Ambience)

Examples:

  • rain particle layer
  • snow
  • dust motes
  • fog
  • waterfalls
  • glowing lights
  • radial gradients

7.1 Continuous Weather Particles

function Weather:update(dt)
  if math.random() < 0.3 then
    self.particles:spawn(math.random(0,800), -10, 5)
  end
  self.particles:update(dt)
end

8. Putting It All Together β€” A Complete SFX/VFX Feedback Loop

function player_attack()
  Audio:play_sfx("slash")

  timeline:add(function()
    hit_pause(0.02)
  end)

  VFX:flash({1,1,1}, 0.05)
  VFX:spark(target.x, target.y)
  camera:shake(6, 0.2)
end

This is essentially what powers action games like:

  • Dead Cells
  • Hades
  • Hollow Knight
  • Streets of Rage
  • Slay the Spire (SFX/juice for cards)

Lua is perfect for this orchestration.

9. Summary of Chapter 10

You now understand:

  • Audio management (SFX, BGM, dynamic mixing)
  • Particle systems
  • VFX manager
  • Flash overlays
  • Camera shake
  • Knockback & hit pause
  • Slow motion systems
  • Timeline integration
  • Weather & ambience layers
  • Dynamic boss fight music and dramatic sequences

Lua excels at creating juice, timing, and feedback loops for modern games.

Keep Reading

Follow the engineering thread

Get the next practical Birdor note, or browse the archive for related systems, tooling, and architecture work.

Join newsletter Browse articles