iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

LÖVE (Lua)

LÖVE (Love2D) is a small, fast Lua framework for making 2D games. The API is intentionally tiny — love.update, love.draw, love.load — and the result runs everywhere from Windows to mobile. Perfect for prototypes, game jams, and learning the loop without a heavy engine in the way.

Setup, callbacks, graphics, input

EXAMPLE
-- 1) Install
-- macOS: brew install love
-- Linux: apt install love
-- Windows: download from love2d.org
-- Run: love path/to/game-folder

-- 2) Project layout — at minimum, a main.lua
-- game/
--   main.lua          — entry; defines love callbacks
--   conf.lua          — window + options config
--   assets/
--     player.png
--     beep.wav
--   src/
--     player.lua
--     enemy.lua

-- 3) conf.lua — window + module options
function love.conf(t)
    t.identity        = 'mygame'
    t.version         = '11.5'
    t.console         = false             -- Windows console window
    t.window.title    = 'My Game'
    t.window.width    = 1280
    t.window.height   = 720
    t.window.vsync    = 1
    t.window.resizable = true
    t.window.minwidth  = 800
    t.window.minheight = 450
    t.modules.physics  = true
    t.modules.video    = false
end

-- 4) main.lua — the loop
local player
function love.load()
    -- Run once at startup
    love.window.setMode(1280, 720, { vsync = 1, resizable = true })
    love.graphics.setBackgroundColor(0.10, 0.12, 0.16)
    love.graphics.setDefaultFilter('nearest', 'nearest')   -- pixel art

    player = { x = 100, y = 100, w = 32, h = 32, speed = 200 }
end

function love.update(dt)
    -- dt = elapsed seconds since last frame
    if love.keyboard.isDown('left')  then player.x = player.x - player.speed * dt end
    if love.keyboard.isDown('right') then player.x = player.x + player.speed * dt end
    if love.keyboard.isDown('up')    then player.y = player.y - player.speed * dt end
    if love.keyboard.isDown('down')  then player.y = player.y + player.speed * dt end
end

function love.draw()
    love.graphics.setColor(0.36, 0.66, 1.0)
    love.graphics.rectangle('fill', player.x, player.y, player.w, player.h)
    love.graphics.setColor(1, 1, 1)
    love.graphics.print('FPS: ' .. love.timer.getFPS(), 8, 8)
end

-- 5) Other callbacks
function love.keypressed(key)
    if key == 'escape' then love.event.quit() end
end

function love.mousepressed(x, y, button)
    print('click', x, y, button)
end

function love.resize(w, h)
    -- recalc UI layout
end

function love.focus(hasFocus)
    -- pause when window loses focus
end

-- 6) Loading images, sounds, fonts
local sprite = love.graphics.newImage('assets/player.png')
local sfx    = love.audio.newSource('assets/beep.wav', 'static')
local font   = love.graphics.newFont('assets/PressStart2P.ttf', 16)
love.graphics.setFont(font)

-- 7) Drawing
function love.draw()
    love.graphics.draw(sprite, player.x, player.y)
    love.graphics.print('Hello', 10, 10)
    love.graphics.circle('fill', 400, 300, 50)
    love.graphics.line(0, 0, love.graphics.getWidth(), love.graphics.getHeight())
end

-- 8) Animation — sprite sheets via love.graphics.newQuad
local sheet  = love.graphics.newImage('assets/hero.png')
local quads  = {}
for i = 0, 7 do
    quads[i + 1] = love.graphics.newQuad(i * 32, 0, 32, 48, sheet:getDimensions())
end
local frame  = 1
local timer  = 0
local FRAME_DURATION = 0.1

function love.update(dt)
    timer = timer + dt
    if timer >= FRAME_DURATION then
        timer = timer - FRAME_DURATION
        frame = frame + 1
        if frame > #quads then frame = 1 end
    end
end
function love.draw()
    love.graphics.draw(sheet, quads[frame], 100, 100)
end

-- 9) Audio — play SFX + music
local music = love.audio.newSource('assets/music.ogg', 'stream')   -- stream for long files
music:setLooping(true); music:setVolume(0.5); music:play()

-- Play SFX (re-trigger by cloning)
function playBeep()
    local clone = sfx:clone()
    clone:setVolume(0.8); clone:play()
end

-- 10) Physics — Box2D bindings
local world = love.physics.newWorld(0, 700, true)   -- gravity
local ground = {}
ground.body  = love.physics.newBody(world, 640, 700, 'static')
ground.shape = love.physics.newRectangleShape(1280, 20)
ground.fixture = love.physics.newFixture(ground.body, ground.shape)

local box = {}
box.body  = love.physics.newBody(world, 640, 100, 'dynamic')
box.shape = love.physics.newRectangleShape(50, 50)
box.fixture = love.physics.newFixture(box.body, box.shape, 1)
box.fixture:setRestitution(0.3)

function love.update(dt) world:update(dt) end

function love.draw()
    love.graphics.polygon('line', box.body:getWorldPoints(box.shape:getPoints()))
    love.graphics.polygon('line', ground.body:getWorldPoints(ground.shape:getPoints()))
end

-- 11) Camera — translate + scale before drawing
local cam = { x = 0, y = 0, scale = 1 }
function love.draw()
    love.graphics.push()
    love.graphics.translate(-cam.x, -cam.y)
    love.graphics.scale(cam.scale, cam.scale)
    -- world draws here
    love.graphics.pop()
    -- HUD draws after pop (screen space)
end

-- 12) Game state machine
local state
local states = { menu = require 'src.menu', game = require 'src.game', pause = require 'src.pause' }

function love.load()    state = states.menu;    state:load()    end
function love.update(dt) state:update(dt)  end
function love.draw()    state:draw()       end

function switchState(name)
    state:leave();
    state = states[name];
    state:load()
end

-- 13) Packaging + distribution
-- love game/ runs it. To distribute:
-- macOS: zip the folder, rename to game.love
-- Windows: copy love.exe to the folder, append game.love to it (.bat to combine)
-- Linux: AppImage
-- Mobile: love-android, love-ios — community ports
-- HTML5: love.js project (slower but works)

-- 14) Helpful libraries (use lua-rocks or copy into project)
-- hump        — state, vector, timer, gamestate
-- anim8       — sprite sheet animations
-- bump        — AABB collision (alternative to Box2D)
-- STI         — Tiled map loader
-- moonshine   — postprocessing shaders
-- baton       — input handling with controllers

-- 15) Common bugs
-- • Forgetting love.graphics.setDefaultFilter('nearest', 'nearest') → pixel art looks blurry
-- • Mutating an asset after :draw → next frame uses the new state (textures are usually safe)
-- • Long love.audio.newSource files without 'stream' → game freezes loading audio
-- • Updating physics every frame WITHOUT a fixed step → unstable simulation; use accumulator pattern
-- • Missing dt on movement → frame-rate dependent speed; ALWAYS multiply by dt
-- • One huge sprite sheet at 4096×4096 → some old phones can't load; split or use texture atlas
-- • Calling love.graphics.* outside love.draw → undefined behaviour on some drivers
-- • Camera scaling rounds non-integer values → pixel jitter; floor positions before drawing pixel art

Why it matters

LÖVE is a delight for 2D game jams and prototypes: tiny API, fast Lua, runs everywhere. Pair love.update(dt) with delta-time movement, fixed-step physics, sprite sheets via quads, and a state machine for screens; reach for hump / anim8 / bump for the recurring patterns and ship a .love file when you’re done.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
function love.load()
    player = { x = 100, y = 100, speed = 200 }
end

function love.update(dt)
    if love.keyboard.isDown('right') then player.x = player.x + player.speed * dt end
end

function love.draw()
    love.graphics.rectangle('fill', player.x, player.y, 32, 32)
end
Try it Yourself »

Discussion

Loading…