love2d-core
gamedev-skills/awesome-gamedev-agent-skills
Set up and debug LÖVE 11.x games: the callback loop, delta-time movement, input, and screen states.
What is love2d-core?
Structures the core loop of a LÖVE (Love2D) game in Lua, covering love.load/update/draw, frame-rate-independent motion, keyboard input, and screen state management. Use when building or fixing the foundation of a LÖVE 11.x game.
- Wire up main.lua and conf.lua entry points with the love.load/update/draw callback loop
- Implement frame-rate-independent movement using delta time (dt) scaling
- Handle polled input (love.keyboard.isDown for held keys) and event input (love.keypressed for discrete actions)
- Manage screen/state transitions with a stack-based pattern for menus, pause, and overlays
- Configure window properties, LÖVE version targeting, and module toggles in conf.lua
- Debug common pitfalls: missing dt scaling, color value ranges (0–1 not 0–255), and input callback timing
How to install love2d-core
npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill love2d-core- LÖVE 11.5 installed and accessible via the `love` command
- A project folder with main.lua and conf.lua files or a .love package
How to use love2d-core
- 1.Create or open conf.lua and set t.version = "11.5", window dimensions, and module flags
- 2.Define love.load() in main.lua for one-time initialization (player position, background color, etc.)
- 3.Implement love.update(dt) with polled input (love.keyboard.isDown) and scale all motion by dt
- 4.Implement love.draw() to render game objects and UI, resetting color with love.graphics.setColor(1,1,1) between elements
- 5.Add love.keypressed(key) for discrete input like menu navigation or quit commands
- 6.Test by running `love .` from the project folder and verifying window, motion speed, and input response
Use cases
- Starting a new LÖVE game and setting up main.lua and conf.lua from scratch
- Fixing movement that runs at inconsistent speeds across different frame rates
- Wiring keyboard input for both continuous movement and discrete actions like menu selection
- Implementing screen transitions between menu, gameplay, and pause states
- Debugging why colors appear washed out or why input doesn't respond as expected
- Game developers building 2D games in LÖVE 11.x
- Lua developers new to the LÖVE framework's callback-driven architecture
- Teams maintaining LÖVE games and needing to standardize core loop patterns
love2d-core FAQ
You are likely not scaling movement by dt (delta time). Every position change must be multiplied by dt: `player.x = player.x + speed * dt`. Without dt, movement is tied to frame rate.
LÖVE 11.x uses normalized color values (0–1), not 0–255. Use `love.graphics.setColor(1, 0, 0)` for red, or convert old values with `love.math.colorFromBytes(255, 0, 0)`.
love.conf must be in its own conf.lua file in the project root. LÖVE runs conf.lua before loading any other modules, so placing it in main.lua has no effect.
Use a screen-state stack pattern: each screen is a table with optional :update(dt), :draw(), and :keypressed(key) methods. Push/pop screens onto a stack to handle overlays and transitions cleanly. See the state-stack reference for a full implementation.
Color is global and persists. Reset it with `love.graphics.setColor(1, 1, 1)` before drawing text or images you want untinted.
Full instructions (SKILL.md)
Source of truth, from gamedev-skills/awesome-gamedev-agent-skills.
name: love2d-core description: > Structure and debug a LÖVE (Love2D) game in Lua: the love.load/update/draw loop, delta-time movement, input, and screen states. Use when building a LÖVE 11.x game (main.lua, conf.lua, .love).
LÖVE (Love2D) Core
Set up and debug the foundation of a LÖVE game in Lua: the callback loop, frame-rate- independent movement, input, and screen states. Targets LÖVE 11.5.
When to use
- Use when starting a LÖVE game, wiring up
main.lua/conf.lua, or fixing the core loop, movement that runs at the wrong speed, input handling, or screen switching. - Use when the workspace has
main.luacallinglove.*, aconf.lua, or a.lovefile.
When not to use: Lua language questions unrelated to LÖVE; physics bodies/joints
(LÖVE uses Box2D via love.physics — a separate concern); shader code (love.graphics
GLSL is its own topic). For cross-engine save/load patterns, use save-systems.
Core workflow
- Confirm the entry points. A LÖVE game runs
main.lua; it should definelove.load()(one-time setup),love.update(dt)(state), andlove.draw()(rendering). Window/version setup goes inconf.lua(run before modules load). - Pin the version. Set
t.version = "11.5"inconf.luaso LÖVE warns on mismatch. - Drive all motion by
dt(delta time, in seconds) so speed is frame-rate independent. - Handle input two ways: polled (
love.keyboard.isDowninupdate, for held keys) and event (love.keypressedcallback, for discrete presses). - Manage screens (menu, game, pause) with a small state stack rather than a pile of
ifflags — see Patterns andreferences/state-stack.md. - Run and observe. Launch with
love .from the project folder; verify the window, motion speed, and input on screen before assuming it works.
Patterns
1. main.lua skeleton (the callback loop + input)
-- main.lua — LÖVE calls these callbacks for you. Colors are 0–1 in LÖVE 11.x.
function love.load()
-- One-time setup. speed is in PIXELS PER SECOND, not per frame.
player = { x = 100, y = 100, size = 40, speed = 220 }
love.graphics.setBackgroundColor(0.1, 0.1, 0.12)
end
function love.update(dt)
-- Polled input: good for continuous movement while a key is held.
if love.keyboard.isDown("right") then player.x = player.x + player.speed * dt end
if love.keyboard.isDown("left") then player.x = player.x - player.speed * dt end
if love.keyboard.isDown("down") then player.y = player.y + player.speed * dt end
if love.keyboard.isDown("up") then player.y = player.y - player.speed * dt end
end
function love.draw()
love.graphics.setColor(0.2, 0.8, 1.0) -- tint ON
love.graphics.rectangle("fill", player.x, player.y, player.size, player.size)
love.graphics.setColor(1, 1, 1) -- reset tint before text/images
love.graphics.print("Arrow keys to move, Esc to quit", 10, 10)
end
function love.keypressed(key)
-- Event input: fires once per physical press. Use for menus, jumps, toggles.
if key == "escape" then love.event.quit() end
end
2. Frame-rate independence (the single most common bug)
-- RIGHT: scaled by dt → same real-world speed at 30 or 240 FPS.
player.x = player.x + player.speed * dt
-- WRONG: "pixels per frame" → moves twice as fast at double the frame rate.
player.x = player.x + player.speed
3. conf.lua (window + version; runs before main.lua)
-- conf.lua — must be its own file; love.conf will NOT run from main.lua.
function love.conf(t)
t.version = "11.5" -- the LÖVE version this game targets (string "X.Y")
t.window.title = "My LÖVE Game"
t.window.width = 800
t.window.height = 600
t.window.vsync = 1 -- number since 11.0: 1 = on, 0 = off, -1 = adaptive
t.window.resizable = false
t.modules.physics = false -- disable modules you don't use to trim startup/memory
end
4. Color is 0–1 in LÖVE 11.x (not 0–255)
-- LÖVE 11.x uses normalized floats. (Pre-11.0 code used 0–255 and will look wrong.)
love.graphics.setColor(1, 0, 0) -- opaque red
love.graphics.setColor(0.2, 0.8, 1.0, 0.5) -- translucent cyan (alpha 0.5)
-- Need to convert old byte values? Use the helper instead of dividing by hand:
love.graphics.setColor(love.math.colorFromBytes(128, 234, 255))
5. Screen states (brief — full manager in references)
-- A screen is a table with optional :update(dt), :draw(), :keypressed(key).
-- Keep the active screen on a stack so pause/menu overlays are trivial to pop.
local Stack = require("state_stack") -- see references/state-stack.md for the module
function love.load() Stack.push(require("screens.menu")) end
function love.update(dt) Stack.current():update(dt) end
function love.draw() Stack.current():draw() end
function love.keypressed(key) Stack.current():keypressed(key) end
Pitfalls
- Speed varies with FPS → you forgot
* dt. Every per-frame change to position, timers, or animation must be scaled bydt. love.confplaced inmain.lua→ it silently does nothing. It must live inconf.lua, which LÖVE runs before loading modules.- Colors washed out or invisible → you used 0–255 values. In 11.x,
setColor(255,0,0)clamps to white; usesetColor(1,0,0)orlove.math.colorFromBytes. - Everything tinted after one
setColor→ color is global and persists across draws. Reset withlove.graphics.setColor(1, 1, 1)before drawing text/images you want untinted. - Nothing happens on key release/repeat →
love.keypressed(key, scancode, isrepeat)fires on press (and OS key-repeat); uselove.keyreleasedfor release, and checkisrepeatif you must ignore held-key repeats.
References
- For a complete push/pop screen-state manager (menu → game → pause, with delegated
callbacks), read
references/state-stack.md.
Related skills
save-systems— saving/loading game state (engine-agnostic).input-systems— rebindable, multi-device input architecture.pygame-core/phaser-core— the same loop concepts in other lightweight engines.
Related skills
More from gamedev-skills/awesome-gamedev-agent-skills and the wider catalog.

performance-optimization
Measure, find the bottleneck, apply the right fix—CPU pooling/allocation control or GPU batching/draw calls.

phaser-arcade-physics
Add movement and collision to Phaser games with the lightweight Arcade Physics engine.

phaser-core
Set up and debug Phaser 4 games: Game config, Scene lifecycle, asset loading, cameras, and cross-scene communication.

physics-tuning
Tune game physics for stable, responsive motion—fixed timestep, interpolation, CCD, mass/drag, and collision layers.

pixijs-rendering
Set up PixiJS v8 rendering: async Application, asset loading, scene graph, ticker loop, and pointer events.

platformer
Build 2D platformers with tight jump feel: coyote time, jump buffering, variable height, and level structure.