From c0db856b822529c8fd6a3646744d1c70ad4e3d6d Mon Sep 17 00:00:00 2001 From: Reuh Date: Sun, 25 Dec 2016 21:05:10 +0100 Subject: [PATCH] Added uqt.input for ctrulua and other stuff --- audio.lua | 2 +- backend/ctrulua.lua | 273 +++++++++++++++++++++++++++++++++++++++++++- backend/love.lua | 80 ++++++++----- draw.lua | 59 ++++++++-- event.lua | 3 +- init.lua | 29 +++-- scene.lua | 65 +++++------ 7 files changed, 430 insertions(+), 81 deletions(-) diff --git a/audio.lua b/audio.lua index c04a057..c580ae0 100644 --- a/audio.lua +++ b/audio.lua @@ -3,7 +3,7 @@ --- Audio functions. return { --- Loads an audio file and returns the corresponding audio object. - -- TODO: audio object doc + -- TODO: audio object doc & API -- @impl backend load = function(filepath) end } diff --git a/backend/ctrulua.lua b/backend/ctrulua.lua index c0fc558..c8baedf 100644 --- a/backend/ctrulua.lua +++ b/backend/ctrulua.lua @@ -10,6 +10,7 @@ local version = "0.0.1" local uqt = require((...):match("^(.-ubiquitousse)%.")) local ctr = require("ctr") local gfx = require("ctr.gfx") +local hid = require("ctr.hid") -- Version compatibility warning do @@ -31,12 +32,17 @@ do end -- Redefine all functions in tbl which also are in toAdd, so when used they call the old function (in tbl) and then the new (in toAdd). +-- Functions with names prefixed by a exclamation mark will overwrite the old function. local function add(tbl, toAdd) for k,v in pairs(toAdd) do local old = tbl[k] - tbl[k] = function(...) - old(...) - return v(...) + if k:sub(1,1) == "!" then + tbl[k] = v + else + tbl[k] = function(...) + old(...) + return v(...) + end end end end @@ -57,4 +63,263 @@ add(uqt.time, { }) end --- uqt.input: TODO +-- uqt.input +if uqt.input then +local keys = {} +local touchX, touchY, dTouchX, dTouchY +add(uqt.input, { + update = function() + hid.read() + + keys = hid.keys() + + local nTouchX, nTouchY = hid.touch() + dTouchX, dTouchY = nTouchX - touchX, nTouchY - touchY + touchX, touchY = nTouchX, nTouchY + end, + + buttonDetector = function(...) + local ret = {} + for _,id in ipairs({...}) do + -- Keys + if id:match("^key%.") then + local key = id:match("^key%.(.+)$") + table.insert(ret, function() + return keys.held[key] + end) + else + error("Unknown button identifier: "..id) + end + end + return unpack(ret) + end, + + axisDetector = function(...) + local ret = {} + for _,id in ipairs({...}) do + -- Binary axis + if id:match(".+%,.+") then + local d1, d2 = uqt.input.buttonDetector(id:match("^(.+)%,(.+)$")) + table.insert(ret, function() + local b1, b2 = d1(), d2() + if b1 and b2 then return 0 + elseif b1 then return -1 + elseif b2 then return 1 + else return 0 end + end) + -- Touch movement + elseif id:match("^touch%.move%.") then + local axis, threshold = id:match("^touch%.move%.(.+)%%(.+)$") + if not axis then axis = id:match("^touch%.move%.(.+)$") end -- no threshold (=0) + threshold = tonumber(threshold) or 0 + table.insert(ret, function() + local val, raw, max + if axis == "x" then + raw, max = dTouchX, gfx.BOTTOM_WIDTH + elseif axis == "y" then + raw, max = dTouchY, gfx.BOTTOM_HEIGHT + end + val = raw / max + return math.abs(val) > math.abs(threshold) and val or 0, raw, max + end) + -- Touch position + elseif id:match("^touch%.position%.") then + local axis, threshold = id:match("^touch%.position%.(.+)%%(.+)$") + if not axis then axis = id:match("^touch%.position%.(.+)$") end -- no threshold (=0) + threshold = tonumber(threshold) or 0 + table.insert(ret, function() + local val, raw, max + if axis == "x" then + max = gfx.BOTTOM_WIDTH / 2 -- /2 because x=0,y=0 is the center of the screen (an axis value is in [-1,1]) + raw = touchX - max + elseif axis == "y" then + max = gfx.BOTTOM_HEIGHT / 2 + raw = touchY - max + end + val = raw / max + return math.abs(val) > math.abs(threshold) and val or 0, raw, max + end) + -- Circle pad axis + elseif id:match("^circle%.") then + local axis, threshold = id:match("^circle%.(.+)%%(.+)$") + if not axis then axis = id:match("^circle%.(.+)$") end -- no threshold (=0) + threshold = tonumber(threshold) or 0 + table.insert(ret, function() + local x, y = hid.circle() + local val, raw, max = 0, 0, 156 + if axis == "x" then raw = x + elseif axis == "y" then raw = y end + val = raw / max + return math.abs(val) > math.abs(threshold) and val or 0, raw, max + end) + -- C-Stick axis + elseif id:match("^cstick%.") then + local axis, threshold = id:match("^cstick%.(.+)%%(.+)$") + if not axis then axis = id:match("^cstick%.(.+)$") end -- no threshold (=0) + threshold = tonumber(threshold) or 0 + table.insert(ret, function() + local x, y = hid.cstick() + local val, raw, max = 0, 0, 146 + if axis == "x" then raw = x + elseif axis == "y" then raw = y end + val = raw / max + return math.abs(val) > math.abs(threshold) and val or 0, raw, max + end) + -- Accelerometer axis + elseif id:match("^accel%.") then + local axis, threshold = id:match("^accel%.(.+)%%(.+)$") + if not axis then axis = id:match("^accel%.(.+)$") end -- no threshold (=0) + threshold = tonumber(threshold) or 0 + table.insert(ret, function() + local x, y, z = hid.accel() + local val, raw, max = 0, 0, 32768 -- no idea actually, but it's a s16 + if axis == "x" then raw = x + elseif axis == "y" then raw = y + elseif axis == "z" then raw = z end + val = raw / max + return math.abs(val) > math.abs(threshold) and val or 0, raw, max + end) + -- Gyroscope axis + elseif id:match("^gyro%.") then + local axis, threshold = id:match("^gyro%.(.+)%%(.+)$") + if not axis then axis = id:match("^gyro%.(.+)$") end -- no threshold (=0) + threshold = tonumber(threshold) or 0 + table.insert(ret, function() + local roll, pitch, yaw = hid.gyro() + local val, raw, max = 0, 0, 32768 -- no idea actually, but it's a s16 + if axis == "roll" then raw = roll + elseif axis == "pitch" then raw = pitch + elseif axis == "yaw" then raw = yaw end + val = raw / max + return math.abs(val) > math.abs(threshold) and val or 0, raw, max + end) + else + error("Unknown axis identifier: "..id) + end + end + return unpack(ret) + end, + + buttonsInUse = function(threshold) + local r = {} + for key, held in pairs(keys.held) do + if held then table.insert(r, "key."..key) end + end + return r + end, + + axesInUse = function(threshold) + local r = {} + threshold = threshold or 0.5 + + if math.abs(touchX) / gfx.BOTTOM_WIDTH > threshold then table.insert(r, "touch.position.x%"..threshold) end + if math.abs(touchY) / gfx.BOTTOM_HEIGHT > threshold then table.insert(r, "touch.position.y%"..threshold) end + + if math.abs(dTouchX) / gfx.BOTTOM_WIDTH > threshold then table.insert(r, "touch.move.x%"..threshold) end + if math.abs(dTouchY) / gfx.BOTTOM_HEIGHT > threshold then table.insert(r, "touch.move.y%"..threshold) end + + local circleX, circleY = hid.circle() + if math.abs(circleX) / 156 > threshold then table.insert(r, "circle.x%"..threshold) end + if math.abs(circleY) / 156 > threshold then table.insert(r, "circle.y%"..threshold) end + + if ctr.apt.isNew3DS() then + local cstickX, cstickY = hid.cstick() + if math.abs(cstickY) / 146 > threshold then table.insert(r, "cstick.y%"..threshold) end + if math.abs(cstickX) / 146 > threshold then table.insert(r, "cstick.x%"..threshold) end + end + + local accelX, accelY, accelZ = hid.accel() + if math.abs(accelX) / 32768 > threshold then table.insert(r, "accel.x%"..threshold) end + if math.abs(accelY) / 32768 > threshold then table.insert(r, "accel.y%"..threshold) end + if math.abs(accelZ) / 32768 > threshold then table.insert(r, "accel.z%"..threshold) end + + -- no gyro, because it is always in use + + return r + end, + + buttonName = function(...) + local ret = {} + for _,id in ipairs({...}) do + -- Key + if id:match("^key%.") then + local key = id:match("^key%.(.+)$") + table.insert(ret, key:sub(1,1):upper()..key:sub(2).." key") + else + table.insert(ret, id) + end + end + return unpack(ret) + end, + + axisName = function(...) + local ret = {} + for _,id in ipairs({...}) do + -- Binary axis + if id:match(".+%,.+") then + local b1, b2 = uqt.input.buttonName(id:match("^(.+)%,(.+)$")) + table.insert(ret, b1.." / "..b2) + -- Touch movement + elseif id:match("^touch%.move%.") then + local axis, threshold = id:match("^touch%.move%.(.+)%%(.+)$") + if not axis then axis = id:match("^touch%.move%.(.+)$") end -- no threshold (=0) + threshold = tonumber(threshold) or 0 + table.insert(ret, ("Touch %s movement (threshold %s%%)"):format(axis, math.abs(threshold*100))) + -- Touch position + elseif id:match("^touch%.position%.") then + local axis, threshold = id:match("^touch%.position%.(.+)%%(.+)$") + if not axis then axis = id:match("^touch%.position%.(.+)$") end -- no threshold (=0) + threshold = tonumber(threshold) or 0 + table.insert(ret, ("Touch %s position (threshold %s%%)"):format(axis, math.abs(threshold*100))) + -- Circle pad axis + elseif id:match("^circle%.") then + local axis, threshold = id:match("^circle%.(.+)%%(.+)$") + if not axis then axis = id:match("^circle%.(.+)$") end -- no threshold (=0) + threshold = tonumber(threshold) or 0 + if axis == "x" then + table.insert(ret, ("Circle pad horizontal axis (deadzone %s%%)"):format(math.abs(threshold*100))) + elseif axis == "y" then + table.insert(ret, ("Circle pad vertical axis (deadzone %s%%)"):format(math.abs(threshold*100))) + else + table.insert(ret, ("Circle pad %s axis (deadzone %s%%)"):format(axis, math.abs(threshold*100))) + end + -- C-Stick axis + elseif id:match("^cstick%.") then + local axis, threshold = id:match("^cstick%.(.+)%%(.+)$") + if not axis then axis = id:match("^cstick%.(.+)$") end -- no threshold (=0) + threshold = tonumber(threshold) or 0 + if axis == "x" then + table.insert(ret, ("C-Stick horizontal axis (deadzone %s%%)"):format(math.abs(threshold*100))) + elseif axis == "y" then + table.insert(ret, ("C-Stick vertical axis (deadzone %s%%)"):format(math.abs(threshold*100))) + else + table.insert(ret, ("C-Stick %s axis (deadzone %s%%)"):format(axis, math.abs(threshold*100))) + end + -- Accelerometer axis + elseif id:match("^accel%.") then + local axis, threshold = id:match("^accel%.(.+)%%(.+)$") + if not axis then axis = id:match("^accel%.(.+)$") end -- no threshold (=0) + threshold = tonumber(threshold) or 0 + table.insert(ret, ("Accelerometer %s axis (deadzone %s%%)"):format(axis, math.abs(threshold*100))) + -- Gyroscope axis + elseif id:match("^gyro%.") then + local axis, threshold = id:match("^gyro%.(.+)%%(.+)$") + if not axis then axis = id:match("^gyro%.(.+)$") end -- no threshold (=0) + threshold = tonumber(threshold) or 0 + table.insert(ret, ("Gyroscope %s axis (deadzone %s%%)"):format(axis, math.abs(threshold*100))) + else + table.insert(ret, id) + end + end + return unpack(ret) + end +}) + +-- Defaults +uqt.input.default.pointer:bind( + { "absolute", "key.left,key.right", "key.up,key.down" }, + { "absolute", "circle.x", "circle.y" } +) +uqt.input.default.confirm:bind("key.a") +uqt.input.default.cancel:bind("key.b") +end diff --git a/backend/love.lua b/backend/love.lua index f54311a..3149674 100644 --- a/backend/love.lua +++ b/backend/love.lua @@ -30,12 +30,17 @@ do end -- Redefine all functions in tbl which also are in toAdd, so when used they call the old function (in tbl) and then the new (in toAdd). +-- Functions with names prefixed by a exclamation mark will overwrite the old function. local function add(tbl, toAdd) for k,v in pairs(toAdd) do local old = tbl[k] - tbl[k] = function(...) - old(...) - return v(...) + if k:sub(1,1) == "!" then + tbl[k] = v + else + tbl[k] = function(...) + old(...) + return v(...) + end end end end @@ -48,9 +53,6 @@ if uqt.event then local updateDefault = uqt.event.update uqt.event.update = function() end function love.update(dt) - -- Value update - uqt.draw.fps = love.timer.getFPS() - -- Stuff defined in ubiquitousse.lua updateDefault(dt*1000) @@ -93,6 +95,9 @@ add(uqt.draw, { resizable = p.resizable }) end, + fps = function() + return love.timer.getFPS() + end, color = function(r, g, b, a) love.graphics.setColor(r, g, b, a) end, @@ -100,16 +105,34 @@ add(uqt.draw, { love.graphics.setFont(defaultFont) love.graphics.print(text, x, y) end, - line = function(x1, y1, x2, y2) - love.graphics.line(x1, y1, x2, y2) + lineWidth = function(width) + love.graphics.setLineWidth(width) end, - rectangle = function(x, y, width, height) + line = function(x1, y1, x2, y2, ...) + love.graphics.line(x1, y1, x2, y2, ...) + end, + polygon = function(...) + love.graphics.polygon("fill", ...) + end, + linedPolygon = function(...) + love.graphics.polygon("line", ...) + end, + ["!rectangle"] = function(x, y, width, height) love.graphics.rectangle("fill", x, y, width, height) end, + ["!linedRectangle"] = function(x, y, width, height) + love.graphics.rectangle("line", x, y, width, height) + end, + circle = function(x, y, radius) + love.graphics.circle("fill", x, y, radius) + end, + linedCircle = function(x, y, radius) + love.graphics.circle("line", x, y, radius) + end, scissor = function(x, y, width, height) love.graphics.setScissor(x, y, width, height) end, - -- TODO: doc + -- TODO: cf draw.lua image = function(filename) local img = love.graphics.newImage(filename) return { @@ -144,7 +167,7 @@ end -- uqt.audio if uqt.audio then add(uqt.audio, { - -- TODO: doc + -- TODO: cf audio.lua load = function(filepath) local audio = love.audio.newSource(filepath) return { @@ -199,18 +222,6 @@ function love.mousemoved(x, y, dx, dy) if dx ~= 0 then axesInUse["mouse.move.x"] = dx/love.graphics.getWidth() end if dy ~= 0 then axesInUse["mouse.move.y"] = dy/love.graphics.getHeight() end end --- love.wheelmoved doesn't trigger when the wheel stop moving, so we need to clear up our stuff after love.update (so in love.draw) -add(love, { - draw = function() - buttonsInUse["mouse.wheel.up"] = nil - buttonsInUse["mouse.wheel.down"] = nil - buttonsInUse["mouse.wheel.right"] = nil - buttonsInUse["mouse.wheel.left"] = nil - -- Same for mouse axis - axesInUse["mouse.move.x"] = nil - axesInUse["mouse.move.y"] = nil - end -}) function love.gamepadpressed(joystick, button) buttonsInUse["gamepad.button."..joystick:getID().."."..button] = true end @@ -228,6 +239,17 @@ end love.mouse.setVisible(false) add(uqt.input, { + -- love.wheelmoved doesn't trigger when the wheel stop moving, so we need to clear up our stuff at each update + update = function() + buttonsInUse["mouse.wheel.up"] = nil + buttonsInUse["mouse.wheel.down"] = nil + buttonsInUse["mouse.wheel.right"] = nil + buttonsInUse["mouse.wheel.left"] = nil + -- Same for mouse axis + axesInUse["mouse.move.x"] = nil + axesInUse["mouse.move.y"] = nil + end, + buttonDetector = function(...) local ret = {} for _,id in ipairs({...}) do @@ -356,7 +378,7 @@ add(uqt.input, { buttonsInUse = function(threshold) local r = {} - local threshold = threshold or 0.5 + threshold = threshold or 0.5 for b in pairs(buttonsInUse) do table.insert(r, b) end @@ -370,7 +392,7 @@ add(uqt.input, { axesInUse = function(threshold) local r = {} - local threshold = threshold or 0.5 + threshold = threshold or 0.5 for b,v in pairs(axesInUse) do if math.abs(v) > threshold then table.insert(r, b.."%"..threshold) @@ -416,7 +438,7 @@ add(uqt.input, { table.insert(ret, ("Gamepad %s axis %s (deadzone %s%%)"):format(gid, axis, math.abs(threshold*100))) end else - table.insert(r, id) + table.insert(ret, id) end end return unpack(ret) @@ -429,13 +451,13 @@ add(uqt.input, { if id:match(".+%,.+") then local b1, b2 = uqt.input.buttonName(id:match("^(.+)%,(.+)$")) table.insert(ret, b1.." / "..b2) - -- Mouse move + -- Mouse movement elseif id:match("^mouse%.move%.") then local axis, threshold = id:match("^mouse%.move%.(.+)%%(.+)$") if not axis then axis = id:match("^mouse%.move%.(.+)$") end -- no threshold (=0) threshold = tonumber(threshold) or 0 table.insert(ret, ("Mouse %s movement (threshold %s%%)"):format(axis, math.abs(threshold*100))) - -- Mouse move + -- Mouse position elseif id:match("^mouse%.position%.") then local axis, threshold = id:match("^mouse%.position%.(.+)%%(.+)$") if not axis then axis = id:match("^mouse%.position%.(.+)$") end -- no threshold (=0) @@ -458,7 +480,7 @@ add(uqt.input, { table.insert(ret, ("Gamepad %s axis %s (deadzone %s%%)"):format(gid, axis, math.abs(threshold*100))) end else - table.insert(r, id) + table.insert(ret, id) end end return unpack(ret) diff --git a/draw.lua b/draw.lua index 1a3a549..f668bbe 100644 --- a/draw.lua +++ b/draw.lua @@ -11,6 +11,8 @@ local uqt = require((...):match("^(.-ubiquitousse)%.")) -- -- x and y values can be float, so make sure to perform math.floor if your engine only support -- integer coordinates. +-- +-- Mostly plagiarized from the Löve API, with some parts from ctrµLua. local draw draw = { --- Initial game view paramters (some defaults). @@ -46,9 +48,10 @@ draw = { draw.height = params.height end, - --- Frames per second (the backend should update this value). + --- Return the number of frames per second. + -- @treturn number the current FPS -- @impl backend - fps = 60, + fps = function() end, --- Sets the drawing color -- @tparam number r the red component (0-255) @@ -65,21 +68,63 @@ draw = { -- @impl backend text = function(x, y, text) end, + --- Sets the width. + -- @tparam number width the line width + -- @impl backend + lineWidth = function(width) end, + --- Draws a line. -- @tparam number x1 line start x coordinate -- @tparam number y1 line start y coordinate -- @tparam number x2 line end x coordinate -- @tparam number y2 line end y coordinate + -- @tparam number ... other vertices to continue drawing a polyline -- @impl backend - line = function(x1, y1, x2, y2) end, + line = function(x1, y1, x2, y2, ...) end, - --- Draws a filled rectangle + --- Draws a filled polygon. + -- @tparam number x1,y1,x2,y2... the vertices of the polygon + -- @impl backend + polygon = function(...) end, + + --- Draws a polygon outline. + -- @tparam number x1,y1,x2,y2... the vertices of the polygon + -- @impl backend + linedPolygon = function(...) end, + + --- Draws a filled rectangle. -- @tparam number x rectangle top-left x coordinate -- @tparam number y rectangle top-left x coordinate -- @tparam number width rectangle width -- @tparam number height rectangle height + -- @impl ubiquitousse + rectangle = function(x, y, width, height) + draw.polygon(x, y, x + width, y, x + width, y + height, x, y + height) + end, + + --- Draws a rectangle outline. + -- @tparam number x rectangle top-left x coordinate + -- @tparam number y rectangle top-left x coordinate + -- @tparam number width rectangle width + -- @tparam number height rectangle height + -- @impl ubiquitousse + linedRectangle = function(x, y, width, height) + draw.linedPolygon(x, y, x + width, y, x + width, y + height, x, y + height) + end, + + --- Draws a filled circle. + -- @tparam number x center x coordinate + -- @tparam number y center x coordinate + -- @tparam number radius circle radius -- @impl backend - rectangle = function(x, y, width, height) end, + circle = function(x, y, radius) end, + + --- Draws a circle outline. + -- @tparam number x center x coordinate + -- @tparam number y center x coordinate + -- @tparam number radius circle radius + -- @impl backend + linedCircle = function(x, y, radius) end, --- Enables the scissor test. -- When enabled, every pixel drawn outside of the scissor rectangle is discarded. @@ -102,9 +147,9 @@ draw = { -- TODO: doc & api push = function() end, pop = function() end, - polygon = function(...) end, - circle = function(x, y, radius) end, translate = function(x, y) end, + rotate = function(angle) end, + scale = function(sx, sy) end, font = function(filename) end, image = function(filename) end, } diff --git a/event.lua b/event.lua index 76b238a..da8a5a1 100644 --- a/event.lua +++ b/event.lua @@ -5,7 +5,8 @@ local scene = require((...):match("^(.-ubiquitousse)%.")..".scene") --- The events: callback functions that will be called when something interesting occurs. -- Theses are expected to be redefined in the game. --- For backend writers: if they already contain code, then this code has to be called on each call. +-- For backend writers: if they already contain code, then this code has to be called on each call, even +-- if the user manually redefines them. -- @usage -- in the game's code -- ubiquitousse.event.draw = function() -- ubiquitousse.draw.text(5, 5, "Hello world") diff --git a/init.lua b/init.lua index 1e79ded..a95e1b8 100644 --- a/init.lua +++ b/init.lua @@ -11,19 +11,30 @@ -- -- For backend writers: -- If a function defined here already contains some code, this means this code is mandatory and you must put/call --- it in your implementation. +-- it in your implementation (except if the backend provides a more efficient implementation). -- Also, a backend file shouldn't redefine the ubiquitousse table itself but only redefine the backend-dependant fields. --- The API doesn't make the difference between numbers and integers, so convert to integers when needed. +-- Lua 5.3: The API doesn't make the difference between numbers and integers, so convert to integers when needed. +-- +-- For game writer: +-- Ubiquitousse works with Lua 5.1 to 5.3, including LuaJit, but doesn't provide any version checking or compatibility layer +-- between the different versions, so it's up to you to handle that in your game (or ignore the problem and sticks to your +-- main's backend Lua version). -- -- Ubiquitousse's goal is to run everywhere with the least porting effort possible. -- To achieve this, the engine needs to stay simple, and only provide features that are almost sure to be -- available everywhere, so writing a backend should be straighforward. --- However, Ubiquitousse still make some small assumptions about the engine: --- * The engine has some kind of main loop, or at least a function called very often (may or may not be the --- same as the redraw screen callback). --- * 32bit color depth. -- --- Regarding data formats, Ubiquitousse reference implemtations expect and recommend: +-- However, a full Ubiquitousse backend still have a few requirement about the destination platform: +-- * The backend needs to have access to some kind of main loop, or at least a function called very often (may or may not be the +-- same as the redraw screen callback). +-- * A 2D matrix graphic output with 32bit RGB color depth. +-- * Inputs which match ubiquitousse.input.default (a pointing/4 direction input, a confirm button, and a cancel button). +-- * Some way of measuring time with millisecond-precision. +-- * Some kind of filesystem. +-- * An available audio output would be preferable. +-- * Lua 5.1, 5.2, 5.3 or LuaJit. +-- +-- Regarding data formats, Ubiquitousse implementations expect and recommend: -- * For images, PNG support is expected. -- * For audio files, OGG Vorbis support is expected. -- * For fonts, TTF support is expected. @@ -47,6 +58,10 @@ -- * ubiquitousse: fully-working version in Ubiquitousse, may or may not be redefined in backend -- The implementation level is indicated using the "@impl level" annotation. -- +-- Regarding the documentation: Ubiquitousse used LDoc/LuaDoc styled-comments, but since LDoc hates me and my code, the +-- generated result is complete garbage, so please read the documentation directly in the comments here. +-- Stuff you're interested in starts with triple - (e.g., "--- This functions saves the world"). +-- -- @usage local ubiquitousse = require("ubiquitousse") local p = ... -- require path diff --git a/scene.lua b/scene.lua index 34d52ad..d1260f8 100644 --- a/scene.lua +++ b/scene.lua @@ -12,9 +12,6 @@ local function getPath(modname) return filepath end --- FIXME: http://hump.readthedocs.io/en/latest/gamestate.html --- FIXME: call order - --- Scene management. -- 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. @@ -22,19 +19,17 @@ end -- 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. --- Currently, the implementation always execute a scene's file before setting it as current, but this may change in the future or --- for some implementations (e.g., on a computer where memory isn't a problem, the scene may be put in a cache). The result of this --- is that you can load assets, libraries, etc. outside of the enter callback, so they can be cached and not reloaded each time +-- Currently, the implementation always execute a scene's file before switching to it or adding it to the stack, but this may change in +-- the future or for some implementations (e.g., on a computer where memory isn't a problem, the scene may be put in a cache). The result +-- of this is that you can load assets, libraries, etc. outside of the enter callback, so they can be cached and not reloaded each time -- the scene is entered, but all the other scene initialization should be done in the enter callback, since it won't be executed on -- each enter otherwise. -- The expected code-organisation is: -- * each scene is in a file, identified by its module name (same identifier used by Lua's require) -- * 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 exit callbacks are called before changing the stack or the current scene (ie, ubiquitousse.scene.current and the --- last stack element is the scene in which the exit or suspend function was called) --- * all scene enter callbacks are called before changing the stack or the current scene (ie, ubiquitousse.scene.current and the --- last stack element is the previous scene which was just exited, and not the new scene) +-- * 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 local scene scene = { --- The current scene table. @@ -53,31 +48,35 @@ scene = { -- @impl ubiquitousse new = function() return { + name = "loading scene", -- The scene name. + time = time.new(), -- Scene-specific TimerRegistry. - enter = function(...) end, -- Called when entering a scene. - exit = function() end, -- Called when exiting a scene, and not expecting to come back (scene may be unloaded). + enter = function(self, ...) end, -- Called when entering a scene. + exit = function(self) end, -- Called when exiting a scene, and not expecting to come back (scene may be unloaded). - suspend = function() end, -- Called when suspending a scene, and expecting to come back (scene won't be unloaded). - resume = function() end, -- Called when resuming a suspended scene (after calling suspend). + suspend = function(self) end, -- Called when suspending a scene, and expecting to come back (scene won't be unloaded). + resume = function(self) end, -- Called when resuming a suspended scene (after calling suspend). - update = function(dt, ...) end, -- Called on each ubiquitousse.event.update on the current scene. - draw = function(...) end -- Called on each ubiquitousse.event.draw on the current scene. + update = function(self, dt, ...) end, -- Called on each ubiquitousse.event.update on the current scene. + draw = function(self, ...) end -- Called on each ubiquitousse.event.draw on the current scene. } end, --- Switch to a new scene. - -- The current scene exit function will be called, the new scene will be loaded, - -- the current scene will then be replaced by the new one, and then the enter callback is called. + -- The new scene will be loaded 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 scenePath the new scene module name -- @param ... arguments to pass to the scene's enter function -- @impl ubiquitousse switch = function(scenePath, ...) - if scene.current then scene.current.exit() end + local previous = scene.current scene.current = dofile(getPath(scene.prefix..scenePath)) - local i = #scene.stack - scene.stack[math.max(i, 1)] = scene.current - scene.current.enter(...) + scene.current.name = scenePath + if previous then previous:exit() end + scene.current:enter(...) + scene.stack[math.max(#scene.stack, 1)] = scene.current end, --- Push a new scene to the scene stack. @@ -88,21 +87,23 @@ scene = { -- @param ... arguments to pass to the scene's enter function -- @impl ubiquitousse push = function(scenePath, ...) - if scene.current then scene.current.suspend() end + local previous = scene.current scene.current = dofile(getPath(scene.prefix..scenePath)) + scene.current.name = scenePath + if previous then previous:suspend() end + scene.current:enter(...) table.insert(scene.stack, scene.current) - scene.current.enter(...) end, --- Pop the current scene from the scene stack. - -- The current scene exit function will be called, then the previous scene resume function will be called. - -- Then the current scene will be removed from the stack, and the previous scene will be set as the current scene. + -- 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. -- @impl ubiquitousse pop = function() - if scene.current then scene.current.exit() end - local previous = scene.stack[#scene.stack-1] - scene.current = previous - if previous then previous.resume() end + local previous = scene.current + scene.current = scene.stack[#scene.stack-1] + if previous then previous:exit() end + if scene.current then scene.current:resume() end table.remove(scene.stack) end, @@ -114,7 +115,7 @@ scene = { update = function(dt, ...) if scene.current then scene.current.time.update(dt) - scene.current.update(dt, ...) + scene.current:update(dt, ...) end end, @@ -123,7 +124,7 @@ scene = { -- @param ... arguments to pass to the scene's draw function -- @impl ubiquitousse draw = function(...) - if scene.current then scene.current.draw(...) end + if scene.current then scene.current:draw(...) end end }