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…