Game Dev
Love2D Patterns
Common reusable patterns for Love2D games — copy, adapt, use as agent prompts.
Love2D Patterns
These are copy-paste-ready patterns for common game mechanics. Each one is also a good starting point for an agent prompt — reference the pattern name and the agent will know what you mean.
Input handling
Centralise input into an input table updated each frame. Avoids scattered love.keyboard.isDown calls:
-- input.lua
local input = {}
function input.update()
input.left = love.keyboard.isDown('left', 'a')
input.right = love.keyboard.isDown('right', 'd')
input.jump = love.keyboard.isDown('space', 'w', 'up')
input.attack = love.keyboard.isDown('z', 'x')
end
return input
-- player.lua
function Player:update(dt)
if input.right then self.x = self.x + self.speed * dt end
if input.left then self.x = self.x - self.speed * dt end
if input.jump and self.grounded then self:jump() end
end
Timer / tween system
Use flux for tweens and timers:
local flux = require('lib.flux')
-- In love.update
flux.update(dt)
-- Tween a value
flux.to(self, 0.3, { x = targetX }):ease('quadout')
-- One-shot timer
flux.to({}, 2.0, {}):oncomplete(function() spawnEnemy() end)
Simple pooling for bullets / particles
Object pools prevent garbage collection spikes:
local BulletPool = {}
BulletPool.__index = BulletPool
function BulletPool.new(size)
local pool = setmetatable({ active = {}, inactive = {} }, BulletPool)
for i = 1, size do
table.insert(pool.inactive, { x=0, y=0, vx=0, vy=0, alive=false })
end
return pool
end
function BulletPool:spawn(x, y, vx, vy)
local b = table.remove(self.inactive) or { alive=false }
b.x, b.y, b.vx, b.vy, b.alive = x, y, vx, vy, true
table.insert(self.active, b)
end
function BulletPool:update(dt)
for i = #self.active, 1, -1 do
local b = self.active[i]
b.x = b.x + b.vx * dt
b.y = b.y + b.vy * dt
if outOfBounds(b) then
b.alive = false
table.remove(self.active, i)
table.insert(self.inactive, b)
end
end
end
Screen shake
local shake = { duration = 0, intensity = 0 }
function triggerShake(duration, intensity)
shake.duration = duration
shake.intensity = intensity
end
function love.update(dt)
if shake.duration > 0 then shake.duration = shake.duration - dt end
end
function love.draw()
local ox, oy = 0, 0
if shake.duration > 0 then
ox = (math.random() * 2 - 1) * shake.intensity
oy = (math.random() * 2 - 1) * shake.intensity
end
love.graphics.push()
love.graphics.translate(ox, oy)
-- draw world
love.graphics.pop()
end
Save / load with love.filesystem
local function saveGame(data)
local encoded = require('lib.json').encode(data)
love.filesystem.write('save.json', encoded)
end
local function loadGame()
if love.filesystem.getInfo('save.json') then
local content = love.filesystem.read('save.json')
return require('lib.json').decode(content)
end
return nil
end
Sound manager
-- sounds.lua
local S = {}
local sources = {}
function S.load()
sources.jump = love.audio.newSource('assets/audio/jump.ogg', 'static')
sources.coin = love.audio.newSource('assets/audio/coin.ogg', 'static')
sources.death = love.audio.newSource('assets/audio/death.ogg', 'static')
sources.music = love.audio.newSource('assets/audio/theme.ogg', 'stream')
sources.music:setLooping(true)
end
function S.play(name)
if sources[name] then
local clone = sources[name]:clone()
clone:play()
end
end
function S.playMusic()
sources.music:play()
end
return S
Have a pattern that worked well in your game? Submit it as a docs contribution — it may end up here and in the agent's training data.