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.