[{"data":1,"prerenderedAt":778},["ShallowReactive",2],{"navigation_docs":3,"-game-dev-love2d-patterns":64,"-game-dev-love2d-patterns-surround":773},[4,26,43],{"title":5,"path":6,"stem":7,"children":8,"page":25},"Using Parallax","\u002Fusing-parallax","1.using-parallax",[9,13,17,21],{"title":10,"path":11,"stem":12},"Getting Started","\u002Fusing-parallax\u002Fgetting-started","1.using-parallax\u002F0.getting-started",{"title":14,"path":15,"stem":16},"Project Structure","\u002Fusing-parallax\u002Fproject-structure","1.using-parallax\u002F1.project-structure",{"title":18,"path":19,"stem":20},"Working with the Agent","\u002Fusing-parallax\u002Fworking-with-agent","1.using-parallax\u002F2.working-with-agent",{"title":22,"path":23,"stem":24},"Deploying Your Game","\u002Fusing-parallax\u002Fdeploying","1.using-parallax\u002F3.deploying",false,{"title":27,"path":28,"stem":29,"children":30,"page":25},"Features","\u002Ffeatures","2.features",[31,35,39],{"title":32,"path":33,"stem":34},"Feature Overview","\u002Ffeatures\u002Foverview","2.features\u002F0.overview",{"title":36,"path":37,"stem":38},"Roadmap","\u002Ffeatures\u002Froadmap","2.features\u002F1.roadmap",{"title":40,"path":41,"stem":42},"Feature Requests","\u002Ffeatures\u002Frequests","2.features\u002F2.requests",{"title":44,"path":45,"stem":46,"children":47,"page":25},"Game Dev","\u002Fgame-dev","3.game-dev",[48,52,56,60],{"title":49,"path":50,"stem":51},"The Mental Model","\u002Fgame-dev\u002Fmental-model","3.game-dev\u002F0.mental-model",{"title":53,"path":54,"stem":55},"Best Practices","\u002Fgame-dev\u002Fbest-practices","3.game-dev\u002F1.best-practices",{"title":57,"path":58,"stem":59},"Love2D Patterns","\u002Fgame-dev\u002Flove2d-patterns","3.game-dev\u002F2.love2d-patterns",{"title":61,"path":62,"stem":63},"Agent Integration (MCP + Context7)","\u002Fgame-dev\u002Fagent-integration","3.game-dev\u002F3.agent-integration",{"id":65,"title":57,"body":66,"description":766,"extension":767,"links":768,"meta":769,"navigation":122,"path":58,"seo":771,"stem":59,"__hash__":772},"docs\u002F3.game-dev\u002F2.love2d-patterns.md",{"type":67,"value":68,"toc":757},"minimark",[69,73,77,82,94,171,205,209,220,272,276,279,440,444,552,559,620,624,741,744,753],[70,71,57],"h1",{"id":72},"love2d-patterns",[74,75,76],"p",{},"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.",[78,79,81],"h2",{"id":80},"input-handling","Input handling",[74,83,84,85,89,90,93],{},"Centralise input into an ",[86,87,88],"code",{},"input"," table updated each frame. Avoids scattered ",[86,91,92],{},"love.keyboard.isDown"," calls:",[95,96,101],"pre",{"className":97,"code":98,"language":99,"meta":100,"style":100},"language-lua shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","-- input.lua\nlocal input = {}\n\nfunction input.update()\n  input.left   = love.keyboard.isDown('left', 'a')\n  input.right  = love.keyboard.isDown('right', 'd')\n  input.jump   = love.keyboard.isDown('space', 'w', 'up')\n  input.attack = love.keyboard.isDown('z', 'x')\nend\n\nreturn input\n","lua","",[86,102,103,111,117,124,130,136,142,148,154,160,165],{"__ignoreMap":100},[104,105,108],"span",{"class":106,"line":107},"line",1,[104,109,110],{},"-- input.lua\n",[104,112,114],{"class":106,"line":113},2,[104,115,116],{},"local input = {}\n",[104,118,120],{"class":106,"line":119},3,[104,121,123],{"emptyLinePlaceholder":122},true,"\n",[104,125,127],{"class":106,"line":126},4,[104,128,129],{},"function input.update()\n",[104,131,133],{"class":106,"line":132},5,[104,134,135],{},"  input.left   = love.keyboard.isDown('left', 'a')\n",[104,137,139],{"class":106,"line":138},6,[104,140,141],{},"  input.right  = love.keyboard.isDown('right', 'd')\n",[104,143,145],{"class":106,"line":144},7,[104,146,147],{},"  input.jump   = love.keyboard.isDown('space', 'w', 'up')\n",[104,149,151],{"class":106,"line":150},8,[104,152,153],{},"  input.attack = love.keyboard.isDown('z', 'x')\n",[104,155,157],{"class":106,"line":156},9,[104,158,159],{},"end\n",[104,161,163],{"class":106,"line":162},10,[104,164,123],{"emptyLinePlaceholder":122},[104,166,168],{"class":106,"line":167},11,[104,169,170],{},"return input\n",[95,172,174],{"className":97,"code":173,"language":99,"meta":100,"style":100},"-- player.lua\nfunction Player:update(dt)\n  if input.right then self.x = self.x + self.speed * dt end\n  if input.left  then self.x = self.x - self.speed * dt end\n  if input.jump and self.grounded then self:jump() end\nend\n",[86,175,176,181,186,191,196,201],{"__ignoreMap":100},[104,177,178],{"class":106,"line":107},[104,179,180],{},"-- player.lua\n",[104,182,183],{"class":106,"line":113},[104,184,185],{},"function Player:update(dt)\n",[104,187,188],{"class":106,"line":119},[104,189,190],{},"  if input.right then self.x = self.x + self.speed * dt end\n",[104,192,193],{"class":106,"line":126},[104,194,195],{},"  if input.left  then self.x = self.x - self.speed * dt end\n",[104,197,198],{"class":106,"line":132},[104,199,200],{},"  if input.jump and self.grounded then self:jump() end\n",[104,202,203],{"class":106,"line":138},[104,204,159],{},[78,206,208],{"id":207},"timer-tween-system","Timer \u002F tween system",[74,210,211,212,219],{},"Use ",[213,214,218],"a",{"href":215,"rel":216},"https:\u002F\u002Fgithub.com\u002Frxi\u002Fflux",[217],"nofollow","flux"," for tweens and timers:",[95,221,223],{"className":97,"code":222,"language":99,"meta":100,"style":100},"local flux = require('lib.flux')\n\n-- In love.update\nflux.update(dt)\n\n-- Tween a value\nflux.to(self, 0.3, { x = targetX }):ease('quadout')\n\n-- One-shot timer\nflux.to({}, 2.0, {}):oncomplete(function() spawnEnemy() end)\n",[86,224,225,230,234,239,244,248,253,258,262,267],{"__ignoreMap":100},[104,226,227],{"class":106,"line":107},[104,228,229],{},"local flux = require('lib.flux')\n",[104,231,232],{"class":106,"line":113},[104,233,123],{"emptyLinePlaceholder":122},[104,235,236],{"class":106,"line":119},[104,237,238],{},"-- In love.update\n",[104,240,241],{"class":106,"line":126},[104,242,243],{},"flux.update(dt)\n",[104,245,246],{"class":106,"line":132},[104,247,123],{"emptyLinePlaceholder":122},[104,249,250],{"class":106,"line":138},[104,251,252],{},"-- Tween a value\n",[104,254,255],{"class":106,"line":144},[104,256,257],{},"flux.to(self, 0.3, { x = targetX }):ease('quadout')\n",[104,259,260],{"class":106,"line":150},[104,261,123],{"emptyLinePlaceholder":122},[104,263,264],{"class":106,"line":156},[104,265,266],{},"-- One-shot timer\n",[104,268,269],{"class":106,"line":162},[104,270,271],{},"flux.to({}, 2.0, {}):oncomplete(function() spawnEnemy() end)\n",[78,273,275],{"id":274},"simple-pooling-for-bullets-particles","Simple pooling for bullets \u002F particles",[74,277,278],{},"Object pools prevent garbage collection spikes:",[95,280,282],{"className":97,"code":281,"language":99,"meta":100,"style":100},"local BulletPool = {}\nBulletPool.__index = BulletPool\n\nfunction BulletPool.new(size)\n  local pool = setmetatable({ active = {}, inactive = {} }, BulletPool)\n  for i = 1, size do\n    table.insert(pool.inactive, { x=0, y=0, vx=0, vy=0, alive=false })\n  end\n  return pool\nend\n\nfunction BulletPool:spawn(x, y, vx, vy)\n  local b = table.remove(self.inactive) or { alive=false }\n  b.x, b.y, b.vx, b.vy, b.alive = x, y, vx, vy, true\n  table.insert(self.active, b)\nend\n\nfunction BulletPool:update(dt)\n  for i = #self.active, 1, -1 do\n    local b = self.active[i]\n    b.x = b.x + b.vx * dt\n    b.y = b.y + b.vy * dt\n    if outOfBounds(b) then\n      b.alive = false\n      table.remove(self.active, i)\n      table.insert(self.inactive, b)\n    end\n  end\nend\n",[86,283,284,289,294,298,303,308,313,318,323,328,332,336,342,348,354,360,365,370,376,382,388,394,400,406,412,418,424,430,435],{"__ignoreMap":100},[104,285,286],{"class":106,"line":107},[104,287,288],{},"local BulletPool = {}\n",[104,290,291],{"class":106,"line":113},[104,292,293],{},"BulletPool.__index = BulletPool\n",[104,295,296],{"class":106,"line":119},[104,297,123],{"emptyLinePlaceholder":122},[104,299,300],{"class":106,"line":126},[104,301,302],{},"function BulletPool.new(size)\n",[104,304,305],{"class":106,"line":132},[104,306,307],{},"  local pool = setmetatable({ active = {}, inactive = {} }, BulletPool)\n",[104,309,310],{"class":106,"line":138},[104,311,312],{},"  for i = 1, size do\n",[104,314,315],{"class":106,"line":144},[104,316,317],{},"    table.insert(pool.inactive, { x=0, y=0, vx=0, vy=0, alive=false })\n",[104,319,320],{"class":106,"line":150},[104,321,322],{},"  end\n",[104,324,325],{"class":106,"line":156},[104,326,327],{},"  return pool\n",[104,329,330],{"class":106,"line":162},[104,331,159],{},[104,333,334],{"class":106,"line":167},[104,335,123],{"emptyLinePlaceholder":122},[104,337,339],{"class":106,"line":338},12,[104,340,341],{},"function BulletPool:spawn(x, y, vx, vy)\n",[104,343,345],{"class":106,"line":344},13,[104,346,347],{},"  local b = table.remove(self.inactive) or { alive=false }\n",[104,349,351],{"class":106,"line":350},14,[104,352,353],{},"  b.x, b.y, b.vx, b.vy, b.alive = x, y, vx, vy, true\n",[104,355,357],{"class":106,"line":356},15,[104,358,359],{},"  table.insert(self.active, b)\n",[104,361,363],{"class":106,"line":362},16,[104,364,159],{},[104,366,368],{"class":106,"line":367},17,[104,369,123],{"emptyLinePlaceholder":122},[104,371,373],{"class":106,"line":372},18,[104,374,375],{},"function BulletPool:update(dt)\n",[104,377,379],{"class":106,"line":378},19,[104,380,381],{},"  for i = #self.active, 1, -1 do\n",[104,383,385],{"class":106,"line":384},20,[104,386,387],{},"    local b = self.active[i]\n",[104,389,391],{"class":106,"line":390},21,[104,392,393],{},"    b.x = b.x + b.vx * dt\n",[104,395,397],{"class":106,"line":396},22,[104,398,399],{},"    b.y = b.y + b.vy * dt\n",[104,401,403],{"class":106,"line":402},23,[104,404,405],{},"    if outOfBounds(b) then\n",[104,407,409],{"class":106,"line":408},24,[104,410,411],{},"      b.alive = false\n",[104,413,415],{"class":106,"line":414},25,[104,416,417],{},"      table.remove(self.active, i)\n",[104,419,421],{"class":106,"line":420},26,[104,422,423],{},"      table.insert(self.inactive, b)\n",[104,425,427],{"class":106,"line":426},27,[104,428,429],{},"    end\n",[104,431,433],{"class":106,"line":432},28,[104,434,322],{},[104,436,438],{"class":106,"line":437},29,[104,439,159],{},[78,441,443],{"id":442},"screen-shake","Screen shake",[95,445,447],{"className":97,"code":446,"language":99,"meta":100,"style":100},"local shake = { duration = 0, intensity = 0 }\n\nfunction triggerShake(duration, intensity)\n  shake.duration  = duration\n  shake.intensity = intensity\nend\n\nfunction love.update(dt)\n  if shake.duration > 0 then shake.duration = shake.duration - dt end\nend\n\nfunction love.draw()\n  local ox, oy = 0, 0\n  if shake.duration > 0 then\n    ox = (math.random() * 2 - 1) * shake.intensity\n    oy = (math.random() * 2 - 1) * shake.intensity\n  end\n  love.graphics.push()\n  love.graphics.translate(ox, oy)\n  -- draw world\n  love.graphics.pop()\nend\n",[86,448,449,454,458,463,468,473,477,481,486,491,495,499,504,509,514,519,524,528,533,538,543,548],{"__ignoreMap":100},[104,450,451],{"class":106,"line":107},[104,452,453],{},"local shake = { duration = 0, intensity = 0 }\n",[104,455,456],{"class":106,"line":113},[104,457,123],{"emptyLinePlaceholder":122},[104,459,460],{"class":106,"line":119},[104,461,462],{},"function triggerShake(duration, intensity)\n",[104,464,465],{"class":106,"line":126},[104,466,467],{},"  shake.duration  = duration\n",[104,469,470],{"class":106,"line":132},[104,471,472],{},"  shake.intensity = intensity\n",[104,474,475],{"class":106,"line":138},[104,476,159],{},[104,478,479],{"class":106,"line":144},[104,480,123],{"emptyLinePlaceholder":122},[104,482,483],{"class":106,"line":150},[104,484,485],{},"function love.update(dt)\n",[104,487,488],{"class":106,"line":156},[104,489,490],{},"  if shake.duration > 0 then shake.duration = shake.duration - dt end\n",[104,492,493],{"class":106,"line":162},[104,494,159],{},[104,496,497],{"class":106,"line":167},[104,498,123],{"emptyLinePlaceholder":122},[104,500,501],{"class":106,"line":338},[104,502,503],{},"function love.draw()\n",[104,505,506],{"class":106,"line":344},[104,507,508],{},"  local ox, oy = 0, 0\n",[104,510,511],{"class":106,"line":350},[104,512,513],{},"  if shake.duration > 0 then\n",[104,515,516],{"class":106,"line":356},[104,517,518],{},"    ox = (math.random() * 2 - 1) * shake.intensity\n",[104,520,521],{"class":106,"line":362},[104,522,523],{},"    oy = (math.random() * 2 - 1) * shake.intensity\n",[104,525,526],{"class":106,"line":367},[104,527,322],{},[104,529,530],{"class":106,"line":372},[104,531,532],{},"  love.graphics.push()\n",[104,534,535],{"class":106,"line":378},[104,536,537],{},"  love.graphics.translate(ox, oy)\n",[104,539,540],{"class":106,"line":384},[104,541,542],{},"  -- draw world\n",[104,544,545],{"class":106,"line":390},[104,546,547],{},"  love.graphics.pop()\n",[104,549,550],{"class":106,"line":396},[104,551,159],{},[78,553,555,556],{"id":554},"save-load-with-lovefilesystem","Save \u002F load with ",[86,557,558],{},"love.filesystem",[95,560,562],{"className":97,"code":561,"language":99,"meta":100,"style":100},"local function saveGame(data)\n  local encoded = require('lib.json').encode(data)\n  love.filesystem.write('save.json', encoded)\nend\n\nlocal function loadGame()\n  if love.filesystem.getInfo('save.json') then\n    local content = love.filesystem.read('save.json')\n    return require('lib.json').decode(content)\n  end\n  return nil\nend\n",[86,563,564,569,574,579,583,587,592,597,602,607,611,616],{"__ignoreMap":100},[104,565,566],{"class":106,"line":107},[104,567,568],{},"local function saveGame(data)\n",[104,570,571],{"class":106,"line":113},[104,572,573],{},"  local encoded = require('lib.json').encode(data)\n",[104,575,576],{"class":106,"line":119},[104,577,578],{},"  love.filesystem.write('save.json', encoded)\n",[104,580,581],{"class":106,"line":126},[104,582,159],{},[104,584,585],{"class":106,"line":132},[104,586,123],{"emptyLinePlaceholder":122},[104,588,589],{"class":106,"line":138},[104,590,591],{},"local function loadGame()\n",[104,593,594],{"class":106,"line":144},[104,595,596],{},"  if love.filesystem.getInfo('save.json') then\n",[104,598,599],{"class":106,"line":150},[104,600,601],{},"    local content = love.filesystem.read('save.json')\n",[104,603,604],{"class":106,"line":156},[104,605,606],{},"    return require('lib.json').decode(content)\n",[104,608,609],{"class":106,"line":162},[104,610,322],{},[104,612,613],{"class":106,"line":167},[104,614,615],{},"  return nil\n",[104,617,618],{"class":106,"line":338},[104,619,159],{},[78,621,623],{"id":622},"sound-manager","Sound manager",[95,625,627],{"className":97,"code":626,"language":99,"meta":100,"style":100},"-- sounds.lua\nlocal S = {}\nlocal sources = {}\n\nfunction S.load()\n  sources.jump   = love.audio.newSource('assets\u002Faudio\u002Fjump.ogg',   'static')\n  sources.coin   = love.audio.newSource('assets\u002Faudio\u002Fcoin.ogg',   'static')\n  sources.death  = love.audio.newSource('assets\u002Faudio\u002Fdeath.ogg',  'static')\n  sources.music  = love.audio.newSource('assets\u002Faudio\u002Ftheme.ogg',  'stream')\n  sources.music:setLooping(true)\nend\n\nfunction S.play(name)\n  if sources[name] then\n    local clone = sources[name]:clone()\n    clone:play()\n  end\nend\n\nfunction S.playMusic()\n  sources.music:play()\nend\n\nreturn S\n",[86,628,629,634,639,644,648,653,658,663,668,673,678,682,686,691,696,701,706,710,714,718,723,728,732,736],{"__ignoreMap":100},[104,630,631],{"class":106,"line":107},[104,632,633],{},"-- sounds.lua\n",[104,635,636],{"class":106,"line":113},[104,637,638],{},"local S = {}\n",[104,640,641],{"class":106,"line":119},[104,642,643],{},"local sources = {}\n",[104,645,646],{"class":106,"line":126},[104,647,123],{"emptyLinePlaceholder":122},[104,649,650],{"class":106,"line":132},[104,651,652],{},"function S.load()\n",[104,654,655],{"class":106,"line":138},[104,656,657],{},"  sources.jump   = love.audio.newSource('assets\u002Faudio\u002Fjump.ogg',   'static')\n",[104,659,660],{"class":106,"line":144},[104,661,662],{},"  sources.coin   = love.audio.newSource('assets\u002Faudio\u002Fcoin.ogg',   'static')\n",[104,664,665],{"class":106,"line":150},[104,666,667],{},"  sources.death  = love.audio.newSource('assets\u002Faudio\u002Fdeath.ogg',  'static')\n",[104,669,670],{"class":106,"line":156},[104,671,672],{},"  sources.music  = love.audio.newSource('assets\u002Faudio\u002Ftheme.ogg',  'stream')\n",[104,674,675],{"class":106,"line":162},[104,676,677],{},"  sources.music:setLooping(true)\n",[104,679,680],{"class":106,"line":167},[104,681,159],{},[104,683,684],{"class":106,"line":338},[104,685,123],{"emptyLinePlaceholder":122},[104,687,688],{"class":106,"line":344},[104,689,690],{},"function S.play(name)\n",[104,692,693],{"class":106,"line":350},[104,694,695],{},"  if sources[name] then\n",[104,697,698],{"class":106,"line":356},[104,699,700],{},"    local clone = sources[name]:clone()\n",[104,702,703],{"class":106,"line":362},[104,704,705],{},"    clone:play()\n",[104,707,708],{"class":106,"line":367},[104,709,322],{},[104,711,712],{"class":106,"line":372},[104,713,159],{},[104,715,716],{"class":106,"line":378},[104,717,123],{"emptyLinePlaceholder":122},[104,719,720],{"class":106,"line":384},[104,721,722],{},"function S.playMusic()\n",[104,724,725],{"class":106,"line":390},[104,726,727],{},"  sources.music:play()\n",[104,729,730],{"class":106,"line":396},[104,731,159],{},[104,733,734],{"class":106,"line":402},[104,735,123],{"emptyLinePlaceholder":122},[104,737,738],{"class":106,"line":408},[104,739,740],{},"return S\n",[742,743],"hr",{},[74,745,746,747,752],{},"Have a pattern that worked well in your game? ",[213,748,751],{"href":749,"rel":750},"https:\u002F\u002Fgithub.com\u002Fthriv-es\u002Fparallax\u002Fissues\u002Fnew",[217],"Submit it as a docs contribution"," — it may end up here and in the agent's training data.",[754,755,756],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":100,"searchDepth":113,"depth":113,"links":758},[759,760,761,762,763,765],{"id":80,"depth":113,"text":81},{"id":207,"depth":113,"text":208},{"id":274,"depth":113,"text":275},{"id":442,"depth":113,"text":443},{"id":554,"depth":113,"text":764},"Save \u002F load with love.filesystem",{"id":622,"depth":113,"text":623},"Common reusable patterns for Love2D games — copy, adapt, use as agent prompts.","md",null,{"ogImage":770},"\u002Flogo.png",{"title":57,"description":766},"kbLSOUaiwIcONr1nUIMHHwN1B9QgXJ7Q5Kw79jQOuXU",[774,776],{"title":53,"path":54,"stem":55,"description":775,"children":-1},"Proven patterns for Love2D games — what works, what to avoid, and why. Used by the Parallax agent.",{"title":61,"path":62,"stem":63,"description":777,"children":-1},"How the Parallax agent consumes this documentation via MCP and Context7 to give better answers.",1778701173953]