- Notifications
You must be signed in to change notification settings - Fork0
File-system based scene routing for LÖVE 2D games inspired by Next.js
License
zhuravkovigor/love-scenes
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
File-system based scene routing for LÖVE 2D games inspired by Next.js
- 📁File-system based routing - Create scenes by simply adding files to your scenes directory
- 🔀Dynamic routes - Support for parameterized routes like
[id].lua - 📱Layout system - Wrap scenes with reusable layouts
- 🎯Simple API - Easy to integrate into existing LÖVE 2D projects
- 🔧Configurable - Customize scenes directory and behavior
luarocks install love-scenes
Install directly from GitHub:
luarocks install --server=https://luarocks.org/manifests/zhuravkovigor love-scenes
Or clone and install manually:
git clone https://github.com/zhuravkovigor/love-scenes.gitcd love-scenesluarocks make love-scenes-1.0-1.rockspec- Download the latest release fromGitHub Releases
- Extract the files to your project directory
- Require the library in your
main.lua
Clone the repository and install locally:
git clone https://github.com/zhuravkovigor/love-scenes.gitcd love-scenesmake install-- main.lualocalLoveScenes=require('love-scenes')functionlove.load()-- Initialize with default settingsLoveScenes.init()-- Navigate to the main sceneLoveScenes.navigate('/')endfunctionlove.update(dt)LoveScenes.update(dt)endfunctionlove.draw()LoveScenes.draw()end-- Forward input eventsfunctionlove.keypressed(key,scancode,isrepeat)LoveScenes.keypressed(key,scancode,isrepeat)end
-- scenes/index.lua (Main menu at route "/")localscene= {}functionscene:load(params)self.title="My Awesome Game"endfunctionscene:update(dt)-- Scene update logicendfunctionscene:draw()love.graphics.printf(self.title,0,100,love.graphics.getWidth(),"center")endfunctionscene:keypressed(key)ifkey=="space"thenrequire('love-scenes').navigate('/game')endendreturnscene
-- scenes/game/index.lua (Game scene at route "/game")localscene= {}functionscene:load(params)self.player= {x=100,y=100}endfunctionscene:update(dt)iflove.keyboard.isDown("left")thenself.player.x=self.player.x-100*dtendiflove.keyboard.isDown("right")thenself.player.x=self.player.x+100*dtendendfunctionscene:draw()love.graphics.circle("fill",self.player.x,self.player.y,20)endreturnscene
Love Scenes uses a file-system based routing approach similar to Next.js:
scenes/├── index.lua # Route: /├── layout.lua # Layout for all scenes├── game/│ ├── index.lua # Route: /game│ └── layout.lua # Layout for /game/* routes├── level/│ └── [level].lua # Route: /level/1-1, /level/forest, etc.├── profile/│ └── [id].lua # Route: /profile/123, /profile/abc, etc.├── shop/│ └── [category].lua # Route: /shop/weapons, /shop/armor, etc.└── settings/ ├── index.lua # Route: /settings └── audio/ └── index.lua # Route: /settings/audioscenes/index.lua→/(root)scenes/about/index.lua→/aboutscenes/game/level/index.lua→/game/level
scenes/user/[id].lua→/user/123,/user/abcscenes/level/[level].lua→/level/1-1,/level/forestscenes/shop/[category].lua→/shop/weapons,/shop/armorscenes/post/[slug]/[id].lua→/post/hello-world/123
-- scenes/level/[level].lualocalscene= {}functionscene:load(params)self.levelId=params.level-- Access the dynamic parameterprint("Loading level:",self.levelId)-- Different logic based on levelifself.levelId=="boss-1"thenself:loadBossLevel()elseself:loadNormalLevel()endendreturnscene
Layouts wrap scenes and provide common UI elements:
-- scenes/layout.lua (Root layout for all scenes)locallayout= {}functionlayout:draw(drawScene)-- Draw headerlove.graphics.setColor(0.2,0.2,0.2)love.graphics.rectangle("fill",0,0,love.graphics.getWidth(),60)-- Draw scene contentlove.graphics.push()love.graphics.translate(0,60)drawScene()-- This renders the current scenelove.graphics.pop()-- Draw footerlove.graphics.setColor(0.2,0.2,0.2)love.graphics.rectangle("fill",0,love.graphics.getHeight()-40,love.graphics.getWidth(),40)endreturnlayout
Navigate between scenes using thenavigate function:
localLoveScenes=require('love-scenes')-- Navigate to different routesLoveScenes.navigate('/')LoveScenes.navigate('/game')LoveScenes.navigate('/profile/123')LoveScenes.navigate('/settings/audio')-- Navigate with additional parametersLoveScenes.navigate('/user/123', {tab="settings"})
All configuration parameters are optional. Love Scenes works out of the box with sensible defaults:
-- Minimal setup - all parameters are optionalLoveScenes.init()-- With custom configurationLoveScenes.init({scenesPath="scenes",-- Directory containing scenes (default: "scenes")autoLoad=true,-- Automatically load scenes on init (default: true)enableLayouts=true,-- Enable layout system (default: true)debugMode=false-- Enable debug logging (default: false)})
| Parameter | Type | Default | Description |
|---|---|---|---|
scenesPath | string | "scenes" | Directory containing your scene files |
autoLoad | boolean | true | Automatically scan and load scenes on initialization |
enableLayouts | boolean | true | Enable the layout system for wrapping scenes |
debugMode | boolean | false | Enable debug logging to console |
Scenes have several lifecycle methods:
localscene= {}functionscene:load(params)-- Called when scene is created and loaded-- Access route parameters via params-- Initialize scene data, load assets, set up stateendfunctionscene:onEnter(next)-- Called when navigating to this scene-- next is an optional callback for controlling transition timingifnextthen-- Perform any animations or async setup-- Call next() when ready for the scene to become activenext()endendfunctionscene:onLeave(next)-- Called when leaving this scene-- next is an optional callback for controlling transition timingifnextthen-- Perform cleanup animations-- Call next() when ready to complete the transitionnext()endendfunctionscene:update(dt)-- Called every frameendfunctionscene:draw()-- Called every frame for renderingend-- LÖVE 2D callbacks are automatically forwardedfunctionscene:keypressed(key,scancode,isrepeat)-- Handle inputendfunctionscene:mousepressed(x,y,button,isTouch,presses)-- Handle mouse inputendreturnscene
localscene= {}localfade_alpha=1functionscene:onEnter(next)ifnextthen-- Start fade-in animationfade_alpha=0-- Don't block transition, let it proceed immediatelynext()elsefade_alpha=1endendfunctionscene:update(dt)-- Simple fade-in animationiffade_alpha<1thenfade_alpha=math.min(1,fade_alpha+dt*2)endendfunctionscene:draw()love.graphics.push()love.graphics.setColor(1,1,1,fade_alpha)-- Draw scene content with fade effectlove.graphics.printf("Scene Content",0,100,love.graphics.getWidth(),"center")love.graphics.pop()endreturnscene
Layouts also have lifecycle methods:
locallayout= {}functionlayout:load()-- Called when layout is createdendfunctionlayout:onEnter(scene,next)-- Called when a scene using this layout is entered-- scene: the scene that will be rendered-- next: optional callback for controlling transition timingifnextthennext()endendfunctionlayout:onLeave(next)-- Called when leaving this layout-- next: optional callback for controlling transition timingifnextthennext()endend-- Called when leaving this layoutendfunctionlayout:update(dt)-- Called every frame before scene updateendfunctionlayout:draw(drawScene)-- Called every frame for rendering-- drawScene() renders the current sceneendreturnlayout
Initialize the library with optional configuration.All parameters are optional - the library works with sensible defaults.
Parameters:
config(table, optional): Configuration options. If omitted, uses default values.scenesPath(string, optional): Directory containing scenes (default: "scenes")autoLoad(boolean, optional): Automatically load scenes on init (default: true)enableLayouts(boolean, optional): Enable layout system (default: true)debugMode(boolean, optional): Enable debug logging (default: false)
Examples:
-- Minimal setupLoveScenes.init()-- With custom scenes directoryLoveScenes.init({scenesPath="game-scenes"})-- With debug modeLoveScenes.init({debugMode=true })
Navigate to a scene.
Parameters:
path(string): Route path (e.g., "/", "/game", "/user/123")params(table, optional): Additional parameters to pass to the scene
Get the current active scene instance.
Get the current active layout instance.
Here's a complete example of a simple game with multiple scenes:
-- main.lualocalLoveScenes=require('love-scenes')functionlove.load()LoveScenes.init({debugMode=true-- Enable debug logging })-- Start at main menuLoveScenes.navigate('/')endfunctionlove.update(dt)LoveScenes.update(dt)endfunctionlove.draw()LoveScenes.draw()end-- Forward all LÖVE callbacksfunctionlove.keypressed(key,scancode,isrepeat)LoveScenes.keypressed(key,scancode,isrepeat)endfunctionlove.mousepressed(x,y,button,isTouch,presses)LoveScenes.mousepressed(x,y,button,isTouch,presses)end
-- scenes/index.lua (Main Menu)localLoveScenes=require('love-scenes')localscene= {}functionscene:load()self.title="My Awesome Game"self.menuItems= {"Start Game","Settings","Quit"}self.selectedIndex=1endfunctionscene:update(dt)-- Menu logic hereendfunctionscene:draw()-- Draw titlelove.graphics.setFont(love.graphics.newFont(32))love.graphics.printf(self.title,0,100,love.graphics.getWidth(),"center")-- Draw menu itemslove.graphics.setFont(love.graphics.newFont(16))fori,iteminipairs(self.menuItems)dolocaly=200+ (i-1)*40localcolor=i==self.selectedIndexand {1,1,0}or {1,1,1}love.graphics.setColor(color)love.graphics.printf(item,0,y,love.graphics.getWidth(),"center")endlove.graphics.setColor(1,1,1)-- Reset colorendfunctionscene:keypressed(key)ifkey=="up"thenself.selectedIndex=math.max(1,self.selectedIndex-1)elseifkey=="down"thenself.selectedIndex=math.min(#self.menuItems,self.selectedIndex+1)elseifkey=="return"thenifself.selectedIndex==1thenLoveScenes.navigate('/game')elseifself.selectedIndex==2thenLoveScenes.navigate('/settings')elseifself.selectedIndex==3thenlove.event.quit()endendendreturnscene
-- scenes/game/index.lua (Game Scene)localLoveScenes=require('love-scenes')localscene= {}functionscene:load()self.player= {x=400,y=300,speed=200 }self.enemies= {}self.score=0endfunctionscene:update(dt)-- Player movementiflove.keyboard.isDown("left","a")thenself.player.x=self.player.x-self.player.speed*dtendiflove.keyboard.isDown("right","d")thenself.player.x=self.player.x+self.player.speed*dtendiflove.keyboard.isDown("up","w")thenself.player.y=self.player.y-self.player.speed*dtendiflove.keyboard.isDown("down","s")thenself.player.y=self.player.y+self.player.speed*dtend-- Keep player on screenself.player.x=math.max(20,math.min(love.graphics.getWidth()-20,self.player.x))self.player.y=math.max(20,math.min(love.graphics.getHeight()-20,self.player.y))endfunctionscene:draw()-- Draw playerlove.graphics.setColor(0,1,0)-- Greenlove.graphics.circle("fill",self.player.x,self.player.y,20)-- Draw UIlove.graphics.setColor(1,1,1)-- Whitelove.graphics.print("Score:"..self.score,10,10)love.graphics.print("Press ESC to return to menu",10,30)endfunctionscene:keypressed(key)ifkey=="escape"thenLoveScenes.navigate('/')endendreturnscene
-- scenes/profile/[id].lua (Dynamic User Profile)localscene= {}functionscene:load(params)self.userId=params.idself.userInfo=self:loadUserInfo(self.userId)endfunctionscene:loadUserInfo(id)-- Simulate loading user datareturn {name="User"..id,level=math.random(1,100),score=math.random(1000,99999) }endfunctionscene:draw()love.graphics.printf("User Profile",0,50,love.graphics.getWidth(),"center")love.graphics.printf("ID:"..self.userId,0,100,love.graphics.getWidth(),"center")love.graphics.printf("Name:"..self.userInfo.name,0,130,love.graphics.getWidth(),"center")love.graphics.printf("Level:"..self.userInfo.level,0,160,love.graphics.getWidth(),"center")love.graphics.printf("Score:"..self.userInfo.score,0,190,love.graphics.getWidth(),"center")love.graphics.printf("Press ESC to go back",0,250,love.graphics.getWidth(),"center")endfunctionscene:keypressed(key)ifkey=="escape"thenrequire('love-scenes').navigate('/')endendreturnscene
Now you can navigate to different user profiles:
LoveScenes.navigate('/profile/123')LoveScenes.navigate('/profile/player1')LoveScenes.navigate('/profile/admin')
- Keep related scenes in subdirectories (e.g.,
scenes/game/,scenes/menu/) - Use descriptive names for dynamic routes:
[playerId].lua,[levelName].lua
- Initialize all scene data in the
load()method - Use
onEnter()andonLeave()for cleanup and transitions - Store global state outside of individual scenes if needed
- Preload assets in
load()method - Use
onLeave()to clean up resources - Consider using layouts for shared UI elements
- Use absolute paths for navigation:
/game,/settings/audio - Pass additional data via the params parameter
- Handle navigation errors gracefully
Check out thescenes/ directory in this repository for complete examples including:
- Main menu with navigation
- Game scene with player movement
- Settings scene with configurable options
- Dynamic profile scenes with URL parameters
- Layout system with header and footer
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License - see LICENSE file for details.
About
File-system based scene routing for LÖVE 2D games inspired by Next.js
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.