├── .gitignore ├── LICENSE.txt ├── README.md └── scenery.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Paltze and Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scenery - A dead simple Love2D Scene/State Manager 2 | 3 | ![image](https://img.shields.io/static/v1?label=L%C3%B6ve2D&message=11.5&labelColor=e64998&color=28abe3&style=for-the-badge) 4 | 5 | Scenery is a dead simple Scene/State Manager for Love2D. 6 | 7 | Scenes (or States) are a very popular organising system for games. Scenery is a simple to use and lightweight implementation of the system for Love2D. 8 | 9 | ## Installation 10 | 11 | Just grab the `scenery.lua` from this repository and `require` it in you `main.lua` file. 12 | 13 | ## Usage 14 | 15 | After initialization of Scenery (described in detail below) just call the used callbacks in corresponding Love2D callbacks. 16 | 17 | For example: 18 | ```lua 19 | local SceneryInit = require("path.to.scenery") 20 | local scenery = SceneryInit(...) 21 | 22 | function love.load() 23 | scenery:load() 24 | end 25 | 26 | function love.draw() 27 | scenery:draw() 28 | end 29 | 30 | function love.update(dt) 31 | scenery:update(dt) 32 | end 33 | ``` 34 | Also, the `scenery` instance has a `hook` method on it, which will do the boilerplate for you. The above example can be shortened as: 35 | ```lua 36 | local SceneryInit = require("path.to.scenery") 37 | local scenery = SceneryInit(...) 38 | scenery:hook(love) 39 | ``` 40 | 41 | > Scenery supports all [Love2D 11.5 callbacks](https://love2d.org/wiki/Category:Callbacks). 42 | 43 | > The `hook` method optionally accepts a second argument, a table, with the callbacks which will be hooked. eg `{ "load", "draw", "update" }` 44 | 45 | ### Scenes 46 | 47 | Scenes are, in Scenery, just tables returned by a file. Each scene must have a separate file for itself and return a table containing all the callback methods. Scene callbacks methods are exactly the same as Love callback methods, except `load`, which has an optional argument containing data transferred by other scenes. 48 | 49 | An Example Scene: 50 | ```lua 51 | local game = {} 52 | 53 | function game:load() 54 | print("Scenery is awesome") 55 | end 56 | 57 | function game:draw() 58 | love.graphics.print("Scenery makes life easier", 200, 300) 59 | end 60 | 61 | function game:update(dt) 62 | print("You agree, don't you?") 63 | end 64 | 65 | return game 66 | ``` 67 | 68 | ### Loading the Scenes 69 | 70 | #### Automatic Loading 71 | Scenery can automatically load your scenes for you. `scenery.lua` returns a function that accepts a default scene as first parameter and path to the folder containing scenes as an optional second parameter. If no path is supplied Scenery will look into `scenes` folder from the folder containing your `main.lua` file for scenes. 72 | 73 | Example: 74 | ```lua 75 | local SceneryInit = require("path.to.scenery") 76 | local scenery = SceneryInit("scene", "path/to/scenes") 77 | ``` 78 | 79 | > The filename of the file (without the extension) containing scene will be considered the scene key. 80 | 81 | > ⚠️ If your file name has periods (.) before the file extension (eg `game.scene.lua`) then only the string before the first period (ie `game` in the above case) will be considered the scene key. 82 | 83 | #### Manual Loading 84 | 85 | Scenery can also manually load you scenes. The function returned by `scenery.lua` can accept multiple tables, each for one scene. 86 | You can have the following properties in the table: 87 | 88 | Property | Description 89 | ---------|------------ 90 | `path` | The path to the file returning a table structured in the form a scene table. 91 | `key` | A unique string identifying the scene. 92 | `default` (optional)| A boolean value representing the default scene. Must not be `true` on more than one scene. If omitted the first scene in the arguments will be considered default. 93 | 94 | Example: 95 | ```lua 96 | local SceneryInit = require("path.to.scenery") 97 | local scenery = SceneryInit( 98 | { path = "path.to.scene1"; key = "scene1"; default = "true" }, 99 | { path = "path.to.scene2"; key = "scene2"; } 100 | ) 101 | ``` 102 | 103 | ### Changing Scenes 104 | 105 | Changing scenes in Scenery is very simple. Scenery creates a `setScene` method on the scene table to change scenes. The function accepts scene key as first parameter and an optional argument which will be passed to the `load` callback of the new scene. It is as simple as: 106 | 107 | ```lua 108 | function scene1:load() 109 | self.setScene("scene2", { score = 52 }) 110 | end 111 | ``` 112 | 113 | Then you can access the score in menu scene by: 114 | ```lua 115 | function scene2:load(args) 116 | print(args.score) -- prints 52 117 | end 118 | ``` 119 | 120 | ## Contributing 121 | 122 | If you have found a bug or have any suggestion, feel free to open an issue. If you fixed a bug or added a new feature, add a pull request. 123 | 124 | ## License 125 | 126 | The project is licensed under the MIT License. A copy of the license can be found in the repository by the name of LICENSE.txt 127 | -------------------------------------------------------------------------------- /scenery.lua: -------------------------------------------------------------------------------- 1 | local Scenery = { 2 | __NAME = "Scenery"; 3 | __VERSION = "0.4"; 4 | __DESCRIPTION = "Scenery - A dead simple Love2D SceneManager"; 5 | __LICENSE = [[ 6 | MIT License 7 | 8 | Copyright (c) 2024 Paltze and Contributors 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | ]] 28 | } 29 | 30 | -- Split file into name and extension 31 | local split = function(inputstr, sep) 32 | local t = {} 33 | for res in string.gmatch(inputstr, "([^"..sep.."]+)") do table.insert(t, res) end 34 | return t[1], t[#t] 35 | end 36 | 37 | -- Automatically load scenes from the given directory 38 | local autoLoad = function(directory) 39 | -- Get the files in the directory 40 | local files = love.filesystem.getDirectoryItems(directory) 41 | local scenes = {} 42 | 43 | for _, value in ipairs(files) do 44 | local file, ext = split(value, ".") 45 | 46 | -- Require scene 47 | if ext == file then 48 | local info = love.filesystem.getInfo(directory .. "/" .. file) 49 | 50 | -- Check if item is a directory 51 | if info and (info.type == "directory" or info.type == "symlink") then 52 | info = love.filesystem.getInfo(directory .. "/" .. file .. "/init.lua") 53 | 54 | -- Check for the init file 55 | if info and info.type == "file" then 56 | scenes[file] = require(directory .. "." .. file) 57 | end 58 | end 59 | elseif ext == "lua" and file ~= "conf" and file ~= "main" then 60 | scenes[file] = require(directory .. "." .. file) 61 | end 62 | end 63 | 64 | return scenes 65 | end 66 | 67 | -- Iterate over the passed tables 68 | local manualLoad = function(config) 69 | local scenes = {} 70 | local currentScene 71 | 72 | -- Loop through the parameters 73 | for _, value in ipairs(config) do 74 | -- Check if path is string 75 | assert(type(value.path) == "string", "Given path not a string.") 76 | 77 | -- Check if key is number or string 78 | assert(type(value.key) == "number" or type(value.key) == "string", "Given key not a number or string.") 79 | 80 | --Check for duplicate scene keys 81 | assert(not scenes[value.key], "Duplicate scene keys provided") 82 | 83 | scenes[value.key] = require(value.path) 84 | 85 | -- Check if default scene present 86 | if value.default then 87 | assert(not currentScene, "More than one default scene defined") 88 | currentScene = value.key 89 | end 90 | end 91 | 92 | -- If no default scene, set first scene as default 93 | if not currentScene then 94 | currentScene = config[1].key 95 | end 96 | 97 | return scenes, currentScene 98 | end 99 | 100 | local checkScenePresent = function(scene, sceneTable) 101 | local present = false 102 | 103 | for index, _ in pairs(sceneTable) do 104 | if index == scene then 105 | present = true 106 | end 107 | end 108 | 109 | return present 110 | end 111 | 112 | -- The base Scenery Class 113 | Scenery.__index = Scenery 114 | 115 | function Scenery.init(...) 116 | -- Set metatable to create a class 117 | local this = setmetatable({}, Scenery) 118 | 119 | -- Get all the parameters 120 | local config = { ... } 121 | 122 | -- Get scenes 123 | if config[1] == nil then 124 | error("No default scene supplied", 2) 125 | elseif type(config[1]) == "table" then 126 | this.scenes, this.currentscene = manualLoad(config) 127 | elseif type(config[1]) =="string" then 128 | this.scenes = autoLoad(config[2] or "scenes") 129 | assert(checkScenePresent(config[1], this.scenes), "No scene '" .. config[1] .. "' present") 130 | this.currentscene = config[1] 131 | else 132 | error("Unknown token '" .. config[1] .. "'", 2) 133 | end 134 | 135 | -- This function is available for all scene. 136 | function this.setScene(key, data) 137 | assert(this.scenes[key], "No such scene '" .. key .. "'") 138 | this.currentscene = key 139 | if this.scenes[this.currentscene].load then 140 | this.scenes[this.currentscene]:load(data) 141 | end 142 | end 143 | 144 | for _, value in pairs(this.scenes) do 145 | value["setScene"] = this.setScene 146 | end 147 | 148 | -- All the callbacks available in Love 11.4 as described on https://love2d.org/wiki/Category:Callbacks 149 | local loveCallbacks = { "load", "draw", "update" } -- Except these three. 150 | for k in pairs(love.handlers) do 151 | table.insert(loveCallbacks, k) 152 | end 153 | 154 | -- Loop through the callbacks creating a function with same name on the base class 155 | for _, value in ipairs(loveCallbacks) do 156 | this[value] = function(self, ...) 157 | assert(type(self.scenes[self.currentscene]) == "table", "Scene '" .. self.currentscene .. "' not a valid scene.") 158 | 159 | -- Check if the function exists on the class 160 | if self.scenes[self.currentscene][value] then 161 | return self.scenes[self.currentscene][value](self.scenes[self.currentscene], ...) 162 | end 163 | end 164 | end 165 | 166 | -- Inject callbacks into a table. Examples: 167 | -- scenery:hook(love) 168 | -- scenery:hook(love, { 'load', 'update', 'draw' }) 169 | function this.hook(self, t, keys) 170 | assert(type(t) == "table", "Given param is not a table") 171 | local registry = {} 172 | keys = keys or loveCallbacks 173 | for _, f in pairs(keys) do 174 | registry[f] = t[f] or function() end 175 | t[f] = function(...) 176 | registry[f](...) 177 | return self[f](self, ...) 178 | end 179 | end 180 | end 181 | 182 | return this 183 | end 184 | 185 | -- Return the initialising function 186 | return Scenery.init 187 | --------------------------------------------------------------------------------