
In this tutorial, we'll create an interactive fireworks display where users can click anywhere on the screen to launch colorful fireworks. Each firework will have realistic physics, including gravity and particle decay.
We'll organize our code into several key components:
Download the full code here: main.lua or follow the steps below.
First, let's create our main data structures and initialize the game:
local fireworks = {}
local particles = {}
local rockets = {}
function love.load()
love.window.setTitle("Interactive Fireworks")
math.randomseed(os.time())
end
We create three empty tables to store our game objects. The love.load() function runs once when the program starts, setting our window title and initializing the random number generator.
Next, we'll detect when the player clicks to launch a rocket:
function love.mousepressed(x, y, button)
if button == 1 then
launchRocket(x, y)
end
end
function launchRocket(targetX, targetY)
table.insert(rockets, {
x = targetX,
y = love.graphics.getHeight(),
targetY = targetY,
speed = 300,
trailParticles = {}
})
end
When the left mouse button (button 1) is clicked, we create a new rocket object. The rocket starts at the bottom of the screen and stores the target coordinates where it should explode.
x, y - Current positiontargetY - Where the rocket should explodespeed - How fast the rocket travelstrailParticles - Array for the glowing trail effectThis is where the magic happens! When a rocket reaches its target, we create an explosion of particles:
function createFirework(x, y)
local colors = {
{1, 0.2, 0.2}, -- red
{0.2, 0.5, 1}, -- blue
{1, 0.8, 0.2}, -- gold
{0.2, 1, 0.3}, -- green
{1, 0.2, 1}, -- magenta
{1, 0.5, 0.2}, -- orange
{0.5, 0.2, 1} -- purple
}
local color = colors[math.random(#colors)]
local numParticles = math.random(60, 100)
for i = 1, numParticles do
local angle = (i / numParticles) * math.pi * 2
local speed = math.random(50, 150)
table.insert(particles, {
x = x,
y = y,
vx = math.cos(angle) * speed,
vy = math.sin(angle) * speed,
life = 1,
decay = math.random(8, 15) / 10,
size = math.random(2, 4),
color = color,
gravity = math.random(20, 40)
})
end
table.insert(fireworks, {x = x, y = y, flash = 0.3})
end
(i / numParticles) * math.pi * 2 to distribute particles evenly in a 360° circle. Then math.cos(angle) and math.sin(angle) convert that angle to x and y velocities.
The love.update() function runs every frame and handles all our physics:
function love.update(dt)
-- Update rockets
for i = #rockets, 1, -1 do
local r = rockets[i]
r.y = r.y - r.speed * dt
-- Trail effect
table.insert(r.trailParticles, {
x = r.x + math.random(-2, 2),
y = r.y,
life = 0.5
})
-- Remove old trail particles
for j = #r.trailParticles, 1, -1 do
r.trailParticles[j].life = r.trailParticles[j].life - dt * 2
if r.trailParticles[j].life <= 0 then
table.remove(r.trailParticles, j)
end
end
-- Explode when reaching target
if r.y <= r.targetY then
createFirework(r.x, r.y)
table.remove(rockets, i)
end
end
-- Update particles
for i = #particles, 1, -1 do
local p = particles[i]
p.x = p.x + p.vx * dt
p.y = p.y + p.vy * dt
p.vy = p.vy + p.gravity * dt
p.life = p.life - p.decay * dt
if p.life <= 0 then
table.remove(particles, i)
end
end
-- Update firework flashes
for i = #fireworks, 1, -1 do
fireworks[i].flash = fireworks[i].flash - dt * 2
if fireworks[i].flash <= 0 then
table.remove(fireworks, i)
end
end
end
The dt parameter represents the time elapsed since the last frame (in seconds). Multiplying movement by dt ensures smooth animation regardless of frame rate.
#array, 1, -1) when removing items to avoid skipping elements.
Finally, we render all our visual elements:
function love.draw()
love.graphics.setBackgroundColor(0.05, 0.05, 0.1)
-- Draw rockets
for _, r in ipairs(rockets) do
love.graphics.setColor(1, 1, 0.8, 0.8)
love.graphics.circle("fill", r.x, r.y, 3)
-- Draw trail
for _, t in ipairs(r.trailParticles) do
local alpha = t.life
love.graphics.setColor(1, 0.8, 0.3, alpha)
love.graphics.circle("fill", t.x, t.y, 2)
end
end
-- Draw firework flashes
for _, fw in ipairs(fireworks) do
if fw.flash > 0 then
love.graphics.setColor(1, 1, 1, fw.flash)
love.graphics.circle("fill", fw.x, fw.y, 30 * fw.flash)
end
end
-- Draw particles
for _, p in ipairs(particles) do
local alpha = p.life
love.graphics.setColor(p.color[1], p.color[2], p.color[3], alpha)
love.graphics.circle("fill", p.x, p.y, p.size)
end
-- Draw instructions
love.graphics.setColor(1, 1, 1, 0.7)
love.graphics.print("Click anywhere to launch fireworks!", 10, 10)
love.graphics.print("Particles: " .. #particles, 10, 30)
end
Now that you have the basic fireworks working, try these enhancements: