mirror of
				https://github.com/Reuh/ubiquitousse.git
				synced 2025-10-27 17:19:31 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			200 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			200 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| --- Scene management for Lua game development.
 | |
| --
 | |
| -- You can use use scenes to seperate the different states of your game: for example, a menu scene and a game scene.
 | |
| -- This module is fully implemented in Ubiquitousse and is mostly a "recommended way" of organising an Ubiquitousse-based game.
 | |
| -- However, you don't have to use this if you don't want to. ubiquitousse.scene handles all the differents Ubiquitousse-states and
 | |
| -- make them scene-independent, for example by creating a scene-specific TimerRegistry (TimedFunctions that are keept accross
 | |
| -- states are generally a bad idea). Theses scene-specific states should be created and available in the table returned by
 | |
| -- ubiquitousse.scene.new.
 | |
| --
 | |
| -- The expected code-organisation is:
 | |
| --
 | |
| -- * each scene is in a file, identified by its module name (scenes will be loaded using require("modulename"))
 | |
| -- * each scene file create a new scene table using ubiquitousse.scene.new and returns it at the end of the file
 | |
| --
 | |
| -- Order of callbacks:
 | |
| --
 | |
| -- * all scene change callbacks are called after setting scene.current to the new scene but before changing scene.stack
 | |
| -- * all scene exit/suspend callbacks are called before scene enter/resume callbacks
 | |
| --
 | |
| -- No mendatory dependency.
 | |
| -- Optional dependencies:
 | |
| --
 | |
| -- * ubiquitousse.timer (to provide each scene a timer registry).
 | |
| -- * ubiquitousse.signal (to bind to update and draw signal in signal.event).
 | |
| -- @module scene
 | |
| -- @usage
 | |
| -- TODO
 | |
| local loaded, signal = pcall(require, (...):match("^(.-)scene").."signal")
 | |
| if not loaded then signal = nil end
 | |
| local loaded, timer = pcall(require, (...):match("^(.-)scene").."timer")
 | |
| if not loaded then timer = nil end
 | |
| 
 | |
| --- Scene object.
 | |
| -- @type Scene
 | |
| local _ = {
 | |
| 	--- The scene name.
 | |
| 	-- @ftype string
 | |
| 	name = name or "unamed",
 | |
| 	--- Scene-specific `timer.TimerRegistry`, if uqt.time is available.
 | |
| 	-- @ftype TimerRegistry
 | |
| 	-- @ftype nil if uqt.time unavailable
 | |
| 	timer = timer and timer.new(),
 | |
| 	--- Scene-specific `signal.SignalRegistry`, if uqt.signal is available.
 | |
| 	-- @ftype SignalRegistry
 | |
| 	-- @ftype nil if uqt.signal unavailable
 | |
| 	signal = signal and signal.new(),
 | |
| 	--- Called when entering a scene.
 | |
| 	-- @param ... additional arguments passed to `scene:switch` or `scene:push`
 | |
| 	-- @callback
 | |
| 	enter = function(self, ...) end,
 | |
| 	--- Called when exiting a scene, and not expecting to come back (scene may be unloaded).
 | |
| 	-- @callback
 | |
| 	exit = function(self) end,
 | |
| 	--- Called when suspending a scene, and expecting to come back (scene won't be unloaded).
 | |
| 	-- @callback
 | |
| 	suspend = function(self) end,
 | |
| 	--- Called when resuming a suspended scene (after calling suspend).
 | |
| 	-- @callback
 | |
| 	resume = function(self) end,
 | |
| 	--- Called on each update on the current scene.
 | |
| 	-- @tparam number dt the delta time
 | |
| 	-- @param ... additional arguments passed to `scene:update`
 | |
| 	-- @callback
 | |
| 	update = function(self, dt, ...) end,
 | |
| 	--- Called on each draw on the current scene.
 | |
| 	-- @param ... additional arguments passed to `scene:draw`
 | |
| 	-- @callback
 | |
| 	draw = function(self, ...) end
 | |
| }
 | |
| 
 | |
| --- Module.
 | |
| -- @section module
 | |
| 
 | |
| local scene
 | |
| scene = {
 | |
| 	--- The current `Scene` object.
 | |
| 	-- @ftype Scene
 | |
| 	current = nil,
 | |
| 
 | |
| 	--- Shortcut for scene.current.timer, the current scene `timer.TimerRegistry`.
 | |
| 	-- @ftype TimerRegistry
 | |
| 	-- @ftype nil if uqt.time unavailable
 | |
| 	timer = nil,
 | |
| 	--- Shortcut for scene.current.signal, the current scene `timer.SignalRegistry`.
 | |
| 	-- @ftype SignalRegistry
 | |
| 	-- @ftype nil if uqt.signal unavailable
 | |
| 	signal = nil,
 | |
| 
 | |
| 	--- The scene stack: list of scene, from the farest one to the nearest.
 | |
| 	-- @ftype {Scene,...}
 | |
| 	stack = {},
 | |
| 
 | |
| 	--- A prefix for scene modules names.
 | |
| 	-- Will search in the "scene" directory by default (`prefix="scene."`). Redefine it to fit your own ridiculous filesystem.
 | |
| 	-- @ftype string
 | |
| 	prefix = "scene.",
 | |
| 
 | |
| 	--- Creates and returns a new Scene object.
 | |
| 	-- @tparam[opt="unamed"] string name the new scene name
 | |
| 	-- @treturn Scene
 | |
| 	new = function(name)
 | |
| 		return {
 | |
| 			name = name or "unamed",
 | |
| 			timer = timer and timer.new(),
 | |
| 			signal = signal and signal.new(),
 | |
| 			enter = function(self, ...) end,
 | |
| 			exit = function(self) end,
 | |
| 			suspend = function(self) end,
 | |
| 			resume = function(self) end,
 | |
| 			update = function(self, dt, ...) end,
 | |
| 			draw = function(self, ...) end
 | |
| 		}
 | |
| 	end,
 | |
| 
 | |
| 	--- Switch to a new scene.
 | |
| 	-- The new scene will be required() and the current scene will be replaced by the new one,
 | |
| 	-- then the previous scene exit function will be called, then the enter callback is called on the new scence.
 | |
| 	-- Then the stack is changed to replace the old scene with the new one.
 | |
| 	-- @tparam string/table scenePath the new scene module name, or the scene table directly
 | |
| 	-- @param ... arguments to pass to the scene's enter function
 | |
| 	switch = function(scenePath, ...)
 | |
| 		local previous = scene.current
 | |
| 		scene.current = type(scenePath) == "string" and require(scene.prefix..scenePath) or scenePath
 | |
| 		scene.timer = scene.current.timer
 | |
| 		scene.signal = scene.current.signal
 | |
| 		scene.current.name = scene.current.name or tostring(scenePath)
 | |
| 		if previous then
 | |
| 			previous:exit()
 | |
| 			if timer then previous.timer:clear() end
 | |
| 		end
 | |
| 		scene.current:enter(...)
 | |
| 		scene.stack[math.max(#scene.stack, 1)] = scene.current
 | |
| 	end,
 | |
| 
 | |
| 	--- Push a new scene to the scene stack.
 | |
| 	-- Similar to ubiquitousse.scene.switch, except suspend is called on the current scene instead of exit,
 | |
| 	-- and the current scene is not replaced: when the new scene call ubiquitousse.scene.pop, the old scene
 | |
| 	-- will be reused.
 | |
| 	-- @tparam string/table scenePath the new scene module name, or the scene table directly
 | |
| 	-- @param ... arguments to pass to the scene's enter function
 | |
| 	push = function(scenePath, ...)
 | |
| 		local previous = scene.current
 | |
| 		scene.current = type(scenePath) == "string" and require(scene.prefix..scenePath) or scenePath
 | |
| 		scene.timer = scene.current.timer
 | |
| 		scene.signal = scene.current.signal
 | |
| 		scene.current.name = scene.current.name or tostring(scenePath)
 | |
| 		if previous then previous:suspend() end
 | |
| 		scene.current:enter(...)
 | |
| 		table.insert(scene.stack, scene.current)
 | |
| 	end,
 | |
| 
 | |
| 	--- Pop the current scene from the scene stack.
 | |
| 	-- The previous scene will be set as the current scene, then the current scene exit function will be called,
 | |
| 	-- then the previous scene resume function will be called, and then the current scene will be removed from the stack.
 | |
| 	pop = function()
 | |
| 		local previous = scene.current
 | |
| 		scene.current = scene.stack[#scene.stack-1]
 | |
| 		scene.timer = scene.current and scene.current.timer or nil
 | |
| 		scene.signal = scene.current and scene.current.signal or nil
 | |
| 		if previous then
 | |
| 			previous:exit()
 | |
| 			if timer then previous.timer:clear() end
 | |
| 		end
 | |
| 		if scene.current then scene.current:resume() end
 | |
| 		table.remove(scene.stack)
 | |
| 	end,
 | |
| 
 | |
| 	--- Pop all scenes.
 | |
| 	popAll = function()
 | |
| 		while scene.current do
 | |
| 			scene.pop()
 | |
| 		end
 | |
| 	end,
 | |
| 
 | |
| 	--- Update the current scene.
 | |
| 	-- Should be called at every game update. If ubiquitousse.signal is available, will be bound to the "update" signal in signal.event.
 | |
| 	-- @tparam number dt the delta-time
 | |
| 	-- @param ... arguments to pass to the scene's update function after dt
 | |
| 	update = function(dt, ...)
 | |
| 		if scene.current then
 | |
| 			if timer then scene.current.timer:update(dt) end
 | |
| 			scene.current:update(dt, ...)
 | |
| 		end
 | |
| 	end,
 | |
| 
 | |
| 	--- Draw the current scene.
 | |
| 	-- Should be called every time the game is draw. If ubiquitousse.signal is available, will be bound to the "draw" signal in signal.event.
 | |
| 	-- @param ... arguments to pass to the scene's draw function
 | |
| 	draw = function(...)
 | |
| 		if scene.current then scene.current:draw(...) end
 | |
| 	end
 | |
| }
 | |
| 
 | |
| -- Bind signals
 | |
| if signal then
 | |
| 	signal.event:bind("update", scene.update)
 | |
| 	signal.event:bind("draw", scene.draw)
 | |
| end
 | |
| 
 | |
| return scene
 |