agentleFS
mattstrublemattstruble/skills/love2d/SKILL.md

love2d

Skill2 starsChanged 4 months agoMITView on GitHub

Use when working on any Love2D project — writing Lua for Love2D, implementing Love2D callbacks (love.load, love.update, love.draw), handling input, writing shaders, structuring a game loop, or asking about Love2D APIs. Also trigger when implementing game patterns (Observer, State, Command, etc.) in a Love2D project — this skill takes precedence over game-patterns for Love2D-specific implementation. NOT for Fennel interactive development workflow (see love2d-fennel). NOT for engine-agnostic pattern theory (see game-patterns).

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 0 are distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use love.graphics.push() / love.graphics.pop() to isolate transforms.

    line 76
  2. Very specific:

    Use Image instead of sampler2D for texture uniforms

    line 188
  3. Very specific:

    Use Texel(tex, uv) instead of texture2D(tex, uv) for sampling

    line 189
  4. Very specific:

    Forgetting to reset color: After love.graphics.setColor(r, g, b, a), everything drawn afterward is tinted.

    line 227

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Love2D 11.x Development

Love2D 11.x (baseline: 11.1–11.5) with Lua. All code examples target Lua — not Fennel. This skill is for hobbyist developers; prefer vanilla Love2D first, community libraries as alternatives.

References

ReferenceWhen to read it
references/shaders.mdFull shader pipeline: uniforms, displacement, masking, reflections
references/love2d-pattern-mappings.mdFull Lua implementations of all 23 game patterns
references/ui-patterns.mdButton drawing, hover detection, click handling, sound effects

Project Structure

my-game/
├── conf.lua        -- window config, identity (runs before love.load)
├── main.lua        -- entry point; all callbacks defined here
└── assets/

conf.lua — configure the window and game identity before Love2D initializes:

function love.conf(t)
    t.identity = "my-game"          -- save directory name
    t.window.title = "My Game"
    t.window.width = 800
    t.window.height = 600
    t.window.resizable = false
end

main.lua — define the standard callbacks:

function love.load()
    -- one-time setup: load assets, initialize state
end

function love.update(dt)
    -- game logic; dt is seconds since last frame
end

function love.draw()
    -- rendering only; no state mutation here
end

Callback lifecycle order (per frame):

  1. Event processing — love.keypressed, love.keyreleased, love.mousepressed, love.mousereleased, love.mousemoved fire here, before update
  2. love.update(dt) — game logic, physics, timers
  3. love.draw() — all rendering

love.run owns the main loop — you can override it for custom loop behavior (fixed timestep, etc.), but rarely need to.


Love2D Idioms

Graphics Stack

Use love.graphics.push() / love.graphics.pop() to isolate transforms. Transforms applied inside a push/pop block do not bleed out.

function love.draw()
    love.graphics.push()
        love.graphics.translate(player.x, player.y)
        love.graphics.rotate(player.angle)
        love.graphics.draw(player.sprite, -16, -16)  -- centered
    love.graphics.pop()
end

love.graphics.scale(sx, sy) scales from the current origin. Chain translate → rotate → scale in that order for predictable results.

Pixel Art

Set the default filter before loading any images:

function love.load()
    love.graphics.setDefaultFilter("nearest", "nearest")
    -- now load sprites; they'll use nearest-neighbor scaling
    player.sprite = love.graphics.newImage("assets/player.png")
end

Calling setDefaultFilter after images are loaded has no effect on already-loaded images.

Canvas (Off-Screen Rendering)

local canvas

function love.load()
    canvas = love.graphics.newCanvas(800, 600)
end

function love.draw()
    love.graphics.setCanvas(canvas)
        love.graphics.clear()
        -- draw scene to canvas
        love.graphics.draw(background)
    love.graphics.setCanvas()  -- restore to screen

    -- draw canvas to screen (apply post-processing shader here)
    love.graphics.draw(canvas, 0, 0)
end

Color Management

love.graphics.setColor(r, g, b, a) affects everything drawn after it. Values are 0–1. Always reset to white after drawing colored elements — forgetting this is the most common Love2D bug.

-- Draw a red health bar, then reset
love.graphics.setColor(1, 0, 0, 1)
love.graphics.rectangle("fill", 10, 10, hp_width, 20)
love.graphics.setColor(1, 1, 1, 1)  -- reset; sprites draw tinted otherwise
love.graphics.draw(sprite, x, y)

Drawing Primitives

love.graphics.draw(image, x, y)                    -- image at position
love.graphics.draw(image, x, y, r, sx, sy, ox, oy) -- with rotation, scale, origin
love.graphics.rectangle("fill", x, y, w, h)        -- filled rect
love.graphics.rectangle("line", x, y, w, h)        -- outline rect
love.graphics.print("text", x, y)                  -- text at position
love.graphics.printf("text", x, y, limit, "center") -- wrapped/aligned text

Shader Basics

For the full shader pipeline (uniforms, displacement, masking, reflections), see references/shaders.md.

Loading (inside love.load — shaders require an active graphics context):

local shader

function love.load()
    shader = love.graphics.newShader("shaders/effect.glsl")
    -- or inline:
    shader = love.graphics.newShader([[
        vec4 effect(vec4 color, Image tex, vec2 uv, vec2 screen) {
            return Texel(tex, uv) * color;
        }
    ]])
end

Applying:

love.graphics.setShader(shader)   -- enable
love.graphics.draw(image, 0, 0)
love.graphics.setShader()         -- reset to default

Sending uniforms:

-- Check before sending to avoid errors on unused/optimized-out uniforms
if shader:hasUniform("time") then
    shader:send("time", love.timer.getTime())
end

Love2D GLSL flavor — two differences from standard GLSL:

  • Use Image instead of sampler2D for texture uniforms
  • Use Texel(tex, uv) instead of texture2D(tex, uv) for sampling

Pattern Mapping (Quick Reference)

This skill takes precedence over game-patterns for Love2D implementation. For full Lua implementations, see references/love2d-pattern-mappings.md.

PatternLove2D ApproachCommunity Alternative
ObserverCallback tables; custom event system—
SingletonModule return value (require caches modules)—
StateModule-per-state with a state manager tablehump.gamestate
CommandTables with execute/undo functions—
FactoryConstructor functions returning configured tables—
StrategySwappable function or table on entity field—
DecoratorWrapper table delegating to inner via __index—
Service LocatorModule-level table with get/set for swappable services—
Event QueueArray buffer, drain in love.update—
ComponentTables with domain-specific data, updated by system functions—
PrototypeDeep copy via recursive table clone, or metatables—
FlyweightShared data tables referenced by multiple entity instances—
Object PoolPre-allocated array, active/inactive flags—
Double Bufferlove.graphics.Canvas (draw to canvas, present)—
Game LoopBuilt-in (love.update, love.draw)—
Update MethodEach entity table has an update(dt) method—
Spatial PartitionManual grid or quadtreebump.lua (AABB)
Dirty FlagBoolean flag on table, recompute on access when dirty—
Data LocalityArray-of-tables with homogeneous structure, iterate sequentially—
BytecodeLua itself (load/loadstring for runtime code)—
Subclass SandboxBase table with methods; "subclass" tables set it as __index—
Type ObjectShared "type" table referenced by instances via metatable __index—
Inputlove.keypressed/love.keyreleased/love.mouse* callbacks—

Common Pitfalls

Forgetting to reset color. After love.graphics.setColor(r, g, b, a), everything drawn afterward is tinted. Always call love.graphics.setColor(1, 1, 1, 1) after drawing colored elements.

Drawing outside push/pop. Transforms applied without a matching pop() bleed into subsequent draw calls. Wrap every entity's draw in push()/pop().

Loading resources in love.draw. love.graphics.newImage, love.graphics.newFont, etc. create new GPU objects every frame if called in love.draw. Load once in love.load, store in variables.

Calling setDefaultFilter after loading images. The filter must be set before any love.graphics.newImage calls. Put it as the first line of love.load.

Not using dt in love.update. Movement like x = x + speed is frame-rate-dependent. Always multiply by dt: x = x + speed * dt.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.