🎆 LÖVE2D Fireworks Tutorial

📋 What We're Building

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.

🏗️ Project Structure

We'll organize our code into several key components:

Step 1: Setting Up the Foundation

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.

Step 2: Handling Mouse Input

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.

Rocket Properties:

Step 3: Creating the Firework Explosion

This 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

How the Explosion Works:

  1. We randomly pick a color from our palette
  2. Generate 60-100 particles in a perfect circle using trigonometry
  3. Each particle gets a random speed for variation
  4. We also create a flash effect at the center
Math Tip: We use (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.

Step 4: Updating Game Logic

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

Understanding Delta Time (dt):

The dt parameter represents the time elapsed since the last frame (in seconds). Multiplying movement by dt ensures smooth animation regardless of frame rate.

Important: We loop backwards through arrays (#array, 1, -1) when removing items to avoid skipping elements.

Step 5: Drawing Everything

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

Drawing Order Matters:

  1. Background color (dark blue)
  2. Rockets with trails
  3. Flash effects
  4. Particles (on top)
  5. UI text (always visible)

🎨 Customization Ideas

Now that you have the basic fireworks working, try these enhancements:

More Lua tutorials