Home Courses Lua Day 4
Day 04 Day 4

Day 4

Day 4

~1 hour Intermediate Hands-on Precision AI Academy

Today's Objective

Install Love2D, understand the game loop, handle input, draw sprites, detect collisions, and ship a playable .love file — all in one session.

The Love2D Framework

Love2D (stylized as LÖVE) is a free, open-source 2D game framework for Lua. It wraps SDL2 and OpenGL, giving you a window, audio, input, and a 60fps game loop — all from Lua scripts. Install it from love2d.org, then run any game with love /path/to/your/game/.

Love2D's three main callbacks: love.load() runs once at startup, love.update(dt) runs every frame with the elapsed time in seconds, love.draw() renders the frame. Everything else is optional.

The Minimal Game Loop

lua_(love2d)_-_main.lua_skeleton.txt
LUA (LOVE2D) — MAIN.LUA SKELETON
-- main.lua — every Love2D game starts here

function love.load()
  -- Called once at startup
  -- Initialize all your game state here
  love.window.setTitle("My Game")
  love.window.setMode(800, 600, {resizable=false, vsync=true})
end

function love.update(dt)
  -- Called every frame; dt = seconds since last frame (usually ~0.016)
  -- Update positions, physics, AI, timers here
end

function love.draw()
  -- Called after update; render everything here
  -- Love2D clears the screen before each draw call
  love.graphics.setColor(1, 1, 1)   -- white (RGBA, 0-1 range)
  love.graphics.print("Hello, Love2D!", 10, 10)
end

function love.keypressed(key)
  -- Called once when a key is first pressed
  if key == "escape" then love.event.quit() end
end

Moving a Player

lua_(love2d)_-_player_movement.txt
LUA (LOVE2D) — PLAYER MOVEMENT
-- main.lua — player controlled with arrow keys or WASD

local player = {}
local SPEED  = 200   -- pixels per second

function love.load()
  love.window.setMode(800, 600)
  player = {
    x     = 400,
    y     = 300,
    w     = 40,
    h     = 40,
    color = {0.2, 0.6, 1},  -- blue
  }
end

function love.update(dt)
  local dx, dy = 0, 0

  if love.keyboard.isDown("left",  "a") then dx = -1 end
  if love.keyboard.isDown("right", "d") then dx =  1 end
  if love.keyboard.isDown("up",    "w") then dy = -1 end
  if love.keyboard.isDown("down",  "s") then dy =  1 end

  -- Normalize diagonal movement
  if dx ~= 0 and dy ~= 0 then
    local len = math.sqrt(dx*dx + dy*dy)
    dx, dy = dx/len, dy/len
  end

  player.x = player.x + dx * SPEED * dt
  player.y = player.y + dy * SPEED * dt

  -- Clamp to screen bounds
  local W, H = love.graphics.getDimensions()
  player.x = math.max(0, math.min(W - player.w, player.x))
  player.y = math.max(0, math.min(H - player.h, player.y))
end

function love.draw()
  love.graphics.setColor(player.color)
  love.graphics.rectangle("fill", player.x, player.y, player.w, player.h)

  -- HUD
  love.graphics.setColor(1, 1, 1)
  love.graphics.print(string.format("x=%.0f y=%.0f", player.x, player.y), 8, 8)
  love.graphics.print(love.timer.getFPS() .. " FPS", 740, 8)
end

function love.keypressed(key)
  if key == "escape" then love.event.quit() end
end

AABB Collision Detection

Axis-Aligned Bounding Box (AABB) collision is the simplest form: two rectangles overlap if neither is fully to the left, right, above, or below the other.

lua_(love2d)_-_enemies_+_collision.txt
LUA (LOVE2D) — ENEMIES + COLLISION
-- AABB collision helper
local function aabb(a, b)
  return a.x < b.x + b.w
     and a.x + a.w > b.x
     and a.y < b.y + b.h
     and a.y + a.h > b.y
end

-- Enemy factory
local function make_enemy(x, y)
  return {
    x = x, y = y,
    w = 32, h = 32,
    vx = math.random(-120, 120),
    vy = math.random(-80, 80),
    alive = true,
  }
end

local enemies = {}
local score   = 0

function love.load()
  love.window.setMode(800, 600)
  math.randomseed(os.time())
  for i = 1, 8 do
    table.insert(enemies, make_enemy(
      math.random(50, 750),
      math.random(50, 550)
    ))
  end
  -- (assume player table from previous example)
end

function love.update(dt)
  local W, H = love.graphics.getDimensions()

  -- Move and bounce enemies
  for _, e in ipairs(enemies) do
    if e.alive then
      e.x = e.x + e.vx * dt
      e.y = e.y + e.vy * dt
      if e.x < 0 or e.x + e.w > W then e.vx = -e.vx end
      if e.y < 0 or e.y + e.h > H then e.vy = -e.vy end

      -- Check collision with player
      if aabb(player, e) then
        e.alive = false
        score   = score + 10
      end
    end
  end

  -- (player movement code from above goes here too)
end

function love.draw()
  -- Player
  love.graphics.setColor(0.2, 0.6, 1)
  love.graphics.rectangle("fill", player.x, player.y, player.w, player.h)

  -- Enemies
  for _, e in ipairs(enemies) do
    if e.alive then
      love.graphics.setColor(1, 0.3, 0.3)
      love.graphics.rectangle("fill", e.x, e.y, e.w, e.h)
    end
  end

  -- Score
  love.graphics.setColor(1, 1, 1)
  love.graphics.print("Score: " .. score, 8, 8)
end

Packaging and Shipping

shell_-_package_as_.love_file.txt
SHELL — PACKAGE AS .LOVE FILE
# A .love file is just a ZIP with main.lua at the root
cd /path/to/your/game
zip -9 -r mygame.love .

# Run it directly
love mygame.love

# On macOS — bundle into an .app (distribute standalone)
# Download the Love2D macOS .app, then:
cp mygame.love "love.app/Contents/Resources/mygame.love"
# Rename love.app to mygame.app and distribute

# Linux AppImage or Windows EXE: see love2d.org/wiki/Game_Distribution
Use a game state machine. As your game grows, separate states (menu, playing, paused, game-over) keep the code manageable. Each state is a table with update(dt) and draw() methods. Your love.update just calls current_state:update(dt).
Exercise
Build a Complete Mini-Game
  1. Create a game where the player (rectangle) must collect coins (small circles) that spawn at random positions. The player moves with WASD and collects a coin by touching it.
  2. Add a timer — the player has 30 seconds. Display the countdown on screen. When time runs out, show "GAME OVER" and final score.
  3. Add a spawn system: every 3 seconds a new coin appears until there are 10 on screen at once.
  4. Add a simple particle effect when a coin is collected: 5-10 small squares fly outward for 0.3 seconds then fade.
  5. Package the game into a .love file and run it from the command line to verify it works standalone.

Add a state machine to your mini-game with three states: menu, playing, and gameover. Each state should have its own update, draw, and keypressed handlers. The menu shows the high score. The gameover screen shows the score and lets the player restart. Persist the high score to a file using love.filesystem.write.

What's Next

The foundations from today carry directly into Day 5. In the next session the focus shifts to Day 5 — building directly on everything covered here.

Supporting Videos & Reading

Go deeper with these external references.

Day 4 Checkpoint

Before moving on, verify you can answer these without looking:

Live Bootcamp

Learn this in person — 2 days, 5 cities

Thu–Fri sessions in Denver, Los Angeles, New York, Chicago, and Dallas. $1,490 per seat. June–October 2026.

Reserve Your Seat →
Continue To Day 5
Day 5: Embedding Lua in C/C++