├── 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 | [](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 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
ldoc
28 |
29 |
32 |
33 |
Contents
34 |
37 |
38 |
39 |
Modules
40 |
60 |
61 |
62 |
63 |
64 |
65 |
Module m.settings
66 |
Module to save user settings to disk
67 |
68 |
Usage:
69 |
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 |
82 |
83 |
84 | save ()
85 | Save settings to disk.
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
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 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
ldoc
28 |
29 |
32 |
33 |
Contents
34 |
37 |
38 |
39 |
Modules
40 |
60 |
61 |
62 |
63 |
64 |
65 |
Module m.bezier
66 |
Create bezier curves
67 |
68 |
69 |
70 |
71 |
77 |
78 |
79 |
80 |
81 |
82 |
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 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
ldoc
28 |
29 |
32 |
33 |
Contents
34 |
37 |
38 |
39 |
Modules
40 |
60 |
61 |
62 |
63 |
64 |
65 |
Module m.file
66 |
File manipulation utilities
67 |
68 |
69 |
70 |
71 |
72 |
73 | fix (filename)
74 | Fix a filename to ensure that it doesn't contain any illegal characters
75 |
76 |
77 | get_save_file_path (filename)
78 | Get an application specific save file path to a filename.
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
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 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
ldoc
28 |
29 |
32 |
33 |
Contents
34 |
37 |
38 |
39 |
Modules
40 |
60 |
61 |
62 |
63 |
64 |
65 |
Module m.util
66 |
Utility functions
67 |
68 |
69 |
70 |
71 |
72 |
73 | lerp (a, b, t)
74 | Linear interpolation between to numbers
75 |
76 |
77 | shuffle (t)
78 | Suffle a Lua table
79 |
80 |
81 | random (list)
82 | Pick a random value from a list
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
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 |
157 | value A random value
158 |
159 | index Index of the value
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 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
ldoc
28 |
29 |
32 |
33 |
Contents
34 |
37 |
38 |
39 |
Modules
40 |
60 |
61 |
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 |
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 |
90 |
91 |
92 | iac
93 | Wrapper for iac.set_listener
94 |
95 |
96 | iap
97 | Wrapper for iap.set_listener
98 |
99 |
100 | window
101 | Wrapper for window.set_listener
102 |
103 |
104 | push
105 | Wrapper for push.set_listener
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
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 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
ldoc
28 |
29 |
32 |
33 |
Contents
34 |
37 |
38 |
39 |
Modules
40 |
60 |
61 |
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 |
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 |
83 |
97 |
98 |
99 |
100 |
101 |
102 |
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 |
167 | success
168 |
169 | error_message
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 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
ldoc
28 |
29 |
32 |
33 |
Contents
34 |
37 |
38 |
39 |
Modules
40 |
60 |
61 |
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 |
72 |
73 |
74 |
75 |
76 |
77 |
95 |
96 |
97 |
98 |
99 |
100 |
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 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
ldoc
28 |
29 |
32 |
33 |
Contents
34 |
37 |
38 |
39 |
Modules
40 |
60 |
61 |
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 |
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 |
83 |
97 |
98 |
99 |
100 |
101 |
102 |
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 |
142 | contents File contents or nil if something went wrong
143 |
144 | error_message Error message if something went wrong while reading
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 |
169 | success
170 |
171 | error_message
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 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
ldoc
28 |
29 |
32 |
33 |
Contents
34 |
37 |
38 |
39 |
Modules
40 |
60 |
61 |
62 |
63 |
64 |
65 |
Module m.simple_input
66 |
Convenience function to acquire input focus
67 |
68 |
69 |
70 |
71 |
92 |
93 |
94 |
95 |
96 |
97 |
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 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
ldoc
28 |
29 |
32 |
33 |
Contents
34 |
37 |
38 |
39 |
Modules
40 |
60 |
61 |
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 |
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 |
97 |
115 |
116 |
117 |
118 |
119 |
120 |
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 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
ldoc
28 |
29 |
30 |
31 |
32 |
Modules
33 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
Modules
61 |
62 |
63 | m.app
64 | Module to simplify the use of several of the engine listeners.
65 |
66 |
67 | m.bezier
68 | Create bezier curves
69 |
70 |
71 | m.broadcast
72 | Module to simplify sending of a message to multiple receivers
73 |
74 |
75 | m.dynamic
76 | Utility functions for working with dynamic collision objects
77 |
78 |
79 | m.file
80 | File manipulation utilities
81 |
82 |
83 | m.flow
84 | The 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.
87 |
88 |
89 | m.gesture
90 | Gesture detection module that can be used to detect gestures such as tap, double tap,
91 | long press and swipe
92 |
93 |
94 | m.input
95 | Module to simplify input handling.
96 |
97 |
98 | m.kinematic
99 | Utility functions for working with kinematic collision objects
100 |
101 |
102 | m.listener
103 | Listener implementation where listeners are added as either urls or functions
104 | and notified when any or specific messages are received
105 |
106 |
107 | m.logger
108 | Simple Lua logger
109 |
110 |
111 | m.platformer
112 | Wrapper for the physics and behaviour involved in creating a platformer
113 | game
114 |
115 |
116 | m.savefile
117 | Wrapper 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.
121 |
122 |
123 | m.savetable
124 | Wrapper 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.
128 |
129 |
130 | m.settings
131 | Module to save user settings to disk
132 |
133 |
134 | m.signal
135 | Module to create a signal system where named signals can be created, listened
136 | to and triggered
137 |
138 |
139 | m.simple_input
140 | Convenience function to acquire input focus
141 |
142 |
143 | m.util
144 | Utility functions
145 |
146 |
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
--------------------------------------------------------------------------------