Skip to content

LOVE2D Cheat Sheet

Overview

LOVE (also written as Love2D) is a free, open-source 2D game framework that uses the Lua programming language. It provides a simple yet powerful set of APIs for creating 2D games, covering graphics rendering, audio playback, physics simulation (via Box2D), input handling, file system access, and window management. LOVE is designed for rapid prototyping and game development, with a philosophy of keeping things simple and getting out of the developer’s way.

LOVE runs on Windows, macOS, Linux, Android, and iOS. It uses OpenGL for hardware-accelerated rendering and supports shaders written in GLSL. The framework is particularly popular in game jam communities due to its minimal setup requirements and fast iteration cycle — you can start coding a game with just a text editor and the LOVE executable. The distribution model is simple: package your Lua files with the LOVE runtime to create standalone executables.

Installation

# macOS
brew install love

# Ubuntu/Debian
sudo add-apt-repository ppa:bartbes/love-stable
sudo apt update
sudo apt install love

# Arch Linux
sudo pacman -S love

# Windows
# Download from https://love2d.org
# Add to PATH or use installer

# Verify installation
love --version

# Run a game
love /path/to/game-directory
love game.love  # Packaged game

# Create project structure
mkdir mygame
cd mygame
touch main.lua conf.lua

Basic Game Structure

-- main.lua

function love.load()
    -- Called once at startup
    player = {
        x = 400,
        y = 300,
        speed = 200,
        width = 32,
        height = 32
    }
    score = 0
    love.window.setTitle("My Game")
end

function love.update(dt)
    -- Called every frame (dt = delta time in seconds)
    if love.keyboard.isDown("left") or love.keyboard.isDown("a") then
        player.x = player.x - player.speed * dt
    end
    if love.keyboard.isDown("right") or love.keyboard.isDown("d") then
        player.x = player.x + player.speed * dt
    end
    if love.keyboard.isDown("up") or love.keyboard.isDown("w") then
        player.y = player.y - player.speed * dt
    end
    if love.keyboard.isDown("down") or love.keyboard.isDown("s") then
        player.y = player.y + player.speed * dt
    end
end

function love.draw()
    -- Called every frame for rendering
    love.graphics.setColor(0, 0.5, 1)
    love.graphics.rectangle("fill", player.x, player.y, player.width, player.height)
    
    love.graphics.setColor(1, 1, 1)
    love.graphics.print("Score: " .. score, 10, 10)
end

Configuration

-- conf.lua (loaded before main.lua)
function love.conf(t)
    t.title = "My Game"
    t.version = "11.5"              -- LOVE version compatibility
    t.window.width = 800
    t.window.height = 600
    t.window.resizable = true
    t.window.vsync = 1
    t.window.msaa = 4               -- Anti-aliasing samples
    t.window.fullscreen = false
    t.window.fullscreentype = "desktop"
    
    -- Modules (disable unused ones for performance)
    t.modules.joystick = true
    t.modules.physics = true
    t.modules.audio = true
    t.modules.video = false          -- Disable if not needed
end

Graphics

-- Drawing primitives
function love.draw()
    -- Colors (0-1 range)
    love.graphics.setColor(1, 0, 0)          -- Red
    love.graphics.setColor(0, 1, 0, 0.5)     -- Semi-transparent green
    
    -- Shapes
    love.graphics.rectangle("fill", 100, 100, 50, 30)
    love.graphics.rectangle("line", 200, 100, 50, 30)
    love.graphics.rectangle("fill", 300, 100, 50, 30, 8, 8)  -- Rounded
    love.graphics.circle("fill", 400, 300, 25)
    love.graphics.circle("line", 500, 300, 25)
    love.graphics.ellipse("fill", 600, 300, 40, 20)
    love.graphics.line(0, 0, 800, 600)
    love.graphics.polygon("fill", 300, 50, 350, 150, 250, 150)
    love.graphics.arc("fill", 400, 400, 50, 0, math.pi)
    love.graphics.points(100, 200, 150, 250, 200, 200)
    
    -- Reset color
    love.graphics.setColor(1, 1, 1)
end

-- Images
function love.load()
    playerImg = love.graphics.newImage("sprites/player.png")
    playerImg:setFilter("nearest", "nearest")  -- Pixel art
end

function love.draw()
    love.graphics.draw(playerImg, x, y)
    -- With rotation, scale, origin
    love.graphics.draw(playerImg, x, y, rotation, scaleX, scaleY, originX, originY)
end

-- Sprite sheets / Quads
function love.load()
    spritesheet = love.graphics.newImage("spritesheet.png")
    quads = {}
    local frameW, frameH = 32, 32
    for y = 0, spritesheet:getHeight() - frameH, frameH do
        for x = 0, spritesheet:getWidth() - frameW, frameW do
            table.insert(quads, love.graphics.newQuad(
                x, y, frameW, frameH,
                spritesheet:getDimensions()
            ))
        end
    end
end

function love.draw()
    love.graphics.draw(spritesheet, quads[currentFrame], x, y)
end

Input Handling

-- Keyboard (event-based)
function love.keypressed(key, scancode, isrepeat)
    if key == "space" then
        player:jump()
    elseif key == "escape" then
        love.event.quit()
    end
end

function love.keyreleased(key)
    if key == "space" then
        player:stopJumping()
    end
end

-- Keyboard (polling in update)
function love.update(dt)
    if love.keyboard.isDown("left") then
        player.x = player.x - speed * dt
    end
end

-- Mouse
function love.mousepressed(x, y, button, istouch)
    if button == 1 then  -- Left click
        shoot(x, y)
    end
end

function love.mousemoved(x, y, dx, dy)
    -- dx, dy are delta movement
    crosshair.x = x
    crosshair.y = y
end

function love.wheelmoved(x, y)
    if y > 0 then zoomIn()
    elseif y < 0 then zoomOut() end
end

-- Touch (mobile)
function love.touchpressed(id, x, y, dx, dy, pressure)
    touches[id] = { x = x, y = y }
end

-- Gamepad
function love.gamepadpressed(joystick, button)
    if button == "a" then player:jump() end
end

function love.update(dt)
    local lx = love.joystick.getGamepadAxis(joystick, "leftx")
    local ly = love.joystick.getGamepadAxis(joystick, "lefty")
    if math.abs(lx) > 0.2 then player.x = player.x + lx * speed * dt end
end

Audio

function love.load()
    -- Sound effects (decode on load, low latency)
    jumpSound = love.audio.newSource("sounds/jump.wav", "static")
    hitSound = love.audio.newSource("sounds/hit.wav", "static")
    
    -- Music (stream from disk, saves memory)
    music = love.audio.newSource("music/background.ogg", "stream")
    music:setLooping(true)
    music:setVolume(0.5)
    music:play()
end

function playSound(sound)
    sound:stop()   -- Reset if already playing
    sound:play()
end

-- Or clone for overlapping sounds
function playSFX(source)
    local clone = source:clone()
    clone:play()
end

Physics (Box2D)

function love.load()
    world = love.physics.newWorld(0, 9.81 * 64, true)  -- Gravity
    
    -- Ground (static body)
    ground = {}
    ground.body = love.physics.newBody(world, 400, 580, "static")
    ground.shape = love.physics.newRectangleShape(800, 40)
    ground.fixture = love.physics.newFixture(ground.body, ground.shape)
    
    -- Player (dynamic body)
    ball = {}
    ball.body = love.physics.newBody(world, 400, 100, "dynamic")
    ball.shape = love.physics.newCircleShape(20)
    ball.fixture = love.physics.newFixture(ball.body, ball.shape, 1)
    ball.fixture:setRestitution(0.5)  -- Bounciness
    ball.fixture:setFriction(0.3)
    
    -- Collision callbacks
    world:setCallbacks(beginContact, endContact, preSolve, postSolve)
end

function beginContact(a, b, contact)
    local userDataA = a:getUserData()
    local userDataB = b:getUserData()
    -- Handle collision
end

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

function love.draw()
    love.graphics.circle("fill", ball.body:getX(), ball.body:getY(), ball.shape:getRadius())
    love.graphics.rectangle("fill",
        ground.body:getX() - 400, ground.body:getY() - 20, 800, 40)
end

Camera System

-- Simple camera
local camera = { x = 0, y = 0, scale = 1, rotation = 0 }

function love.draw()
    love.graphics.push()
    love.graphics.translate(-camera.x + love.graphics.getWidth()/2,
                            -camera.y + love.graphics.getHeight()/2)
    love.graphics.scale(camera.scale)
    love.graphics.rotate(camera.rotation)
    
    -- Draw world objects here
    drawWorld()
    
    love.graphics.pop()
    
    -- Draw UI (not affected by camera)
    drawUI()
end

function love.update(dt)
    -- Follow player with lerp
    local lerpSpeed = 5 * dt
    camera.x = camera.x + (player.x - camera.x) * lerpSpeed
    camera.y = camera.y + (player.y - camera.y) * lerpSpeed
end

-- Screen shake
local shake = { duration = 0, magnitude = 0 }

function screenShake(duration, magnitude)
    shake.duration = duration
    shake.magnitude = magnitude
end

function love.update(dt)
    if shake.duration > 0 then
        shake.duration = shake.duration - dt
        camera.x = camera.x + love.math.random(-shake.magnitude, shake.magnitude)
        camera.y = camera.y + love.math.random(-shake.magnitude, shake.magnitude)
    end
end

Animation System

local Animation = {}
Animation.__index = Animation

function Animation.new(image, frameWidth, frameHeight, duration)
    local anim = setmetatable({}, Animation)
    anim.image = image
    anim.frames = {}
    anim.duration = duration or 0.1
    anim.currentTime = 0
    anim.currentFrame = 1
    
    local imgW = image:getWidth()
    local imgH = image:getHeight()
    for y = 0, imgH - frameHeight, frameHeight do
        for x = 0, imgW - frameWidth, frameWidth do
            table.insert(anim.frames, love.graphics.newQuad(
                x, y, frameWidth, frameHeight, imgW, imgH
            ))
        end
    end
    
    return anim
end

function Animation:update(dt)
    self.currentTime = self.currentTime + dt
    if self.currentTime >= self.duration then
        self.currentTime = self.currentTime - self.duration
        self.currentFrame = self.currentFrame % #self.frames + 1
    end
end

function Animation:draw(x, y, r, sx, sy)
    love.graphics.draw(self.image, self.frames[self.currentFrame], x, y, r or 0, sx or 1, sy or 1)
end

-- Usage
function love.load()
    local sheet = love.graphics.newImage("player_walk.png")
    walkAnim = Animation.new(sheet, 32, 32, 0.1)
end

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

function love.draw()
    walkAnim:draw(player.x, player.y)
end

Advanced Usage

-- Shaders (GLSL)
local shader = love.graphics.newShader([[
    extern number time;
    vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
        vec4 pixel = Texel(texture, texture_coords);
        pixel.r = pixel.r + sin(time + screen_coords.x * 0.01) * 0.3;
        return pixel * color;
    }
]])

function love.draw()
    shader:send("time", love.timer.getTime())
    love.graphics.setShader(shader)
    love.graphics.draw(image, 0, 0)
    love.graphics.setShader()  -- Reset
end

-- Canvas (render to texture)
local canvas = love.graphics.newCanvas(800, 600)
love.graphics.setCanvas(canvas)
    love.graphics.clear()
    -- Draw to canvas
love.graphics.setCanvas()

-- Draw canvas to screen
love.graphics.draw(canvas, 0, 0)

-- Threads for heavy computation
local thread = love.thread.newThread("worker.lua")
local channel = love.thread.getChannel("results")
thread:start()
local result = channel:pop()  -- Non-blocking
local result = channel:demand()  -- Blocking

-- Save/Load data
local saveData = { score = 100, level = 3 }
local serialized = "return " .. serialize(saveData)
love.filesystem.write("save.dat", serialized)

local data = love.filesystem.load("save.dat")()

Building and Distribution

# Create .love file (zip renamed)
cd mygame
zip -r ../mygame.love .

# Windows executable
cat love.exe mygame.love > mygame_win.exe
# Then distribute with love DLLs

# macOS app bundle
cp -r love.app mygame.app
cp mygame.love mygame.app/Contents/Resources/

# Use love-release tool for automated builds
luarocks install love-release
love-release -W -M -L  # Build for Windows, macOS, Linux

# Android (via love-android)
git clone https://github.com/love2d/love-android.git
# Place game in app/src/embed/assets/
./gradlew assembleRelease

Troubleshooting

IssueSolution
”Module not found”Check file paths; use require("folder.module") with dots
Blurry pixel artSet filter to nearest: image:setFilter("nearest", "nearest")
Game runs too fast/slowAlways multiply movement by dt (delta time)
Physics bodies fly apartScale down; use pixels-per-meter (64 pixels = 1 meter)
Audio cracklingUse “static” source type for short sounds; check buffer sizes
Out of memoryReuse objects; call collectgarbage() periodically
No gamepad inputCheck love.joystick.getJoysticks(); verify gamepad mapping
Window not resizingSet t.window.resizable = true in conf.lua
Drawing order wrongLOVE draws in order of calls; draw background first
Save file not foundUse love.filesystem.getSaveDirectory() to check location