├── examples ├── assets │ ├── fonts │ │ ├── kenpixel.ttf │ │ └── kenpixel15.font │ ├── images │ │ ├── blue_circle.png │ │ ├── hitman1_gun.png │ │ ├── set1_tiles.png │ │ ├── blue_button07.png │ │ ├── green_button07.png │ │ ├── green_circle.png │ │ ├── grey_button08.png │ │ ├── playerGreen_walk1.png │ │ ├── playerGreen_walk2.png │ │ ├── playerGreen_walk3.png │ │ ├── playerGreen_walk4.png │ │ ├── playerGreen_walk5.png │ │ ├── tilesheet_complete.png │ │ ├── playerBlue_walk1_new.png │ │ ├── playerBlue_walk2_new.png │ │ ├── playerBlue_walk3_new.png │ │ ├── playerBlue_walk4_new.png │ │ ├── playerBlue_walk5_new.png │ │ └── tilesheet_topdown_complete.png │ ├── button.gui │ └── examples.atlas ├── flow │ ├── a.gui_script │ ├── b.gui │ ├── a.collection │ ├── b.collection │ ├── a.gui │ ├── flow.gui_script │ ├── flow.gui │ ├── flow.collection │ └── flow.script ├── broadcast │ ├── receiver1.script │ ├── receiver2.script │ ├── broadcaster.script │ └── broadcast.collection ├── listener │ ├── some_data.lua │ ├── listener1.script │ ├── listener_main.gui_script │ ├── listener2.script │ ├── listener3.script │ ├── listener.collection │ └── listener_main.gui ├── logger │ ├── logger.script │ ├── logger.collection │ └── logger.gui_script ├── savefile │ ├── savefile.collection │ └── savefile.gui_script ├── savetable │ ├── savetable.collection │ └── savetable.gui_script ├── bezier │ ├── bezier.script │ └── bezier.collection ├── sequence │ ├── sequence.collection │ └── sequence.script ├── examples.gui_script ├── kinematic │ ├── kinematic.script │ └── kinematic.collection ├── dynamic │ ├── dynamic.script │ └── dynamic.collection └── examples.collection ├── ludobits └── m │ ├── input │ └── README.md │ ├── json.lua │ ├── platformer.lua.md │ ├── settings.md │ ├── app.md │ ├── async.lua.md │ ├── io │ ├── file.lua │ ├── savefile.lua │ └── savetable.lua │ ├── broadcast.md │ ├── logger.md │ ├── thread.md │ ├── signal.md │ ├── settings.lua │ ├── bezier.lua │ ├── util.lua │ ├── async.lua │ ├── listener.md │ ├── thread.lua │ ├── signal.lua │ ├── app.lua │ ├── logger.lua │ ├── broadcast.lua │ ├── physics │ ├── dynamic.lua │ └── kinematic.lua │ ├── procgen │ ├── conway.lua │ └── perlin.lua │ ├── listener.lua │ ├── flow.md │ └── sequence.lua ├── .gitignore ├── .github └── workflows │ └── ci-workflow.yml ├── game.project ├── test ├── test_file.lua ├── test.script ├── test_savefile.lua ├── test_savetable.lua ├── test_signal.lua ├── test_broadcast.lua ├── test_settings.lua ├── test.collection ├── test_listener.lua └── test_flow.lua ├── LICENSE.md ├── .test └── run.sh ├── README.md └── doc ├── modules ├── m.settings.html ├── m.bezier.html ├── m.file.html ├── m.util.html ├── m.app.html ├── m.savetable.html ├── m.signal.html ├── m.savefile.html ├── m.simple_input.html └── m.input.html └── index.html /examples/assets/fonts/kenpixel.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/fonts/kenpixel.ttf -------------------------------------------------------------------------------- /examples/assets/images/blue_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/blue_circle.png -------------------------------------------------------------------------------- /examples/assets/images/hitman1_gun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/hitman1_gun.png -------------------------------------------------------------------------------- /examples/assets/images/set1_tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/set1_tiles.png -------------------------------------------------------------------------------- /examples/assets/images/blue_button07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/blue_button07.png -------------------------------------------------------------------------------- /examples/assets/images/green_button07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/green_button07.png -------------------------------------------------------------------------------- /examples/assets/images/green_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/green_circle.png -------------------------------------------------------------------------------- /examples/assets/images/grey_button08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/grey_button08.png -------------------------------------------------------------------------------- /ludobits/m/input/README.md: -------------------------------------------------------------------------------- 1 | The input modules have been moved to [their own repository](https://github.com/britzl/defold-input). 2 | -------------------------------------------------------------------------------- /ludobits/m/json.lua: -------------------------------------------------------------------------------- 1 | -- rxi.json has been removed and replaced by Defold native json.encode and json.decode 2 | 3 | return _G.json -------------------------------------------------------------------------------- /ludobits/m/platformer.lua.md: -------------------------------------------------------------------------------- 1 | The platformer.lua module has been moved to [it's own repository](https://github.com/britzl/platypus). 2 | -------------------------------------------------------------------------------- /examples/assets/images/playerGreen_walk1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/playerGreen_walk1.png -------------------------------------------------------------------------------- /examples/assets/images/playerGreen_walk2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/playerGreen_walk2.png -------------------------------------------------------------------------------- /examples/assets/images/playerGreen_walk3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/playerGreen_walk3.png -------------------------------------------------------------------------------- /examples/assets/images/playerGreen_walk4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/playerGreen_walk4.png -------------------------------------------------------------------------------- /examples/assets/images/playerGreen_walk5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/playerGreen_walk5.png -------------------------------------------------------------------------------- /examples/assets/images/tilesheet_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/tilesheet_complete.png -------------------------------------------------------------------------------- /examples/assets/fonts/kenpixel15.font: -------------------------------------------------------------------------------- 1 | font: "/examples/assets/fonts/kenpixel.ttf" 2 | material: "/builtins/fonts/font.material" 3 | size: 15 4 | -------------------------------------------------------------------------------- /examples/assets/images/playerBlue_walk1_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/playerBlue_walk1_new.png -------------------------------------------------------------------------------- /examples/assets/images/playerBlue_walk2_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/playerBlue_walk2_new.png -------------------------------------------------------------------------------- /examples/assets/images/playerBlue_walk3_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/playerBlue_walk3_new.png -------------------------------------------------------------------------------- /examples/assets/images/playerBlue_walk4_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/playerBlue_walk4_new.png -------------------------------------------------------------------------------- /examples/assets/images/playerBlue_walk5_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/playerBlue_walk5_new.png -------------------------------------------------------------------------------- /examples/assets/images/tilesheet_topdown_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/ludobits/HEAD/examples/assets/images/tilesheet_topdown_complete.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .externalToolBuilders 3 | .DS_Store 4 | .lock-wscript 5 | build 6 | *.pyc 7 | .project 8 | .cproject 9 | builtins 10 | .internal 11 | /.editor_settings -------------------------------------------------------------------------------- /examples/flow/a.gui_script: -------------------------------------------------------------------------------- 1 | local flow = require "ludobits.m.flow" 2 | 3 | function init(self) 4 | for k,v in pairs(_G) do print(k,v) end 5 | local box = gui.get_node("box") 6 | flow(function() 7 | print("flow a") 8 | flow.delay(1) 9 | print("a delay") 10 | gui.set_position(box, vmath.vector3(200, 200, 0)) 11 | end) 12 | end 13 | -------------------------------------------------------------------------------- /examples/broadcast/receiver1.script: -------------------------------------------------------------------------------- 1 | local broadcast = require "ludobits.m.broadcast" 2 | 3 | function init(self) 4 | broadcast.register("foo") 5 | broadcast.register("bar") 6 | end 7 | 8 | function final(self) 9 | broadcast.unregister("foo") 10 | broadcast.unregister("bar") 11 | end 12 | 13 | function on_message(self, message_id, message, sender) 14 | print("Received", message_id) 15 | --pprint(message) 16 | end 17 | -------------------------------------------------------------------------------- /examples/listener/some_data.lua: -------------------------------------------------------------------------------- 1 | local listener = require "ludobits.m.listener" 2 | 3 | local M = {} 4 | 5 | M.FOO = hash("foo") 6 | M.BAR = hash("bar") 7 | 8 | M.listeners = listener.create() 9 | 10 | function M.trigger_foo() 11 | print("trigger_foo()") 12 | M.listeners.trigger(M.FOO, { text = "foo" }) 13 | end 14 | 15 | function M.trigger_bar() 16 | print("trigger_bar()") 17 | M.listeners.trigger(M.BAR, { text = "bar" }) 18 | end 19 | 20 | return M -------------------------------------------------------------------------------- /ludobits/m/settings.md: -------------------------------------------------------------------------------- 1 | # Settings 2 | Use the Settings module to save user settings to disk. 3 | 4 | # Usage 5 | 6 | ```lua 7 | local settings = require "ludobits.m.settings" 8 | 9 | settings.volume = 0.7 10 | settings.language = "en" 11 | settings.username = "Johnny Defold" 12 | 13 | settings.save() 14 | ``` 15 | 16 | # Limitations 17 | The Settings module will serialize the settings using the Savetable module and is thus limited to the data types supported by that module. 18 | -------------------------------------------------------------------------------- /examples/listener/listener1.script: -------------------------------------------------------------------------------- 1 | local some_data = require "examples.listener.some_data" 2 | 3 | function init(self) 4 | -- this will use the default behavior of registering 5 | -- with the script url (equivalent to some_data.listeners.add(msg.url())) 6 | some_data.listeners.add() 7 | end 8 | 9 | function final(self) 10 | some_data.listeners.remove() 11 | end 12 | 13 | function on_message(self, message_id, message, sender) 14 | print("listener1 on_message() received", message_id) 15 | end 16 | -------------------------------------------------------------------------------- /examples/broadcast/receiver2.script: -------------------------------------------------------------------------------- 1 | local broadcast = require "ludobits.m.broadcast" 2 | 3 | local function receive_boo(message) 4 | print("Received boo") 5 | --pprint(message) 6 | end 7 | 8 | function init(self) 9 | broadcast.register("foo") 10 | broadcast.register("boo", receive_boo) 11 | end 12 | 13 | function final(self) 14 | broadcast.unregister("foo") 15 | broadcast.unregister("boo", receive_boo) 16 | end 17 | 18 | function on_message(self, message_id, message, sender) 19 | print("Received", message_id) 20 | --pprint(message) 21 | end 22 | -------------------------------------------------------------------------------- /examples/logger/logger.script: -------------------------------------------------------------------------------- 1 | local logger = require "ludobits.m.logger" 2 | 3 | local log = logger.create("[script]") 4 | 5 | function init(self) 6 | log("init") 7 | end 8 | 9 | function on_input(self, action_id, action) 10 | if action_id == hash("touch") then 11 | if action.released then 12 | log.i("released mouse button") 13 | elseif action.pressed then 14 | log.i("pressed mouse button") 15 | end 16 | end 17 | end 18 | 19 | function on_reload(self) 20 | -- Add reload-handling code here 21 | -- Remove this function if not needed 22 | end 23 | -------------------------------------------------------------------------------- /examples/listener/listener_main.gui_script: -------------------------------------------------------------------------------- 1 | local button = require "in.button" 2 | local some_data = require "examples.listener.some_data" 3 | 4 | 5 | function init(self) 6 | button.acquire() 7 | button.register("foo/button", function() 8 | some_data.trigger_foo() 9 | end) 10 | button.register("bar/button", function() 11 | some_data.trigger_bar() 12 | end) 13 | end 14 | 15 | function final(self) 16 | button.release() 17 | button.unregister() 18 | end 19 | 20 | function on_input(self, action_id, action) 21 | button.on_input(action_id, action) 22 | end 23 | -------------------------------------------------------------------------------- /examples/listener/listener2.script: -------------------------------------------------------------------------------- 1 | local some_data = require "examples.listener.some_data" 2 | 3 | local function any(message_id, message) 4 | print("listener2 any() received", message_id) 5 | end 6 | 7 | function init(self) 8 | -- this adds a function as callback when the listener is invoked 9 | some_data.listeners.add(any) 10 | end 11 | 12 | function final(self) 13 | some_data.listeners.remove(any) 14 | end 15 | 16 | function on_message(self, message_id, message, sender) 17 | error("listener2 on_message() should not receive any messages " .. tostring(message_id)) 18 | end 19 | -------------------------------------------------------------------------------- /ludobits/m/app.md: -------------------------------------------------------------------------------- 1 | # App 2 | Module to simplify the use of several of the engine listeners. The module allows the user to define multiple listeners for the iac, iap, push and window listeners. 3 | 4 | ## Usage 5 | 6 | ```lua 7 | local app = require "ludobits.app" 8 | 9 | local function iac_listener1(self, playload, type) 10 | print("This function will receive callbacks") 11 | end 12 | 13 | local function iac_listener2(self, playload, type) 14 | print("And this function too") 15 | end 16 | 17 | app.iac.add_listener(iac_listener1) 18 | app.iac.add_listener(iac_listener2) 19 | ``` -------------------------------------------------------------------------------- /examples/flow/b.gui: -------------------------------------------------------------------------------- 1 | fonts { 2 | name: "system_font" 3 | font: "/builtins/fonts/default.font" 4 | } 5 | nodes { 6 | position { 7 | x: 640.0 8 | y: 260.0 9 | } 10 | size { 11 | x: 200.0 12 | y: 100.0 13 | } 14 | type: TYPE_TEXT 15 | text: "COLLECTION B" 16 | font: "system_font" 17 | id: "text" 18 | outline { 19 | x: 1.0 20 | y: 1.0 21 | z: 1.0 22 | } 23 | shadow { 24 | x: 1.0 25 | y: 1.0 26 | z: 1.0 27 | } 28 | inherit_alpha: true 29 | } 30 | material: "/builtins/materials/gui.material" 31 | adjust_reference: ADJUST_REFERENCE_PARENT 32 | -------------------------------------------------------------------------------- /examples/broadcast/broadcaster.script: -------------------------------------------------------------------------------- 1 | local broadcast = require "ludobits.m.broadcast" 2 | 3 | function init(self) 4 | msg.post(".", "acquire_input_focus") 5 | end 6 | 7 | function final(self) 8 | msg.post(".", "release_input_focus") 9 | end 10 | 11 | function update(self, dt) 12 | msg.post("@render:", "draw_text", { text = "Click to broadcast", position = vmath.vector3(10, 20, 0)}) 13 | end 14 | 15 | function on_input(self, action_id, action) 16 | if action.released then 17 | local messages = { "foo", "bar", "boo" } 18 | broadcast.send(messages[math.random(1, #messages)], { time = os.time() }) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /examples/flow/a.collection: -------------------------------------------------------------------------------- 1 | name: "a" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"gui\"\n" 7 | " component: \"/examples/flow/a.gui\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "" 21 | position { 22 | x: 0.0 23 | y: 0.0 24 | z: 0.0 25 | } 26 | rotation { 27 | x: 0.0 28 | y: 0.0 29 | z: 0.0 30 | w: 1.0 31 | } 32 | scale3 { 33 | x: 1.0 34 | y: 1.0 35 | z: 1.0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/flow/b.collection: -------------------------------------------------------------------------------- 1 | name: "b" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"gui\"\n" 7 | " component: \"/examples/flow/b.gui\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "" 21 | position { 22 | x: 0.0 23 | y: 0.0 24 | z: 0.0 25 | } 26 | rotation { 27 | x: 0.0 28 | y: 0.0 29 | z: 0.0 30 | w: 1.0 31 | } 32 | scale3 { 33 | x: 1.0 34 | y: 1.0 35 | z: 1.0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/savefile/savefile.collection: -------------------------------------------------------------------------------- 1 | name: "default" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"gui\"\n" 7 | " component: \"/examples/savefile/savefile.gui\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "" 21 | position { 22 | x: 0.0 23 | y: 0.0 24 | z: 0.0 25 | } 26 | rotation { 27 | x: 0.0 28 | y: 0.0 29 | z: 0.0 30 | w: 1.0 31 | } 32 | scale3 { 33 | x: 1.0 34 | y: 1.0 35 | z: 1.0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/savetable/savetable.collection: -------------------------------------------------------------------------------- 1 | name: "default" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"gui\"\n" 7 | " component: \"/examples/savetable/savetable.gui\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "" 21 | position { 22 | x: 0.0 23 | y: 0.0 24 | z: 0.0 25 | } 26 | rotation { 27 | x: 0.0 28 | y: 0.0 29 | z: 0.0 30 | w: 1.0 31 | } 32 | scale3 { 33 | x: 1.0 34 | y: 1.0 35 | z: 1.0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/ci-workflow.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build_and_run: 7 | name: Build and run 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: pyvista/setup-headless-display-action@v3 12 | - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 13 | with: 14 | java-version: '21.0.5+11.0.LTS' 15 | distribution: 'temurin' 16 | - name: Run.sh 17 | env: 18 | DEFOLD_USER: foo 19 | DEFOLD_AUTH: bar 20 | DEFOLD_BOOSTRAP_COLLECTION: /test/test.collectionc 21 | run: ./.test/run.sh 22 | -------------------------------------------------------------------------------- /game.project: -------------------------------------------------------------------------------- 1 | [project] 2 | title = Ludobits 3 | version = 0.7.6 4 | dependencies#0 = https://github.com/britzl/deftest/archive/2.7.0.zip 5 | dependencies#1 = https://github.com/britzl/defold-input/archive/1.3.0.zip 6 | 7 | [bootstrap] 8 | main_collection = /examples/examples.collectionc 9 | render = /builtins/render/default.renderc 10 | 11 | [input] 12 | game_binding = /in/bindings/all.input_bindingc 13 | gamepads = /in/gamepads/in.gamepadsc 14 | 15 | [display] 16 | width = 1280 17 | height = 720 18 | 19 | [physics] 20 | scale = 0.02 21 | debug = 0 22 | gravity_y = 0 23 | 24 | [script] 25 | shared_state = 1 26 | 27 | [library] 28 | include_dirs = ludobits 29 | 30 | [graphics] 31 | max_debug_vertices = 50000 32 | 33 | [collection_proxy] 34 | max_count = 16 35 | 36 | -------------------------------------------------------------------------------- /examples/listener/listener3.script: -------------------------------------------------------------------------------- 1 | local some_data = require "examples.listener.some_data" 2 | 3 | local function foo(message_id, message) 4 | assert(message_id == some_data.FOO, "Expected only messages of type FOO to be received") 5 | print("listener3 foo() received", message_id) 6 | end 7 | 8 | function init(self) 9 | some_data.listeners.add(foo, some_data.FOO) 10 | some_data.listeners.add(msg.url(), some_data.BAR) 11 | end 12 | 13 | function final(self) 14 | some_data.listeners.remove(foo) 15 | some_data.listeners.remove(msg.url()) 16 | end 17 | 18 | function on_message(self, message_id, message, sender) 19 | assert(message_id == some_data.BAR, "Expected only messages of type BAR to be received") 20 | print("listener3 on_message() received", message_id) 21 | end 22 | -------------------------------------------------------------------------------- /ludobits/m/async.lua.md: -------------------------------------------------------------------------------- 1 | ## Co-routine wrapper for asynchronous operations 2 | 3 | Usage: 4 | 5 | ```lua 6 | local async = require("ludobits.m.async") 7 | 8 | local co = coroutine.create(function() 9 | -- do an http request and wait for the response 10 | local response = async.http_request("https://www.foobar.com", "GET", headers) 11 | print(response.code) 12 | 13 | -- animate a game object over time and wait until completed 14 | local to = vmath.vector3(100, 100, 0) 15 | local duration = 5 -- seconds 16 | local delay = 1 -- second 17 | async.go_animate(".", "position", go.PLAYBACK_ONCE_FORWARD, to, go.EASING_LINEAR, duration, delay) 18 | 19 | -- wait 5 seconds 20 | async.delay(5) 21 | 22 | print("We're done!) 23 | end) 24 | coroutine.resume(co) 25 | ``` 26 | -------------------------------------------------------------------------------- /examples/flow/a.gui: -------------------------------------------------------------------------------- 1 | script: "/examples/flow/a.gui_script" 2 | fonts { 3 | name: "system_font" 4 | font: "/builtins/fonts/default.font" 5 | } 6 | nodes { 7 | position { 8 | x: 640.0 9 | y: 460.0 10 | } 11 | size { 12 | x: 200.0 13 | y: 100.0 14 | } 15 | type: TYPE_TEXT 16 | text: "COLLECTION A" 17 | font: "system_font" 18 | id: "text" 19 | outline { 20 | x: 1.0 21 | y: 1.0 22 | z: 1.0 23 | } 24 | shadow { 25 | x: 1.0 26 | y: 1.0 27 | z: 1.0 28 | } 29 | inherit_alpha: true 30 | } 31 | nodes { 32 | size { 33 | x: 200.0 34 | y: 100.0 35 | } 36 | type: TYPE_BOX 37 | id: "box" 38 | inherit_alpha: true 39 | size_mode: SIZE_MODE_AUTO 40 | } 41 | material: "/builtins/materials/gui.material" 42 | adjust_reference: ADJUST_REFERENCE_PARENT 43 | -------------------------------------------------------------------------------- /test/test_file.lua: -------------------------------------------------------------------------------- 1 | local mock = require "deftest.mock.mock" 2 | local mock_fs = require "deftest.mock.fs" 3 | local unload = require "deftest.util.unload" 4 | 5 | return function() 6 | local file 7 | 8 | describe("file", function() 9 | before(function() 10 | unload.unload("ludobits.") 11 | mock_fs.mock() 12 | file = require "ludobits.m.io.file" 13 | end) 14 | 15 | after(function() 16 | mock_fs.unmock() 17 | end) 18 | 19 | it("should be able to fix filenames with illegal characters", function() 20 | assert(file.fix("foobar") == "foobar") 21 | assert(file.fix("foo.bar") == "foo.bar") 22 | assert(file.fix("foo_bar") == "foo_bar") 23 | assert(file.fix("foo bar") == "foo+bar") 24 | assert(file.fix("O/>") == "O%2F%3E") 25 | assert(file.fix("foo\nbar") == "foo%0Abar") 26 | end) 27 | end) 28 | end -------------------------------------------------------------------------------- /test/test.script: -------------------------------------------------------------------------------- 1 | local test_broadcast = require "test.test_broadcast" 2 | local test_flow = require "test.test_flow" 3 | local test_savetable = require "test.test_savetable" 4 | local test_savefile = require "test.test_savefile" 5 | local test_file = require "test.test_file" 6 | local test_settings = require "test.test_settings" 7 | local test_listener = require "test.test_listener" 8 | local test_signal = require "test.test_signal" 9 | 10 | local deftest = require "deftest.deftest" 11 | 12 | function init(self) 13 | deftest.add(test_broadcast) 14 | deftest.add(test_flow) 15 | deftest.add(test_savetable) 16 | deftest.add(test_savefile) 17 | deftest.add(test_file) 18 | deftest.add(test_listener) 19 | deftest.add(test_settings) 20 | deftest.add(test_signal) 21 | deftest.run({ 22 | --pattern = "should be able to pause until a message is received" 23 | }) 24 | end 25 | -------------------------------------------------------------------------------- /test/test_savefile.lua: -------------------------------------------------------------------------------- 1 | local mock = require "deftest.mock.mock" 2 | local mock_fs = require "deftest.mock.fs" 3 | local unload = require "deftest.util.unload" 4 | 5 | return function() 6 | local savefile 7 | 8 | describe("savefile", function() 9 | before(function() 10 | unload.unload("ludobits.") 11 | mock_fs.mock() 12 | savefile = require "ludobits.m.io.savefile" 13 | end) 14 | 15 | after(function() 16 | mock_fs.unmock() 17 | end) 18 | 19 | it("should be able to save and load files", function() 20 | local file1 = savefile.open("foobar1") 21 | local file2 = savefile.open("foobar2") 22 | local t1 = file1.load() 23 | local t2 = file2.load() 24 | assert(not t1) 25 | assert(not t2) 26 | file1.save("foobar") 27 | 28 | local t1 = file1.load() 29 | local t2 = file2.load() 30 | assert(t1 == "foobar") 31 | assert(not t2) 32 | end) 33 | end) 34 | end -------------------------------------------------------------------------------- /ludobits/m/io/file.lua: -------------------------------------------------------------------------------- 1 | --- File manipulation utilities 2 | 3 | local M = {} 4 | 5 | --- Fix a filename to ensure that it doesn't contain any illegal characters 6 | -- @param filename 7 | -- @return Filename with illegal characters replaced 8 | function M.fix(filename) 9 | filename = filename:gsub("([^0-9a-zA-Z%._ ])", function(c) return string.format("%%%02X", string.byte(c)) end) 10 | filename = filename:gsub(" ", "+") 11 | return filename 12 | end 13 | 14 | --- Get an application specific save file path to a filename. The path will be 15 | -- based on the sys.get_save_file() function and the project title (with whitespace) 16 | -- replaced by underscore 17 | -- @param filename 18 | -- @return Save file path 19 | function M.get_save_file_path(filename) 20 | local path = sys.get_save_file(M.fix(sys.get_config("project.title"):gsub(" ", "_")), filename) 21 | return path 22 | end 23 | 24 | return M 25 | -------------------------------------------------------------------------------- /examples/flow/flow.gui_script: -------------------------------------------------------------------------------- 1 | local flow = require "ludobits.m.flow" 2 | 3 | function init(self) 4 | flow.start(function() 5 | local box = gui.get_node("box") 6 | while true do 7 | flow.gui_animate(box, gui.PROP_POSITION, gui.PLAYBACK_ONCE_FORWARD, vmath.vector3(100, 500, 0), gui.EASING_INOUTCUBIC, 1, 0.2) 8 | flow.gui_animate(box, gui.PROP_COLOR, gui.PLAYBACK_ONCE_FORWARD, vmath.vector4(1, 1, 1, 0), gui.EASING_INOUTCUBIC, 1, 0) 9 | flow.gui_animate(box, gui.PROP_COLOR, gui.PLAYBACK_ONCE_FORWARD, vmath.vector4(1, 1, 1, 1), gui.EASING_INOUTCUBIC, 1, 0) 10 | flow.gui_animate(box, gui.PROP_POSITION, gui.PLAYBACK_ONCE_FORWARD, vmath.vector3(500, 500, 0), gui.EASING_INOUTCUBIC, 1, 0.2) 11 | end 12 | end) 13 | end 14 | 15 | function final(self) 16 | flow.stop() 17 | end 18 | 19 | function on_message(self, message_id, message, sender) 20 | flow.on_message(message_id, message, sender) 21 | end 22 | -------------------------------------------------------------------------------- /ludobits/m/broadcast.md: -------------------------------------------------------------------------------- 1 | # Broadcast 2 | Module to simplify sending of a message to multiple receivers 3 | 4 | ## Usage 5 | 6 | Register a listener: 7 | 8 | ```lua 9 | -- receiver_a.script 10 | local broadcast = require "ludobits.m.broadcast" 11 | 12 | function init(self) 13 | -- listen to the "foo" message 14 | broadcast.register("foo") 15 | end 16 | 17 | function final(self) 18 | broadcast.unregister("foo") 19 | end 20 | 21 | function on_message(self, message_id, message, sender) 22 | if message_id == hash("foo") then 23 | -- handle message "foo" 24 | pprint(message) 25 | end 26 | end 27 | ``` 28 | 29 | Broadcast a "foo" message: 30 | 31 | ```lua 32 | -- broadcaster.script 33 | local broadcast = require "ludobits.m.broadcast" 34 | 35 | function update(self, dt) 36 | if some_condition then 37 | -- broadcast a "foo" message to anyone listening 38 | broadcast.send("foo", { something = 123 }) 39 | end 40 | end 41 | ``` 42 | -------------------------------------------------------------------------------- /ludobits/m/logger.md: -------------------------------------------------------------------------------- 1 | # Logger 2 | The Logger module provides a simple logging framework to log application events of different severities to standard out. The module supports simple filtering based on severity. 3 | 4 | # Usage 5 | 6 | ```lua 7 | local logger = require "ludobits.m.logger" 8 | 9 | local log = logger.create("foo") 10 | 11 | log.d("This will be logged with level DEBUG") 12 | log.debug("And this too") 13 | log("And this too") 14 | log.i("This will be logged with level INFO") 15 | log.info("And this too") 16 | log.w("This will be logged with level WARN") 17 | log.warn("And this too") 18 | log.e("This will be logged with level ERROR") 19 | log.error("And this too") 20 | log.f("This will be logged with level FATAL") 21 | log.fatal("And this too") 22 | 23 | -- only log WARN and above 24 | logger.level(logger.WARN) 25 | 26 | log.d("This will not be logged since the minimum log level is set to WARN") 27 | ``` 28 | -------------------------------------------------------------------------------- /ludobits/m/thread.md: -------------------------------------------------------------------------------- 1 | # Thread 2 | Module to wrap and run a long-running task over multiple frames using coroutines 3 | 4 | ## Usage 5 | 6 | ```lua 7 | local thread = require "ludobits.m.thread" 8 | 9 | -- long-running task 10 | -- call pause frequently to hand over execution to the engine 11 | -- the task will be resumed the next frame again 12 | local function do_long_running_task(pause) 13 | local numbers = {} 14 | for i=1,100 do 15 | numbers[#numbers+1] = math.random() 16 | pause() 17 | end 18 | return numbers 19 | end 20 | 21 | function init(self) 22 | -- create a new thread 23 | -- run the long-running task until it completes at which point it will 24 | -- invoke the provided callback 25 | local handle = thread.create(do_long_running_task, function(ok, numbers) 26 | print(ok) -- true if long-running task completed without errors 27 | pprint(numbers) -- the return value from the task or an error message 28 | end) 29 | 30 | -- call thread.cancel() to stop task early 31 | thread.cancel(handle) 32 | end 33 | ``` -------------------------------------------------------------------------------- /examples/logger/logger.collection: -------------------------------------------------------------------------------- 1 | name: "logger" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"logger\"\n" 7 | " component: \"/examples/logger/logger.gui\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "components {\n" 21 | " id: \"logger1\"\n" 22 | " component: \"/examples/logger/logger.script\"\n" 23 | " position {\n" 24 | " x: 0.0\n" 25 | " y: 0.0\n" 26 | " z: 0.0\n" 27 | " }\n" 28 | " rotation {\n" 29 | " x: 0.0\n" 30 | " y: 0.0\n" 31 | " z: 0.0\n" 32 | " w: 1.0\n" 33 | " }\n" 34 | "}\n" 35 | "" 36 | position { 37 | x: 0.0 38 | y: 0.0 39 | z: 0.0 40 | } 41 | rotation { 42 | x: 0.0 43 | y: 0.0 44 | z: 0.0 45 | w: 1.0 46 | } 47 | scale3 { 48 | x: 1.0 49 | y: 1.0 50 | z: 1.0 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ludobits/m/signal.md: -------------------------------------------------------------------------------- 1 | # Signal 2 | Module to create a signal system where named signals can be created, listened to and triggered. 3 | 4 | ## Usage 5 | 6 | ```lua 7 | -- example_module.lua 8 | local signal = require "ludobits.m.signal" 9 | 10 | local M = {} 11 | 12 | M.LOGIN_SUCCESS_SIGNAL = signal.create("login_success") 13 | M.LOGOUT_SIGNAL = signal.create("logout") 14 | 15 | function M.login() 16 | .. perform async login and then 17 | M.LOGIN_SUCCESS_SIGNAL.trigger({ user = "Foobar" }) 18 | end 19 | 20 | function M.logout() 21 | M.LOGOUT_SIGNAL.trigger() 22 | end 23 | 24 | return M 25 | 26 | 27 | --some.script 28 | local example_module = require "example_module" 29 | 30 | function init(self) 31 | example_module.LOGIN_SUCCESS_SIGNAL.add(function(message) 32 | print("login success", message.user) 33 | end) 34 | example_module.LOGOUT_SIGNAL.add() 35 | end 36 | 37 | function on_message(self, message_id, message, sender) 38 | if message_id == hash(example_module.LOGOUT_SIGNAL.id) then 39 | print("User logged out") 40 | end 41 | end 42 | ``` -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Björn Ritzl 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. 22 | -------------------------------------------------------------------------------- /ludobits/m/settings.lua: -------------------------------------------------------------------------------- 1 | local savetable = require "ludobits.m.io.savetable" 2 | 3 | local M = {} 4 | 5 | local settings_filename 6 | local settings 7 | 8 | --- Load settings from disk 9 | -- @param filename File to load from or nil for the default file ("__settings") 10 | function M.load(filename) 11 | settings_filename = filename or "__settings" 12 | settings = savetable.load(settings_filename, savetable.FORMAT_IO) 13 | end 14 | 15 | --- Get the filename of the currently loaded settings file 16 | -- @return Filename 17 | function M.filename() 18 | return settings_filename 19 | end 20 | 21 | --- Check if the settings are empty 22 | -- @return true if empty 23 | function M.is_empty() 24 | return next(settings) == nil 25 | end 26 | 27 | --- Save settings to disk 28 | -- @param filename File to save to or nil for the currently loaded file 29 | function M.save(filename) 30 | settings_filename = filename or settings_filename 31 | savetable.save(settings, settings_filename, savetable.FORMAT_IO) 32 | end 33 | 34 | M.load() 35 | 36 | local mt = {} 37 | function mt.__index(t, k) 38 | return settings[k] 39 | end 40 | function mt.__newindex(t, k, v) 41 | settings[k] = v 42 | end 43 | 44 | 45 | return setmetatable(M, mt) 46 | -------------------------------------------------------------------------------- /examples/bezier/bezier.script: -------------------------------------------------------------------------------- 1 | local bezier = require "ludobits.m.bezier" 2 | 3 | 4 | local function animate(self) 5 | local p = table.remove(self.points, 1) 6 | if not p then 7 | return 8 | end 9 | go.animate(".", "position", go.PLAYBACK_ONCE_FORWARD, p, go.EASING_LINEAR, 0.05, 0, animate) 10 | end 11 | 12 | function init(self) 13 | local cps = { 14 | go.get_position("cp1"), 15 | go.get_position("cp2"), 16 | go.get_position("cp3"), 17 | go.get_position("cp4"), 18 | } 19 | self.points = bezier.create(cps, 20) 20 | go.set_position(cps[1]) 21 | animate(self) 22 | end 23 | 24 | function final(self) 25 | -- Add finalization code here 26 | -- Remove this function if not needed 27 | end 28 | 29 | function update(self, dt) 30 | -- Add update code here 31 | -- Remove this function if not needed 32 | end 33 | 34 | function on_message(self, message_id, message, sender) 35 | -- Add message-handling code here 36 | -- Remove this function if not needed 37 | end 38 | 39 | function on_input(self, action_id, action) 40 | -- Add input-handling code here 41 | -- Remove this function if not needed 42 | end 43 | 44 | function on_reload(self) 45 | -- Add reload-handling code here 46 | -- Remove this function if not needed 47 | end 48 | -------------------------------------------------------------------------------- /examples/flow/flow.gui: -------------------------------------------------------------------------------- 1 | script: "/examples/flow/flow.gui_script" 2 | background_color { 3 | x: 0.0 4 | y: 0.0 5 | z: 0.0 6 | w: 1.0 7 | } 8 | nodes { 9 | position { 10 | x: 1148.2771 11 | y: 638.22754 12 | z: 0.0 13 | w: 1.0 14 | } 15 | rotation { 16 | x: 0.0 17 | y: 0.0 18 | z: 0.0 19 | w: 1.0 20 | } 21 | scale { 22 | x: 1.0 23 | y: 1.0 24 | z: 1.0 25 | w: 1.0 26 | } 27 | size { 28 | x: 100.0 29 | y: 100.0 30 | z: 0.0 31 | w: 1.0 32 | } 33 | color { 34 | x: 1.0 35 | y: 1.0 36 | z: 1.0 37 | w: 1.0 38 | } 39 | type: TYPE_BOX 40 | blend_mode: BLEND_MODE_ALPHA 41 | texture: "" 42 | id: "box" 43 | xanchor: XANCHOR_NONE 44 | yanchor: YANCHOR_NONE 45 | pivot: PIVOT_CENTER 46 | adjust_mode: ADJUST_MODE_FIT 47 | layer: "" 48 | inherit_alpha: true 49 | slice9 { 50 | x: 0.0 51 | y: 0.0 52 | z: 0.0 53 | w: 0.0 54 | } 55 | clipping_mode: CLIPPING_MODE_NONE 56 | clipping_visible: true 57 | clipping_inverted: false 58 | alpha: 1.0 59 | template_node_child: false 60 | size_mode: SIZE_MODE_MANUAL 61 | } 62 | material: "/builtins/materials/gui.material" 63 | adjust_reference: ADJUST_REFERENCE_PARENT 64 | max_nodes: 512 65 | -------------------------------------------------------------------------------- /examples/sequence/sequence.collection: -------------------------------------------------------------------------------- 1 | name: "sequence" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"sequence\"\n" 7 | " component: \"/examples/sequence/sequence.script\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "embedded_components {\n" 21 | " id: \"sprite\"\n" 22 | " type: \"sprite\"\n" 23 | " data: \"tile_set: \\\"/examples/assets/examples.atlas\\\"\\n" 24 | "default_animation: \\\"green_circle\\\"\\n" 25 | "material: \\\"/builtins/materials/sprite.material\\\"\\n" 26 | "blend_mode: BLEND_MODE_ALPHA\\n" 27 | "\"\n" 28 | " position {\n" 29 | " x: 0.0\n" 30 | " y: 0.0\n" 31 | " z: 0.0\n" 32 | " }\n" 33 | " rotation {\n" 34 | " x: 0.0\n" 35 | " y: 0.0\n" 36 | " z: 0.0\n" 37 | " w: 1.0\n" 38 | " }\n" 39 | "}\n" 40 | "" 41 | position { 42 | x: 0.0 43 | y: 0.0 44 | z: 0.0 45 | } 46 | rotation { 47 | x: 0.0 48 | y: 0.0 49 | z: 0.0 50 | w: 1.0 51 | } 52 | scale3 { 53 | x: 1.0 54 | y: 1.0 55 | z: 1.0 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ludobits/m/bezier.lua: -------------------------------------------------------------------------------- 1 | --- Create bezier curves 2 | local M = {} 3 | 4 | 5 | local function point_on_cubic_bezier(cp, t) 6 | local ax, bx, cx 7 | local ay, by, cy 8 | local tSquared, tCubed 9 | 10 | -- /* calculation of the polinomial coeficients */ 11 | 12 | cx = 3.0 * (cp[2].x - cp[1].x) 13 | bx = 3.0 * (cp[1].x - cp[2].x) - cx 14 | ax = cp[4].x - cp[1].x - cx - bx 15 | 16 | cy = 3.0 * (cp[2].y - cp[1].y) 17 | by = 3.0 * (cp[3].y - cp[2].y) - cy 18 | ay = cp[4].y - cp[1].y - cy - by 19 | 20 | -- /* calculate the curve point at parameter value t */ 21 | 22 | tSquared = t * t 23 | tCubed = tSquared * t 24 | 25 | local result = vmath.vector3() 26 | result.x = (ax * tCubed) + (bx * tSquared) + (cx * t) + cp[1].x 27 | result.y = (ay * tCubed) + (by * tSquared) + (cy * t) + cp[1].y 28 | return result 29 | end 30 | 31 | --- Create a bezier curve 32 | -- @param cp Table with control points (pairs of { x, y }) 33 | -- @param points The number of points to generate along the curve 34 | -- @return Table with all of the vmath.vector3() points along the curve 35 | function M.create(cp, points) 36 | local curve = {} 37 | local dt = 1.0 / (points - 1) 38 | local i 39 | 40 | for i = 1, points do 41 | curve[i] = point_on_cubic_bezier(cp, i * dt) 42 | i = i + 1 43 | end 44 | return curve 45 | end 46 | 47 | return M 48 | -------------------------------------------------------------------------------- /examples/sequence/sequence.script: -------------------------------------------------------------------------------- 1 | local sequence = require "ludobits.m.sequence" 2 | 3 | local function random_position() 4 | return vmath.vector3(math.random(20, 800), math.random(20, 500), 0) 5 | end 6 | 7 | function init(self) 8 | sequence.run_loop(function() 9 | print("Start of loop") 10 | 11 | -- animate game object and wait until done 12 | print("go.animate") 13 | sequence.go_animate(".", "position", go.PLAYBACK_ONCE_FORWARD, random_position(), go.EASING_INOUTQUAD, 1, 0) 14 | 15 | -- make http GET and wait for response 16 | print("http.request") 17 | local response = sequence.http_get("https://postman-echo.com/get?foo1=bar1&foo2=bar2") 18 | pprint(response) 19 | 20 | -- wait 2.5 seconds 21 | print("delay") 22 | sequence.delay(2.5) 23 | 24 | -- call any function and pass a callback to be invoked when done 25 | local one, two, three = sequence.call(function(cb) 26 | -- anything async or sync operation can be done in here 27 | -- just make sure to call "cb" when done 28 | print("hello") 29 | -- done, return some values 30 | cb(1, 2, 3) 31 | end) 32 | print(one, two, three) 33 | 34 | local countdown = 10 35 | sequence.wait_until_false(function() 36 | print(countdown) 37 | countdown = countdown - 1 38 | return countdown > 0 39 | end) 40 | 41 | print("End of loop") 42 | end) 43 | end 44 | -------------------------------------------------------------------------------- /.test/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -eq 0 ]; then 4 | PLATFORM="x86_64-linux" 5 | else 6 | PLATFORM="$1" 7 | fi 8 | 9 | 10 | echo "${PLATFORM}" 11 | 12 | # {"version": "1.2.89", "sha1": "5ca3dd134cc960c35ecefe12f6dc81a48f212d40"} 13 | # Get SHA1 of the current Defold stable release 14 | SHA1=$(curl -s http://d.defold.com/stable/info.json | sed 's/.*sha1": "\(.*\)".*/\1/') 15 | echo "Using Defold dmengine_headless version ${SHA1}" 16 | 17 | # Create dmengine_headless and bob.jar URLs 18 | DMENGINE_URL="http://d.defold.com/archive/${SHA1}/engine/${PLATFORM}/dmengine_headless" 19 | BOB_URL="http://d.defold.com/archive/${SHA1}/bob/bob.jar" 20 | 21 | # Download dmengine_headless 22 | if [ ! -f dmengine_headless ]; then 23 | echo "Downloading ${DMENGINE_URL}" 24 | curl -L -o dmengine_headless ${DMENGINE_URL} 25 | chmod +x dmengine_headless 26 | fi 27 | 28 | # Download bob.jar 29 | if [ ! -f bob.jar ]; then 30 | echo "Downloading ${BOB_URL}" 31 | curl -L -o bob.jar ${BOB_URL} 32 | fi 33 | 34 | # Fetch libraries 35 | echo "Running bob.jar - resolving dependencies" 36 | java -jar bob.jar resolve 37 | 38 | echo "Running bob.jar - building" 39 | java -jar bob.jar --verbose build 40 | 41 | echo "Starting dmengine_headless" 42 | if [ -n "${DEFOLD_BOOSTRAP_COLLECTION}" ]; then 43 | ./dmengine_headless --config=bootstrap.main_collection=${DEFOLD_BOOSTRAP_COLLECTION} 44 | else 45 | ./dmengine_headless 46 | fi 47 | -------------------------------------------------------------------------------- /ludobits/m/util.lua: -------------------------------------------------------------------------------- 1 | --- Utility functions 2 | 3 | local M = {} 4 | 5 | --- Suffle a Lua table 6 | -- @param t The table to shuffle 7 | function M.shuffle(t) 8 | local size = #t 9 | for i = size, 1, -1 do 10 | local rand = math.random(size) 11 | t[i], t[rand] = t[rand], t[i] 12 | end 13 | return t 14 | end 15 | 16 | --- Pick a random value from a list 17 | -- @param list 18 | -- @return value A random value 19 | -- @return index Index of the value 20 | function M.random(list) 21 | local i = math.random(1, #list) 22 | return list[i], i 23 | end 24 | 25 | --- Clamp a value to within a specific range 26 | -- @param value The value to clamp 27 | -- @param min Minimum value 28 | -- @param max Maximum value 29 | -- @return The value clamped to between min and max 30 | function M.clamp(value, min, max) 31 | if value > max then return max end 32 | if value < min then return min end 33 | return value 34 | end 35 | 36 | -- http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ 37 | -- return vmath.lerp(1 - math.pow(t, dt), v1, v2) 38 | -- https://www.gamasutra.com/blogs/ScottLembcke/20180404/316046/Improved_Lerp_Smoothing.php 39 | local UPDATE_FREQUENCY = tonumber(sys.get_config("display.update_frequency")) or 60 40 | function M.lerp(t, dt, v1, v2) 41 | local rate = UPDATE_FREQUENCY * math.log10(1 - t) 42 | return vmath.lerp(1 - math.pow(10, rate * dt), v1, v2) 43 | end 44 | 45 | return M 46 | -------------------------------------------------------------------------------- /ludobits/m/async.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local unpack = _G.unpack or table.unpack 4 | 5 | 6 | function M.async(fn, ...) 7 | assert(fn) 8 | local co = coroutine.running() 9 | assert(co) 10 | local results = nil 11 | local state = "RUNNING" 12 | fn(function(...) 13 | results = { ... } 14 | if state == "YIELDED" then 15 | coroutine.resume(co) 16 | else 17 | state = "DONE" 18 | end 19 | end, ...) 20 | if state == "RUNNING" then 21 | state = "YIELDED" 22 | coroutine.yield() 23 | state = "DONE" -- not really needed 24 | end 25 | return unpack(results) 26 | end 27 | 28 | 29 | function M.http_request(url, method, headers, post_data, options) 30 | return M.async(function(done) 31 | http.request(url, method, function(self, id, response) 32 | done(response) 33 | end, headers, post_data, options) 34 | end) 35 | end 36 | 37 | function M.go_animate(url, property, playback, to, easing, duration, delay) 38 | return M.async(function(done) 39 | go.animate(url, property, playback, to, easing, duration, delay, function(self, url, property) 40 | done() 41 | end) 42 | end) 43 | end 44 | 45 | function M.delay(delay) 46 | return M.async(function(done) 47 | local handle = timer.delay(delay, false, function() 48 | done() 49 | end) 50 | if handle == timer.INVALID_TIMER_HANDLE then 51 | print("Unable to create timer") 52 | done() 53 | end 54 | end) 55 | end 56 | 57 | setmetatable(M, { 58 | __call = function(t, ...) 59 | return M.async(...) 60 | end 61 | }) 62 | 63 | return M 64 | -------------------------------------------------------------------------------- /examples/savetable/savetable.gui_script: -------------------------------------------------------------------------------- 1 | local savetable = require "ludobits.m.io.savetable" 2 | local button = require "in.button" 3 | 4 | 5 | function init(self) 6 | button.acquire() 7 | 8 | button.register("load/button", function() 9 | local data = savetable.load("savetable") 10 | gui.set_text(gui.get_node("text"), table.concat(data, " ")) 11 | end) 12 | 13 | button.register("save/button", function() 14 | local lorem = { "Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "Curabitur", "at", "tincidunt", "tortor", "Donec", "consectetur", "massa", "a", "purus", "fringilla", "rhoncus", "Donec", "non", "mi", "nulla", "Fusce", "ultrices", "consequat", "bibendum", "Nulla", "feugiat", "dui", "at", "vehicula", "porttitor", "Mauris", "eleifend", "efficitur", "velit", "sit", "amet", "porttitor", "Sed", "dui", "ipsum", "interdum", "quis", "cursus", "sit", "amet", "consectetur", "eget", "massa" } 15 | for _=1,#lorem do 16 | local i1 = math.random(1, #lorem) 17 | local i2 = math.random(1, #lorem) 18 | lorem[i1], lorem[i2] = lorem[i2], lorem[i1] 19 | end 20 | savetable.save(lorem, "savetable") 21 | gui.set_text(gui.get_node("text"), table.concat(lorem, " ")) 22 | end) 23 | end 24 | 25 | function final(self) 26 | button.release() 27 | button.unregister() 28 | end 29 | 30 | function on_input(self, action_id, action) 31 | button.on_input(action_id, action) 32 | end 33 | 34 | function on_reload(self) 35 | -- Add input-handling code here 36 | -- Remove this function if not needed 37 | end 38 | -------------------------------------------------------------------------------- /examples/examples.gui_script: -------------------------------------------------------------------------------- 1 | local button = require "in.button" 2 | 3 | local function load_example(self, proxy) 4 | gui.set_enabled(gui.get_node("buttons"), false) 5 | gui.set_enabled(gui.get_node("back/button"), true) 6 | msg.post(proxy, "async_load") 7 | self.example = proxy 8 | end 9 | 10 | local function unload_example(self) 11 | gui.set_enabled(gui.get_node("buttons"), true) 12 | gui.set_enabled(gui.get_node("back/button"), false) 13 | msg.post(self.example, "unload") 14 | self.example = nil 15 | end 16 | 17 | function init(self) 18 | button.acquire() 19 | 20 | gui.set_enabled(gui.get_node("back/button"), false) 21 | 22 | button.register("back/button", function() 23 | unload_example(self) 24 | end) 25 | 26 | local examples = { 27 | "bezier", "broadcast", "dynamic", "flow", "kinematic", "listener", 28 | "savefile", "savetable", "logger", "sequence" 29 | } 30 | 31 | for _,example in ipairs(examples) do 32 | button.register(example .. "/button", function() 33 | load_example(self, "#" .. example .. "proxy") 34 | end) 35 | end 36 | end 37 | 38 | function final(self) 39 | button.release() 40 | button.unregister() 41 | end 42 | 43 | function on_input(self, action_id, action) 44 | button.on_input(action_id, action) 45 | end 46 | 47 | function on_message(self, message_id, message, sender) 48 | if message_id == hash("proxy_loaded") then 49 | msg.post(sender, "enable") 50 | end 51 | end 52 | 53 | function on_reload(self) 54 | -- Add input-handling code here 55 | -- Remove this function if not needed 56 | end 57 | -------------------------------------------------------------------------------- /examples/savefile/savefile.gui_script: -------------------------------------------------------------------------------- 1 | local savefile = require "ludobits.m.io.savefile" 2 | local button = require "in.button" 3 | 4 | 5 | function init(self) 6 | button.acquire() 7 | 8 | self.file = savefile.open("savefile") 9 | button.register("load/button", function() 10 | local data = self.file.load() 11 | data = data or "nil" 12 | gui.set_text(gui.get_node("text"), data) 13 | end) 14 | 15 | button.register("save/button", function() 16 | local lorem = { "Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "Curabitur", "at", "tincidunt", "tortor", "Donec", "consectetur", "massa", "a", "purus", "fringilla", "rhoncus", "Donec", "non", "mi", "nulla", "Fusce", "ultrices", "consequat", "bibendum", "Nulla", "feugiat", "dui", "at", "vehicula", "porttitor", "Mauris", "eleifend", "efficitur", "velit", "sit", "amet", "porttitor", "Sed", "dui", "ipsum", "interdum", "quis", "cursus", "sit", "amet", "consectetur", "eget", "massa" } 17 | for _=1,#lorem do 18 | local i1 = math.random(1, #lorem) 19 | local i2 = math.random(1, #lorem) 20 | lorem[i1], lorem[i2] = lorem[i2], lorem[i1] 21 | end 22 | local file = table.concat(lorem, " ") 23 | self.file.save(file) 24 | gui.set_text(gui.get_node("text"), file) 25 | end) 26 | end 27 | 28 | function final(self) 29 | button.release() 30 | button.unregister() 31 | end 32 | 33 | function on_input(self, action_id, action) 34 | button.on_input(action_id, action) 35 | end 36 | 37 | function on_reload(self) 38 | -- Add input-handling code here 39 | -- Remove this function if not needed 40 | end 41 | -------------------------------------------------------------------------------- /ludobits/m/io/savefile.lua: -------------------------------------------------------------------------------- 1 | --- Save and load using the io.* functions 2 | -- 3 | -- @usage 4 | -- local savefile = require "ludobits.m.io.savefile" 5 | -- 6 | -- local file = savefile.open("foobar") 7 | -- file.save("something large to save") 8 | -- local data = file.load() 9 | -- print(data) -- "something large to save" 10 | 11 | local file = require "ludobits.m.io.file" 12 | 13 | local M = {} 14 | 15 | --- Open a file for reading and writing using the io.* functions 16 | -- @param filename 17 | -- @return file instance 18 | function M.open(filename) 19 | local path = file.get_save_file_path(filename) 20 | local instance = {} 21 | 22 | --- Load the table stored in the file 23 | -- @param mode The read mode, defaults to "rb" 24 | -- @return contents File contents or nil if something went wrong 25 | -- @return error_message Error message if something went wrong while reading 26 | function instance.load(mode) 27 | local f, err = io.open(path, mode or "rb") 28 | if err then 29 | return nil, err 30 | end 31 | return f:read("*a") 32 | end 33 | 34 | --- Save string to the file 35 | -- @param s The string to save 36 | -- @param mode The write mode, defaults to "wb" 37 | -- @return success 38 | -- @return error_message 39 | function instance.save(s, mode) 40 | assert(s and type(s) == "string", "You must provide a string to save") 41 | local f, err = io.open(path, mode or "wb") 42 | if err then 43 | return nil, err 44 | end 45 | f:write(s) 46 | f:flush() 47 | f:close() 48 | return true 49 | end 50 | 51 | return instance 52 | end 53 | 54 | 55 | 56 | return M 57 | -------------------------------------------------------------------------------- /ludobits/m/listener.md: -------------------------------------------------------------------------------- 1 | # Listener 2 | Listener implementation where listeners are added as either urls or functions and notified when any or specific messages are received 3 | 4 | # Usage 5 | 6 | ```lua 7 | -- a.script 8 | local listener = require "ludobits.m.listener" 9 | 10 | local l = listener.create() 11 | 12 | local function handler1(message_id, message) 13 | print(message_id) 14 | end 15 | 16 | local function handler2(message_id, message) 17 | print(message_id) 18 | end 19 | 20 | -- add listener function handler1 and listen to all messages 21 | 22 | l.add(handler1) 23 | 24 | -- add listener function handler2 and only listen to "mymessage1" and "mymessage2" 25 | l.add(handler2, "mymessage1") 26 | l.add(handler2, "mymessage2") 27 | 28 | -- add listener url "#myscript1" and listen to all messages 29 | l.add(msg.url("#myscript1")) 30 | 31 | -- add listener url "#myscript2" and only listen to "mymessage1" and "mymessage2" 32 | l.add(msg.url("#myscript2"), "mymessage1") 33 | l.add(msg.url("#myscript2"), "mymessage2") 34 | 35 | 36 | -- trigger some messages 37 | l.trigger(hash("mymessage1"), { text = "lorem ipsum" }) 38 | l.trigger(hash("mymessage2"), { text = "lorem ipsum" }) 39 | l.trigger(hash("mymessage3"), { text = "lorem ipsum" }) 40 | l.trigger(hash("foobar"), { foo = "bar" }) 41 | ``` 42 | 43 | ```lua 44 | --myscript1.script 45 | function on_message(self, mesage_id, message, sender) 46 | print(message_id) 47 | end 48 | ``` 49 | 50 | ```lua 51 | -- myscript2.script 52 | function on_message(self, mesage_id, message, sender) 53 | print(message_id) 54 | end 55 | ``` -------------------------------------------------------------------------------- /ludobits/m/thread.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local threads = {} 4 | 5 | local id = 0 6 | 7 | local handle = nil 8 | 9 | local function start_timer() 10 | if handle then 11 | return 12 | end 13 | handle = timer.delay(0, true, function(self, handle, dt) 14 | for id, thread in pairs(threads) do 15 | if thread.cancelled then 16 | threads[id] = nil 17 | else 18 | local status = coroutine.status(thread.co) 19 | if status == "suspended" then 20 | local ok, result = coroutine.resume(thread.co) 21 | if not ok then 22 | threads[id] = nil 23 | pcall(thread.cb, false, result) 24 | end 25 | elseif status == "dead" then 26 | threads[id] = nil 27 | pcall(thread.cb, true, thread.result) 28 | end 29 | end 30 | end 31 | 32 | if not next(threads) then 33 | timer.cancel(handle) 34 | handle = nil 35 | end 36 | end) 37 | end 38 | 39 | function M.create(fn, cb) 40 | assert(fn, "You must provide a function to run") 41 | assert(cb, "You must provide a result callback") 42 | id = id + 1 43 | local thread = { 44 | id = id, 45 | cb = cb, 46 | } 47 | thread.co = coroutine.create(function() 48 | local ok, result = pcall(fn, coroutine.yield) 49 | if not ok then 50 | pcall(cb, false, result) 51 | else 52 | thread.result = result 53 | end 54 | end) 55 | threads[id] = thread 56 | 57 | local ok, result = coroutine.resume(thread.co) 58 | if not ok then 59 | threads[id] = nil 60 | pcall(cb, false, result) 61 | return id 62 | end 63 | 64 | start_timer() 65 | 66 | return id 67 | end 68 | 69 | 70 | function M.cancel(id) 71 | local t = threads[id] 72 | assert(t) 73 | t.cancelled = true 74 | end 75 | 76 | 77 | return M -------------------------------------------------------------------------------- /test/test_savetable.lua: -------------------------------------------------------------------------------- 1 | local mock = require "deftest.mock.mock" 2 | local mock_fs = require "deftest.mock.fs" 3 | local unload = require "deftest.util.unload" 4 | 5 | return function() 6 | local savetable 7 | 8 | describe("savetable", function() 9 | before(function() 10 | unload.unload("ludobits.") 11 | mock_fs.mock() 12 | savetable = require "ludobits.m.io.savetable" 13 | end) 14 | 15 | after(function() 16 | mock_fs.unmock() 17 | end) 18 | 19 | it("should be able to load and save tables", function() 20 | local file1 = savetable.load("foobar1", savetable.FORMAT_IO) 21 | local file2 = savetable.load("foobar2", savetable.FORMAT_SYS) 22 | file1.boba = "fett" 23 | file2.luke = "skywalker" 24 | savetable.save(file1) 25 | savetable.save(file2) 26 | 27 | local file1 = savetable.load("foobar1", savetable.FORMAT_IO) 28 | local file2 = savetable.load("foobar2", savetable.FORMAT_SYS) 29 | assert(file1.boba == "fett") 30 | assert(file2.luke == "skywalker") 31 | 32 | local file3 = {} 33 | file3.darth = "vader" 34 | savetable.save(file3, "foobar3", savetable.FORMAT_SYS) 35 | 36 | local file3 = savetable.load("foobar3", savetable.FORMAT_SYS) 37 | assert(file3.darth == "vader") 38 | end) 39 | 40 | it("should be able to save and load tables", function() 41 | local file3 = { darth = "vader" } 42 | local file4 = { han ="solo" } 43 | savetable.save(file3, "foobar3", savetable.FORMAT_IO) 44 | savetable.save(file4, "foobar4", savetable.FORMAT_SYS) 45 | 46 | local file3 = savetable.load("foobar3", savetable.FORMAT_IO) 47 | local file4 = savetable.load("foobar4", savetable.FORMAT_SYS) 48 | assert(file3.darth == "vader") 49 | assert(file4.han == "solo") 50 | end) 51 | end) 52 | end -------------------------------------------------------------------------------- /examples/kinematic/kinematic.script: -------------------------------------------------------------------------------- 1 | local input = require "in.state" 2 | local mapper = require "in.mapper" 3 | local kinematic = require "ludobits.m.physics.kinematic" 4 | 5 | local UP = hash("up") 6 | local DOWN = hash("down") 7 | local LEFT = hash("left") 8 | local RIGHT = hash("right") 9 | 10 | local WIDTH = 1280 11 | local HEIGHT = 720 12 | 13 | function init(self) 14 | input.acquire() 15 | mapper.bind(mapper.KEY_LEFT, LEFT) 16 | mapper.bind(mapper.KEY_RIGHT, RIGHT) 17 | mapper.bind(mapper.KEY_UP, UP) 18 | mapper.bind(mapper.KEY_DOWN, DOWN) 19 | mapper.bind(mapper.KEY_A, LEFT) 20 | mapper.bind(mapper.KEY_D, RIGHT) 21 | mapper.bind(mapper.KEY_W, UP) 22 | mapper.bind(mapper.KEY_S, DOWN) 23 | self.k = kinematic.create() 24 | end 25 | 26 | function final(self) 27 | input.release() 28 | end 29 | 30 | function update(self, dt) 31 | self.k.update(dt) 32 | if input.is_pressed(UP) then 33 | self.k.forward(200 * dt) 34 | elseif input.is_pressed(DOWN) then 35 | self.k.backwards(100 * dt) 36 | end 37 | if input.is_pressed(LEFT) then 38 | self.k.rotate(math.rad(150 * dt)) 39 | elseif input.is_pressed(RIGHT) then 40 | self.k.rotate(math.rad(-150 * dt)) 41 | end 42 | end 43 | 44 | function on_message(self, message_id, message, sender) 45 | self.k.on_message(message_id, message, sender) 46 | end 47 | 48 | function on_input(self, action_id, action) 49 | action_id = mapper.on_input(action_id) 50 | input.on_input(action_id, action) 51 | 52 | if not action_id then 53 | local mouse = vmath.vector3(action.screen_x, action.screen_y, 0) 54 | go.set_position(mouse, "mouse") 55 | self.k.look_at(mouse) 56 | end 57 | end 58 | 59 | function on_reload(self) 60 | -- Add reload-handling code here 61 | -- Remove this function if not needed 62 | end 63 | -------------------------------------------------------------------------------- /ludobits/m/signal.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | 4 | --- Create a signal 5 | -- @param signal_id The unique id of the signal 6 | -- @return The created signal 7 | function M.create(signal_id) 8 | assert(signal_id, "You must provide a signal_id") 9 | signal_id = type(signal_id) == "string" and hash(signal_id) or signal_id 10 | local signal = { 11 | id = signal_id, 12 | } 13 | 14 | local listeners = {} 15 | 16 | --- Add a listener to the signal 17 | -- @param cb Function callback or message url (defaults to current url) 18 | function signal.add(cb) 19 | cb = cb or msg.url() 20 | if type(cb) == "function" then 21 | listeners[cb] = { fn = cb } 22 | else 23 | local key = hash_to_hex(cb.socket or hash("")) .. hash_to_hex(cb.path or hash("")) .. hash_to_hex(cb.fragment or hash("")) 24 | listeners[key] = { 25 | fn = function(message) 26 | if not message then 27 | msg.post(cb, signal_id) 28 | else 29 | if type(message) ~= "table" then 30 | message = { message = message } 31 | end 32 | msg.post(cb, signal_id, message) 33 | end 34 | end 35 | } 36 | end 37 | end 38 | 39 | --- Remove a listener from the signal 40 | -- @param cb Function callback or message url (defaults to current url) 41 | function signal.remove(cb) 42 | cb = cb or msg.url() 43 | if type(cb) == "function" then 44 | listeners[cb] = nil 45 | else 46 | local key = hash_to_hex(cb.socket or hash("")) .. hash_to_hex(cb.path or hash("")) .. hash_to_hex(cb.fragment or hash("")) 47 | listeners[key] = nil 48 | end 49 | end 50 | 51 | --- Trigger the signal 52 | -- @param message Optional message to pass to listeners 53 | function signal.trigger(message) 54 | for _,v in pairs(listeners) do 55 | v.fn(message) 56 | end 57 | end 58 | 59 | return signal 60 | end 61 | 62 | return M 63 | -------------------------------------------------------------------------------- /examples/dynamic/dynamic.script: -------------------------------------------------------------------------------- 1 | local dynamic = require "ludobits.m.physics.dynamic" 2 | local input = require "in.state" 3 | local mapper = require "in.mapper" 4 | 5 | local LEFT = hash("left") 6 | local RIGHT = hash("right") 7 | local UP = hash("up") 8 | local DOWN = hash("down") 9 | 10 | function init(self) 11 | input.acquire() 12 | mapper.bind(mapper.KEY_LEFT, LEFT) 13 | mapper.bind(mapper.KEY_RIGHT, RIGHT) 14 | mapper.bind(mapper.KEY_UP, UP) 15 | mapper.bind(mapper.KEY_DOWN, DOWN) 16 | mapper.bind(mapper.KEY_A, LEFT) 17 | mapper.bind(mapper.KEY_D, RIGHT) 18 | mapper.bind(mapper.KEY_W, UP) 19 | mapper.bind(mapper.KEY_S, DOWN) 20 | end 21 | 22 | function final(self) 23 | input.release() 24 | end 25 | 26 | function update(self, dt) 27 | if input.is_pressed(LEFT) then 28 | dynamic.rotate("#collisionobject", vmath.vector3(0, -1000, 0) * dt) 29 | elseif input.is_pressed(RIGHT) then 30 | dynamic.rotate("#collisionobject", vmath.vector3(0, 1000, 0) * dt) 31 | else 32 | dynamic.stop_rotating("#collisionobject") 33 | end 34 | 35 | if input.is_pressed(UP) then 36 | dynamic.forward("#collisionobject", vmath.vector3(0, 10000, 0) * dt) 37 | elseif input.is_pressed(DOWN) then 38 | dynamic.backwards("#collisionobject", vmath.vector3(0, 10000, 0) * dt) 39 | else 40 | dynamic.stop_moving("#collisionobject") 41 | end 42 | end 43 | 44 | function on_input(self, action_id, action) 45 | action_id = mapper.on_input(action_id) 46 | input.on_input(action_id, action) 47 | if action_id == hash("toggle_physics_debug") and action.released then 48 | msg.post("@system:", "toggle_physics_debug") 49 | elseif action_id == hash("toggle_profile") and action.released then 50 | msg.post("@system:", "toggle_profile") 51 | end 52 | end 53 | 54 | function on_reload(self) 55 | -- Add reload-handling code here 56 | -- Remove this function if not needed 57 | end 58 | -------------------------------------------------------------------------------- /ludobits/m/app.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local function create_listener() 4 | local instance = {} 5 | 6 | local listeners = {} 7 | 8 | function instance.add(fn) 9 | listeners[fn] = true 10 | end 11 | 12 | function instance.remove(fn) 13 | listeners[fn] = nil 14 | end 15 | 16 | function instance.trigger(...) 17 | for fn,_ in pairs(listeners) do 18 | pcall(fn, ...) 19 | end 20 | end 21 | 22 | return instance 23 | end 24 | 25 | 26 | 27 | 28 | --- Wrapper for iac.set_listener 29 | M.iac = {} 30 | 31 | local iac_listener = create_listener() 32 | 33 | function M.iac.add_listener(fn) 34 | if not iac then return end 35 | iac_listener.add(fn) 36 | iac.set_listener(iac_listener.trigger) 37 | end 38 | 39 | function M.iac.remove_listener(fn) 40 | iac_listener.remove(fn) 41 | end 42 | 43 | --- Wrapper for iap.set_listener 44 | M.iap = {} 45 | 46 | local iap_listener = create_listener() 47 | 48 | function M.iap.add_listener(fn) 49 | if not iap then return end 50 | iap_listener.add(fn) 51 | iap.set_listener(iap_listener.trigger) 52 | end 53 | 54 | function M.iap.remove_listener(fn) 55 | iap_listener.remove(fn) 56 | end 57 | 58 | 59 | --- Wrapper for window.set_listener 60 | M.window = {} 61 | 62 | local window_listener = create_listener() 63 | 64 | function M.window.add_listener(fn) 65 | if not window then return end 66 | window_listener.add(fn) 67 | window.set_listener(window_listener.trigger) 68 | end 69 | 70 | function M.window.remove_listener(fn) 71 | window_listener.remove(fn) 72 | end 73 | 74 | --- Wrapper for push.set_listener 75 | M.push = {} 76 | 77 | local push_listener = create_listener() 78 | 79 | function M.push.add_listener(fn) 80 | if not push then return end 81 | push_listener.add(fn) 82 | push.set_listener(push_listener.trigger) 83 | end 84 | 85 | function M.push.remove_listener(fn) 86 | push_listener.remove(fn) 87 | end 88 | 89 | 90 | 91 | return M 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ludobits 2 | Utilities for game development using the [Defold](http://www.defold.com) engine. 3 | 4 | [![Travis-CI](https://travis-ci.org/britzl/ludobits.svg?branch=master)](https://travis-ci.org/britzl/ludobits) 5 | 6 | ## Modules 7 | 8 | ### ludobits.m.io.file 9 | File name and path utilities 10 | 11 | ### ludobits.m.io.savefile 12 | Read/write files 13 | 14 | ### ludobits.m.io.savetable 15 | Read/write Lua tables 16 | 17 | ### ludobits.m.app 18 | Wrap engine callbacks from iac, iap, push and window. Refer to [app.md](ludobits/m/app.md) for usage details. 19 | 20 | ### ludobits.m.bezier 21 | Create bezier curves 22 | 23 | ### ludobits.m.broadcast 24 | Broadcast Defold messages (using msg.post) and set up optional function callbacks when messages are received. Refer to [broadcast.md](ludobits/m/broadcast.md) for usage details. 25 | 26 | ### ludobits.m.flow 27 | Simplifies asynchronous flows of execution where your code needs to wait for one asynchronous operation to finish before starting with the next one. 28 | 29 | ### ludobits.m.json 30 | JSON encode (using rxi.json) 31 | 32 | ### ludobits.m.listener 33 | Listener implementation where listeners are added as either urls or functions and notified when any or specific messages are received. Refer to [listener.md](ludobits/m/listener.md) for usage details. 34 | 35 | ### ludobits.m.logger 36 | The Logger module provides a simple logging framework to log application events of different severities to standard out. The module supports simple filtering based on severity. Refer to [logger.md](ludobits/m/logger.md) for usage details. 37 | 38 | ### ludobits.m.settings 39 | Store user settings to disk. Refer to [settings.md](ludobits/m/settings.md) for usage details. 40 | 41 | ### ludobits.m.signal 42 | Signal system where named signals can be created, listened to and triggered. Inspired by as3-signals. Refer to [signal.md](ludobits/m/signal.md) for usage details. 43 | -------------------------------------------------------------------------------- /test/test_signal.lua: -------------------------------------------------------------------------------- 1 | local mock = require "deftest.mock.mock" 2 | local unload = require "deftest.util.unload" 3 | local signal = require "ludobits.m.signal" 4 | 5 | return function() 6 | 7 | describe("signal", function() 8 | before(function() 9 | unload.unload("ludobits.") 10 | end) 11 | 12 | after(function() 13 | end) 14 | 15 | it("should have an id", function() 16 | local foo = signal.create("FOO") 17 | local bar = signal.create("BAR") 18 | assert(foo.id == hash("FOO")) 19 | assert(bar.id == hash("BAR")) 20 | end) 21 | 22 | it("should invoke added callbacks when triggered", function() 23 | local foo = signal.create("FOO") 24 | local bar = signal.create("BAR") 25 | 26 | local r = "" 27 | foo.add(function() r = r .. "foo" end) 28 | foo.add(function() r = r .. "foo" end) 29 | bar.add(function() r = r .. "bar" end) 30 | foo.trigger() 31 | assert(r == "foofoo") 32 | bar.trigger() 33 | assert(r == "foofoobar") 34 | end) 35 | 36 | it("should not invoke removed callbacks when triggered", function() 37 | local foo = signal.create("FOO") 38 | 39 | local r = "" 40 | local function a() r = r .. "a" end 41 | local function b() r = r .. "b" end 42 | foo.add(a) 43 | foo.add(b) 44 | foo.trigger() 45 | assert(r == "ab" or r == "ba") 46 | foo.remove(a) 47 | foo.trigger() 48 | assert(r == "abb" or r == "bab") 49 | end) 50 | 51 | it("should pass the message to callbacks when triggered", function() 52 | local foo = signal.create("FOO") 53 | local bar = signal.create("BAR") 54 | 55 | local r = "" 56 | foo.add(function(message) r = r .. message end) 57 | foo.add(function(message) r = r .. message end) 58 | bar.add(function(message) r = r .. message end) 59 | foo.trigger("a") 60 | assert(r == "aa") 61 | foo.trigger("b") 62 | assert(r == "aabb") 63 | bar.trigger("c") 64 | assert(r == "aabbc") 65 | end) 66 | end) 67 | end -------------------------------------------------------------------------------- /examples/logger/logger.gui_script: -------------------------------------------------------------------------------- 1 | local button = require "in.button" 2 | local logger = require "ludobits.m.logger" 3 | 4 | local log = logger.create("[gui]") 5 | 6 | local function update_log_level_buttons(current_level) 7 | for _,level in pairs({ "debug", "info", "warn", "error", "fatal" }) do 8 | gui.set_color(gui.get_node("set_" .. level .. "/button"), vmath.vector4(0.5, 0.5, 0.5, 1)) 9 | end 10 | gui.set_color(gui.get_node("set_" .. current_level .. "/button"), vmath.vector4(1)) 11 | end 12 | 13 | function init(self) 14 | log.d("init") 15 | button.acquire() 16 | button.register("debug/button", function() 17 | log.d("debug button pressed") 18 | end) 19 | button.register("info/button", function() 20 | log.i("info button pressed") 21 | end) 22 | button.register("warn/button", function() 23 | log.w("warn button pressed") 24 | end) 25 | button.register("error/button", function() 26 | log.e("error button pressed") 27 | end) 28 | button.register("fatal/button", function() 29 | log.f("fatal button pressed") 30 | end) 31 | 32 | button.register("set_debug/button", function() 33 | update_log_level_buttons("debug") 34 | logger.level(logger.DEBUG) 35 | end) 36 | button.register("set_info/button", function() 37 | update_log_level_buttons("info") 38 | logger.level(logger.INFO) 39 | end) 40 | button.register("set_warn/button", function() 41 | update_log_level_buttons("warn") 42 | logger.level(logger.WARN) 43 | end) 44 | button.register("set_error/button", function() 45 | update_log_level_buttons("error") 46 | logger.level(logger.ERROR) 47 | end) 48 | button.register("set_fatal/button", function() 49 | update_log_level_buttons("fatal") 50 | logger.level(logger.FATAL) 51 | end) 52 | update_log_level_buttons("debug") 53 | end 54 | 55 | function final(self) 56 | log.d("final") 57 | button.unregister() 58 | end 59 | 60 | function on_input(self, action_id, action) 61 | button.on_input(action_id, action) 62 | end 63 | 64 | function on_reload(self) 65 | -- Add input-handling code here 66 | -- Remove this function if not needed 67 | end 68 | -------------------------------------------------------------------------------- /examples/broadcast/broadcast.collection: -------------------------------------------------------------------------------- 1 | name: "default" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "broadcaster" 5 | data: "components {\n" 6 | " id: \"script\"\n" 7 | " component: \"/examples/broadcast/broadcaster.script\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "" 21 | position { 22 | x: 0.0 23 | y: 0.0 24 | z: 0.0 25 | } 26 | rotation { 27 | x: 0.0 28 | y: 0.0 29 | z: 0.0 30 | w: 1.0 31 | } 32 | scale3 { 33 | x: 1.0 34 | y: 1.0 35 | z: 1.0 36 | } 37 | } 38 | embedded_instances { 39 | id: "receiver1" 40 | data: "components {\n" 41 | " id: \"script\"\n" 42 | " component: \"/examples/broadcast/receiver1.script\"\n" 43 | " position {\n" 44 | " x: 0.0\n" 45 | " y: 0.0\n" 46 | " z: 0.0\n" 47 | " }\n" 48 | " rotation {\n" 49 | " x: 0.0\n" 50 | " y: 0.0\n" 51 | " z: 0.0\n" 52 | " w: 1.0\n" 53 | " }\n" 54 | "}\n" 55 | "" 56 | position { 57 | x: 0.0 58 | y: 0.0 59 | z: 0.0 60 | } 61 | rotation { 62 | x: 0.0 63 | y: 0.0 64 | z: 0.0 65 | w: 1.0 66 | } 67 | scale3 { 68 | x: 1.0 69 | y: 1.0 70 | z: 1.0 71 | } 72 | } 73 | embedded_instances { 74 | id: "receiver2" 75 | data: "components {\n" 76 | " id: \"script\"\n" 77 | " component: \"/examples/broadcast/receiver2.script\"\n" 78 | " position {\n" 79 | " x: 0.0\n" 80 | " y: 0.0\n" 81 | " z: 0.0\n" 82 | " }\n" 83 | " rotation {\n" 84 | " x: 0.0\n" 85 | " y: 0.0\n" 86 | " z: 0.0\n" 87 | " w: 1.0\n" 88 | " }\n" 89 | "}\n" 90 | "" 91 | position { 92 | x: 0.0 93 | y: 0.0 94 | z: 0.0 95 | } 96 | rotation { 97 | x: 0.0 98 | y: 0.0 99 | z: 0.0 100 | w: 1.0 101 | } 102 | scale3 { 103 | x: 1.0 104 | y: 1.0 105 | z: 1.0 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /ludobits/m/logger.lua: -------------------------------------------------------------------------------- 1 | --- Simple Lua logger 2 | 3 | local M = {} 4 | 5 | M.DEBUG = 1 6 | M.INFO = 2 7 | M.WARN = 3 8 | M.ERROR = 4 9 | M.FATAL = 5 10 | M.NONE = 6 11 | 12 | local function silent() end 13 | 14 | local debug = _G.print 15 | local info = _G.print 16 | local warn = _G.print 17 | local error = _G.print 18 | local fatal = _G.print 19 | 20 | --- Set the current accepted log level. Any logger calls with a level less than 21 | -- this will be silenced 22 | -- @param level The minimum accepted level 23 | function M.level(level) 24 | debug = level > M.DEBUG and silent or _G.print 25 | info = level > M.INFO and silent or _G.print 26 | warn = level > M.WARN and silent or _G.print 27 | error = level > M.ERROR and silent or _G.print 28 | fatal = level > M.FATAL and silent or _G.print 29 | end 30 | 31 | --- Create a logger instance 32 | -- @param tag Optional tag to prepend to all log output 33 | -- @return Logger instance 34 | function M.create(tag) 35 | tag = tag or "" 36 | 37 | local instance = {} 38 | 39 | --- Log with level set to DEBUG 40 | function instance.debug(...) debug("DEBUG", tag, ...) end 41 | --- Log with level set to INFO 42 | function instance.info(...) info("INFO", tag, ...) end 43 | --- Log with level set to WARN 44 | function instance.warn(...) warn("WARN", tag, ...) end 45 | --- Log with level set to ERROR 46 | function instance.error(...) error("ERROR", tag, ...) end 47 | --- Log with level set to FATAL 48 | function instance.fatal(...) fatal("FATAL", tag, ...) end 49 | --- Log with level set to DEBUG 50 | function instance.d(...) debug("DEBUG", tag, ...) end 51 | --- Log with level set to INFO 52 | function instance.i(...) info("INFO", tag, ...) end 53 | --- Log with level set to WARN 54 | function instance.w(...) warn("WARN", tag, ...) end 55 | --- Log with level set to ERROR 56 | function instance.e(...) error("ERROR", tag, ...) end 57 | --- Log with level set to FATAL 58 | function instance.f(...) fatal("FATAL", tag, ...) end 59 | 60 | setmetatable(instance, { 61 | __call = function(t, ...) 62 | return instance.debug(...) 63 | end 64 | }) 65 | return instance 66 | end 67 | 68 | 69 | 70 | return M 71 | -------------------------------------------------------------------------------- /test/test_broadcast.lua: -------------------------------------------------------------------------------- 1 | local mock = require "deftest.mock.mock" 2 | local unload = require "deftest.util.unload" 3 | 4 | return function() 5 | local broadcast 6 | 7 | describe("broadcast", function() 8 | before(function() 9 | unload.unload("ludobits.") 10 | broadcast = require "ludobits.m.broadcast" 11 | mock.mock(msg) 12 | msg.post.replace(function() end) 13 | end) 14 | 15 | after(function() 16 | mock.unmock(msg) 17 | end) 18 | 19 | -- the broadcast1 and broadcast2 game objects are completely empty and they exist in the test.collection 20 | it("should post a message to each registered recipient", function() 21 | msg.url.returns({ msg.url("broadcast1"), msg.url("broadcast2") }) 22 | broadcast.register("foo") 23 | broadcast.register("foo") 24 | 25 | msg.url.returns({ msg.url("broadcast1"), msg.url("broadcast2") }) 26 | broadcast.register("bar") 27 | broadcast.register("bar") 28 | 29 | broadcast.send("foo") 30 | assert(msg.post.calls == 2, "Expected 2 calls") 31 | end) 32 | 33 | it("should not send a message to an unregistered recipient", function() 34 | msg.url.returns({ msg.url("broadcast1"), msg.url("broadcast2") }) 35 | broadcast.register("foo") 36 | broadcast.register("foo") 37 | 38 | broadcast.send("foo") 39 | assert(msg.post.calls == 2, "Expected 2 calls") 40 | 41 | msg.url.returns({ msg.url("broadcast1") }) 42 | broadcast.unregister("foo") 43 | 44 | broadcast.send("foo") 45 | assert(msg.post.calls == 3, "Expected 3 calls") 46 | end) 47 | 48 | it("should be possible to register a message handler function", function() 49 | local message_handler_called = false 50 | msg.url.returns({ msg.url("broadcast1"), msg.url("broadcast2") }) 51 | broadcast.register("foo", function() 52 | message_handler_called = true 53 | end) 54 | broadcast.register("foo") 55 | 56 | msg.url.returns({ msg.url("broadcast2") }) 57 | broadcast.on_message(hash("foo"), {}) 58 | assert(not message_handler_called) 59 | 60 | msg.url.returns({ msg.url("broadcast1") }) 61 | broadcast.on_message(hash("foo"), {}) 62 | assert(message_handler_called) 63 | end) 64 | end) 65 | end -------------------------------------------------------------------------------- /test/test_settings.lua: -------------------------------------------------------------------------------- 1 | local mock = require "deftest.mock.mock" 2 | local mock_fs = require "deftest.mock.fs" 3 | local unload = require "deftest.util.unload" 4 | 5 | return function() 6 | local settings 7 | 8 | describe("settings", function() 9 | before(function() 10 | unload.unload("ludobits.") 11 | mock_fs.mock() 12 | settings = require "ludobits.m.settings" 13 | end) 14 | 15 | after(function() 16 | mock_fs.unmock() 17 | end) 18 | 19 | it("should start empty", function() 20 | assert(settings.is_empty()) 21 | end) 22 | 23 | it("should be possible to set and get values", function() 24 | assert(settings.foo == nil) 25 | assert(settings.boo == nil) 26 | settings.foo = "bar" 27 | settings.boo = "car" 28 | assert(settings.foo == "bar") 29 | assert(settings.boo == "car") 30 | assert(not settings.is_empty()) 31 | end) 32 | 33 | it("should be possible to set, save and later load values", function() 34 | settings.foo = "bar" 35 | settings.boo = "car" 36 | settings.save() 37 | 38 | -- unload and require again, make sure we get a new table 39 | package.loaded["ludobits.m.settings"] = nil 40 | local newsettings = require("ludobits.m.settings") 41 | assert(settings ~= newsettings) 42 | 43 | assert(newsettings.foo == "bar") 44 | assert(newsettings.boo == "car") 45 | end) 46 | 47 | it("should be possible to load and save to multiple files", function() 48 | -- set and save to default file 49 | assert(settings.is_empty()) 50 | settings.foo = "foodefault" 51 | settings.save() 52 | 53 | -- set and save to file1 54 | settings.load("file1") 55 | assert(settings.filename() == "file1") 56 | assert(settings.is_empty()) 57 | settings.foo = "foofile1" 58 | settings.save() 59 | 60 | -- load default file again and save it to a file2 61 | settings.load() 62 | assert(settings.foo == "foodefault") 63 | settings.foo = "foofile2" 64 | settings.save("file2") 65 | assert(settings.filename() == "file2") 66 | 67 | -- load file1 and make sure it is unchanged 68 | settings.load("file1") 69 | assert(settings.filename() == "file1") 70 | assert(settings.foo == "foofile1") 71 | end) 72 | end) 73 | end -------------------------------------------------------------------------------- /test/test.collection: -------------------------------------------------------------------------------- 1 | name: "test" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "broadcast1" 5 | data: "" 6 | position { 7 | x: 0.0 8 | y: 0.0 9 | z: 0.0 10 | } 11 | rotation { 12 | x: 0.0 13 | y: 0.0 14 | z: 0.0 15 | w: 1.0 16 | } 17 | scale3 { 18 | x: 1.0 19 | y: 1.0 20 | z: 1.0 21 | } 22 | } 23 | embedded_instances { 24 | id: "broadcast2" 25 | data: "" 26 | position { 27 | x: 0.0 28 | y: 0.0 29 | z: 0.0 30 | } 31 | rotation { 32 | x: 0.0 33 | y: 0.0 34 | z: 0.0 35 | w: 1.0 36 | } 37 | scale3 { 38 | x: 1.0 39 | y: 1.0 40 | z: 1.0 41 | } 42 | } 43 | embedded_instances { 44 | id: "listener1" 45 | data: "" 46 | position { 47 | x: 0.0 48 | y: 0.0 49 | z: 0.0 50 | } 51 | rotation { 52 | x: 0.0 53 | y: 0.0 54 | z: 0.0 55 | w: 1.0 56 | } 57 | scale3 { 58 | x: 1.0 59 | y: 1.0 60 | z: 1.0 61 | } 62 | } 63 | embedded_instances { 64 | id: "listener2" 65 | data: "" 66 | position { 67 | x: 0.0 68 | y: 0.0 69 | z: 0.0 70 | } 71 | rotation { 72 | x: 0.0 73 | y: 0.0 74 | z: 0.0 75 | w: 1.0 76 | } 77 | scale3 { 78 | x: 1.0 79 | y: 1.0 80 | z: 1.0 81 | } 82 | } 83 | embedded_instances { 84 | id: "listener3" 85 | data: "" 86 | position { 87 | x: 0.0 88 | y: 0.0 89 | z: 0.0 90 | } 91 | rotation { 92 | x: 0.0 93 | y: 0.0 94 | z: 0.0 95 | w: 1.0 96 | } 97 | scale3 { 98 | x: 1.0 99 | y: 1.0 100 | z: 1.0 101 | } 102 | } 103 | embedded_instances { 104 | id: "tests" 105 | data: "components {\n" 106 | " id: \"script\"\n" 107 | " component: \"/test/test.script\"\n" 108 | " position {\n" 109 | " x: 0.0\n" 110 | " y: 0.0\n" 111 | " z: 0.0\n" 112 | " }\n" 113 | " rotation {\n" 114 | " x: 0.0\n" 115 | " y: 0.0\n" 116 | " z: 0.0\n" 117 | " w: 1.0\n" 118 | " }\n" 119 | "}\n" 120 | "" 121 | position { 122 | x: 0.0 123 | y: 0.0 124 | z: 0.0 125 | } 126 | rotation { 127 | x: 0.0 128 | y: 0.0 129 | z: 0.0 130 | w: 1.0 131 | } 132 | scale3 { 133 | x: 1.0 134 | y: 1.0 135 | z: 1.0 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /examples/dynamic/dynamic.collection: -------------------------------------------------------------------------------- 1 | name: "default" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"script\"\n" 7 | " component: \"/examples/dynamic/dynamic.script\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "embedded_components {\n" 21 | " id: \"collisionobject\"\n" 22 | " type: \"collisionobject\"\n" 23 | " data: \"collision_shape: \\\"\\\"\\n" 24 | "type: COLLISION_OBJECT_TYPE_DYNAMIC\\n" 25 | "mass: 330.0\\n" 26 | "friction: 0.1\\n" 27 | "restitution: 0.5\\n" 28 | "group: \\\"default\\\"\\n" 29 | "mask: \\\"default\\\"\\n" 30 | "embedded_collision_shape {\\n" 31 | " shapes {\\n" 32 | " shape_type: TYPE_SPHERE\\n" 33 | " position {\\n" 34 | " x: 0.0\\n" 35 | " y: 0.0\\n" 36 | " z: 0.0\\n" 37 | " }\\n" 38 | " rotation {\\n" 39 | " x: 0.0\\n" 40 | " y: 0.0\\n" 41 | " z: 0.0\\n" 42 | " w: 1.0\\n" 43 | " }\\n" 44 | " index: 0\\n" 45 | " count: 1\\n" 46 | " }\\n" 47 | " data: 25.0\\n" 48 | "}\\n" 49 | "linear_damping: 0.5\\n" 50 | "angular_damping: 0.0\\n" 51 | "locked_rotation: false\\n" 52 | "\"\n" 53 | " position {\n" 54 | " x: 0.0\n" 55 | " y: 0.0\n" 56 | " z: 0.0\n" 57 | " }\n" 58 | " rotation {\n" 59 | " x: 0.0\n" 60 | " y: 0.0\n" 61 | " z: 0.0\n" 62 | " w: 1.0\n" 63 | " }\n" 64 | "}\n" 65 | "embedded_components {\n" 66 | " id: \"sprite\"\n" 67 | " type: \"sprite\"\n" 68 | " data: \"tile_set: \\\"/examples/assets/examples.atlas\\\"\\n" 69 | "default_animation: \\\"blue_idle\\\"\\n" 70 | "material: \\\"/builtins/materials/sprite.material\\\"\\n" 71 | "blend_mode: BLEND_MODE_ALPHA\\n" 72 | "\"\n" 73 | " position {\n" 74 | " x: 0.0\n" 75 | " y: 0.0\n" 76 | " z: 0.0\n" 77 | " }\n" 78 | " rotation {\n" 79 | " x: 0.0\n" 80 | " y: 0.0\n" 81 | " z: 0.0\n" 82 | " w: 1.0\n" 83 | " }\n" 84 | "}\n" 85 | "" 86 | position { 87 | x: 568.0 88 | y: 320.0 89 | z: 0.0 90 | } 91 | rotation { 92 | x: 0.0 93 | y: 0.0 94 | z: 0.0 95 | w: 1.0 96 | } 97 | scale3 { 98 | x: 1.0 99 | y: 1.0 100 | z: 1.0 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/flow/flow.collection: -------------------------------------------------------------------------------- 1 | name: "flow" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"gui\"\n" 7 | " component: \"/examples/flow/flow.gui\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "components {\n" 21 | " id: \"script\"\n" 22 | " component: \"/examples/flow/flow.script\"\n" 23 | " position {\n" 24 | " x: 0.0\n" 25 | " y: 0.0\n" 26 | " z: 0.0\n" 27 | " }\n" 28 | " rotation {\n" 29 | " x: 0.0\n" 30 | " y: 0.0\n" 31 | " z: 0.0\n" 32 | " w: 1.0\n" 33 | " }\n" 34 | "}\n" 35 | "embedded_components {\n" 36 | " id: \"a_proxy\"\n" 37 | " type: \"collectionproxy\"\n" 38 | " data: \"collection: \\\"/examples/flow/a.collection\\\"\\n" 39 | "exclude: false\\n" 40 | "\"\n" 41 | " position {\n" 42 | " x: 0.0\n" 43 | " y: 0.0\n" 44 | " z: 0.0\n" 45 | " }\n" 46 | " rotation {\n" 47 | " x: 0.0\n" 48 | " y: 0.0\n" 49 | " z: 0.0\n" 50 | " w: 1.0\n" 51 | " }\n" 52 | "}\n" 53 | "embedded_components {\n" 54 | " id: \"b_proxy\"\n" 55 | " type: \"collectionproxy\"\n" 56 | " data: \"collection: \\\"/examples/flow/b.collection\\\"\\n" 57 | "exclude: false\\n" 58 | "\"\n" 59 | " position {\n" 60 | " x: 0.0\n" 61 | " y: 0.0\n" 62 | " z: 0.0\n" 63 | " }\n" 64 | " rotation {\n" 65 | " x: 0.0\n" 66 | " y: 0.0\n" 67 | " z: 0.0\n" 68 | " w: 1.0\n" 69 | " }\n" 70 | "}\n" 71 | "embedded_components {\n" 72 | " id: \"sprite\"\n" 73 | " type: \"sprite\"\n" 74 | " data: \"tile_set: \\\"/examples/assets/examples.atlas\\\"\\n" 75 | "default_animation: \\\"green_player_walk\\\"\\n" 76 | "material: \\\"/builtins/materials/sprite.material\\\"\\n" 77 | "blend_mode: BLEND_MODE_ALPHA\\n" 78 | "\"\n" 79 | " position {\n" 80 | " x: 0.0\n" 81 | " y: 0.0\n" 82 | " z: 0.0\n" 83 | " }\n" 84 | " rotation {\n" 85 | " x: 0.0\n" 86 | " y: 0.0\n" 87 | " z: 0.0\n" 88 | " w: 1.0\n" 89 | " }\n" 90 | "}\n" 91 | "" 92 | position { 93 | x: 241.79016 94 | y: 297.64926 95 | z: 0.0 96 | } 97 | rotation { 98 | x: 0.0 99 | y: 0.0 100 | z: 0.0 101 | w: 1.0 102 | } 103 | scale3 { 104 | x: 1.0 105 | y: 1.0 106 | z: 1.0 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ludobits/m/broadcast.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local receivers = {} 4 | 5 | local function ensure_hash(string_or_hash) 6 | return type(string_or_hash) == "string" and hash(string_or_hash) or string_or_hash 7 | end 8 | 9 | local function url_to_key(url) 10 | return hash_to_hex(url.socket) .. hash_to_hex(url.path) .. hash_to_hex(url.fragment or hash("")) 11 | end 12 | 13 | --- Send a message to all registered receivers 14 | -- @param message_id 15 | -- @param message 16 | function M.send(message_id, message) 17 | assert(message_id) 18 | local key = ensure_hash(message_id) 19 | if receivers[key] then 20 | message = message or {} 21 | for _,receiver in pairs(receivers[key]) do 22 | msg.post(receiver.url, message_id, message) 23 | end 24 | end 25 | end 26 | 27 | --- Register the current script as a receiver for a specific message 28 | -- @param message_id 29 | -- @param on_message_handler Optional message handler function to call 30 | -- when a message is received. The function will receive the message 31 | -- and sender as it's arguments. You must call @{on_message} for this 32 | -- to work 33 | function M.register(message_id, on_message_handler) 34 | assert(message_id) 35 | local url = msg.url() 36 | local key = ensure_hash(message_id) 37 | receivers[key] = receivers[key] or {} 38 | receivers[key][url_to_key(url)] = { url = url, handler = on_message_handler } 39 | end 40 | 41 | --- Unregister the current script from receiving a previously registered message 42 | -- @param message_id 43 | function M.unregister(message_id) 44 | assert(message_id) 45 | local key = ensure_hash(message_id) 46 | if receivers[key] then 47 | receivers[key][url_to_key(msg.url())] = nil 48 | end 49 | end 50 | 51 | --- Forward received messages in scripts where the broadcast module is used and where 52 | -- registered messages have also provide a message handler function. If no message 53 | -- handler functions are used then there is no need to call this function. 54 | -- @param message_id 55 | -- @param message 56 | -- @param sender 57 | -- @return true if the message was handled 58 | function M.on_message(message_id, message, sender) 59 | local message_receivers = receivers[message_id] 60 | if not message_receivers then 61 | return false 62 | end 63 | 64 | local url = msg.url() 65 | for _,message_receiver in pairs(receivers[message_id]) do 66 | if message_receiver.url == url and message_receiver.handler then 67 | message_receiver.handler(message, sender) 68 | return true 69 | end 70 | end 71 | return false 72 | end 73 | 74 | 75 | return setmetatable(M, { 76 | __call = function(self, ...) 77 | return M.send(...) 78 | end 79 | }) 80 | -------------------------------------------------------------------------------- /ludobits/m/physics/dynamic.lua: -------------------------------------------------------------------------------- 1 | --- Utility functions for working with dynamic collision objects 2 | local M = {} 3 | 4 | local LINEAR_VELOCITY = hash("linear_velocity") 5 | local ANGULAR_VELOCITY = hash("angular_velocity") 6 | local MASS = hash("mass") 7 | 8 | --- Rotate a collision object be applying opposing and offset forces 9 | -- @param collisionobject_url 10 | -- @param force In the format of vmath.vector3(0, force, 0) 11 | function M.rotate(collisionobject_url, force) 12 | local mass = go.get(collisionobject_url, MASS) 13 | local rotation = go.get_rotation() 14 | local world_position = go.get_world_position() 15 | msg.post(collisionobject_url, "apply_force", { force = vmath.rotate(rotation, force * mass), position = world_position + vmath.rotate(rotation, vmath.vector3(-50, 50, 0)) }) 16 | msg.post(collisionobject_url, "apply_force", { force = vmath.rotate(rotation, -force * mass), position = world_position + vmath.rotate(rotation, vmath.vector3(50, -50, 0)) }) 17 | end 18 | 19 | 20 | --- Move a dynamic collision object in its direction of rotation by 21 | -- applying a force 22 | -- @param collisionobject_url 23 | -- @param force In the format of vmath.vector3(0, force, 0) 24 | function M.forward(collisionobject_url, force) 25 | local mass = go.get(collisionobject_url, MASS) 26 | msg.post(collisionobject_url, "apply_force", { force = vmath.rotate(go.get_rotation(), force * mass), position = go.get_world_position() }) 27 | end 28 | 29 | --- Move a dynamic collision object in the opposite direction of 30 | -- its rotation by applying a force 31 | -- @param collisionobject_url 32 | -- @param force In the format of vmath.vector3(0, force, 0) 33 | function M.backwards(collisionobject_url, force) 34 | local mass = go.get(collisionobject_url, MASS) 35 | msg.post(collisionobject_url, "apply_force", { force = vmath.rotate(go.get_rotation(), -force * mass), position = go.get_world_position() }) 36 | end 37 | 38 | --- Stop the movemembt of a dynamic collision object by applying a force opposit 39 | -- to the linear velocity and mass of the object 40 | -- @param collisionobject_url 41 | function M.stop_moving(collisionobject_url) 42 | local mass = go.get(collisionobject_url, MASS) 43 | local linv = go.get(collisionobject_url, LINEAR_VELOCITY) 44 | msg.post(collisionobject_url, "apply_force", { force = -linv * 100 * mass, position = go.get_world_position() }) 45 | end 46 | 47 | --- Stop the rotation of a dynamic collision object by applying a force opposite 48 | -- to the angular velocity of the object 49 | -- @param collisionobject_url 50 | function M.stop_rotating(collisionobject_url) 51 | local angv = go.get(collisionobject_url, ANGULAR_VELOCITY) 52 | angv.x = angv.z 53 | angv.z = 0 54 | M.rotate(collisionobject_url, angv * 100) 55 | end 56 | 57 | return M 58 | -------------------------------------------------------------------------------- /examples/assets/button.gui: -------------------------------------------------------------------------------- 1 | script: "" 2 | fonts { 3 | name: "kenpixel15" 4 | font: "/examples/assets/fonts/kenpixel15.font" 5 | } 6 | textures { 7 | name: "examples" 8 | texture: "/examples/assets/examples.atlas" 9 | } 10 | background_color { 11 | x: 0.0 12 | y: 0.0 13 | z: 0.0 14 | w: 1.0 15 | } 16 | nodes { 17 | position { 18 | x: 0.0 19 | y: 0.0 20 | z: 0.0 21 | w: 1.0 22 | } 23 | rotation { 24 | x: 0.0 25 | y: 0.0 26 | z: 0.0 27 | w: 1.0 28 | } 29 | scale { 30 | x: 1.0 31 | y: 1.0 32 | z: 1.0 33 | w: 1.0 34 | } 35 | size { 36 | x: 200.0 37 | y: 49.0 38 | z: 0.0 39 | w: 1.0 40 | } 41 | color { 42 | x: 1.0 43 | y: 1.0 44 | z: 1.0 45 | w: 1.0 46 | } 47 | type: TYPE_BOX 48 | blend_mode: BLEND_MODE_ALPHA 49 | texture: "examples/blue_button07" 50 | id: "button" 51 | xanchor: XANCHOR_NONE 52 | yanchor: YANCHOR_NONE 53 | pivot: PIVOT_CENTER 54 | adjust_mode: ADJUST_MODE_FIT 55 | layer: "below" 56 | inherit_alpha: true 57 | slice9 { 58 | x: 6.0 59 | y: 6.0 60 | z: 6.0 61 | w: 9.0 62 | } 63 | clipping_mode: CLIPPING_MODE_NONE 64 | clipping_visible: true 65 | clipping_inverted: false 66 | alpha: 1.0 67 | template_node_child: false 68 | size_mode: SIZE_MODE_MANUAL 69 | } 70 | nodes { 71 | position { 72 | x: 0.0 73 | y: 0.0 74 | z: 0.0 75 | w: 1.0 76 | } 77 | rotation { 78 | x: 0.0 79 | y: 0.0 80 | z: 0.0 81 | w: 1.0 82 | } 83 | scale { 84 | x: 1.0 85 | y: 1.0 86 | z: 1.0 87 | w: 1.0 88 | } 89 | size { 90 | x: 188.0 91 | y: 38.0 92 | z: 0.0 93 | w: 1.0 94 | } 95 | color { 96 | x: 1.0 97 | y: 1.0 98 | z: 1.0 99 | w: 1.0 100 | } 101 | type: TYPE_TEXT 102 | blend_mode: BLEND_MODE_ALPHA 103 | text: "" 104 | font: "kenpixel15" 105 | id: "label" 106 | xanchor: XANCHOR_NONE 107 | yanchor: YANCHOR_NONE 108 | pivot: PIVOT_CENTER 109 | outline { 110 | x: 1.0 111 | y: 1.0 112 | z: 1.0 113 | w: 1.0 114 | } 115 | shadow { 116 | x: 1.0 117 | y: 1.0 118 | z: 1.0 119 | w: 1.0 120 | } 121 | adjust_mode: ADJUST_MODE_FIT 122 | line_break: false 123 | parent: "button" 124 | layer: "text" 125 | inherit_alpha: true 126 | alpha: 1.0 127 | outline_alpha: 1.0 128 | shadow_alpha: 1.0 129 | template_node_child: false 130 | text_leading: 1.0 131 | text_tracking: 0.0 132 | } 133 | layers { 134 | name: "below" 135 | } 136 | layers { 137 | name: "text" 138 | } 139 | material: "/builtins/materials/gui.material" 140 | adjust_reference: ADJUST_REFERENCE_PARENT 141 | max_nodes: 512 142 | -------------------------------------------------------------------------------- /examples/listener/listener.collection: -------------------------------------------------------------------------------- 1 | name: "default" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"gui\"\n" 7 | " component: \"/examples/listener/listener_main.gui\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "" 21 | position { 22 | x: 0.0 23 | y: 0.0 24 | z: 0.0 25 | } 26 | rotation { 27 | x: 0.0 28 | y: 0.0 29 | z: 0.0 30 | w: 1.0 31 | } 32 | scale3 { 33 | x: 1.0 34 | y: 1.0 35 | z: 1.0 36 | } 37 | } 38 | embedded_instances { 39 | id: "listener1" 40 | data: "components {\n" 41 | " id: \"script\"\n" 42 | " component: \"/examples/listener/listener1.script\"\n" 43 | " position {\n" 44 | " x: 0.0\n" 45 | " y: 0.0\n" 46 | " z: 0.0\n" 47 | " }\n" 48 | " rotation {\n" 49 | " x: 0.0\n" 50 | " y: 0.0\n" 51 | " z: 0.0\n" 52 | " w: 1.0\n" 53 | " }\n" 54 | "}\n" 55 | "" 56 | position { 57 | x: 0.0 58 | y: 0.0 59 | z: 0.0 60 | } 61 | rotation { 62 | x: 0.0 63 | y: 0.0 64 | z: 0.0 65 | w: 1.0 66 | } 67 | scale3 { 68 | x: 1.0 69 | y: 1.0 70 | z: 1.0 71 | } 72 | } 73 | embedded_instances { 74 | id: "listener2" 75 | data: "components {\n" 76 | " id: \"script\"\n" 77 | " component: \"/examples/listener/listener2.script\"\n" 78 | " position {\n" 79 | " x: 0.0\n" 80 | " y: 0.0\n" 81 | " z: 0.0\n" 82 | " }\n" 83 | " rotation {\n" 84 | " x: 0.0\n" 85 | " y: 0.0\n" 86 | " z: 0.0\n" 87 | " w: 1.0\n" 88 | " }\n" 89 | "}\n" 90 | "" 91 | position { 92 | x: 0.0 93 | y: 0.0 94 | z: 0.0 95 | } 96 | rotation { 97 | x: 0.0 98 | y: 0.0 99 | z: 0.0 100 | w: 1.0 101 | } 102 | scale3 { 103 | x: 1.0 104 | y: 1.0 105 | z: 1.0 106 | } 107 | } 108 | embedded_instances { 109 | id: "listener3" 110 | data: "components {\n" 111 | " id: \"script\"\n" 112 | " component: \"/examples/listener/listener3.script\"\n" 113 | " position {\n" 114 | " x: 0.0\n" 115 | " y: 0.0\n" 116 | " z: 0.0\n" 117 | " }\n" 118 | " rotation {\n" 119 | " x: 0.0\n" 120 | " y: 0.0\n" 121 | " z: 0.0\n" 122 | " w: 1.0\n" 123 | " }\n" 124 | "}\n" 125 | "" 126 | position { 127 | x: 0.0 128 | y: 0.0 129 | z: 0.0 130 | } 131 | rotation { 132 | x: 0.0 133 | y: 0.0 134 | z: 0.0 135 | w: 1.0 136 | } 137 | scale3 { 138 | x: 1.0 139 | y: 1.0 140 | z: 1.0 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /ludobits/m/procgen/conway.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local function count_orthogonal_neighbors(m, x, y, w, h) 4 | local count = 0 5 | if x == 1 or m[x - 1][y] then 6 | count = count + 1 7 | end 8 | if x == w or m[x + 1][y] then 9 | count = count + 1 10 | end 11 | if y == 1 or m[x][y - 1] then 12 | count = count + 1 13 | end 14 | if y == h or m[x][y + 1] then 15 | count = count + 1 16 | end 17 | return count 18 | end 19 | 20 | local function count_diagonal_neighbors(m, x, y, w, h) 21 | local count = 0 22 | -- down left 23 | if y == 1 or x == 1 or m[x - 1][y - 1] then 24 | count = count + 1 25 | end 26 | -- down right 27 | if y == 1 or x == w or m[x + 1][y - 1] then 28 | count = count + 1 29 | end 30 | -- up left 31 | if y == h or x == 1 or m[x - 1][y + 1] then 32 | count = count + 1 33 | end 34 | -- up right 35 | if y == h or x == w or m[x + 1][y + 1] then 36 | count = count + 1 37 | end 38 | return count 39 | end 40 | 41 | local function mutate(m, w, h, rules_fn) 42 | local n = {} 43 | for x=1,w do 44 | n[x] = {} 45 | for y=1,h do 46 | n[x][y] = rules_fn(m, x, y, w, h) 47 | end 48 | end 49 | return n 50 | end 51 | 52 | local function dump_map(m, w, h) 53 | local s = "" 54 | for y=h,1,-1 do 55 | s = s .. "\n" 56 | for x=1,w do 57 | s = s .. (m[x][y] and "#" or ".") 58 | end 59 | end 60 | return s 61 | end 62 | 63 | function M.orthogonal_only(m, x, y, w, h) 64 | local live = m[x][y] 65 | local count = count_orthogonal_neighbors(m, x, y, w, h) 66 | if live then 67 | return count == 2 or count == 3 68 | else 69 | return count == 3 70 | end 71 | end 72 | 73 | 74 | function M.all_directions(m, x, y, w, h) 75 | local live = m[x][y] 76 | local ortho_count = count_orthogonal_neighbors(m, x, y, w, h) 77 | local diag_count = count_diagonal_neighbors(m, x, y, w, h) 78 | local count = ortho_count + diag_count 79 | if live then 80 | return count >= 4 81 | else 82 | return count >= 5 83 | end 84 | end 85 | 86 | 87 | --- Generate a map using ceullular automata 88 | -- @param w Width of map 89 | -- @param h Height of map 90 | -- @param fillrate How much of the map that should be live initially (0.0-1.0) 91 | -- @param iterations The number of times the map should mutate 92 | -- @param rules_fn How to evolve the map (one of all_directions and orthogonal_only) 93 | -- @return Two dimensional array [x][y] with booleans 94 | function M.generate(w, h, fillrate, iterations, rules_fn) 95 | assert(w and w > 0) 96 | assert(h and h > 0) 97 | fillrate = fillrate or 0.5 98 | iterations = iterations or 4 99 | rules_fn = rules_fn or M.orthogonal_only 100 | local m = {} 101 | 102 | for x=1,w do 103 | m[x] = {} 104 | for y=1,h do 105 | m[x][y] = math.random() <= fillrate 106 | end 107 | end 108 | 109 | --print(dump_map(m, w, h)) 110 | 111 | for i=1,iterations do 112 | m = mutate(m, w, h, rules_fn) 113 | --print(dump_map(m, w, h)) 114 | end 115 | 116 | return m 117 | end 118 | 119 | 120 | return M 121 | -------------------------------------------------------------------------------- /ludobits/m/io/savetable.lua: -------------------------------------------------------------------------------- 1 | --- Save and load tables, either using io.* or sys.* 2 | -- 3 | -- @usage 4 | -- local savetable = require "ludobits.m.io.savetable" 5 | -- 6 | -- local data = savetable.load("foobar", savetable.FORMAT_IO) 7 | -- data.foo = "bar" 8 | -- savetable.save(data) 9 | -- 10 | -- local data = savetable.load("foobar") 11 | -- print(data.foo) --> "bar" 12 | 13 | local savefile = require "ludobits.m.io.savefile" 14 | local file = require "ludobits.m.io.file" 15 | local json = require "ludobits.m.json" 16 | 17 | local M = {} 18 | 19 | M.FORMAT_IO = "io" 20 | M.FORMAT_SYS = "sys" 21 | 22 | 23 | 24 | local function load(path, format) 25 | if format == M.FORMAT_IO then 26 | local f, err = io.open(path, "rb") 27 | if err then 28 | return nil, err 29 | end 30 | local s = f:read("*a") 31 | if not s then 32 | return nil 33 | end 34 | return json.decode(s) 35 | elseif format == M.FORMAT_SYS then 36 | return sys.load(path) 37 | end 38 | end 39 | 40 | 41 | local function save(data, path, format) 42 | if format == M.FORMAT_IO then 43 | local s = json.encode(data) 44 | if not s then 45 | return false 46 | end 47 | local f, err = io.open(path, "wb") 48 | if err then 49 | return nil, err 50 | end 51 | f:write(s) 52 | f:flush() 53 | f:close() 54 | return true 55 | elseif format == M.FORMAT_SYS then 56 | return sys.save(path, data) 57 | end 58 | end 59 | 60 | 61 | function M.load(filename, format) 62 | assert(filename, "You must provide a filename") 63 | format = format or M.FORMAT_SYS 64 | assert(format == M.FORMAT_IO or format == M.FORMAT_SYS, "Unknown format") 65 | 66 | local path = file.get_save_file_path(filename) 67 | local data = load(path, format) 68 | data = data or {} 69 | 70 | local mt = {} 71 | mt.path = path 72 | mt.format = format 73 | 74 | return setmetatable(data, mt) 75 | end 76 | 77 | 78 | function M.save(data, filename, format) 79 | assert(data, "You must provide some data to save") 80 | local mt = getmetatable(data) 81 | if not mt then 82 | assert(filename, "You must provide a filename") 83 | local path = file.get_save_file_path(filename) 84 | format = format or M.FORMAT_SYS 85 | mt = {} 86 | mt.path = path 87 | mt.format = format 88 | else 89 | if filename then 90 | local path = file.get_save_file_path(filename) 91 | mt.path = path 92 | end 93 | if format then 94 | mt.format = format 95 | end 96 | end 97 | setmetatable(data, mt) 98 | return save(data, mt.path, mt.format) 99 | end 100 | 101 | 102 | 103 | 104 | 105 | 106 | function M.open(filename) 107 | print("savetable.open() is deprecated. Use savetable.load() and save() instead") 108 | local instance = {} 109 | function instance.load() 110 | local ok, t_or_err = pcall(function() 111 | local s, err = file.load() 112 | if err then 113 | return nil 114 | end 115 | return json.decode(s) 116 | end) 117 | return ok and t_or_err, not ok and t_or_err 118 | end 119 | function instance.save(t) 120 | assert(t and type(t) == "table", "You must provide a table to save") 121 | return pcall(function() 122 | local s = json.encode(t) 123 | return file.save(s) 124 | end) 125 | end 126 | return instance 127 | end 128 | 129 | 130 | return M 131 | -------------------------------------------------------------------------------- /ludobits/m/listener.lua: -------------------------------------------------------------------------------- 1 | --- Refer to listener.md for documentation 2 | 3 | local M = {} 4 | 5 | local function ensure_hash(s) 6 | return (type(s) == "string") and hash(s) or s 7 | end 8 | 9 | local function trigger_url(url, message_id, message) 10 | msg.post(url, message_id, message) 11 | end 12 | 13 | local function trigger_function(fn, message_id, message) 14 | fn(message_id, message) 15 | end 16 | 17 | --- Create a listener instance 18 | -- @return Listener 19 | function M.create() 20 | local any_listeners_url = {} 21 | local listeners = {} 22 | 23 | local instance = {} 24 | 25 | --- Add a function or url to invoke when the listener is triggered 26 | -- @param url_or_fn_to_add URL or function to call. Can be nil in which case the current URL is used. 27 | -- @param message_id Optional message id to filter on 28 | function instance.add(url_or_fn_to_add, message_id) 29 | url_or_fn_to_add = url_or_fn_to_add or msg.url() 30 | message_id = message_id and ensure_hash(message_id) or nil 31 | listeners[url_or_fn_to_add] = listeners[url_or_fn_to_add] or {} 32 | 33 | instance.remove(url_or_fn_to_add, message_id) 34 | table.insert(listeners[url_or_fn_to_add], { 35 | message_id = message_id, 36 | trigger = type(url_or_fn_to_add) == "userdata" and trigger_url or trigger_function 37 | }) 38 | end 39 | 40 | --- Remove a previously added callback function or url 41 | -- @param url_or_fn_to_remove 42 | -- @param message_id Optional message_id to limit removal to 43 | function instance.remove(url_or_fn_to_remove, message_id) 44 | url_or_fn_to_remove = url_or_fn_to_remove or msg.url() 45 | message_id = message_id and ensure_hash(message_id) or nil 46 | 47 | local is_url = type(url_or_fn_to_remove) == "userdata" 48 | 49 | for url_fn,url_fn_listeners in pairs(listeners) do 50 | -- make sure to only check against urls if we are removing a url and vice versa 51 | if (is_url and type(url_fn) == "userdata") or (not is_url and type(url_fn) ~= "userdata") then 52 | for k,data in pairs(url_fn_listeners) do 53 | if is_url then 54 | if url_fn.socket == url_or_fn_to_remove.socket 55 | and url_fn.path == url_or_fn_to_remove.path 56 | and url_fn.fragment == url_or_fn_to_remove.fragment 57 | and (not message_id or message_id == data.message_id) then 58 | url_fn_listeners[k] = nil 59 | end 60 | else 61 | if url_fn == url_or_fn_to_remove and (not message_id or message_id == data.message_id) then 62 | url_fn_listeners[k] = nil 63 | end 64 | end 65 | end 66 | end 67 | end 68 | end 69 | 70 | --- Trigger this listener 71 | -- @param message_id Id of message to trigger 72 | -- @param message The message itself (can be nil) 73 | function instance.trigger(message_id, message) 74 | assert(message_id, "You must provide a message_id") 75 | assert(not message or type(message) == "table", "You must either provide no message or a message of type 'table'") 76 | message_id = ensure_hash(message_id) 77 | for url_fn,url_fn_listeners in pairs(listeners) do 78 | for _,listener in pairs(url_fn_listeners) do 79 | if not listener.message_id or listener.message_id == message_id then 80 | listener.trigger(url_fn, message_id, message or {}) 81 | end 82 | end 83 | end 84 | end 85 | 86 | return instance 87 | end 88 | 89 | 90 | return M 91 | -------------------------------------------------------------------------------- /ludobits/m/procgen/perlin.lua: -------------------------------------------------------------------------------- 1 | -- https://stackoverflow.com/questions/33425333/lua-perlin-noise-generation-getting-bars-rather-than-squares 2 | -- original code by Ken Perlin: http://mrl.nyu.edu/~perlin/noise/ 3 | local function BitAND(a,b)--Bitwise and 4 | local p,c=1,0 5 | while a>0 and b>0 do 6 | local ra,rb=a%2,b%2 7 | if ra+rb>1 then c=c+p end 8 | a,b,p=(a-ra)/2,(b-rb)/2,p*2 9 | end 10 | return c 11 | end 12 | 13 | 14 | 15 | local function fade( t ) 16 | return t * t * t * (t * (t * 6 - 15) + 10) 17 | end 18 | 19 | local function lerp( t, a, b ) 20 | return a + t * (b - a) 21 | end 22 | 23 | local function grad( hash, x, y, z ) 24 | local h = BitAND(hash, 15) 25 | local u = h < 8 and x or y 26 | local v = h < 4 and y or ((h == 12 or h == 14) and x or z) 27 | return ((h and 1) == 0 and u or -u) + ((h and 2) == 0 and v or -v) 28 | end 29 | 30 | local perlin = { 31 | p = {}, 32 | permutation = { 151,160,137,91,90,15, 33 | 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 34 | 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 35 | 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 36 | 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 37 | 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 38 | 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 39 | 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 40 | 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 41 | 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 42 | 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, 43 | 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 44 | 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 45 | }, 46 | size = 256, 47 | gx = {}, 48 | gy = {}, 49 | randMax = 256, 50 | } 51 | 52 | function perlin.init() 53 | for i=1,perlin.size do 54 | perlin.p[i] = perlin.permutation[i] 55 | perlin.p[255+i] = perlin.p[i] 56 | end 57 | end 58 | 59 | function perlin.noise( x, y, z ) 60 | local X = BitAND(math.floor(x), 255) + 1 61 | local Y = BitAND(math.floor(y), 255) + 1 62 | local Z = BitAND(math.floor(z), 255) + 1 63 | 64 | x = x - math.floor(x) 65 | y = y - math.floor(y) 66 | z = z - math.floor(z) 67 | local u = fade(x) 68 | local v = fade(y) 69 | local w = fade(z) 70 | local A = perlin.p[X]+Y 71 | local AA = perlin.p[A]+Z 72 | local AB = perlin.p[A+1]+Z 73 | local B = perlin.p[X+1]+Y 74 | local BA = perlin.p[B]+Z 75 | local BB = perlin.p[B+1]+Z 76 | 77 | return lerp(w, lerp(v, lerp(u, grad(perlin.p[AA ], x , y , z ), 78 | grad(perlin.p[BA ], x-1, y , z )), 79 | lerp(u, grad(perlin.p[AB ], x , y-1, z ), 80 | grad(perlin.p[BB ], x-1, y-1, z ))), 81 | lerp(v, lerp(u, grad(perlin.p[AA+1], x , y , z-1), 82 | grad(perlin.p[BA+1], x-1, y , z-1)), 83 | lerp(u, grad(perlin.p[AB+1], x , y-1, z-1), 84 | grad(perlin.p[BB+1], x-1, y-1, z-1)))) 85 | end 86 | 87 | 88 | 89 | return perlin -------------------------------------------------------------------------------- /examples/assets/examples.atlas: -------------------------------------------------------------------------------- 1 | images { 2 | image: "/examples/assets/images/set1_tiles.png" 3 | } 4 | images { 5 | image: "/examples/assets/images/blue_circle.png" 6 | } 7 | images { 8 | image: "/examples/assets/images/green_circle.png" 9 | } 10 | images { 11 | image: "/examples/assets/images/green_button07.png" 12 | } 13 | images { 14 | image: "/examples/assets/images/blue_button07.png" 15 | } 16 | images { 17 | image: "/examples/assets/images/hitman1_gun.png" 18 | } 19 | animations { 20 | id: "blue_player" 21 | images { 22 | image: "/examples/assets/images/playerBlue_walk1_new.png" 23 | } 24 | images { 25 | image: "/examples/assets/images/playerBlue_walk2_new.png" 26 | } 27 | images { 28 | image: "/examples/assets/images/playerBlue_walk3_new.png" 29 | } 30 | images { 31 | image: "/examples/assets/images/playerBlue_walk4_new.png" 32 | } 33 | images { 34 | image: "/examples/assets/images/playerBlue_walk5_new.png" 35 | } 36 | playback: PLAYBACK_LOOP_PINGPONG 37 | fps: 15 38 | flip_horizontal: 0 39 | flip_vertical: 0 40 | } 41 | animations { 42 | id: "green_player_walk" 43 | images { 44 | image: "/examples/assets/images/playerGreen_walk1.png" 45 | } 46 | images { 47 | image: "/examples/assets/images/playerGreen_walk2.png" 48 | } 49 | images { 50 | image: "/examples/assets/images/playerGreen_walk3.png" 51 | } 52 | images { 53 | image: "/examples/assets/images/playerGreen_walk4.png" 54 | } 55 | images { 56 | image: "/examples/assets/images/playerGreen_walk5.png" 57 | } 58 | playback: PLAYBACK_LOOP_PINGPONG 59 | fps: 15 60 | flip_horizontal: 0 61 | flip_vertical: 0 62 | } 63 | animations { 64 | id: "blue_walk_once" 65 | images { 66 | image: "/examples/assets/images/playerBlue_walk1_new.png" 67 | } 68 | images { 69 | image: "/examples/assets/images/playerBlue_walk2_new.png" 70 | } 71 | images { 72 | image: "/examples/assets/images/playerBlue_walk3_new.png" 73 | } 74 | images { 75 | image: "/examples/assets/images/playerBlue_walk4_new.png" 76 | } 77 | images { 78 | image: "/examples/assets/images/playerBlue_walk5_new.png" 79 | } 80 | playback: PLAYBACK_ONCE_PINGPONG 81 | fps: 15 82 | flip_horizontal: 0 83 | flip_vertical: 0 84 | } 85 | animations { 86 | id: "green_walk_once" 87 | images { 88 | image: "/examples/assets/images/playerGreen_walk1.png" 89 | } 90 | images { 91 | image: "/examples/assets/images/playerGreen_walk2.png" 92 | } 93 | images { 94 | image: "/examples/assets/images/playerGreen_walk3.png" 95 | } 96 | images { 97 | image: "/examples/assets/images/playerGreen_walk4.png" 98 | } 99 | images { 100 | image: "/examples/assets/images/playerGreen_walk5.png" 101 | } 102 | playback: PLAYBACK_ONCE_PINGPONG 103 | fps: 15 104 | flip_horizontal: 0 105 | flip_vertical: 0 106 | } 107 | animations { 108 | id: "blue_idle" 109 | images { 110 | image: "/examples/assets/images/playerBlue_walk1_new.png" 111 | } 112 | playback: PLAYBACK_ONCE_FORWARD 113 | fps: 30 114 | flip_horizontal: 0 115 | flip_vertical: 0 116 | } 117 | animations { 118 | id: "green_player_idle" 119 | images { 120 | image: "/examples/assets/images/playerGreen_walk1.png" 121 | } 122 | playback: PLAYBACK_LOOP_FORWARD 123 | fps: 60 124 | flip_horizontal: 0 125 | flip_vertical: 0 126 | } 127 | margin: 0 128 | extrude_borders: 2 129 | inner_padding: 0 130 | -------------------------------------------------------------------------------- /doc/modules/m.settings.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 62 | 63 |
64 | 65 |

Module m.settings

66 |

Module to save user settings to disk

67 |

68 |

Usage:

69 |
    70 |
    local settings = require "ludobits.m.settings"
     71 | 
     72 | settings.volume = 0.7
     73 | settings.language = "en"
     74 | settings.username = "Johnny Defold"
     75 | 
     76 | settings.save()
     77 | 
    78 |
79 | 80 | 81 |

Functions

82 | 83 | 84 | 85 | 86 | 87 |
save ()Save settings to disk.
88 | 89 |
90 |
91 | 92 | 93 |

Functions

94 | 95 |
96 |
97 | 98 | save () 99 |
100 |
101 | Save settings to disk. The settings will be saved to a file named __settings 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 |
110 |
111 | 112 | 113 |
114 |
115 |
116 | generated by LDoc 1.4.6 117 | Last updated 2017-11-27 06:48:33 118 |
119 |
120 | 121 | 122 | -------------------------------------------------------------------------------- /doc/modules/m.bezier.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 62 | 63 |
64 | 65 |

Module m.bezier

66 |

Create bezier curves

67 |

68 | 69 | 70 |

Functions

71 | 72 | 73 | 74 | 75 | 76 |
create (cp, points)Create a bezier curve
77 | 78 |
79 |
80 | 81 | 82 |

Functions

83 | 84 |
85 |
86 | 87 | create (cp, points) 88 |
89 |
90 | Create a bezier curve 91 | 92 | 93 |

Parameters:

94 |
    95 |
  • cp 96 | Table with control points (pairs of { x, y }) 97 |
  • 98 |
  • points 99 | The number of points to generate along the curve 100 |
  • 101 |
102 | 103 |

Returns:

104 |
    105 | 106 | Table with all of the vmath.vector3() points along the curve 107 |
108 | 109 | 110 | 111 | 112 |
113 |
114 | 115 | 116 |
117 |
118 |
119 | generated by LDoc 1.4.6 120 | Last updated 2017-11-27 06:48:33 121 |
122 |
123 | 124 | 125 | -------------------------------------------------------------------------------- /ludobits/m/flow.md: -------------------------------------------------------------------------------- 1 | The flow module simplifies asynchronous flows of execution where your code needs to wait for one asynchronous operation to finish before starting with the next one. 2 | 3 | Example: 4 | 5 | ```lua 6 | local flow = require "ludobits.m.flow" 7 | 8 | function init(self) 9 | flow.start(function() 10 | -- animate a gameobject and wait for animation to complete 11 | flow.go_animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, 0, go.EASING_INCUBIC, 2) 12 | -- wait for one and a half seconds 13 | flow.delay(1.5) 14 | -- wait until a function returns true 15 | flow.until_true(is_game_over) 16 | -- animate go again 17 | flow.go_animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, 400, go.EASING_INCUBIC, 2) 18 | end) 19 | end 20 | 21 | function final(self) 22 | flow.stop() 23 | end 24 | 25 | function on_message(self, message_id, message, sender) 26 | flow.on_message(message_id, message, sender) 27 | end 28 | ``` 29 | 30 | Complete API below (also refer to the examples for usage): 31 | 32 | ```lua 33 | -- Start a new flow 34 | local instance = flow.start(fn, options, on_error) 35 | 36 | 37 | -- Stop a created flow before it has completed 38 | flow.stop(instance) 39 | 40 | 41 | -- Wait until a certain time has elapsed 42 | flow.delay(seconds) 43 | 44 | 45 | -- Wait until a certain number of frames have elapsed 46 | flow.frames(frames) 47 | 48 | 49 | -- Yield execution for one frame 50 | flow.yield() 51 | 52 | 53 | -- Wait until a function returns true 54 | flow.until_true(fn) 55 | 56 | 57 | -- Wait until any message is received 58 | -- NOTE: You need to call flow.on_message() 59 | flow.until_any_message() 60 | 61 | 62 | -- Wait until a specific message is received 63 | -- NOTE: You need to call flow.on_message() 64 | flow.until_message(...) 65 | 66 | 67 | -- Waiting to receive all specific messages. 68 | -- NOTE: You need to call flow.on_message() 69 | flow.until_all_messages(...) 70 | 71 | 72 | -- Wait until input action with pressed state 73 | -- NOTE: You need to call flow.on_message() 74 | flow.until_input_pressed(...) 75 | 76 | 77 | -- Wait until input action with released state 78 | -- NOTE: You need to call flow.on_input() 79 | flow.until_input_released(...) 80 | 81 | 82 | -- Wait until a callback function is invoked 83 | flow.until_callback(fn, ...) 84 | 85 | 86 | -- Load a collection and wait until it is loaded and enabled 87 | -- NOTE: You need to call flow.on_message() 88 | flow.load(collection_url) 89 | 90 | 91 | -- Load a collection asynchronously and wait until it is loaded and enabled 92 | -- NOTE: You need to call flow.on_message() 93 | flow.load_async(collection_url) 94 | 95 | -- Unload a collection and wait until it is unloaded 96 | -- NOTE: You need to call flow.on_message() 97 | flow.unload(collection_url) 98 | 99 | 100 | -- Load the resources used by a factory 101 | -- NOTE: You need to call flow.on_message() 102 | flow.load_factory(factory_url) 103 | 104 | -- Load the resources used by a collection factory 105 | -- NOTE: You need to call flow.on_message() 106 | flow.load_collection_factory(collectionfactory_url) 107 | 108 | 109 | -- Call go.animate and wait until it has finished 110 | flow.go_animate(url, property, playback, to, easing, duration, delay) 111 | 112 | 113 | -- Call gui.animate and wait until it has finished 114 | flow.gui_animate(node, property, playback, to, easing, duration, delay) 115 | 116 | 117 | -- Play a sprite animation and wait until it has finished 118 | flow.play_animation(sprite_url, id) 119 | 120 | 121 | -- Wait until other flow coroutines were finished 122 | flow.until_flows(flows) 123 | 124 | 125 | -- Wait until other flow coroutines with a specific group_id were finished 126 | flow.until_group(group_id) 127 | 128 | 129 | -- Forward any received messages in your scripts to this function 130 | flow.on_message(message_id, message, sender) 131 | 132 | 133 | -- Forward any received input in your scripts to this function 134 | flow.on_input(action_id, action) 135 | 136 | ``` 137 | -------------------------------------------------------------------------------- /ludobits/m/physics/kinematic.lua: -------------------------------------------------------------------------------- 1 | --- Utility functions for working with kinematic collision objects 2 | local M = {} 3 | 4 | 5 | M.CONTACT_POINT_RESPONSE = hash("contact_point_response") 6 | 7 | 8 | --- Handle geometry contact (ie collision) by separating this object from 9 | -- the collision. 10 | -- @param correction Aggregated correction vector (for when dealing with 11 | -- multiple collisions per frame) 12 | -- @param normal Collision normal (as provided from a contact_point_response 13 | -- message) 14 | -- @param distance Collision distance/overlap 15 | -- @param id Optional id of the game object to separate 16 | function M.handle_geometry_contact(correction, normal, distance, id) 17 | -- project the correction vector onto the contact normal 18 | -- (the correction vector is the 0-vector for the first contact point) 19 | local proj = vmath.dot(correction, normal) 20 | -- calculate the compensation we need to make for this contact point 21 | local comp = (distance - proj) * normal 22 | -- add it to the correction vector 23 | correction.x = correction.x + comp.x 24 | correction.y = correction.y + comp.y 25 | correction.z = correction.z + comp.z 26 | -- apply the compensation to the player character 27 | go.set_position(go.get_position(id) + comp, id) 28 | end 29 | 30 | --- Set rotation around z-axis in such a way that the game object is 31 | -- facing a specific position 32 | -- @param look_at_position The position to look at 33 | -- @param id Optional id of the game object to rotate 34 | function M.look_at(look_at_position, id) 35 | local pos = go.get_world_position(id) 36 | local target_angle = -math.atan2(look_at_position.x - pos.x, look_at_position.y - pos.y) 37 | local target_quat = vmath.quat_rotation_z(target_angle) 38 | go.set_rotation(target_quat, id) 39 | end 40 | 41 | --- Rotate around the z-axis 42 | -- @param angle Amount to rotate in radians 43 | -- @param id Optional id of the game object to rotate 44 | function M.rotate(angle, id) 45 | go.set_rotation(go.get_rotation(id) * vmath.quat_rotation_z(angle), id) 46 | end 47 | 48 | --- Set the absolute rotation around z-axis 49 | -- @param angle Angle in radians (use math.rad(deg) to convert from degrees to radians) 50 | -- @param id Optional id of the game object to rotate 51 | function M.set_rotation(angle, id) 52 | go.set_rotation(vmath.quat_rotation_z(angle), id) 53 | end 54 | 55 | --- Move forward in the current direction of rotation around z-axis 56 | -- @param amount Distance to move 57 | -- @param id Optional id of the game object to move 58 | function M.forward(amount, id) 59 | local rotation = go.get_rotation(id) 60 | local direction = vmath.rotate(rotation, vmath.vector3(0, amount, 0)) 61 | go.set_position(go.get_position(id) + direction, id) 62 | end 63 | 64 | --- Move backwards in the current direction of rotation around z-axis 65 | -- @param amount Distance to move 66 | -- @param id Optional id of the game object to move 67 | function M.backwards(amount, id) 68 | local rotation = go.get_rotation(id) 69 | local direction = vmath.rotate(rotation, vmath.vector3(0, amount, 0)) 70 | go.set_position(go.get_position(id) - direction, id) 71 | end 72 | 73 | --- Create a wrapper for a kinematic collision object 74 | function M.create() 75 | local instance = {} 76 | 77 | local correction = vmath.vector3() 78 | 79 | function instance.look_at(position) 80 | M.look_at(position) 81 | end 82 | 83 | function instance.set_rotation(angle) 84 | M.set_rotation(angle) 85 | end 86 | 87 | function instance.rotate(amount) 88 | M.rotate(amount) 89 | end 90 | 91 | function instance.forward(amount) 92 | M.forward(amount) 93 | end 94 | 95 | function instance.backwards(amount) 96 | M.backwards(amount) 97 | end 98 | 99 | function instance.on_message(message_id, message) 100 | if message_id == M.CONTACT_POINT_RESPONSE then 101 | M.handle_geometry_contact(correction, message.normal, message.distance) 102 | end 103 | end 104 | 105 | function instance.update(dt) 106 | correction = vmath.vector3() 107 | end 108 | 109 | return instance 110 | end 111 | 112 | return M 113 | -------------------------------------------------------------------------------- /ludobits/m/sequence.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | 4 | 5 | local function resume(co, ...) 6 | local ok, err = coroutine.resume(co, ...) 7 | if err then print(err) end 8 | end 9 | 10 | function M.run_once(fn, ...) 11 | coroutine.wrap(fn)(...) 12 | end 13 | 14 | function M.run_loop(fn, ...) 15 | coroutine.wrap(function(...) 16 | while true do 17 | fn(...) 18 | end 19 | end)(...) 20 | end 21 | 22 | function M.delay(seconds) 23 | local co = coroutine.running() 24 | assert(co, "You must call this from inside a sequence") 25 | timer.delay(seconds, false, function() 26 | resume(co) 27 | end) 28 | coroutine.yield() 29 | end 30 | 31 | function M.wait_until_false(fn) 32 | local co = coroutine.running() 33 | timer.delay(0, true, function(self, handle, time_elapsed) 34 | if not fn() then 35 | timer.cancel(handle) 36 | coroutine.resume(co) 37 | end 38 | end) 39 | coroutine.yield() 40 | end 41 | 42 | function M.wait_until_true(fn) 43 | local co = coroutine.running() 44 | timer.delay(0, true, function(self, handle, time_elapsed) 45 | if fn() then 46 | timer.cancel(handle) 47 | coroutine.resume(co) 48 | end 49 | end) 50 | coroutine.yield() 51 | end 52 | 53 | function M.gui_animate(node, property, to, easing, duration, delay, playback) 54 | local co = coroutine.running() 55 | assert(co, "You must call this from inside a sequence") 56 | gui.animate(node, property, to, easing, duration, delay, function() 57 | resume(co) 58 | end, playback) 59 | coroutine.yield() 60 | end 61 | 62 | function M.go_animate(url, property, playback, to, easing, duration, delay) 63 | local co = coroutine.running() 64 | assert(co, "You must call this from inside a sequence") 65 | delay = delay or 0 66 | if property == "position.xy" then 67 | go.animate(url, "position.x", playback, to.x, easing, duration, delay, nil, playback) 68 | go.animate(url, "position.y", playback, to.y, easing, duration, delay, function() 69 | resume(co) 70 | end, playback) 71 | else 72 | go.animate(url, property, playback, to, easing, duration, delay, function() 73 | resume(co) 74 | end, playback) 75 | end 76 | coroutine.yield() 77 | end 78 | 79 | function M.spine_play_anim(url, anim_id, playback, play_properties) 80 | local co = coroutine.running() 81 | assert(co, "You must call this from inside a sequence") 82 | spine.play_anim(url, anim_id, playback, play_properties, function(self, message_id, message, sender) 83 | resume(co) 84 | end) 85 | coroutine.yield() 86 | end 87 | 88 | function M.gui_play_flipbook(node, id) 89 | local co = coroutine.running() 90 | assert(co, "You must call this from inside a sequence") 91 | gui.play_flipbook(node, id, function() 92 | resume(co) 93 | end) 94 | coroutine.yield() 95 | end 96 | 97 | function M.sprite_play_flipbook(url, id) 98 | local co = coroutine.running() 99 | assert(co, "You must call this from inside a sequence") 100 | sprite.play_flipbook(url, id, function() 101 | resume(co) 102 | end) 103 | coroutine.yield() 104 | end 105 | 106 | function M.http_request(url, method, headers, post_data, options) 107 | local co = coroutine.running() 108 | assert(co, "You must call this from inside a sequence") 109 | 110 | http.request(url, method, function(self, id, response) 111 | resume(co, response) 112 | end,headers, post_data, options) 113 | return coroutine.yield() 114 | end 115 | 116 | function M.http_get(url, headers, options) 117 | return M.http_request(url, "GET", headers, nil, options) 118 | end 119 | 120 | function M.http_post(url, headers, post_data, options) 121 | return M.http_request(url, "POST", headers, post_data, options) 122 | end 123 | 124 | function M.call(fn, ...) 125 | local co = coroutine.running() 126 | assert(co, "You must call this from inside a sequence") 127 | local results = nil 128 | local yielded = false 129 | local done = false 130 | fn(function(...) 131 | done = true 132 | if yielded then 133 | resume(co, ...) 134 | else 135 | results = { ... } 136 | end 137 | end, ...) 138 | if not done then 139 | print("not done, yielding") 140 | yielded = true 141 | results = { coroutine.yield() } 142 | end 143 | return unpack(results) 144 | end 145 | 146 | return M 147 | -------------------------------------------------------------------------------- /doc/modules/m.file.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 62 | 63 |
64 | 65 |

Module m.file

66 |

File manipulation utilities

67 |

68 | 69 | 70 |

Functions

71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
fix (filename)Fix a filename to ensure that it doesn't contain any illegal characters
get_save_file_path (filename)Get an application specific save file path to a filename.
81 | 82 |
83 |
84 | 85 | 86 |

Functions

87 | 88 |
89 |
90 | 91 | fix (filename) 92 |
93 |
94 | Fix a filename to ensure that it doesn't contain any illegal characters 95 | 96 | 97 |

Parameters:

98 |
    99 |
  • filename 100 | 101 |
  • 102 |
103 | 104 |

Returns:

105 |
    106 | 107 | Filename with illegal characters replaced 108 |
109 | 110 | 111 | 112 | 113 |
114 |
115 | 116 | get_save_file_path (filename) 117 |
118 |
119 | Get an application specific save file path to a filename. The path will be 120 | based on the sys.get_save_file() function and the project title (with whitespace) 121 | replaced by underscore 122 | 123 | 124 |

Parameters:

125 |
    126 |
  • filename 127 | 128 |
  • 129 |
130 | 131 |

Returns:

132 |
    133 | 134 | Save file path 135 |
136 | 137 | 138 | 139 | 140 |
141 |
142 | 143 | 144 |
145 |
146 |
147 | generated by LDoc 1.4.6 148 | Last updated 2017-11-27 06:48:33 149 |
150 |
151 | 152 | 153 | -------------------------------------------------------------------------------- /doc/modules/m.util.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 62 | 63 |
64 | 65 |

Module m.util

66 |

Utility functions

67 |

68 | 69 | 70 |

Functions

71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
lerp (a, b, t)Linear interpolation between to numbers
shuffle (t)Suffle a Lua table
random (list)Pick a random value from a list
85 | 86 |
87 |
88 | 89 | 90 |

Functions

91 | 92 |
93 |
94 | 95 | lerp (a, b, t) 96 |
97 |
98 | Linear interpolation between to numbers 99 | 100 | 101 |

Parameters:

102 |
    103 |
  • a 104 | Start 105 |
  • 106 |
  • b 107 | To 108 |
  • 109 |
  • t 110 | Time (0.0 - 1.0) 111 |
  • 112 |
113 | 114 | 115 | 116 | 117 | 118 |
119 |
120 | 121 | shuffle (t) 122 |
123 |
124 | Suffle a Lua table 125 | 126 | 127 |

Parameters:

128 |
    129 |
  • t 130 | The table to shuffle 131 |
  • 132 |
133 | 134 | 135 | 136 | 137 | 138 |
139 |
140 | 141 | random (list) 142 |
143 |
144 | Pick a random value from a list 145 | 146 | 147 |

Parameters:

148 |
    149 |
  • list 150 | 151 |
  • 152 |
153 | 154 |

Returns:

155 |
    156 |
  1. 157 | value A random value
  2. 158 |
  3. 159 | index Index of the value
  4. 160 |
161 | 162 | 163 | 164 | 165 |
166 |
167 | 168 | 169 |
170 |
171 |
172 | generated by LDoc 1.4.6 173 | Last updated 2017-11-27 06:48:33 174 |
175 |
176 | 177 | 178 | -------------------------------------------------------------------------------- /examples/kinematic/kinematic.collection: -------------------------------------------------------------------------------- 1 | name: "kinematic" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "mouse" 5 | data: "embedded_components {\n" 6 | " id: \"sprite\"\n" 7 | " type: \"sprite\"\n" 8 | " data: \"tile_set: \\\"/builtins/graphics/particle_blob.tilesource\\\"\\n" 9 | "default_animation: \\\"anim\\\"\\n" 10 | "material: \\\"/builtins/materials/sprite.material\\\"\\n" 11 | "blend_mode: BLEND_MODE_ALPHA\\n" 12 | "\"\n" 13 | " position {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " }\n" 18 | " rotation {\n" 19 | " x: 0.0\n" 20 | " y: 0.0\n" 21 | " z: 0.0\n" 22 | " w: 1.0\n" 23 | " }\n" 24 | "}\n" 25 | "" 26 | position { 27 | x: 864.0 28 | y: 640.0 29 | z: 0.0 30 | } 31 | rotation { 32 | x: 0.0 33 | y: 0.0 34 | z: 0.0 35 | w: 1.0 36 | } 37 | scale3 { 38 | x: 1.0 39 | y: 1.0 40 | z: 1.0 41 | } 42 | } 43 | embedded_instances { 44 | id: "level" 45 | data: "components {\n" 46 | " id: \"tilemap\"\n" 47 | " component: \"/examples/kinematic/topdown.tilemap\"\n" 48 | " position {\n" 49 | " x: 0.0\n" 50 | " y: 0.0\n" 51 | " z: 0.0\n" 52 | " }\n" 53 | " rotation {\n" 54 | " x: 0.0\n" 55 | " y: 0.0\n" 56 | " z: 0.0\n" 57 | " w: 1.0\n" 58 | " }\n" 59 | "}\n" 60 | "embedded_components {\n" 61 | " id: \"collisionobject\"\n" 62 | " type: \"collisionobject\"\n" 63 | " data: \"collision_shape: \\\"/examples/kinematic/topdown.tilemap\\\"\\n" 64 | "type: COLLISION_OBJECT_TYPE_STATIC\\n" 65 | "mass: 0.0\\n" 66 | "friction: 0.1\\n" 67 | "restitution: 0.5\\n" 68 | "group: \\\"wall\\\"\\n" 69 | "mask: \\\"player\\\"\\n" 70 | "linear_damping: 0.0\\n" 71 | "angular_damping: 0.0\\n" 72 | "locked_rotation: false\\n" 73 | "\"\n" 74 | " position {\n" 75 | " x: 0.0\n" 76 | " y: 0.0\n" 77 | " z: 0.0\n" 78 | " }\n" 79 | " rotation {\n" 80 | " x: 0.0\n" 81 | " y: 0.0\n" 82 | " z: 0.0\n" 83 | " w: 1.0\n" 84 | " }\n" 85 | "}\n" 86 | "" 87 | position { 88 | x: 0.0 89 | y: 0.0 90 | z: 0.0 91 | } 92 | rotation { 93 | x: 0.0 94 | y: 0.0 95 | z: 0.0 96 | w: 1.0 97 | } 98 | scale3 { 99 | x: 1.0 100 | y: 1.0 101 | z: 1.0 102 | } 103 | } 104 | embedded_instances { 105 | id: "player" 106 | data: "components {\n" 107 | " id: \"script\"\n" 108 | " component: \"/examples/kinematic/kinematic.script\"\n" 109 | " position {\n" 110 | " x: 0.0\n" 111 | " y: 0.0\n" 112 | " z: 0.0\n" 113 | " }\n" 114 | " rotation {\n" 115 | " x: 0.0\n" 116 | " y: 0.0\n" 117 | " z: 0.0\n" 118 | " w: 1.0\n" 119 | " }\n" 120 | "}\n" 121 | "embedded_components {\n" 122 | " id: \"collisionobject\"\n" 123 | " type: \"collisionobject\"\n" 124 | " data: \"collision_shape: \\\"\\\"\\n" 125 | "type: COLLISION_OBJECT_TYPE_KINEMATIC\\n" 126 | "mass: 0.0\\n" 127 | "friction: 0.1\\n" 128 | "restitution: 0.5\\n" 129 | "group: \\\"player\\\"\\n" 130 | "mask: \\\"wall\\\"\\n" 131 | "embedded_collision_shape {\\n" 132 | " shapes {\\n" 133 | " shape_type: TYPE_SPHERE\\n" 134 | " position {\\n" 135 | " x: 0.0\\n" 136 | " y: 0.0\\n" 137 | " z: 0.0\\n" 138 | " }\\n" 139 | " rotation {\\n" 140 | " x: 0.0\\n" 141 | " y: 0.0\\n" 142 | " z: 0.0\\n" 143 | " w: 1.0\\n" 144 | " }\\n" 145 | " index: 0\\n" 146 | " count: 1\\n" 147 | " }\\n" 148 | " data: 25.0\\n" 149 | "}\\n" 150 | "linear_damping: 0.0\\n" 151 | "angular_damping: 0.0\\n" 152 | "locked_rotation: false\\n" 153 | "\"\n" 154 | " position {\n" 155 | " x: 0.0\n" 156 | " y: 0.0\n" 157 | " z: 0.0\n" 158 | " }\n" 159 | " rotation {\n" 160 | " x: 0.0\n" 161 | " y: 0.0\n" 162 | " z: 0.0\n" 163 | " w: 1.0\n" 164 | " }\n" 165 | "}\n" 166 | "embedded_components {\n" 167 | " id: \"sprite\"\n" 168 | " type: \"sprite\"\n" 169 | " data: \"tile_set: \\\"/examples/assets/examples.atlas\\\"\\n" 170 | "default_animation: \\\"hitman1_gun\\\"\\n" 171 | "material: \\\"/builtins/materials/sprite.material\\\"\\n" 172 | "blend_mode: BLEND_MODE_ALPHA\\n" 173 | "\"\n" 174 | " position {\n" 175 | " x: 0.0\n" 176 | " y: 0.0\n" 177 | " z: 0.0\n" 178 | " }\n" 179 | " rotation {\n" 180 | " x: 0.0\n" 181 | " y: 0.0\n" 182 | " z: 0.0\n" 183 | " w: 1.0\n" 184 | " }\n" 185 | "}\n" 186 | "" 187 | position { 188 | x: 568.0 189 | y: 320.0 190 | z: 1.0 191 | } 192 | rotation { 193 | x: 0.0 194 | y: 0.0 195 | z: 0.0 196 | w: 1.0 197 | } 198 | scale3 { 199 | x: 1.0 200 | y: 1.0 201 | z: 1.0 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /doc/modules/m.app.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 62 | 63 |
64 | 65 |

Module m.app

66 |

Module to simplify the use of several of the engine listeners.

67 |

68 | The module allows the user to define multiple listeners for the iac, iap, 69 | push and window listeners. 70 |

71 |

Usage:

72 |
    73 |
    local app = require "ludobits.app"
     74 | 
     75 | local function iac_listener1(self, playload type)
     76 | 	print("This function will receive callbacks")
     77 | end
     78 | 
     79 | local function iac_listener2(self, playload type)
     80 | 	print("And this function too")
     81 | end
     82 | 
     83 | app.iac.add_listener(iac_listener1)
     84 | app.iac.add_listener(iac_listener2)
     85 | 
    86 |
87 | 88 | 89 |

Tables

90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
iacWrapper for iac.set_listener
iapWrapper for iap.set_listener
windowWrapper for window.set_listener
pushWrapper for push.set_listener
108 | 109 |
110 |
111 | 112 | 113 |

Tables

114 | 115 |
116 |
117 | 118 | iac 119 |
120 |
121 | Wrapper for iac.set_listener 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 |
130 |
131 | 132 | iap 133 |
134 |
135 | Wrapper for iap.set_listener 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
144 |
145 | 146 | window 147 |
148 |
149 | Wrapper for window.set_listener 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 |
158 |
159 | 160 | push 161 |
162 |
163 | Wrapper for push.set_listener 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 |
172 |
173 | 174 | 175 |
176 |
177 |
178 | generated by LDoc 1.4.6 179 | Last updated 2017-11-27 06:48:33 180 |
181 |
182 | 183 | 184 | -------------------------------------------------------------------------------- /examples/bezier/bezier.collection: -------------------------------------------------------------------------------- 1 | name: "default" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"bezier\"\n" 7 | " component: \"/examples/bezier/bezier.script\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "embedded_components {\n" 21 | " id: \"sprite\"\n" 22 | " type: \"sprite\"\n" 23 | " data: \"tile_set: \\\"/examples/assets/examples.atlas\\\"\\n" 24 | "default_animation: \\\"blue_circle\\\"\\n" 25 | "material: \\\"/builtins/materials/sprite.material\\\"\\n" 26 | "blend_mode: BLEND_MODE_ALPHA\\n" 27 | "\"\n" 28 | " position {\n" 29 | " x: 0.0\n" 30 | " y: 0.0\n" 31 | " z: 0.0\n" 32 | " }\n" 33 | " rotation {\n" 34 | " x: 0.0\n" 35 | " y: 0.0\n" 36 | " z: 0.0\n" 37 | " w: 1.0\n" 38 | " }\n" 39 | "}\n" 40 | "" 41 | position { 42 | x: 0.0 43 | y: 0.0 44 | z: 0.0 45 | } 46 | rotation { 47 | x: 0.0 48 | y: 0.0 49 | z: 0.0 50 | w: 1.0 51 | } 52 | scale3 { 53 | x: 1.0 54 | y: 1.0 55 | z: 1.0 56 | } 57 | } 58 | embedded_instances { 59 | id: "cp1" 60 | data: "embedded_components {\n" 61 | " id: \"sprite\"\n" 62 | " type: \"sprite\"\n" 63 | " data: \"tile_set: \\\"/examples/assets/examples.atlas\\\"\\n" 64 | "default_animation: \\\"green_circle\\\"\\n" 65 | "material: \\\"/builtins/materials/sprite.material\\\"\\n" 66 | "blend_mode: BLEND_MODE_ALPHA\\n" 67 | "\"\n" 68 | " position {\n" 69 | " x: 0.0\n" 70 | " y: 0.0\n" 71 | " z: 0.0\n" 72 | " }\n" 73 | " rotation {\n" 74 | " x: 0.0\n" 75 | " y: 0.0\n" 76 | " z: 0.0\n" 77 | " w: 1.0\n" 78 | " }\n" 79 | "}\n" 80 | "" 81 | position { 82 | x: 102.845 83 | y: 77.718 84 | z: 0.0 85 | } 86 | rotation { 87 | x: 0.0 88 | y: 0.0 89 | z: 0.0 90 | w: 1.0 91 | } 92 | scale3 { 93 | x: 1.0 94 | y: 1.0 95 | z: 1.0 96 | } 97 | } 98 | embedded_instances { 99 | id: "cp2" 100 | data: "embedded_components {\n" 101 | " id: \"sprite\"\n" 102 | " type: \"sprite\"\n" 103 | " data: \"tile_set: \\\"/examples/assets/examples.atlas\\\"\\n" 104 | "default_animation: \\\"green_circle\\\"\\n" 105 | "material: \\\"/builtins/materials/sprite.material\\\"\\n" 106 | "blend_mode: BLEND_MODE_ALPHA\\n" 107 | "\"\n" 108 | " position {\n" 109 | " x: 0.0\n" 110 | " y: 0.0\n" 111 | " z: 0.0\n" 112 | " }\n" 113 | " rotation {\n" 114 | " x: 0.0\n" 115 | " y: 0.0\n" 116 | " z: 0.0\n" 117 | " w: 1.0\n" 118 | " }\n" 119 | "}\n" 120 | "" 121 | position { 122 | x: 303.463 123 | y: 201.442 124 | z: 0.0 125 | } 126 | rotation { 127 | x: 0.0 128 | y: 0.0 129 | z: 0.0 130 | w: 1.0 131 | } 132 | scale3 { 133 | x: 1.0 134 | y: 1.0 135 | z: 1.0 136 | } 137 | } 138 | embedded_instances { 139 | id: "cp3" 140 | data: "embedded_components {\n" 141 | " id: \"sprite\"\n" 142 | " type: \"sprite\"\n" 143 | " data: \"tile_set: \\\"/examples/assets/examples.atlas\\\"\\n" 144 | "default_animation: \\\"green_circle\\\"\\n" 145 | "material: \\\"/builtins/materials/sprite.material\\\"\\n" 146 | "blend_mode: BLEND_MODE_ALPHA\\n" 147 | "\"\n" 148 | " position {\n" 149 | " x: 0.0\n" 150 | " y: 0.0\n" 151 | " z: 0.0\n" 152 | " }\n" 153 | " rotation {\n" 154 | " x: 0.0\n" 155 | " y: 0.0\n" 156 | " z: 0.0\n" 157 | " w: 1.0\n" 158 | " }\n" 159 | "}\n" 160 | "" 161 | position { 162 | x: 349.744 163 | y: 466.354 164 | z: 0.0 165 | } 166 | rotation { 167 | x: 0.0 168 | y: 0.0 169 | z: 0.0 170 | w: 1.0 171 | } 172 | scale3 { 173 | x: 1.0 174 | y: 1.0 175 | z: 1.0 176 | } 177 | } 178 | embedded_instances { 179 | id: "cp4" 180 | data: "embedded_components {\n" 181 | " id: \"sprite\"\n" 182 | " type: \"sprite\"\n" 183 | " data: \"tile_set: \\\"/examples/assets/examples.atlas\\\"\\n" 184 | "default_animation: \\\"green_circle\\\"\\n" 185 | "material: \\\"/builtins/materials/sprite.material\\\"\\n" 186 | "blend_mode: BLEND_MODE_ALPHA\\n" 187 | "\"\n" 188 | " position {\n" 189 | " x: 0.0\n" 190 | " y: 0.0\n" 191 | " z: 0.0\n" 192 | " }\n" 193 | " rotation {\n" 194 | " x: 0.0\n" 195 | " y: 0.0\n" 196 | " z: 0.0\n" 197 | " w: 1.0\n" 198 | " }\n" 199 | "}\n" 200 | "" 201 | position { 202 | x: 797.818 203 | y: 283.467 204 | z: 0.0 205 | } 206 | rotation { 207 | x: 0.0 208 | y: 0.0 209 | z: 0.0 210 | w: 1.0 211 | } 212 | scale3 { 213 | x: 1.0 214 | y: 1.0 215 | z: 1.0 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /doc/modules/m.savetable.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 62 | 63 |
64 | 65 |

Module m.savetable

66 |

Wrapper module for sys.load() and sys.save() 67 | Files will be saved in a path created from a call to sys.get_save_file() with 68 | application id equal to the game.project config project.title with spaces 69 | replaced with underscores.

70 |

71 |

Usage:

72 |
    73 |
    local savetable = require "ludobits.m.savetable"
     74 | 
     75 | local file = savetable.open("foobar")
     76 | local data = file.load()
     77 | file.save({ foo = "bar" })
     78 | 
    79 |
80 | 81 | 82 |

Functions

83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
open (filename)Open a file for reading and writing using sys.save and sys.load
instance.load ()Load the table stored in the file
instance.save (t)Save table to the file
97 | 98 |
99 |
100 | 101 | 102 |

Functions

103 | 104 |
105 |
106 | 107 | open (filename) 108 |
109 |
110 | Open a file for reading and writing using sys.save and sys.load 111 | 112 | 113 |

Parameters:

114 |
    115 |
  • filename 116 | 117 |
  • 118 |
119 | 120 |

Returns:

121 |
    122 | 123 | file instance 124 |
125 | 126 | 127 | 128 | 129 |
130 |
131 | 132 | instance.load () 133 |
134 |
135 | Load the table stored in the file 136 | 137 | 138 | 139 |

Returns:

140 |
    141 | 142 | File contents 143 |
144 | 145 | 146 | 147 | 148 |
149 |
150 | 151 | instance.save (t) 152 |
153 |
154 | Save table to the file 155 | 156 | 157 |

Parameters:

158 |
    159 |
  • t 160 | The table to save 161 |
  • 162 |
163 | 164 |

Returns:

165 |
    166 |
  1. 167 | success
  2. 168 |
  3. 169 | error_message
  4. 170 |
171 | 172 | 173 | 174 | 175 |
176 |
177 | 178 | 179 |
180 |
181 |
182 | generated by LDoc 1.4.6 183 | Last updated 2017-11-27 06:48:33 184 |
185 |
186 | 187 | 188 | -------------------------------------------------------------------------------- /doc/modules/m.signal.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 62 | 63 |
64 | 65 |

Module m.signal

66 |

Module to create a signal system where named signals can be created, listened 67 | to and triggered

68 |

69 |

Usage:

70 |
    71 |
     72 | 
    73 |
74 | 75 | 76 |

Functions

77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
create (signal_id)Create a signal
add (cb)Add a listener to the signal
remove (cb)Remove a listener from the signal
trigger (message)Trigger the signal
95 | 96 |
97 |
98 | 99 | 100 |

Functions

101 | 102 |
103 |
104 | 105 | create (signal_id) 106 |
107 |
108 | Create a signal 109 | 110 | 111 |

Parameters:

112 |
    113 |
  • signal_id 114 | The unique id of the signal 115 |
  • 116 |
117 | 118 |

Returns:

119 |
    120 | 121 | The created signal 122 |
123 | 124 | 125 | 126 | 127 |
128 |
129 | 130 | add (cb) 131 |
132 |
133 | Add a listener to the signal 134 | 135 | 136 |

Parameters:

137 |
    138 |
  • cb 139 | Function callback or message url (defaults to current url) 140 |
  • 141 |
142 | 143 | 144 | 145 | 146 | 147 |
148 |
149 | 150 | remove (cb) 151 |
152 |
153 | Remove a listener from the signal 154 | 155 | 156 |

Parameters:

157 |
    158 |
  • cb 159 | Function callback or message url (defaults to current url) 160 |
  • 161 |
162 | 163 | 164 | 165 | 166 | 167 |
168 |
169 | 170 | trigger (message) 171 |
172 |
173 | Trigger the signal 174 | 175 | 176 |

Parameters:

177 |
    178 |
  • message 179 | Message to pass to listeners 180 |
  • 181 |
182 | 183 | 184 | 185 | 186 | 187 |
188 |
189 | 190 | 191 |
192 |
193 |
194 | generated by LDoc 1.4.6 195 | Last updated 2017-11-27 06:48:33 196 |
197 |
198 | 199 | 200 | -------------------------------------------------------------------------------- /doc/modules/m.savefile.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 62 | 63 |
64 | 65 |

Module m.savefile

66 |

Wrapper module for io.open and io.write 67 | Files will be saved in a path created from a call to sys.get_save_file() with 68 | application id equal to the game.project config project.title with spaces 69 | replaced with underscores.

70 |

71 |

Usage:

72 |
    73 |
    local savefile = require "ludobits.m.savefile"
     74 | 
     75 | local file = savefile.open("foobar")
     76 | local data = file.load()
     77 | file.save("something large to save")
     78 | 
    79 |
80 | 81 | 82 |

Functions

83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
open (filename)Open a file for reading and writing using the io.* functions
instance.load ()Load the table stored in the file
instance.save (s)Save string to the file
97 | 98 |
99 |
100 | 101 | 102 |

Functions

103 | 104 |
105 |
106 | 107 | open (filename) 108 |
109 |
110 | Open a file for reading and writing using the io.* functions 111 | 112 | 113 |

Parameters:

114 |
    115 |
  • filename 116 | 117 |
  • 118 |
119 | 120 |

Returns:

121 |
    122 | 123 | file instance 124 |
125 | 126 | 127 | 128 | 129 |
130 |
131 | 132 | instance.load () 133 |
134 |
135 | Load the table stored in the file 136 | 137 | 138 | 139 |

Returns:

140 |
    141 |
  1. 142 | contents File contents or nil if something went wrong
  2. 143 |
  3. 144 | error_message Error message if something went wrong while reading
  4. 145 |
146 | 147 | 148 | 149 | 150 |
151 |
152 | 153 | instance.save (s) 154 |
155 |
156 | Save string to the file 157 | 158 | 159 |

Parameters:

160 |
    161 |
  • s 162 | The string to save 163 |
  • 164 |
165 | 166 |

Returns:

167 |
    168 |
  1. 169 | success
  2. 170 |
  3. 171 | error_message
  4. 172 |
173 | 174 | 175 | 176 | 177 |
178 |
179 | 180 | 181 |
182 |
183 |
184 | generated by LDoc 1.4.6 185 | Last updated 2017-11-27 06:48:33 186 |
187 |
188 | 189 | 190 | -------------------------------------------------------------------------------- /examples/examples.collection: -------------------------------------------------------------------------------- 1 | name: "examples" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"examples\"\n" 7 | " component: \"/examples/examples.gui\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "embedded_components {\n" 21 | " id: \"bezierproxy\"\n" 22 | " type: \"collectionproxy\"\n" 23 | " data: \"collection: \\\"/examples/bezier/bezier.collection\\\"\\n" 24 | "exclude: false\\n" 25 | "\"\n" 26 | " position {\n" 27 | " x: 0.0\n" 28 | " y: 0.0\n" 29 | " z: 0.0\n" 30 | " }\n" 31 | " rotation {\n" 32 | " x: 0.0\n" 33 | " y: 0.0\n" 34 | " z: 0.0\n" 35 | " w: 1.0\n" 36 | " }\n" 37 | "}\n" 38 | "embedded_components {\n" 39 | " id: \"broadcastproxy\"\n" 40 | " type: \"collectionproxy\"\n" 41 | " data: \"collection: \\\"/examples/broadcast/broadcast.collection\\\"\\n" 42 | "exclude: false\\n" 43 | "\"\n" 44 | " position {\n" 45 | " x: 0.0\n" 46 | " y: 0.0\n" 47 | " z: 0.0\n" 48 | " }\n" 49 | " rotation {\n" 50 | " x: 0.0\n" 51 | " y: 0.0\n" 52 | " z: 0.0\n" 53 | " w: 1.0\n" 54 | " }\n" 55 | "}\n" 56 | "embedded_components {\n" 57 | " id: \"dynamicproxy\"\n" 58 | " type: \"collectionproxy\"\n" 59 | " data: \"collection: \\\"/examples/dynamic/dynamic.collection\\\"\\n" 60 | "exclude: false\\n" 61 | "\"\n" 62 | " position {\n" 63 | " x: 0.0\n" 64 | " y: 0.0\n" 65 | " z: 0.0\n" 66 | " }\n" 67 | " rotation {\n" 68 | " x: 0.0\n" 69 | " y: 0.0\n" 70 | " z: 0.0\n" 71 | " w: 1.0\n" 72 | " }\n" 73 | "}\n" 74 | "embedded_components {\n" 75 | " id: \"flowproxy\"\n" 76 | " type: \"collectionproxy\"\n" 77 | " data: \"collection: \\\"/examples/flow/flow.collection\\\"\\n" 78 | "exclude: false\\n" 79 | "\"\n" 80 | " position {\n" 81 | " x: 0.0\n" 82 | " y: 0.0\n" 83 | " z: 0.0\n" 84 | " }\n" 85 | " rotation {\n" 86 | " x: 0.0\n" 87 | " y: 0.0\n" 88 | " z: 0.0\n" 89 | " w: 1.0\n" 90 | " }\n" 91 | "}\n" 92 | "embedded_components {\n" 93 | " id: \"kinematicproxy\"\n" 94 | " type: \"collectionproxy\"\n" 95 | " data: \"collection: \\\"/examples/kinematic/kinematic.collection\\\"\\n" 96 | "exclude: false\\n" 97 | "\"\n" 98 | " position {\n" 99 | " x: 0.0\n" 100 | " y: 0.0\n" 101 | " z: 0.0\n" 102 | " }\n" 103 | " rotation {\n" 104 | " x: 0.0\n" 105 | " y: 0.0\n" 106 | " z: 0.0\n" 107 | " w: 1.0\n" 108 | " }\n" 109 | "}\n" 110 | "embedded_components {\n" 111 | " id: \"listenerproxy\"\n" 112 | " type: \"collectionproxy\"\n" 113 | " data: \"collection: \\\"/examples/listener/listener.collection\\\"\\n" 114 | "exclude: false\\n" 115 | "\"\n" 116 | " position {\n" 117 | " x: 0.0\n" 118 | " y: 0.0\n" 119 | " z: 0.0\n" 120 | " }\n" 121 | " rotation {\n" 122 | " x: 0.0\n" 123 | " y: 0.0\n" 124 | " z: 0.0\n" 125 | " w: 1.0\n" 126 | " }\n" 127 | "}\n" 128 | "embedded_components {\n" 129 | " id: \"savefileproxy\"\n" 130 | " type: \"collectionproxy\"\n" 131 | " data: \"collection: \\\"/examples/savefile/savefile.collection\\\"\\n" 132 | "exclude: false\\n" 133 | "\"\n" 134 | " position {\n" 135 | " x: 0.0\n" 136 | " y: 0.0\n" 137 | " z: 0.0\n" 138 | " }\n" 139 | " rotation {\n" 140 | " x: 0.0\n" 141 | " y: 0.0\n" 142 | " z: 0.0\n" 143 | " w: 1.0\n" 144 | " }\n" 145 | "}\n" 146 | "embedded_components {\n" 147 | " id: \"savetableproxy\"\n" 148 | " type: \"collectionproxy\"\n" 149 | " data: \"collection: \\\"/examples/savetable/savetable.collection\\\"\\n" 150 | "exclude: false\\n" 151 | "\"\n" 152 | " position {\n" 153 | " x: 0.0\n" 154 | " y: 0.0\n" 155 | " z: 0.0\n" 156 | " }\n" 157 | " rotation {\n" 158 | " x: 0.0\n" 159 | " y: 0.0\n" 160 | " z: 0.0\n" 161 | " w: 1.0\n" 162 | " }\n" 163 | "}\n" 164 | "embedded_components {\n" 165 | " id: \"loggerproxy\"\n" 166 | " type: \"collectionproxy\"\n" 167 | " data: \"collection: \\\"/examples/logger/logger.collection\\\"\\n" 168 | "exclude: false\\n" 169 | "\"\n" 170 | " position {\n" 171 | " x: 0.0\n" 172 | " y: 0.0\n" 173 | " z: 0.0\n" 174 | " }\n" 175 | " rotation {\n" 176 | " x: 0.0\n" 177 | " y: 0.0\n" 178 | " z: 0.0\n" 179 | " w: 1.0\n" 180 | " }\n" 181 | "}\n" 182 | "embedded_components {\n" 183 | " id: \"sequenceproxy\"\n" 184 | " type: \"collectionproxy\"\n" 185 | " data: \"collection: \\\"/examples/sequence/sequence.collection\\\"\\n" 186 | "exclude: false\\n" 187 | "\"\n" 188 | " position {\n" 189 | " x: 0.0\n" 190 | " y: 0.0\n" 191 | " z: 0.0\n" 192 | " }\n" 193 | " rotation {\n" 194 | " x: 0.0\n" 195 | " y: 0.0\n" 196 | " z: 0.0\n" 197 | " w: 1.0\n" 198 | " }\n" 199 | "}\n" 200 | "" 201 | position { 202 | x: 0.0 203 | y: 0.0 204 | z: 0.0 205 | } 206 | rotation { 207 | x: 0.0 208 | y: 0.0 209 | z: 0.0 210 | w: 1.0 211 | } 212 | scale3 { 213 | x: 1.0 214 | y: 1.0 215 | z: 1.0 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /doc/modules/m.simple_input.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 62 | 63 |
64 | 65 |

Module m.simple_input

66 |

Convenience function to acquire input focus

67 |

68 | 69 | 70 |

Functions

71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 80 | 81 | 82 | 83 | 85 | 86 | 87 | 88 | 90 | 91 |
release ()Convenience function to release input focus
register (node_or_string, callback)Register a node and a callback to invoke when the node 79 | receives input
unregister (node_or_string)Unregister a previously registered node or all nodes 84 | registered from the calling script
on_input (action_id, action)Forward on_input calls to this function to detect input 89 | for registered nodes
92 | 93 |
94 |
95 | 96 | 97 |

Functions

98 | 99 |
100 |
101 | 102 | release () 103 |
104 |
105 | Convenience function to release input focus 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
114 |
115 | 116 | register (node_or_string, callback) 117 |
118 |
119 | Register a node and a callback to invoke when the node 120 | receives input 121 | 122 | 123 |

Parameters:

124 |
    125 |
  • node_or_string 126 | 127 |
  • 128 |
  • callback 129 | 130 |
  • 131 |
132 | 133 | 134 | 135 | 136 | 137 |
138 |
139 | 140 | unregister (node_or_string) 141 |
142 |
143 | Unregister a previously registered node or all nodes 144 | registered from the calling script 145 | 146 | 147 |

Parameters:

148 |
    149 |
  • node_or_string 150 | 151 |
  • 152 |
153 | 154 | 155 | 156 | 157 | 158 |
159 |
160 | 161 | on_input (action_id, action) 162 |
163 |
164 | Forward on_input calls to this function to detect input 165 | for registered nodes 166 | 167 | 168 |

Parameters:

169 |
    170 |
  • action_id 171 | , 172 |
  • 173 |
  • action 174 | 175 |
  • 176 |
177 | 178 |

Returns:

179 |
    180 | 181 | true if input a registerd node received input 182 |
183 | 184 | 185 | 186 | 187 |
188 |
189 | 190 | 191 |
192 |
193 |
194 | generated by LDoc 1.4.6 195 | Last updated 2017-11-27 06:48:33 196 |
197 |
198 | 199 | 200 | -------------------------------------------------------------------------------- /doc/modules/m.input.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 62 | 63 |
64 | 65 |

Module m.input

66 |

Module to simplify input handling.

67 |

The module will keep track of 68 | pressed and released states for all input that it receives.

69 |

Usage:

70 |
    71 |
    local input = require "ludobits.m.input"
     72 | 
     73 | function init(self)
     74 | 	input.acquire()
     75 | end
     76 | 
     77 | function final(self)
     78 | 	input.release()
     79 | end
     80 | 
     81 | function update(self, dt)
     82 | 	if input.is_pressed(hash("left")) then
     83 | 		go.set_position(go.get_position() - vmath.vector3(50, 0, 0) * dt)
     84 | 	elseif input.is_pressed(hash("right")) then
     85 | 		go.set_position(go.get_position() + vmath.vector3(50, 0, 0) * dt)
     86 | 	end
     87 | end
     88 | 
     89 | function on_input(self, action_id, action)
     90 | 	input.on_input(action_id, action)
     91 | end
     92 | 
    93 |
94 | 95 | 96 |

Functions

97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 |
acquire (url)Acquire input focus for the current script
release (url)Release input focus for the current script
is_pressed (action_id)Check if an action is pressed/active
update (action_id, action)Forward any calls to on_input from scripts using this module
115 | 116 |
117 |
118 | 119 | 120 |

Functions

121 | 122 |
123 |
124 | 125 | acquire (url) 126 |
127 |
128 | Acquire input focus for the current script 129 | 130 | 131 |

Parameters:

132 |
    133 |
  • url 134 | 135 |
  • 136 |
137 | 138 | 139 | 140 | 141 | 142 |
143 |
144 | 145 | release (url) 146 |
147 |
148 | Release input focus for the current script 149 | 150 | 151 |

Parameters:

152 |
    153 |
  • url 154 | 155 |
  • 156 |
157 | 158 | 159 | 160 | 161 | 162 |
163 |
164 | 165 | is_pressed (action_id) 166 |
167 |
168 | Check if an action is pressed/active 169 | 170 | 171 |

Parameters:

172 |
    173 |
  • action_id 174 | 175 |
  • 176 |
177 | 178 |

Returns:

179 |
    180 | 181 | true if pressed/active 182 |
183 | 184 | 185 | 186 | 187 |
188 |
189 | 190 | update (action_id, action) 191 |
192 |
193 | Forward any calls to on_input from scripts using this module 194 | 195 | 196 |

Parameters:

197 |
    198 |
  • action_id 199 | 200 |
  • 201 |
  • action 202 | 203 |
  • 204 |
205 | 206 | 207 | 208 | 209 | 210 |
211 |
212 | 213 | 214 |
215 |
216 |
217 | generated by LDoc 1.4.6 218 | Last updated 2017-11-27 06:48:33 219 |
220 |
221 | 222 | 223 | -------------------------------------------------------------------------------- /examples/flow/flow.script: -------------------------------------------------------------------------------- 1 | local flow = require "ludobits.m.flow" 2 | 3 | function init(self) 4 | msg.post(".", "acquire_input_focus") 5 | flow(function() 6 | print("Flow has started") 7 | 8 | -- wait until 0.5 seconds have elapsed 9 | flow.delay(0.5) 10 | print("flow.delay() half a second has elapsed") 11 | 12 | -- wait until 10 updates/frames have been registered for this flow 13 | flow.frames(10) 14 | print("flow.frames() 10 frames have elapsed") 15 | 16 | print("Waiting for input") 17 | local action_id, action = flow.until_input_released(hash("touch")) 18 | print("flow.until_input_released()", action_id) 19 | 20 | -- load a collection proxy and wait for it to become loaded 21 | flow.load("#a_proxy") 22 | print("flow.load() a_proxy loaded") 23 | 24 | -- the running flow will wait for this flow to finish 25 | flow(function() 26 | 27 | -- animate a game object and wait until it's position is a specific value 28 | go.animate(".", "position.x", go.PLAYBACK_ONCE_FORWARD, 500, go.EASING_INOUTCUBIC, 1, 0) 29 | flow.until_true(function() 30 | return go.get_position().x == 500 31 | end) 32 | print("flow.until_true() go.animate() done") 33 | 34 | -- flows can be nested to any depth 35 | flow(function() 36 | flow.delay(1) 37 | 38 | flow.load("#b_proxy") 39 | print("flow.load() b_proxy loaded") 40 | end) 41 | 42 | flow.go_animate(".", "position.y", go.PLAYBACK_ONCE_PINGPONG, 500, go.EASING_INOUTCUBIC, 1, 0) 43 | print("flow.go_animate() done") 44 | end) 45 | 46 | -- unload a collection proxy and wait until it is unloaded 47 | flow.unload("#a_proxy") 48 | print("flow.unload() a_proxy unloaded") 49 | 50 | -- the running flow will not wait for this flow to finish 51 | flow(function() 52 | print("Parallel flow starting") 53 | flow.delay(2) 54 | print("The outer flow did not wait for me") 55 | msg.post("#", "fooo") 56 | flow.delay(0.2) 57 | msg.post("#", "abc") 58 | flow.delay(0.2) 59 | msg.post("#", "booo") 60 | 61 | flow.delay(0.2) 62 | msg.post("#", "msg3") 63 | flow.delay(0.2) 64 | msg.post("#", "msg1") 65 | flow.delay(0.2) 66 | msg.post("#", "msg2") 67 | end, { parallel = true }) 68 | 69 | print("This flow will continue to run without waiting for the parallel flow to finish") 70 | 71 | -- wait for any message (the message will be posted by the parallel flow created above) 72 | local message_id, message, sender = flow.until_any_message() 73 | print("flow.until_any_message()", message_id, message, sender) 74 | 75 | -- wait for a specific message (the message will be posted by the parallel flow created above) 76 | local message_id, message, sender = flow.until_message(hash("booo")) 77 | print("flow.until_message()", message_id, message, sender) 78 | 79 | -- wait for all specific messages 80 | print("Waiting for all specified messages...") 81 | flow(function() 82 | local last_message_id, last_message, last_sender = flow.until_all_messages(hash("msg1"), hash("msg2"), hash("msg3")) 83 | print("flow.until_all_messages()", last_message_id, last_message, last_sender) 84 | end) 85 | 86 | -- wait until a callback is invoked 87 | -- in this case we make a http.request call and wait for the callback 88 | local self, id, response = flow.until_callback(function(callback) 89 | http.request("http://www.google.com", "GET", callback) 90 | end) 91 | print("flow.until_callback() http.request() with callback done", response.status) 92 | 93 | -- waiting for a callback must also work when the callback is 94 | -- invoked immediately (as opposed to the result of some async operation) 95 | local foo, bar = flow.until_callback(function(callback) 96 | callback("foo", "bar") 97 | end) 98 | print("flow.until_callback() immediate callback", foo, bar) 99 | 100 | flow.load("#a_proxy") 101 | print("flow.load() a_proxy loaded") 102 | 103 | -- play a sprite animation and wait until it is done 104 | flow.play_animation("#sprite", "green_walk_once") 105 | print("flow.play_animation() is done") 106 | 107 | -- start two parallel flows and store them to the table "foo" 108 | print("Parallel flows stored to the table 'foo' are starting") 109 | local foo = {} 110 | 111 | -- start a first parallel flow 112 | foo[1] = flow.start(function() 113 | flow.delay(math.random()) 114 | print("Parallel flow foo 1 completed") 115 | end) 116 | 117 | -- start a second parallel flow 118 | foo[2] = flow.start(function() 119 | flow.delay(math.random()) 120 | print("Parallel flow foo 2 completed") 121 | end) 122 | 123 | -- wait until all the flows in the table "foo" have completed 124 | flow.until_flows(foo) 125 | print("All the parallel stored to the table completed") 126 | 127 | -- start two parallel flows with the same group_id "bar" 128 | print("Parallel flows with group_id 'bar' are starting") 129 | 130 | -- start a parallel flow with the group_id "bar" 131 | flow.parallel_group("bar", function() 132 | flow.delay(math.random()) 133 | print("Parallel flow bar 1 completed") 134 | end) 135 | 136 | -- start an another parallel flow with the group_id "bar" 137 | flow.parallel_group("bar", function() 138 | flow.delay(math.random()) 139 | print("Parallel flow bar 2 completed") 140 | end) 141 | 142 | -- wait until all the flows with the group_id "bar" have completed 143 | flow.until_group("bar") 144 | print("All the parallel flows with group_id 'bar' completed") 145 | 146 | print("DONE") 147 | end) 148 | 149 | flow(function() 150 | print("In another flow") 151 | flow.delay(1) 152 | error("Crashing the flow") 153 | print("This will not print") 154 | end, nil, function(error) 155 | print("Something went wrong in my flow!", error) 156 | end) 157 | end 158 | 159 | function final(self) 160 | msg.post(".", "release_input_focus") 161 | end 162 | 163 | function on_message(self, message_id, message, sender) 164 | flow.on_message(message_id, message, sender) 165 | end 166 | 167 | function on_input(self, action_id, action) 168 | flow.on_input(action_id, action) 169 | end 170 | -------------------------------------------------------------------------------- /test/test_listener.lua: -------------------------------------------------------------------------------- 1 | local mock = require "deftest.mock.mock" 2 | local unload = require "deftest.util.unload" 3 | 4 | return function() 5 | local listener 6 | 7 | describe("listener", function() 8 | before(function() 9 | unload.unload("ludobits.") 10 | listener = require "ludobits.m.listener" 11 | mock.mock(msg) 12 | msg.post.replace(function() end) 13 | end) 14 | 15 | after(function() 16 | mock.unmock(msg) 17 | package.loaded["ludobits.m.listener"] = nil 18 | end) 19 | 20 | it("should be able to create multiple listeners", function() 21 | local l1 = listener.create() 22 | local l2 = listener.create() 23 | assert(l1) 24 | assert(l2) 25 | assert(l1 ~= l2) 26 | end) 27 | 28 | it("should be able to add listeners as functions and trigger them", function() 29 | local t = { 30 | fn1 = function() end, 31 | fn2 = function() end, 32 | fn3 = function() end, 33 | } 34 | mock.mock(t) 35 | 36 | local l1 = listener.create() 37 | local l2 = listener.create() 38 | l1.add(t.fn1) 39 | l1.add(t.fn2) 40 | l2.add(t.fn1) 41 | l2.add(t.fn3) 42 | 43 | l1.trigger("message_id1") 44 | assert(t.fn1.calls == 1) 45 | assert(t.fn2.calls == 1) 46 | assert(t.fn3.calls == 0) 47 | assert(t.fn1.params[1] == hash("message_id1")) 48 | assert(t.fn2.params[1] == hash("message_id1")) 49 | assert(t.fn3.params[1] == nil) 50 | 51 | l2.trigger("message_id2") 52 | assert(t.fn1.calls == 2) 53 | assert(t.fn2.calls == 1) 54 | assert(t.fn3.calls == 1) 55 | assert(t.fn1.params[1] == hash("message_id2")) 56 | assert(t.fn2.params[1] == hash("message_id1")) 57 | assert(t.fn3.params[1] == hash("message_id2")) 58 | end) 59 | 60 | it("should be able add listener as urls and trigger them", function() 61 | local l = listener.create() 62 | l.add(msg.url("listener1")) 63 | l.add(msg.url("listener2")) 64 | 65 | l.trigger("message_id") 66 | assert(msg.post.calls == 2) 67 | end) 68 | 69 | it("should be able to pass a message to listeners when triggered", function() 70 | local t = { 71 | fn1 = function() end, 72 | } 73 | mock.mock(t) 74 | 75 | local l = listener.create() 76 | l.add(t.fn1) 77 | l.add(msg.url("listener1")) 78 | 79 | l.trigger("message_id", { foo = "bar" }) 80 | assert(t.fn1.params[1] == hash("message_id")) 81 | assert(t.fn1.params[2].foo == "bar") 82 | assert(msg.post.params[1] == msg.url("listener1")) 83 | assert(msg.post.params[2] == hash("message_id")) 84 | assert(msg.post.params[3].foo == "bar") 85 | end) 86 | 87 | it("should be able to add listeners that react on specific message ids", function() 88 | local t = { 89 | fn1 = function() end, 90 | fn2 = function() end, 91 | fn3 = function() end, 92 | } 93 | mock.mock(t) 94 | 95 | local l = listener.create() 96 | l.add(t.fn1) 97 | l.add(t.fn2, "message_id1") 98 | l.add(t.fn2, "message_id2") 99 | l.add(t.fn3, "message_id2") 100 | l.add(msg.url("listener1")) 101 | l.add(msg.url("listener2"), "message_id1") 102 | l.add(msg.url("listener3"), "message_id2") 103 | 104 | l.trigger("message_id1") -- fn1, fn2, listener1 and listener3 105 | assert(t.fn1.calls == 1) 106 | assert(t.fn2.calls == 1) 107 | assert(t.fn3.calls == 0) 108 | assert(msg.post.calls == 2) 109 | 110 | l.trigger("message_id2") -- fn1, fn2, fn3, listener1, listner3 111 | assert(t.fn1.calls == 2) 112 | assert(t.fn2.calls == 2) 113 | assert(t.fn3.calls == 1) 114 | assert(msg.post.calls == 4) 115 | end) 116 | 117 | it("should ignore duplicate listeners", function() 118 | local t = { 119 | fn1 = function() end, 120 | fn2 = function() end, 121 | fn3 = function() end, 122 | } 123 | mock.mock(t) 124 | 125 | local l = listener.create() 126 | l.add(msg.url("listener1")) 127 | l.add(msg.url("listener1")) 128 | l.add(msg.url("listener2")) 129 | l.add(msg.url("listener3"), "message_id") 130 | l.add(msg.url("listener3"), "message_id") 131 | l.add(t.fn1) 132 | l.add(t.fn1) 133 | l.add(t.fn2) 134 | l.add(t.fn3, "message_id") 135 | l.add(t.fn3, "message_id") 136 | 137 | l.trigger("message_id") 138 | assert(msg.post.calls == 3) 139 | assert(t.fn1.calls == 1) 140 | assert(t.fn2.calls == 1) 141 | assert(t.fn3.calls == 1) 142 | end) 143 | 144 | it("should be able to remove listener previously added as functions", function() 145 | local t = { 146 | fn1 = function() end, 147 | fn2 = function() end, 148 | fn3 = function() end, 149 | } 150 | mock.mock(t) 151 | 152 | local l = listener.create() 153 | l.add(t.fn1) 154 | l.add(t.fn2) 155 | l.add(t.fn3, "message_id1") 156 | l.add(t.fn3, "message_id2") 157 | l.add(msg.url("listener1")) 158 | l.add(msg.url("listener2")) 159 | l.add(msg.url("listener3"), "message_id1") 160 | l.add(msg.url("listener3"), "message_id2") 161 | 162 | l.trigger("message_id1") 163 | assert(t.fn1.calls == 1) 164 | assert(t.fn2.calls == 1) 165 | assert(t.fn3.calls == 1) 166 | assert(msg.post.calls == 3) 167 | 168 | l.remove(t.fn2) 169 | l.remove(msg.url("listener2")) 170 | l.trigger("message_id1") 171 | assert(t.fn1.calls == 2) 172 | assert(t.fn2.calls == 1) 173 | assert(t.fn3.calls == 2) 174 | assert(msg.post.calls == 5) 175 | 176 | l.remove(t.fn3) 177 | l.remove(msg.url("listener3")) 178 | l.trigger("message_id1") 179 | assert(t.fn1.calls == 3) 180 | assert(t.fn2.calls == 1) 181 | assert(t.fn3.calls == 2) 182 | assert(msg.post.calls == 6) 183 | 184 | l.trigger("message_id2") 185 | assert(t.fn1.calls == 4) 186 | assert(t.fn2.calls == 1) 187 | assert(t.fn3.calls == 2) 188 | assert(msg.post.calls == 7) 189 | 190 | -- remove the rest of the listeners and trigger both message ids 191 | -- no new calls should be made 192 | l.remove(t.fn1) 193 | l.remove(msg.url("listener1")) 194 | l.trigger("message_id1") 195 | l.trigger("message_id2") 196 | assert(t.fn1.calls == 4) 197 | assert(t.fn2.calls == 1) 198 | assert(t.fn3.calls == 2) 199 | assert(msg.post.calls == 7) 200 | end) 201 | 202 | end) 203 | end -------------------------------------------------------------------------------- /examples/listener/listener_main.gui: -------------------------------------------------------------------------------- 1 | script: "/examples/listener/listener_main.gui_script" 2 | background_color { 3 | x: 0.0 4 | y: 0.0 5 | z: 0.0 6 | w: 1.0 7 | } 8 | nodes { 9 | position { 10 | x: 520.0 11 | y: 360.0 12 | z: 0.0 13 | w: 1.0 14 | } 15 | rotation { 16 | x: 0.0 17 | y: 0.0 18 | z: 0.0 19 | w: 1.0 20 | } 21 | scale { 22 | x: 1.0 23 | y: 1.0 24 | z: 1.0 25 | w: 1.0 26 | } 27 | size { 28 | x: 200.0 29 | y: 100.0 30 | z: 0.0 31 | w: 1.0 32 | } 33 | color { 34 | x: 1.0 35 | y: 1.0 36 | z: 1.0 37 | w: 1.0 38 | } 39 | type: TYPE_TEMPLATE 40 | id: "foo" 41 | layer: "" 42 | inherit_alpha: true 43 | alpha: 1.0 44 | template: "/examples/assets/button.gui" 45 | template_node_child: false 46 | } 47 | nodes { 48 | position { 49 | x: 0.0 50 | y: 0.0 51 | z: 0.0 52 | w: 1.0 53 | } 54 | rotation { 55 | x: 0.0 56 | y: 0.0 57 | z: 0.0 58 | w: 1.0 59 | } 60 | scale { 61 | x: 1.0 62 | y: 1.0 63 | z: 1.0 64 | w: 1.0 65 | } 66 | size { 67 | x: 200.0 68 | y: 49.0 69 | z: 0.0 70 | w: 1.0 71 | } 72 | color { 73 | x: 1.0 74 | y: 1.0 75 | z: 1.0 76 | w: 1.0 77 | } 78 | type: TYPE_BOX 79 | blend_mode: BLEND_MODE_ALPHA 80 | texture: "examples/blue_button07" 81 | id: "foo/button" 82 | xanchor: XANCHOR_NONE 83 | yanchor: YANCHOR_NONE 84 | pivot: PIVOT_CENTER 85 | adjust_mode: ADJUST_MODE_FIT 86 | parent: "foo" 87 | layer: "below" 88 | inherit_alpha: true 89 | slice9 { 90 | x: 6.0 91 | y: 6.0 92 | z: 6.0 93 | w: 9.0 94 | } 95 | clipping_mode: CLIPPING_MODE_NONE 96 | clipping_visible: true 97 | clipping_inverted: false 98 | alpha: 1.0 99 | template_node_child: true 100 | size_mode: SIZE_MODE_MANUAL 101 | } 102 | nodes { 103 | position { 104 | x: 0.0 105 | y: 0.0 106 | z: 0.0 107 | w: 1.0 108 | } 109 | rotation { 110 | x: 0.0 111 | y: 0.0 112 | z: 0.0 113 | w: 1.0 114 | } 115 | scale { 116 | x: 1.0 117 | y: 1.0 118 | z: 1.0 119 | w: 1.0 120 | } 121 | size { 122 | x: 188.0 123 | y: 38.0 124 | z: 0.0 125 | w: 1.0 126 | } 127 | color { 128 | x: 1.0 129 | y: 1.0 130 | z: 1.0 131 | w: 1.0 132 | } 133 | type: TYPE_TEXT 134 | blend_mode: BLEND_MODE_ALPHA 135 | text: "FOO" 136 | font: "kenpixel15" 137 | id: "foo/label" 138 | xanchor: XANCHOR_NONE 139 | yanchor: YANCHOR_NONE 140 | pivot: PIVOT_CENTER 141 | outline { 142 | x: 1.0 143 | y: 1.0 144 | z: 1.0 145 | w: 1.0 146 | } 147 | shadow { 148 | x: 1.0 149 | y: 1.0 150 | z: 1.0 151 | w: 1.0 152 | } 153 | adjust_mode: ADJUST_MODE_FIT 154 | line_break: false 155 | parent: "foo/button" 156 | layer: "text" 157 | inherit_alpha: true 158 | alpha: 1.0 159 | outline_alpha: 1.0 160 | shadow_alpha: 1.0 161 | overridden_fields: 8 162 | template_node_child: true 163 | text_leading: 1.0 164 | text_tracking: 0.0 165 | } 166 | nodes { 167 | position { 168 | x: 760.0 169 | y: 360.0 170 | z: 0.0 171 | w: 1.0 172 | } 173 | rotation { 174 | x: 0.0 175 | y: 0.0 176 | z: 0.0 177 | w: 1.0 178 | } 179 | scale { 180 | x: 1.0 181 | y: 1.0 182 | z: 1.0 183 | w: 1.0 184 | } 185 | size { 186 | x: 200.0 187 | y: 100.0 188 | z: 0.0 189 | w: 1.0 190 | } 191 | color { 192 | x: 1.0 193 | y: 1.0 194 | z: 1.0 195 | w: 1.0 196 | } 197 | type: TYPE_TEMPLATE 198 | id: "bar" 199 | layer: "" 200 | inherit_alpha: true 201 | alpha: 1.0 202 | template: "/examples/assets/button.gui" 203 | template_node_child: false 204 | } 205 | nodes { 206 | position { 207 | x: 0.0 208 | y: 0.0 209 | z: 0.0 210 | w: 1.0 211 | } 212 | rotation { 213 | x: 0.0 214 | y: 0.0 215 | z: 0.0 216 | w: 1.0 217 | } 218 | scale { 219 | x: 1.0 220 | y: 1.0 221 | z: 1.0 222 | w: 1.0 223 | } 224 | size { 225 | x: 200.0 226 | y: 49.0 227 | z: 0.0 228 | w: 1.0 229 | } 230 | color { 231 | x: 1.0 232 | y: 1.0 233 | z: 1.0 234 | w: 1.0 235 | } 236 | type: TYPE_BOX 237 | blend_mode: BLEND_MODE_ALPHA 238 | texture: "examples/blue_button07" 239 | id: "bar/button" 240 | xanchor: XANCHOR_NONE 241 | yanchor: YANCHOR_NONE 242 | pivot: PIVOT_CENTER 243 | adjust_mode: ADJUST_MODE_FIT 244 | parent: "bar" 245 | layer: "below" 246 | inherit_alpha: true 247 | slice9 { 248 | x: 6.0 249 | y: 6.0 250 | z: 6.0 251 | w: 9.0 252 | } 253 | clipping_mode: CLIPPING_MODE_NONE 254 | clipping_visible: true 255 | clipping_inverted: false 256 | alpha: 1.0 257 | template_node_child: true 258 | size_mode: SIZE_MODE_MANUAL 259 | } 260 | nodes { 261 | position { 262 | x: 0.0 263 | y: 0.0 264 | z: 0.0 265 | w: 1.0 266 | } 267 | rotation { 268 | x: 0.0 269 | y: 0.0 270 | z: 0.0 271 | w: 1.0 272 | } 273 | scale { 274 | x: 1.0 275 | y: 1.0 276 | z: 1.0 277 | w: 1.0 278 | } 279 | size { 280 | x: 188.0 281 | y: 38.0 282 | z: 0.0 283 | w: 1.0 284 | } 285 | color { 286 | x: 1.0 287 | y: 1.0 288 | z: 1.0 289 | w: 1.0 290 | } 291 | type: TYPE_TEXT 292 | blend_mode: BLEND_MODE_ALPHA 293 | text: "BAR" 294 | font: "kenpixel15" 295 | id: "bar/label" 296 | xanchor: XANCHOR_NONE 297 | yanchor: YANCHOR_NONE 298 | pivot: PIVOT_CENTER 299 | outline { 300 | x: 1.0 301 | y: 1.0 302 | z: 1.0 303 | w: 1.0 304 | } 305 | shadow { 306 | x: 1.0 307 | y: 1.0 308 | z: 1.0 309 | w: 1.0 310 | } 311 | adjust_mode: ADJUST_MODE_FIT 312 | line_break: false 313 | parent: "bar/button" 314 | layer: "text" 315 | inherit_alpha: true 316 | alpha: 1.0 317 | outline_alpha: 1.0 318 | shadow_alpha: 1.0 319 | overridden_fields: 8 320 | template_node_child: true 321 | text_leading: 1.0 322 | text_tracking: 0.0 323 | } 324 | layers { 325 | name: "below" 326 | } 327 | layers { 328 | name: "text" 329 | } 330 | material: "/builtins/materials/gui.material" 331 | adjust_reference: ADJUST_REFERENCE_PARENT 332 | max_nodes: 512 333 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 55 | 56 |
57 | 58 | 59 | 60 |

Modules

61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 114 | 115 | 116 | 117 | 121 | 122 | 123 | 124 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 |
m.appModule to simplify the use of several of the engine listeners.
m.bezierCreate bezier curves
m.broadcastModule to simplify sending of a message to multiple receivers
m.dynamicUtility functions for working with dynamic collision objects
m.fileFile manipulation utilities
m.flowThe flow module simplifies asynchronous flows of execution where your 85 | code needs to wait for one asynchronous operation to finish before 86 | starting with the next one.
m.gestureGesture detection module that can be used to detect gestures such as tap, double tap, 91 | long press and swipe
m.inputModule to simplify input handling.
m.kinematicUtility functions for working with kinematic collision objects
m.listenerListener implementation where listeners are added as either urls or functions 104 | and notified when any or specific messages are received
m.loggerSimple Lua logger
m.platformerWrapper for the physics and behaviour involved in creating a platformer 113 | game
m.savefileWrapper module for io.open and io.write 118 | Files will be saved in a path created from a call to sys.get_save_file() with 119 | application id equal to the game.project config project.title with spaces 120 | replaced with underscores.
m.savetableWrapper module for sys.load() and sys.save() 125 | Files will be saved in a path created from a call to sys.get_save_file() with 126 | application id equal to the game.project config project.title with spaces 127 | replaced with underscores.
m.settingsModule to save user settings to disk
m.signalModule to create a signal system where named signals can be created, listened 136 | to and triggered
m.simple_inputConvenience function to acquire input focus
m.utilUtility functions
147 | 148 |
149 |
150 |
151 | generated by LDoc 1.4.6 152 | Last updated 2017-11-27 06:48:33 153 |
154 |
155 | 156 | 157 | -------------------------------------------------------------------------------- /test/test_flow.lua: -------------------------------------------------------------------------------- 1 | local mock = require "deftest.mock.mock" 2 | local unload = require "deftest.util.unload" 3 | 4 | local function wait_until(cb) 5 | local co = coroutine.running() 6 | timer.delay(0.01, true, function(self, handle, elapsed_time) 7 | if cb() then 8 | timer.cancel(handle) 9 | coroutine.resume(co) 10 | end 11 | end) 12 | coroutine.yield() 13 | end 14 | 15 | local function wait_seconds(seconds) 16 | local co = coroutine.running() 17 | timer.delay(seconds, false, function(self, handle, elapsed_time) 18 | coroutine.resume(co) 19 | end) 20 | coroutine.yield() 21 | end 22 | 23 | return function() 24 | local flow 25 | 26 | local MSG_RESUME = hash("FLOW_RESUME") 27 | local broadcast1 = msg.url("broadcast1") 28 | local broadcast2 = msg.url("broadcast2") 29 | 30 | describe("flow", function() 31 | before(function() 32 | unload.unload("ludobits.") 33 | flow = require "ludobits.m.flow" 34 | mock.mock(msg) 35 | msg.post.replace(function(url, message_id, message, sender) 36 | flow.on_message(type(message_id) == "string" and hash(message_id) or message_id, message, sender) 37 | end) 38 | end) 39 | 40 | after(function() 41 | mock.unmock(msg) 42 | package.loaded["ludobits.m.flow"] = nil 43 | end) 44 | 45 | it("should return a reference to the flow when started", function() 46 | local instance = flow.start(function() end) 47 | assert(instance) 48 | end) 49 | 50 | it("should run the flow immediately when started", function() 51 | local flow_finished = false 52 | flow.start(function() 53 | flow_finished = true 54 | end) 55 | assert(flow_finished) 56 | end) 57 | 58 | it("should run the flow on a timer", function() 59 | msg.url.replace(function() return broadcast1 end) 60 | 61 | local flow_finished = false 62 | local instance = flow.start(function() 63 | flow_finished = true 64 | end) 65 | 66 | wait_until(function() 67 | return flow_finished 68 | end) 69 | 70 | assert(flow_finished) 71 | end) 72 | 73 | it("should be able to pause for a specific number of seconds", function() 74 | msg.url.replace(function() return broadcast1 end) 75 | 76 | local flow_finished = false 77 | local instance = flow.start(function() 78 | flow.delay(1) 79 | flow_finished = true 80 | end) 81 | wait_seconds(0.5) 82 | assert(not flow_finished) 83 | 84 | wait_seconds(0.75) 85 | assert(flow_finished) 86 | end) 87 | 88 | it("should be able to pause for a specific number of frames", function() 89 | msg.url.replace(function() return broadcast1 end) 90 | 91 | local flow_finished = false 92 | local instance = flow.start(function() 93 | flow.frames(20) 94 | flow_finished = true 95 | end) 96 | 97 | wait_seconds(20 * 0.02) 98 | assert(flow_finished) 99 | end) 100 | 101 | it("should be able to pause until a condition is true", function() 102 | msg.url.replace(function() return broadcast1 end) 103 | 104 | local flow_finished = false 105 | local is_it_true_yet = false 106 | local instance = flow.start(function() 107 | flow.until_true(function() 108 | return is_it_true_yet 109 | end) 110 | flow_finished = true 111 | end) 112 | 113 | wait_seconds(0.25) 114 | assert(not flow_finished) 115 | is_it_true_yet = true 116 | wait_seconds(0.25) 117 | assert(flow_finished) 118 | end) 119 | 120 | it("should be able to pause until a message is received", function() 121 | msg.url.replace(function() return broadcast1 end) 122 | 123 | local flow_finished = false 124 | local instance = flow.start(function() 125 | flow.until_any_message() 126 | flow_finished = true 127 | end) 128 | 129 | wait_seconds(0.25) 130 | assert(not flow_finished) 131 | msg.post(broadcast1, "foobar", {}) 132 | wait_seconds(0.25) 133 | assert(flow_finished) 134 | end) 135 | 136 | it("should be able to pause until a specific message is received", function() 137 | msg.url.replace(function() return broadcast1 end) 138 | 139 | local flow_finished = false 140 | local instance = flow.start(function() 141 | flow.until_message("foo", "bar") 142 | flow.until_message("boo", "car") 143 | flow_finished = true 144 | end) 145 | 146 | wait_seconds(0.25) 147 | assert(not flow_finished) 148 | 149 | msg.post(broadcast1, "bar", {}) 150 | wait_seconds(0.25) 151 | assert(not flow_finished) 152 | msg.post(broadcast1, "boo", {}) 153 | wait_seconds(0.25) 154 | assert(flow_finished) 155 | end) 156 | 157 | it("should be possible to pause until a callback is invoked", function() 158 | msg.url.replace(function() return broadcast1 end) 159 | 160 | local callback 161 | local flow_finished = false 162 | local instance = flow.start(function() 163 | flow.until_callback(function(cb, foo, bar) 164 | assert(foo == "foo") 165 | assert(bar == "bar") 166 | callback = cb 167 | end, "foo", "bar") 168 | flow_finished = true 169 | end) 170 | 171 | wait_seconds(0.25) 172 | assert(not flow_finished) 173 | 174 | callback() 175 | wait_seconds(0.25) 176 | assert(flow_finished) 177 | end) 178 | 179 | it("should be possible to pause until flows complete", function() 180 | local flow_finished = false 181 | local instance = flow.start(function() 182 | local f1 = flow.parallel(function() 183 | flow.delay(0.3) 184 | end) 185 | 186 | local f2 = flow.parallel(function() 187 | flow.delay(0.3) 188 | end) 189 | 190 | flow.until_flows(f1) 191 | flow.until_flows({ f1, f2 }) 192 | flow_finished = true 193 | end) 194 | 195 | wait_seconds(0.2) 196 | assert(not flow_finished) 197 | 198 | wait_seconds(0.2) 199 | assert(flow_finished) 200 | end) 201 | 202 | it("should be possible to pause until flows with the specific group_id complete", function() 203 | local flow_finished = false 204 | local instance = flow.start(function() 205 | flow.parallel_group("foo", function() 206 | flow.delay(0.1) 207 | end) 208 | 209 | flow.parallel_group("foo", function() 210 | flow.delay(0.2) 211 | end) 212 | 213 | flow.parallel_group("foo", function() 214 | flow.delay(0.3) 215 | end) 216 | 217 | flow.until_group("foo") 218 | flow_finished = true 219 | end) 220 | 221 | wait_seconds(0.2) 222 | assert(not flow_finished) 223 | 224 | wait_seconds(0.2) 225 | assert(flow_finished) 226 | end) 227 | end) 228 | end --------------------------------------------------------------------------------