https://github.com/zhuravkovigor/love-scenes
File-system based scene routing for LÖVE 2D games inspired by Next.js
https://github.com/zhuravkovigor/love-scenes
docs love2d lua lua-module routing types
Last synced: 6 months ago
JSON representation
File-system based scene routing for LÖVE 2D games inspired by Next.js
- Host: GitHub
- URL: https://github.com/zhuravkovigor/love-scenes
- Owner: zhuravkovigor
- License: mit
- Created: 2025-07-14T05:38:26.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2025-07-23T09:05:42.000Z (6 months ago)
- Last Synced: 2025-07-31T10:51:49.314Z (6 months ago)
- Topics: docs, love2d, lua, lua-module, routing, types
- Language: Lua
- Homepage:
- Size: 54.7 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Love Scenes 🎮
File-system based scene routing for LÖVE 2D games inspired by Next.js
## Features
- 📁 **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
## Installation
### LuaRocks (Recommended)
```bash
luarocks install love-scenes
```
### Git/GitHub
Install directly from GitHub:
```bash
luarocks install --server=https://luarocks.org/manifests/zhuravkovigor love-scenes
```
Or clone and install manually:
```bash
git clone https://github.com/zhuravkovigor/love-scenes.git
cd love-scenes
luarocks make love-scenes-1.0-1.rockspec
```
### Manual Installation
1. Download the latest release from [GitHub Releases](https://github.com/zhuravkovigor/love-scenes/releases)
2. Extract the files to your project directory
3. Require the library in your `main.lua`
### Development Installation
Clone the repository and install locally:
```bash
git clone https://github.com/zhuravkovigor/love-scenes.git
cd love-scenes
make install
```
## Quick Start
### 1. Basic Setup
```lua
-- main.lua
local LoveScenes = require('love-scenes')
function love.load()
-- Initialize with default settings
LoveScenes.init()
-- Navigate to the main scene
LoveScenes.navigate('/')
end
function love.update(dt)
LoveScenes.update(dt)
end
function love.draw()
LoveScenes.draw()
end
-- Forward input events
function love.keypressed(key, scancode, isrepeat)
LoveScenes.keypressed(key, scancode, isrepeat)
end
```
### 2. Create Your First Scene
```lua
-- scenes/index.lua (Main menu at route "/")
local scene = {}
function scene:load(params)
self.title = "My Awesome Game"
end
function scene:update(dt)
-- Scene update logic
end
function scene:draw()
love.graphics.printf(self.title, 0, 100, love.graphics.getWidth(), "center")
end
function scene:keypressed(key)
if key == "space" then
require('love-scenes').navigate('/game')
end
end
return scene
```
### 3. Add More Scenes
```lua
-- scenes/game/index.lua (Game scene at route "/game")
local scene = {}
function scene:load(params)
self.player = {x = 100, y = 100}
end
function scene:update(dt)
if love.keyboard.isDown("left") then
self.player.x = self.player.x - 100 * dt
end
if love.keyboard.isDown("right") then
self.player.x = self.player.x + 100 * dt
end
end
function scene:draw()
love.graphics.circle("fill", self.player.x, self.player.y, 20)
end
return scene
```
## File Structure
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/audio
```
## Routing Examples
### Static Routes
- `scenes/index.lua` → `/` (root)
- `scenes/about/index.lua` → `/about`
- `scenes/game/level/index.lua` → `/game/level`
### Dynamic Routes
- `scenes/user/[id].lua` → `/user/123`, `/user/abc`
- `scenes/level/[level].lua` → `/level/1-1`, `/level/forest`
- `scenes/shop/[category].lua` → `/shop/weapons`, `/shop/armor`
- `scenes/post/[slug]/[id].lua` → `/post/hello-world/123`
```lua
-- scenes/level/[level].lua
local scene = {}
function scene:load(params)
self.levelId = params.level -- Access the dynamic parameter
print("Loading level:", self.levelId)
-- Different logic based on level
if self.levelId == "boss-1" then
self:loadBossLevel()
else
self:loadNormalLevel()
end
end
return scene
```
## Layouts
Layouts wrap scenes and provide common UI elements:
```lua
-- scenes/layout.lua (Root layout for all scenes)
local layout = {}
function layout:draw(drawScene)
-- Draw header
love.graphics.setColor(0.2, 0.2, 0.2)
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), 60)
-- Draw scene content
love.graphics.push()
love.graphics.translate(0, 60)
drawScene() -- This renders the current scene
love.graphics.pop()
-- Draw footer
love.graphics.setColor(0.2, 0.2, 0.2)
love.graphics.rectangle("fill", 0, love.graphics.getHeight() - 40,
love.graphics.getWidth(), 40)
end
return layout
```
## Navigation
Navigate between scenes using the `navigate` function:
```lua
local LoveScenes = require('love-scenes')
-- Navigate to different routes
LoveScenes.navigate('/')
LoveScenes.navigate('/game')
LoveScenes.navigate('/profile/123')
LoveScenes.navigate('/settings/audio')
-- Navigate with additional parameters
LoveScenes.navigate('/user/123', {tab = "settings"})
```
## Configuration
All configuration parameters are optional. Love Scenes works out of the box with sensible defaults:
```lua
-- Minimal setup - all parameters are optional
LoveScenes.init()
-- With custom configuration
LoveScenes.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)
})
```
### Configuration Options
| 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 |
## Scene Lifecycle
Scenes have several lifecycle methods:
```lua
local scene = {}
function scene:load(params)
-- Called when scene is created and loaded
-- Access route parameters via params
-- Initialize scene data, load assets, set up state
end
function scene:onEnter(next)
-- Called when navigating to this scene
-- next is an optional callback for controlling transition timing
if next then
-- Perform any animations or async setup
-- Call next() when ready for the scene to become active
next()
end
end
function scene:onLeave(next)
-- Called when leaving this scene
-- next is an optional callback for controlling transition timing
if next then
-- Perform cleanup animations
-- Call next() when ready to complete the transition
next()
end
end
function scene:update(dt)
-- Called every frame
end
function scene:draw()
-- Called every frame for rendering
end
-- LÖVE 2D callbacks are automatically forwarded
function scene:keypressed(key, scancode, isrepeat)
-- Handle input
end
function scene:mousepressed(x, y, button, isTouch, presses)
-- Handle mouse input
end
return scene
```
### Example with Transition Animation
```lua
local scene = {}
local fade_alpha = 1
function scene:onEnter(next)
if next then
-- Start fade-in animation
fade_alpha = 0
-- Don't block transition, let it proceed immediately
next()
else
fade_alpha = 1
end
end
function scene:update(dt)
-- Simple fade-in animation
if fade_alpha < 1 then
fade_alpha = math.min(1, fade_alpha + dt * 2)
end
end
function scene:draw()
love.graphics.push()
love.graphics.setColor(1, 1, 1, fade_alpha)
-- Draw scene content with fade effect
love.graphics.printf("Scene Content", 0, 100, love.graphics.getWidth(), "center")
love.graphics.pop()
end
return scene
```
## Layout Lifecycle
Layouts also have lifecycle methods:
```lua
local layout = {}
function layout:load()
-- Called when layout is created
end
function layout: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 timing
if next then
next()
end
end
function layout:onLeave(next)
-- Called when leaving this layout
-- next: optional callback for controlling transition timing
if next then
next()
end
end
-- Called when leaving this layout
end
function layout:update(dt)
-- Called every frame before scene update
end
function layout:draw(drawScene)
-- Called every frame for rendering
-- drawScene() renders the current scene
end
return layout
```
## API Reference
### LoveScenes.init(config)
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:**
```lua
-- Minimal setup
LoveScenes.init()
-- With custom scenes directory
LoveScenes.init({ scenesPath = "game-scenes" })
-- With debug mode
LoveScenes.init({ debugMode = true })
```
### LoveScenes.navigate(path, params)
Navigate to a scene.
**Parameters:**
- `path` (string): Route path (e.g., "/", "/game", "/user/123")
- `params` (table, optional): Additional parameters to pass to the scene
### LoveScenes.getCurrentScene()
Get the current active scene instance.
### LoveScenes.getCurrentLayout()
Get the current active layout instance.
## Complete Examples
### Basic Game Setup
Here's a complete example of a simple game with multiple scenes:
```lua
-- main.lua
local LoveScenes = require('love-scenes')
function love.load()
LoveScenes.init({
debugMode = true -- Enable debug logging
})
-- Start at main menu
LoveScenes.navigate('/')
end
function love.update(dt)
LoveScenes.update(dt)
end
function love.draw()
LoveScenes.draw()
end
-- Forward all LÖVE callbacks
function love.keypressed(key, scancode, isrepeat)
LoveScenes.keypressed(key, scancode, isrepeat)
end
function love.mousepressed(x, y, button, isTouch, presses)
LoveScenes.mousepressed(x, y, button, isTouch, presses)
end
```
```lua
-- scenes/index.lua (Main Menu)
local LoveScenes = require('love-scenes')
local scene = {}
function scene:load()
self.title = "My Awesome Game"
self.menuItems = {"Start Game", "Settings", "Quit"}
self.selectedIndex = 1
end
function scene:update(dt)
-- Menu logic here
end
function scene:draw()
-- Draw title
love.graphics.setFont(love.graphics.newFont(32))
love.graphics.printf(self.title, 0, 100, love.graphics.getWidth(), "center")
-- Draw menu items
love.graphics.setFont(love.graphics.newFont(16))
for i, item in ipairs(self.menuItems) do
local y = 200 + (i - 1) * 40
local color = i == self.selectedIndex and {1, 1, 0} or {1, 1, 1}
love.graphics.setColor(color)
love.graphics.printf(item, 0, y, love.graphics.getWidth(), "center")
end
love.graphics.setColor(1, 1, 1) -- Reset color
end
function scene:keypressed(key)
if key == "up" then
self.selectedIndex = math.max(1, self.selectedIndex - 1)
elseif key == "down" then
self.selectedIndex = math.min(#self.menuItems, self.selectedIndex + 1)
elseif key == "return" then
if self.selectedIndex == 1 then
LoveScenes.navigate('/game')
elseif self.selectedIndex == 2 then
LoveScenes.navigate('/settings')
elseif self.selectedIndex == 3 then
love.event.quit()
end
end
end
return scene
```
```lua
-- scenes/game/index.lua (Game Scene)
local LoveScenes = require('love-scenes')
local scene = {}
function scene:load()
self.player = {
x = 400,
y = 300,
speed = 200
}
self.enemies = {}
self.score = 0
end
function scene:update(dt)
-- Player movement
if love.keyboard.isDown("left", "a") then
self.player.x = self.player.x - self.player.speed * dt
end
if love.keyboard.isDown("right", "d") then
self.player.x = self.player.x + self.player.speed * dt
end
if love.keyboard.isDown("up", "w") then
self.player.y = self.player.y - self.player.speed * dt
end
if love.keyboard.isDown("down", "s") then
self.player.y = self.player.y + self.player.speed * dt
end
-- Keep player on screen
self.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))
end
function scene:draw()
-- Draw player
love.graphics.setColor(0, 1, 0) -- Green
love.graphics.circle("fill", self.player.x, self.player.y, 20)
-- Draw UI
love.graphics.setColor(1, 1, 1) -- White
love.graphics.print("Score: " .. self.score, 10, 10)
love.graphics.print("Press ESC to return to menu", 10, 30)
end
function scene:keypressed(key)
if key == "escape" then
LoveScenes.navigate('/')
end
end
return scene
```
### Dynamic Routes Example
```lua
-- scenes/profile/[id].lua (Dynamic User Profile)
local scene = {}
function scene:load(params)
self.userId = params.id
self.userInfo = self:loadUserInfo(self.userId)
end
function scene:loadUserInfo(id)
-- Simulate loading user data
return {
name = "User " .. id,
level = math.random(1, 100),
score = math.random(1000, 99999)
}
end
function scene: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")
end
function scene:keypressed(key)
if key == "escape" then
require('love-scenes').navigate('/')
end
end
return scene
```
Now you can navigate to different user profiles:
- `LoveScenes.navigate('/profile/123')`
- `LoveScenes.navigate('/profile/player1')`
- `LoveScenes.navigate('/profile/admin')`
## Tips and Best Practices
### 1. Scene Organization
- Keep related scenes in subdirectories (e.g., `scenes/game/`, `scenes/menu/`)
- Use descriptive names for dynamic routes: `[playerId].lua`, `[levelName].lua`
### 2. State Management
- Initialize all scene data in the `load()` method
- Use `onEnter()` and `onLeave()` for cleanup and transitions
- Store global state outside of individual scenes if needed
### 3. Performance
- Preload assets in `load()` method
- Use `onLeave()` to clean up resources
- Consider using layouts for shared UI elements
### 4. Navigation
- Use absolute paths for navigation: `/game`, `/settings/audio`
- Pass additional data via the params parameter
- Handle navigation errors gracefully
## Examples
Check out the `scenes/` 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
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
MIT License - see LICENSE file for details.