diff --git a/README.md b/README.md index 4c432a3..5ea82cb 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Set of various libraries I use for game development, mainly with LÖVE. Most of This provides, sorting the one with the fewest existing alterative as far as I know first: * `ldtk` provides a [LDtk](https://ldtk.io/) level importer -* `gltf` provides a [glTF](https://www.khronos.org/gltf/) model loader +* `gltf` provides a [glTF](https://www.khronos.org/gltf/) model loader (documentation WIP) * `ecs` provides [ECS](https://en.wikipedia.org/wiki/Entity_component_system) facilities * `input` provides input management facilities * `timer` provides time management facilities diff --git a/docs/index.html b/docs/index.html index cfac48e..2055bf1 100644 --- a/docs/index.html +++ b/docs/index.html @@ -113,7 +113,7 @@
generated by LDoc 1.4.6 -Last updated 2022-09-16 20:07:07 +Last updated 2022-09-20 01:08:14
diff --git a/docs/modules/asset.html b/docs/modules/asset.html index cac1531..8ab79b3 100644 --- a/docs/modules/asset.html +++ b/docs/modules/asset.html @@ -337,7 +337,7 @@
generated by LDoc 1.4.6 -Last updated 2022-09-16 20:07:07 +Last updated 2022-09-20 01:08:14
diff --git a/docs/modules/ecs.html b/docs/modules/ecs.html index 89b780b..ba4d9f5 100644 --- a/docs/modules/ecs.html +++ b/docs/modules/ecs.html @@ -1729,7 +1729,7 @@ its sibling systems (i.e. completely stop the propagation of the event).
generated by LDoc 1.4.6 -Last updated 2022-09-16 20:07:07 +Last updated 2022-09-20 01:08:14
diff --git a/docs/modules/input.html b/docs/modules/input.html index 5256c33..a6afc72 100644 --- a/docs/modules/input.html +++ b/docs/modules/input.html @@ -34,7 +34,9 @@

Contents

@@ -62,12 +64,59 @@

Module input

Input management facilities.

-

The module returns a single function, input.

+

The module returns a single function, input.

-

Requires ubiquitousse.signal.

+

You can find in the module uqt.input.default (in file input/default.lua) some common input configuration for +2D movement, confirmation and cancellation (for both keyboard and joystick). Feel free to +use them as-is, or as a base for your own inputs.

+ +

Requires ubiquitousse.signal.

Usage:

@@ -75,10 +124,53 @@

Functions

- +
input ()input ([config]) Make a new input object.
+

Input expressions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
floor (x)Same as Lua’s math.floor.
floor (x)Same as Lua’s math.ceil.
floor (x)Same as Lua’s math.abs.
clamp (x, xmin, xmax)Clamp x between xmin and xmax.
min (x, y, ...)Returns the minimal value among all parameters.
max (x, y, ...)Returns the maximal value among all parameters.
deadzone (x, deadzone)If x < deadzone, returns 0; otherwise returns the value.
clamped (x, y)Returns a normalized version of the vector (x,y), i.e.
passive (source)Mark an input source as passive.
passive (source)Mark an input source as active.

Input objects

@@ -86,20 +178,20 @@ - - + + - + - - + + - - + + @@ -115,7 +207,7 @@ - + @@ -123,7 +215,7 @@ - + @@ -147,15 +239,15 @@ - + - + - + @@ -165,13 +257,64 @@ +
Input configuration table.
Input.childrenList and map of children inputs.Input.children [read-only]List and map of children Inputs.
Input.nameInput.name [read-only] Name of the input.
Input.grabbedFalse if the input is currently not grabbed, a subinput otherwise.Input.grabbed [read-only]false if the input is currently not grabbed, a subInput otherwise.
Input.grabbingFalse if the input is not a subinput, the input it grabbed otherwise.Input.grabbing [read-only]false if the input is not a subinput, the Input it was grabbed from otherwise.
Input.event
Input:reload ()Relond the input config, and do the same for its children.Reload the input config, and do the same for its children.
Input:disable ()
Input:onNextActiveSource (fn[, filter])Will call fn(source) on the next activated source (including sources not currently used by this input).Will call fn(source) on the next activated source (including sources not currently used by this input).
Input:grab ()
Input:down ()Returns true if the input is currently down.Returns true if the input is currently down, false otherwise.
Input:pressed ()Returns true if the input has just been pressed.Returns true if the input has just been pressed, false otherwise.
Input:released ()Returns true if the input has just been released.Returns true if the input has just been released, false otherwise.
Input:value ()Input:delta () Returns the delta value of the input since the last call to update.
+

Input sources

+ - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Input:pointer ()If there is a horizontal and vertical children inputs, this returns the horizontal value and the vertical value.key.XKeyboard input: 1 if the key X is down, 0 otherwise.
Input:clamped ()Same as pointer, but normalize the returned vector, i.e.scancode.XKeyboard input: 1 if the key with scancode X is down, 0 otherwise.
text.XText input: 1 if the text X was entered, 0 otherwise.
mouse.NMouse input: 1 if the mouse button is down, 0 otherwise.
mouse.xMouse input: X position of the mouse cursor.
mouse.yMouse input: Y position of the mouse cursor.
mouse.dxMouse input: latest X movement of the mouse cursor.
mouse.dyMouse input: latest Y movement of the mouse cursor.
wheel.dxMouse input: latest X movement of the mouse wheel.
wheel.dyMouse input: latest Y movement of the mouse wheel.
button.XGamepad input: 1 if the button X is down, 0 otherwise.
axis.XGamepad input: current value of the gamepad axis (between -1 and 1).
dtOn new frame: current delta time value since last frame.
child.XChildren inputs: current value of a child input of the current input.
@@ -184,18 +327,452 @@
- input () + input ([config])
- Make a new input object. - t: input configuration table (optional) + Make a new input object.

+ +

This constructor is returned by the uqt.input module. +

Parameters:

+ +

Returns:

+
    + + Input + Input object +
+ + + +

Usage:

+ + +
+
+

Input expressions

+ +
+ Each Input is associated with a list of input expressions.

+ +

An input expression is a string representing a valid Lua expression. + These Lua expressions are used to compute the values returned by the input. + An input expression should returns as many values as the dimension of its input.

+ +

When referring to a variable in an expression, it will be interpreted as an input source. + An input source is where the initial input data comes from: for example, an input source could be the current state of + the Q key: "key.q" (=1 when the key is down, 0 otherwise). + See sources for details on how to define sources and built-in sources.

+ +

When a source that is present is the expression is updated, the input will be automatically updated in a reactive way, unless + the input is passive – that is, it can not trigger input update.

+ +

Additionally, you can also define your own variables to be used in input expression by giving arguments.

+ +

In input expression, you have no access to the Lua standard library; some other, more specific functions are available instead + and described below. +

+

Usage:

+
 -- Example input configs for various input expression features.
+ fire = {
+    -- Basic input expression: 1 when Q is pressed, 0 otherwise. Will automatically update when Q is pressed.
+    "key.q",
+    -- Example using two inputs sources: when either A or Q is pressed, this will update, and returns 1 is at least one of them is pressed.
+    "max(key.q, key.a)",
+    -- This input has two input expression: it will be updated when either of them is updated and keep the value of the expression that was updated last.
+ }
+ horizontal = {
+    -- Another example, typically used to define an input for horizontal movement: returns 1 when pressing only right, -1 when pressing only left, and 0 otherwise.
+    "key.right - key.left",
+ }
+ arguments = {
+    -- You can give arguments to an expression by wrapping the expression in a table containing the arguments.
+    { "axis.leftx + offset", offset = 1 }
+ }
+ passive = {
+   -- Same as the example above, but this will only update when Q is pressed - pressing A will not update the input on its own.
+   -- Passive input are typically used for modifiers keys (shift, alt, etc.) that should not trigger an update when pressed on their own.
+   "max(key.q, passive(key.a))"
+ }
+ dimension = {
+   -- A two-dimensional input should have input expressions that return two values.
+   -- Here a common example that could be used to move something in 2D.
+   "key.right - key.left, key.down - key.up",
+   dimension = 2
+ }
+ special = {
+   -- A special case: if the dt source is present in an expression, it will make every other input source passive by default in the expression.
+   -- This is the case since dt will trigger an update every frame, and is therefore mostly relevant for input that is used once per frame only
+   -- (while other input sources might cause the input to update several times per frame).
+   "axis.leftx * dt"
+ }
+ child = {
+   -- Children input can be used as input sources using the child.name syntax.
+   -- If the children input has more than one dimension, you will need to specify it using a numeric index like child.fire[2] (for the dimension 2 of the child).
+   "child.fire",
+   fire = { "key.q" }
+}
+
+
+ + floor (x) +
+
+ Same as Lua’s math.floor. + + + + + + +

Parameters:

+ + +

Returns:

+
    + + number + floored value +
+ + + + +
+
+ + floor (x) +
+
+ Same as Lua’s math.ceil. + + + + + + +

Parameters:

+ + +

Returns:

+
    + + number + ceiled value +
+ + + + +
+
+ + floor (x) +
+
+ Same as Lua’s math.abs. + + + + + + +

Parameters:

+ + +

Returns:

+
    + + number + absolute value +
+ + + + +
+
+ + clamp (x, xmin, xmax) +
+
+ Clamp x between xmin and xmax. + + + + + + +

Parameters:

+ + +

Returns:

+
    + + number + clamped value +
+ + + + +
+
+ + min (x, y, ...) +
+
+ Returns the minimal value among all parameters. + + + + + + +

Parameters:

+ + +

Returns:

+
    + + number + smallest value among the arguments +
+ + + + +
+
+ + max (x, y, ...) +
+
+ Returns the maximal value among all parameters. + + + + + + +

Parameters:

+ + +

Returns:

+
    + + number + biggest value among the arguments +
+ + + + +
+
+ + deadzone (x, deadzone) +
+
+ If x < deadzone, returns 0; otherwise returns the value. + + + + + + +

Parameters:

+ + +

Returns:

+
    + + number + 0 if x < deadzone; x otherwise +
+ + + + +
+
+ + clamped (x, y) +
+
+ Returns a normalized version of the vector (x,y), i.e. “clamp” the returned x,y coordinates into a circle of radius 1. + Typically used to avoid faster movement on diagonals, as if both horizontal and vertical values are 1, the (1,1) vector has √2 magnitude, higher than the 1 magnitude of a purely vertical or horizontal movement. + + + + + + +

Parameters:

+ + +

Returns:

+
    +
  1. + number + clamped x value
  2. +
  3. + number + clamped y value
  4. +
+ + + + +
+
+ + passive (source) +
+
+ Mark an input source as passive. + + + + + + +

Parameters:

+ + +

Returns:

+
    + + InputSource + the same input source +
+ + + + +
+
+ + passive (source) +
+
+ Mark an input source as active. + Note that input sources are active by default in most cases. + + + + + + +

Parameters:

+ + +

Returns:

+
    + + InputSource + the same input source +
@@ -205,7 +782,9 @@

Input objects

- Input methods. + Input methods.

+ +

Methods and attributes available on Input objects. See input to create such an object.

@@ -213,11 +792,15 @@ Input.config
- Input configuration table. - It can be used to recreate this input object later (by passing the table as an argument for the input constructor). + Input configuration table.

+ +

It can be used to recreate this input object later (by passing the table as an argument for the input constructor). This table does not contain any userdata and should be easily serializable (e.g. to save custom input binding config). - This doesn’t include input state, grab state, the event registry and the selected joystick since they may change often during runtime. - Can be changed anytime, but you may need to call reload to apply changes. + This doesn’t include input state, grab state, the event registry and the selected joystick since they may change often during runtime.

+ +

Can be changed anytime, but you may need to call reload to apply changes.

+ +

See expressions for an explanation on how to write input expressions. @@ -231,21 +814,33 @@

Usage:

    player.config = {
    -  "key.a", "key.d - key.a", {"key.left + x", x=0.5}, -- list of input sources expressions
    -  jump = {...}, -- children input
    -  deadzone = 0.05, -- The deadzone for analog inputs (e.g. joystick axes): if the input absolute value is strictly below this, it will be considered as 0.
    -  threshold = 0.05 -- The pressed threshold: an input is considered down if above or equal to this value.
    -}
    + -- list of input sources expressions: either a string, or a table to specify some arguments for the expression + "key.a", "key.d - key.a", {"key.left + x", x=0.5}, + -- children input: the table take the same fields as this + jump = {...}, + -- The deadzone for analog inputs (e.g. joystick axes): if the input absolute value is strictly below this, it will be considered as 0. 0.05 by default. + -- This is applied automatically after the evaluation of input expressions. + deadzone = 0.05, + -- The pressed threshold: an input is considered down if above or equal to this value. 0.05 by default. + -- This is considered when determining if the input is pressed, odwn and released. + threshold = 0.05, + -- Dimension of the input (i.e. the number of values returned by this input). 1 by default. + dimension = 1 +}
- Input.children + Input.children [read-only]
- List and map of children inputs. - {[child1.name]=child1, [child2.name]=child2, child1, child2…} + List and map of children Inputs.

+ +

Takes the form {[child1.name]=child1, [child2.name]=child2, child1, child2…}. + Each child input is present both an element of this list and as the value associated with its name in the table.

+ +

Note that children are also set directly on the input object for easier access. @@ -256,17 +851,27 @@ +

Usage:

+
    +
    local player = input{ fire = "button.a" }
    +local fire = player.fire
    +-- Is the same as:
    +local fire = player.children.fire
    +
- Input.name + Input.name [read-only]
Name of the input. Defined on children inputs only. +

Type:

+
    + string
@@ -279,10 +884,10 @@
- Input.grabbed + Input.grabbed [read-only]
- False if the input is currently not grabbed, a subinput otherwise. + false if the input is currently not grabbed, a subInput otherwise. This may be different between each subinput. @@ -298,10 +903,10 @@
- Input.grabbing + Input.grabbing [read-only]
- False if the input is not a subinput, the input it grabbed otherwise. + false if the input is not a subinput, the Input it was grabbed from otherwise. This may be different between each subinput. @@ -324,16 +929,9 @@ The following events are available:

    -
  • "moved": called when the input value change, with arguments (new value, delta since last event)
  • -
  • "pressed": called when the input is pressed
  • -
  • "released": called when the input is released
  • -
- - -

For pointer inputs (have a “horizontal” and “vertical” children inputs) is also avaible:

- -
    -
  • "pointer moved": called when the pointer position change, with arguments (new pointer x, new pointer y, delta x since last event, delta y since last event)
  • +
  • "moved": called when the input value change, with arguments (new value, delta since last event). For inputs with dimension > 1, arguments are (new value[1], new value[2], …, delta[1], delta[2], …).

  • +
  • "pressed": called when the input is pressed, with arguments (1, new value, delta since last event). For inputs with dimension > 1, arguments are (dimensions that was pressed, new value[1], new value[2], …, delta[1], delta[2], …).

  • +
  • "released": called when the input is released, with arguments (1, new value, delta since last event). For inputs with dimension > 1, arguments are (dimensions that was pressed, new value[1], new value[2], …, delta[1], delta[2], …).

@@ -357,8 +955,9 @@
Update the input and its children. Should be called every frame, typically after you've done all your input handling - (otherwise pressed and released may never return true and delta might be wrong). - (Note: this should not be called on subinputs) + (otherwise pressed and released may never return true and delta might be wrong).

+ +

(Note: this should not be called on subinputs) @@ -394,7 +993,7 @@ Input:reload ()

- Relond the input config, and do the same for its children. + Reload the input config, and do the same for its children. This will reenable the input if it was disabled using disable. @@ -432,7 +1031,7 @@ Input:onNextActiveSource (fn[, filter])
- Will call fn(source) on the next activated source (including sources not currently used by this input). + Will call fn(source) on the next activated source (including sources not currently used by this input). Typically used to detect an input in your game input binding settings. @@ -466,7 +1065,7 @@

A grabbed input will no longer update and instead pass all new update to the subinput. This is typically used for contextual action or pause menus: by grabbing the player input, all the direct use of this input in the game will stop (can’t move caracter, …) and instead you can use the subinput to handle input in the pause menu. - To stop grabbing an input, you will need to :release the subinput.

+ To stop grabbing an input, you will need to release the subinput.

This will also reset the input to a neutral state. The subinput will share everything with this input, except grabbed, grabbing, event (a new event registry is created), and of course its current state. @@ -506,7 +1105,7 @@ Input:neutralize ()

- Set the state of this input to a neutral position (i.e. value = 0). + Set the state of this input to a neutral position (i.e. value = 0 for every dimension). @@ -525,7 +1124,7 @@
Set the joystick associated with this input. - The input will ignore every other joystick. + This input will then ignore every other joystick. Set joystick to nil to disable and get input from every connected joystick. @@ -558,6 +1157,12 @@ +

Returns:

+
    + + joystick + LÖVE jostick object +
@@ -568,7 +1173,7 @@ Input:down ()
- Returns true if the input is currently down. + Returns true if the input is currently down, false otherwise. @@ -576,6 +1181,12 @@ +

Returns:

+
    + + boolean + if input is down on at least one dimensions +
@@ -586,7 +1197,7 @@ Input:pressed ()
- Returns true if the input has just been pressed. + Returns true if the input has just been pressed, false otherwise. @@ -594,6 +1205,12 @@ +

Returns:

+
    + + boolean + if input has just been pressed on at least one dimensions +
@@ -604,7 +1221,7 @@ Input:released ()
- Returns true if the input has just been released. + Returns true if the input has just been released, false otherwise. @@ -612,6 +1229,12 @@ +

Returns:

+
    + + boolean + if input has just been released on at least one dimensions +
@@ -623,6 +1246,7 @@
Returns the current value of the input. + If dimension > 1, this will return several values, one per dimension. @@ -630,6 +1254,12 @@ +

Returns:

+
    + + number,... + current value of the input for every dimension +
@@ -641,6 +1271,46 @@
Returns the delta value of the input since the last call to update. + If dimension > 1, this will return several values, one per dimension. + + + + + + + +

Returns:

+
    + + number,... + delta of the input for every dimension +
+ + + + +
+
+

Input sources

+ +
+ Input sources are the initial source of input data. They are identified by a Lua identifier name. + See expressions on how to use them in expressions.

+ +

Input sources are provided for common input methods (keyboard, mouse, gamepad) by default; see below for a list of built-in input sources.

+ +

Additionally, you can define your own input sources, by emitting events in the SignalRegistry returned by uqt.input.event. + See the file input/event.lua to see a description of the events you will need to emit. The file also contains the definition + of the built-in input sources that you may use as an example. +

+
+
+ + key.X +
+
+ Keyboard input: 1 if the key X is down, 0 otherwise. + X can be any of LÖVE’s KeyConstant. @@ -654,12 +1324,12 @@
- - Input:pointer () + + scancode.X
- If there is a horizontal and vertical children inputs, this returns the horizontal value and the vertical value. - Typically used for movement/axes pairs (e.g. to get x,y of a stick or directional pad). + Keyboard input: 1 if the key with scancode X is down, 0 otherwise. + X can be any of LÖVE’s Scancode. @@ -673,13 +1343,236 @@
- - Input:clamped () + + text.X
- Same as pointer, but normalize the returned vector, i.e. “clamp” the returned x,y coordinates into a circle of radius 1. - Typically used to avoid faster movement on diagonals - (as if both horizontal and vertical values are 1, the pointer vector has √2 magnitude, higher than the 1 magnitude of a purely vertical or horizontal movement). + Text input: 1 if the text X was entered, 0 otherwise. + X can be any text. + + + + + + + + + + + +
+
+ + mouse.N +
+
+ Mouse input: 1 if the mouse button is down, 0 otherwise. + N is either 1 for the primary mouse button, 2 for secondary or 3 for middle button. + + + + + + + + + + + +
+
+ + mouse.x +
+
+ Mouse input: X position of the mouse cursor. + + + + + + + + + + + +
+
+ + mouse.y +
+
+ Mouse input: Y position of the mouse cursor. + + + + + + + + + + + +
+
+ + mouse.dx +
+
+ Mouse input: latest X movement of the mouse cursor.

+ +

mouse.dx.p and mouse.dx.n will respectively only report movement in the positive or negative direction and return absolute value. + + + + + + + + + + + +

+
+ + mouse.dy +
+
+ Mouse input: latest Y movement of the mouse cursor.

+ +

mouse.dy.p and mouse.dy.n will respectively only report movement in the positive or negative direction and return absolute value. + + + + + + + + + + + +

+
+ + wheel.dx +
+
+ Mouse input: latest X movement of the mouse wheel.

+ +

wheel.dx.p and wheel.dx.n will respectively only report movement in the positive or negative direction and return absolute value. + + + + + + + + + + + +

+
+ + wheel.dy +
+
+ Mouse input: latest Y movement of the mouse wheel.

+ +

wheel.dy.p and wheel.dy.n will respectively only report movement in the positive or negative direction and return absolute value. + + + + + + + + + + + +

+
+ + button.X +
+
+ Gamepad input: 1 if the button X is down, 0 otherwise. + X can be any of LÖVE’s GamepadButton. + + + + + + + + + + + +
+
+ + axis.X +
+
+ Gamepad input: current value of the gamepad axis (between -1 and 1). + X can be any of LÖVE’s GamepadAxis.

+ +

axis.X.p and axis.X.n will respectively only report movement in the positive or negative direction and return absolute value. + + + + + + + + + + + +

+
+ + dt +
+
+ On new frame: current delta time value since last frame. Updated on each call to love.update. + Note that if this input source is present in an expression, the other input sources in the same expression will be set as passive by default. + + + + + + +
    +
  • dt + + +
  • +
+ + + + + +
+
+ + child.X +
+
+ Children inputs: current value of a child input of the current input. + For child input of dimension 1. + Replace X with the name of the child input.

+ +

If the child input is of dimension at least 2, instead use child.X[N], which gives the + current value of the Nth dimension of a child input of the current input. + Replace X with the name of the child input, and N with the index of the dimension you want. @@ -699,7 +1592,7 @@

generated by LDoc 1.4.6 -Last updated 2022-09-16 20:07:07 +Last updated 2022-09-20 01:08:14
diff --git a/docs/modules/ldtk.html b/docs/modules/ldtk.html index 4ff7347..864704d 100644 --- a/docs/modules/ldtk.html +++ b/docs/modules/ldtk.html @@ -2066,7 +2066,7 @@ end Level background.

If there is a background image, background.image contains a table {image=image, x=number, y=number, sx=number, sy=number} - where image is the LÖVE image (or image filepath if LÖVE not available) x and y are the top-left position, + where image is the LÖVE image (or image filepath if LÖVE not available) x and y are the top-left position, and sx and sy the horizontal and vertical scale factors. @@ -2142,7 +2142,7 @@ end

  • Enum are converted into a Lua string giving the currently selected enum value.
  • Filepath are converted into a Lua string giving the file path.
  • Arrays are converted into a Lua table with the elements in it as a list.
  • -
  • Points are converted into a Lua table with the fields x and y, in pixels: { x=number, y=number }.
  • +
  • Points are converted into a Lua table with the fields x and y, in pixels: { x=number, y=number }.
  • Colors are converted into a Lua table with the red, green and blue components in [0-1] as a list: {r,g,b}.
  • Tiles are converted into a Lua table { tileset = associated tileset object, quad = associated quad } where quad is a LÖVE Quad if LÖVE is available, otherwise a table { x, y, width, height }.
  • EntityRef are converted into a Lua table { level = level, layerIid = layer IID, entityIid = entity IID, entity = see explanation }. If the entity being refernced belongs to another level and this level is not loaded, entity will be nil; otherwise (same level or the other level is also loaded), it will contain the entity.
  • @@ -2170,7 +2170,7 @@ end
    generated by LDoc 1.4.6 -Last updated 2022-09-16 20:07:07 +Last updated 2022-09-20 01:08:14
    diff --git a/docs/modules/scene.html b/docs/modules/scene.html index a18e0ef..692ab51 100644 --- a/docs/modules/scene.html +++ b/docs/modules/scene.html @@ -703,7 +703,7 @@
    generated by LDoc 1.4.6 -Last updated 2022-09-16 20:07:07 +Last updated 2022-09-20 01:08:14
    diff --git a/docs/modules/signal.html b/docs/modules/signal.html index 321c2f3..359b7a6 100644 --- a/docs/modules/signal.html +++ b/docs/modules/signal.html @@ -788,7 +788,7 @@ signal.event:bind("keypressed", function(key, scancode) print("pr
    generated by LDoc 1.4.6 -Last updated 2022-09-16 20:07:07 +Last updated 2022-09-20 01:08:14
    diff --git a/docs/modules/timer.html b/docs/modules/timer.html index 9a33d7f..7efb9d4 100644 --- a/docs/modules/timer.html +++ b/docs/modules/timer.html @@ -1154,7 +1154,7 @@
    generated by LDoc 1.4.6 -Last updated 2022-09-16 20:07:07 +Last updated 2022-09-20 01:08:14
    diff --git a/docs/modules/ubiquitousse.html b/docs/modules/ubiquitousse.html index 2d4b091..8413201 100644 --- a/docs/modules/ubiquitousse.html +++ b/docs/modules/ubiquitousse.html @@ -238,7 +238,6 @@ the repository to save you a few seconds.

    Input management, if available. - TODO: documentation not currently generated with LDoc. @@ -395,7 +394,7 @@ the repository to save you a few seconds.

    generated by LDoc 1.4.6 -Last updated 2022-09-16 20:07:07 +Last updated 2022-09-20 01:08:14
    diff --git a/docs/modules/util.html b/docs/modules/util.html index 9414737..09748e1 100644 --- a/docs/modules/util.html +++ b/docs/modules/util.html @@ -785,7 +785,7 @@
    generated by LDoc 1.4.6 -Last updated 2022-09-16 20:07:07 +Last updated 2022-09-20 01:08:14
    diff --git a/docs/topics/LICENSE.html b/docs/topics/LICENSE.html index 5c2811f..6f2ede6 100644 --- a/docs/topics/LICENSE.html +++ b/docs/topics/LICENSE.html @@ -65,7 +65,7 @@
    generated by LDoc 1.4.6 -Last updated 2022-09-16 20:07:07 +Last updated 2022-09-20 01:08:14
    diff --git a/docs/topics/README.md.html b/docs/topics/README.md.html index 9784fa6..f868198 100644 --- a/docs/topics/README.md.html +++ b/docs/topics/README.md.html @@ -63,7 +63,7 @@
    • ldtk provides a LDtk level importer
    • -
    • gltf provides a glTF model loader
    • +
    • gltf provides a glTF model loader (documentation WIP)
    • ecs provides ECS facilities
    • input provides input management facilities
    • timer provides time management facilities
    • @@ -87,7 +87,7 @@
      generated by LDoc 1.4.6 -Last updated 2022-09-16 20:07:07 +Last updated 2022-09-20 01:08:14
      diff --git a/init.lua b/init.lua index 7ec352d..582b36e 100644 --- a/init.lua +++ b/init.lua @@ -62,7 +62,6 @@ ubiquitousse = { -- @see ecs ecs = nil, --- Input management, if available. - -- TODO: documentation not currently generated with LDoc. -- @see input input = nil, --- LDtk level import, if available. diff --git a/input/default.lua b/input/default.lua index c1c68ba..22f6265 100644 --- a/input/default.lua +++ b/input/default.lua @@ -1,15 +1,11 @@ return { move = { - horizontal = { - "child.positive - child.negative", - positive = { "scancode.right", "scancode.d", "axis.leftx.p", "button.dpright" }, - negative = { "scancode.left", "scancode.a", "axis.leftx.n", "button.dpleft" }, - }, - vertical = { - "child.positive - child.negative", - positive = { "scancode.down", "scancode.s", "axis.lefty.p", "button.dpdown" }, - negative = { "scancode.up", "scancode.w", "axis.lefty.n", "button.dpup" }, - }, + "clamped(child.right - child.left, child.down - child.up)", + dimension = 2, + right = { "scancode.right", "scancode.d", "axis.leftx.p", "button.dpright" }, + left = { "scancode.left", "scancode.a", "axis.leftx.n", "button.dpleft" }, + down = { "scancode.down", "scancode.s", "axis.lefty.p", "button.dpdown" }, + up = { "scancode.up", "scancode.w", "axis.lefty.n", "button.dpup" }, }, confirm = { "scancode['return']", "scancode.space", "scancode.e", "button.a" }, cancel = { "scancode.escape", "scancode.backspace", "button.b" }, diff --git a/input/event.lua b/input/event.lua index 69e4c11..00c4b94 100644 --- a/input/event.lua +++ b/input/event.lua @@ -4,13 +4,13 @@ local max, min = math.max, math.min --- This event registry is where every input object will listen for source events. -- -- Available events: --- * `"source.name"`: triggered when source.name (example name) is updated. --- Will pass the arguments _new value_ (number), _filter_ (optional), _..._ (additional arguments for the filter, optional). --- `filter` is an optional filter function that will be called by the listening inputs with arguments filter(input object, new value, ...), +-- * `"source.name"`: triggered when source.name (example input source name, replace with your own) is updated. +-- Must pass the arguments _new value_ (number), _filter_ (optional), _..._ (additional arguments for the filter, optional). +-- `filter` is an optional filter function that will be called by the listening inputs with arguments `filter(input object, new value, ...)`, -- and should return the (eventually modified) new value. If it returns `nil`, the input will ignore the event (for example if the event concerns -- a joystick that is not linked with the input). -- * `"_active"`: triggered when any input is active, used for input detection in `onActiveNextSource`. --- Will pass arguments _source name_ (string), _new value_, _filter_, _..._ (same arguments as other source updates, with source name added). +-- Must pass arguments _source name_ (string), _new value_, _filter_, _..._ (same arguments as other source updates, with source name added). local event = signal.new() local function update(source, new, filter, ...) diff --git a/input/input.lua b/input/input.lua index a55c293..cf4c955 100644 --- a/input/input.lua +++ b/input/input.lua @@ -1,20 +1,74 @@ ---- Input management facilities. --- --- The module returns a single function, `input`. --- --- **Requires** ubiquitousse.signal. --- @module input --- @usage --- TODO +--[[- Input management facilities. + +The module returns a single function, `input`. + +You can find in the module `uqt.input.default` (in file `input/default.lua`) some common input configuration for +2D movement, confirmation and cancellation (for both keyboard and joystick). Feel free to +use them as-is, or as a base for your own inputs. + +**Requires** ubiquitousse.signal. + +@module input +@usage +local input = require("ubiquitousse.input") + +-- Joystick-only control for player 1 +local player1 = { + move = { + -- 2D movement, clamped on a circle + "clamped(child.right - child.left, child.down - child.up)", + dimension = 2, + + -- All the directions we can go, using both the left joystick and the D-pad + right = { "axis.leftx.p", "button.dpright" }, + left = { "axis.leftx.n", "button.dpleft" }, + down = { "axis.lefty.p", "button.dpdown" }, + up = { "axis.lefty.n", "button.dpup" } + }, + + fire = { "button.a" } +} + +-- Only consider inputs from the first joystick (in practice you will want to check if the joystick exists before running this line) +player1:setJoystick(love.joystick.getJoysticks()[1]) + +-- Player 2, using a second gamepad! +local player2 = player1:clone() +player2:setJoystick(love.joystick.getJoysticks()[2]) + +-- Define input callbacks. +player1.fire.event:bind("pressed", function() print("player 1 starts firing!") end) +player1.fire.event:bind("released", function() print("player 1 stop firing...") end) + +function love.update() + -- Get player 1's 2D movement + local x, y = player1.move:value() + movePlayer1(x, y) + + -- Check current state of the firing input + if player1.fire:down() then + -- currently firing! + end + + -- Update inputs. + player1:update() + player2:update() +end +--]] local signal = require((...):gsub("input%.input$", "signal")) local event = require((...):gsub("input$", "event")) local abs, sqrt, floor, ceil, min, max = math.abs, math.sqrt, math.floor, math.ceil, math.min, math.max --- TODO: --- friendly name for sources --- write doc, incl how to define your own source and source expressions, default inputs +-- TODO: friendly name for sources + +-- TODO: way to handle text input +-- don't want to change how everything is number based here (it's clean), but would be ok to eg give an additionnal metdatat (text string) along with the "text key pressed" input + +-- Table to contain temporary calculations (mainly to compute input deltas without needing to allocate a new table). +-- Note that this table is never cleared; don't fill it with large data and don't assume its length. +local tmp = {} -- Always returns 0. local function zero() return 0 end @@ -30,74 +84,28 @@ local function loadexp(exp, env) return fn end --- Set a value in a table using its path string. -local function setPath(t, path, val) - for part in path:gmatch("(.-)%.") do - assert(t[part]) - t = t[part] - end - t[path:match("[^%.]+$")] = val -end -local function ensurePath(t, path, default) - for part in path:gmatch("(.-)%.") do - if not t[part] then t[part] = {} end - t = t[part] - end - local final = path:match("[^%.]+$") - if not t[final] then - t[final] = default - end -end - --- Functions available in input expressions. -local expressionEnv -expressionEnv = { - floor = floor, - ceil = ceil, - abs = abs, - clamp = function(x, xmin, xmax) - return min(max(x, xmin), xmax) - end, - min = function(x, y, ...) - local m = min(x, y) - if ... then - return expressionEnv.min(m, ...) - else - return m - end - end, - max = function(x, y, ...) - local m = max(x, y) - if ... then - return expressionEnv.max(m, ...) - else - return m - end - end, - deadzone = function(x, deadzone) - if abs(x) < deadzone then - return 0 - end - return x - end -} - --- List of modifiers that can be applied to a source in an expression -local sourceModifiers = { "passive", "active" } -for _, mod in ipairs(sourceModifiers) do - expressionEnv[mod] = function(...) return ... end -end - local input_mt --- Make a new input object. --- t: input configuration table (optional) +-- +-- This constructor is returned by the `uqt.input` module. +-- +-- @tparam[opt] table config input configuration table, see `Input.config` +-- @treturn Input Input object +-- @usage +-- local player = uqt.input { +-- fire = { "key.a" }, +-- jump = { "key.space" } +-- } +-- player.fire.event:bind("pressed", function() print("pew pew") end) -- @function input local function make_input(t) local self = setmetatable({ config = t or {}, children = {}, event = signal.new(), + _value = { 0 }, + _prevValue = { 0 }, _sourceCache = {}, _event = signal.group(), _afterFilterEvent = signal.new(), @@ -107,68 +115,255 @@ local function make_input(t) return self end +--- Input expressions. +-- +-- Each `Input` is associated with a list of *input expressions*. +-- +-- An input expression is a `string` representing a valid Lua expression. +-- These Lua expressions are used to compute the values returned by the input. +-- An input expression should returns as many values as the dimension of its input. +-- +-- When referring to a variable in an expression, it will be interpreted as an *input source*. +-- An input source is where the initial input data comes from: for example, an input source could be the current state of +-- the Q key: `"key.q"` (=1 when the key is down, 0 otherwise). +-- See [sources](#Input_sources) for details on how to define sources and built-in sources. +-- +-- When a source that is present is the expression is updated, the input will be automatically updated in a reactive way, unless +-- the input is *passive* - that is, it can not trigger input update. +-- +-- Additionally, you can also define your own variables to be used in input expression by giving arguments. +-- +-- In input expression, you have no access to the Lua standard library; some other, more specific functions are available instead +-- and described below. +-- +-- @usage +-- -- Example input configs for various input expression features. +-- fire = { +-- -- Basic input expression: 1 when Q is pressed, 0 otherwise. Will automatically update when Q is pressed. +-- "key.q", +-- -- Example using two inputs sources: when either A or Q is pressed, this will update, and returns 1 is at least one of them is pressed. +-- "max(key.q, key.a)", +-- -- This input has two input expression: it will be updated when either of them is updated and keep the value of the expression that was updated last. +-- } +-- horizontal = { +-- -- Another example, typically used to define an input for horizontal movement: returns 1 when pressing only right, -1 when pressing only left, and 0 otherwise. +-- "key.right - key.left", +-- } +-- arguments = { +-- -- You can give arguments to an expression by wrapping the expression in a table containing the arguments. +-- { "axis.leftx + offset", offset = 1 } +-- } +-- passive = { +-- -- Same as the example above, but this will only update when Q is pressed - pressing A will not update the input on its own. +-- -- Passive input are typically used for modifiers keys (shift, alt, etc.) that should not trigger an update when pressed on their own. +-- "max(key.q, passive(key.a))" +-- } +-- dimension = { +-- -- A two-dimensional input should have input expressions that return two values. +-- -- Here a common example that could be used to move something in 2D. +-- "key.right - key.left, key.down - key.up", +-- dimension = 2 +-- } +-- special = { +-- -- A special case: if the `dt` source is present in an expression, it will make every other input source passive by default in the expression. +-- -- This is the case since `dt` will trigger an update every frame, and is therefore mostly relevant for input that is used once per frame only +-- -- (while other input sources might cause the input to update several times per frame). +-- "axis.leftx * dt" +-- } +-- child = { +-- -- Children input can be used as input sources using the `child.name` syntax. +-- -- If the children input has more than one dimension, you will need to specify it using a numeric index like `child.fire[2]` (for the dimension 2 of the child). +-- "child.fire", +-- fire = { "key.q" } +--} +-- @section Expressions +local expressionEnv +expressionEnv = { + --- Same as Lua's `math.floor`. + -- @function floor + -- @tparam number x number to round + -- @treturn number floored value + floor = floor, + --- Same as Lua's `math.ceil`. + -- @function floor + -- @tparam number x number to round + -- @treturn number ceiled value + ceil = ceil, + --- Same as Lua's `math.abs`. + -- @function floor + -- @tparam number x number to absolute + -- @treturn number absolute value + abs = abs, + --- Clamp x between xmin and xmax. + -- @tparam number x number clamp + -- @tparam number xmin minimal value + -- @tparam number xmax maximal value + -- @treturn number clamped value + clamp = function(x, xmin, xmax) + return min(max(x, xmin), xmax) + end, + --- Returns the minimal value among all parameters. + -- @tparam number x first value + -- @tparam number y second value + -- @tparam number ... other values + -- @treturn number smallest value among the arguments + min = function(x, y, ...) + local m = min(x, y) + if ... then + return expressionEnv.min(m, ...) + else + return m + end + end, + --- Returns the maximal value among all parameters. + -- @tparam number x first value + -- @tparam number y second value + -- @tparam number ... other values + -- @treturn number biggest value among the arguments + max = function(x, y, ...) + local m = max(x, y) + if ... then + return expressionEnv.max(m, ...) + else + return m + end + end, + --- If x < deadzone, returns 0; otherwise returns the value. + -- @tparam number x value + -- @tparam number deadzone deadzone + -- @treturn number 0 if x < deadzone; x otherwise + deadzone = function(x, deadzone) + if abs(x) < deadzone then + return 0 + end + return x + end, + --- Returns a normalized version of the vector (x,y), i.e. "clamp" the returned x,y coordinates into a circle of radius 1. + -- Typically used to avoid faster movement on diagonals, as if both horizontal and vertical values are 1, the (1,1) vector has √2 magnitude, higher than the 1 magnitude of a purely vertical or horizontal movement. + -- @tparam number x value + -- @tparam number y value + -- @treturn number clamped x value + -- @treturn number clamped y value + clamped = function(x, y) + local mag = x*x + y*y + if mag > 1 then + local d = sqrt(mag) + return x/d, y/d + else + return x, y + end + end, + --- Mark an input source as passive. + -- @function passive + -- @tparam InputSource source input source to mark as passive + -- @treturn InputSource the same input source + passive = nil, + --- Mark an input source as active. + -- Note that input sources are active by default in most cases. + -- @function passive + -- @tparam InputSource source input source to mark as active + -- @treturn InputSource the same input source + active = nil +} + +-- List of modifiers that can be applied to a source in an expression +local sourceModifiers = { "passive", "active" } +for _, mod in ipairs(sourceModifiers) do + expressionEnv[mod] = function(...) return ... end +end + --- Input methods. +-- +-- Methods and attributes available on Input objects. See `input` to create such an object. -- @type Input input_mt = { --- Input configuration table. + -- -- It can be used to recreate this input object later (by passing the table as an argument for the input constructor). -- This table does not contain any userdata and should be easily serializable (e.g. to save custom input binding config). -- This doesn't include input state, grab state, the event registry and the selected joystick since they may change often during runtime. + -- -- Can be changed anytime, but you may need to call `reload` to apply changes. + -- + -- See [expressions](#Input_expressions) for an explanation on how to write input expressions. -- @usage -- player.config = { - -- "key.a", "key.d - key.a", {"key.left + x", x=0.5}, -- list of input sources expressions - -- jump = {...}, -- children input - -- deadzone = 0.05, -- The deadzone for analog inputs (e.g. joystick axes): if the input absolute value is strictly below this, it will be considered as 0. - -- threshold = 0.05 -- The pressed threshold: an input is considered down if above or equal to this value. + -- -- list of input sources expressions: either a string, or a table to specify some arguments for the expression + -- "key.a", "key.d - key.a", {"key.left + x", x=0.5}, + -- -- children input: the table take the same fields as this + -- jump = {...}, + -- -- The deadzone for analog inputs (e.g. joystick axes): if the input absolute value is strictly below this, it will be considered as 0. 0.05 by default. + -- -- This is applied automatically after the evaluation of input expressions. + -- deadzone = 0.05, + -- -- The pressed threshold: an input is considered down if above or equal to this value. 0.05 by default. + -- -- This is considered when determining if the input is pressed, odwn and released. + -- threshold = 0.05, + -- -- Dimension of the input (i.e. the number of values returned by this input). 1 by default. + -- dimension = 1 -- } config = {}, - --- List and map of children inputs. - -- {[child1.name]=child1, [child2.name]=child2, child1, child2...} + --- List and map of children `Input`s. + -- + -- Takes the form `{[child1.name]=child1, [child2.name]=child2, child1, child2...}`. + -- Each child input is present both an element of this list and as the value associated with its name in the table. + -- + -- Note that children are *also* set directly on the input object for easier access. + -- @usage + -- local player = input{ fire = "button.a" } + -- local fire = player.fire + -- -- Is the same as: + -- local fire = player.children.fire + -- @ro children = {}, --- Name of the input. -- Defined on children inputs only. + -- @ftype string + -- @ro name = nil, - --- False if the input is currently not grabbed, a subinput otherwise. + --- `false` if the input is currently not grabbed, a sub`Input` otherwise. -- This may be different between each subinput. + -- @ro grabbed = false, - --- False if the input is not a subinput, the input it grabbed otherwise. + --- `false` if the input is not a subinput, the `Input` it was grabbed from otherwise. -- This may be different between each subinput. + -- @ro grabbing = false, --- Input event registry. -- The following events are available: -- - -- * `"moved"`: called when the input value change, with arguments (new value, delta since last event) - -- * `"pressed"`: called when the input is pressed - -- * `"released"`: called when the input is released + -- * `"moved"`: called when the input value change, with arguments `(new value, delta since last event)`. For inputs with dimension > 1, arguments are `(new value[1], new value[2], ..., delta[1], delta[2], ...)`. -- - -- For pointer inputs (have a "horizontal" and "vertical" children inputs) is also avaible: + -- * `"pressed"`: called when the input is pressed, with arguments `(1, new value, delta since last event)`. For inputs with dimension > 1, arguments are `(dimensions that was pressed, new value[1], new value[2], ..., delta[1], delta[2], ...)`. -- - -- * `"pointer moved"`: called when the pointer position change, with arguments (new pointer x, new pointer y, delta x since last event, delta y since last event) + -- * `"released"`: called when the input is released, with arguments `(1, new value, delta since last event)`. For inputs with dimension > 1, arguments are `(dimensions that was pressed, new value[1], new value[2], ..., delta[1], delta[2], ...)`. -- -- Each subinput has a different event registry. event = nil, -- Input state, independendant between each grab. Reset by :neutralize(). _state = "none", -- none, pressed or released - _value = 0, -- input value - _prevValue = 0, -- value last frame + _value = { 0 }, -- input value + _prevValue = { 0 }, -- value last frame -- Input state, shared between grabs. _event = nil, -- Event group for all event binded by this input. - _sourceCache = {}, -- Map of the values currently taken by every source this input use. + _sourceCache = {}, -- Map of the values currently taken by every source this input use. Sources are expected to return a single number value. _afterFilterEvent = nil, -- Event registry that resend the source events after applying the eventual filter function. _boundSourceEvents = {}, -- Map of sources events that are binded (and thus will send events to _afterFilterEvent). _joystick = nil, -- Currently selected joystick for this player. Also shared with children inputs. + _dimension = 1, -- Dimension of the input. --- Update the input and its children. -- Should be called every frame, typically _after_ you've done all your input handling -- (otherwise `pressed` and `released` may never return true and `delta` might be wrong). + -- -- (Note: this should not be called on subinputs) update = function(self) self:_update() - self._prevValue = self._value + for i=1, self._dimension do + self._prevValue[i] = self._value[i] + end for _, i in ipairs(self.children) do i:update() end @@ -179,9 +374,22 @@ input_mt = { return make_input(self.config) end, - --- Relond the input `config`, and do the same for its children. + --- Reload the input `config`, and do the same for its children. -- This will reenable the input if it was disabled using `disable`. reload = function(self) + -- resize dimensions + self._dimension = self.config.dimension or 1 + if #self._value > self._dimension then + for i=self._dimension+1, #self._value do + self._value[i] = nil + self._prevValue[i] = nil + end + elseif #self._value < self._dimension then + for i=#self._value+1, self._dimension do + self._value[i] = 0 + self._prevValue[i] = 0 + end + end -- clear all events we bounded previously self._event:clear() self._boundSourceEvents = {} @@ -191,6 +399,8 @@ input_mt = { if not self.config[c.name] then c:disable() table.remove(self.children, i) + self.children[c.name] = nil + self[c.name] = nil end end -- reload children @@ -228,7 +438,7 @@ input_mt = { return self._sourceCache[key] or expressionEnv[key] end }) - -- extract sources + -- extract sources and set initial values in _sourceCache local sources = {} local srcmt srcmt = { -- metamethods of sources values during the scanning process @@ -237,11 +447,29 @@ input_mt = { __mod = zero, __pow = zero, __unm = zero, __idiv = zero, __index = function(t, key) - local i = rawget(t, 1) - if i then sources[i][1] = sources[i][1] .. "." .. key - else table.insert(sources, { key }) + local i = rawget(t, "_") + if i then -- has a parent source key + local source = sources[i] + local parentKey = source[#source] + -- turn parent source into a table in the cache + if source.parentCache[parentKey] == 0 then + source.parentCache[parentKey] = {} + end + source.parentCache = source.parentCache[parentKey] + -- set value in the cache for this source + if not source.parentCache[key] then + source.parentCache[key] = 0 + end + -- add key to source name for this current object + table.insert(source, key) + else -- new root source key + table.insert(sources, { key, parentCache = self._sourceCache }) + -- set value in the cache for this source + if not self._sourceCache[key] then + self._sourceCache[key] = 0 + end end - return setmetatable({ i or #sources }, srcmt) + return setmetatable({ _ = i or #sources }, srcmt) end } local scanEnv = setmetatable({ value = 0 }, { __index = srcmt.__index }) -- value is not a source @@ -250,19 +478,33 @@ input_mt = { for _, mod in ipairs(sourceModifiers) do -- add modifiers functions scanEnv[mod] = function(source) assert(getmetatable(source) == srcmt, ("trying to apply %s modifier on a non-source value"):format(mod)) - sources[rawget(source, 1)][mod] = true + sources[rawget(source, "_")][mod] = true return source end end loadexp(exp, scanEnv)() -- scan! + -- build source names + for _, s in ipairs(sources) do + s.lastKey = s[#s] -- keep last key to allow setting the value in cache using parentCache[lastKey] = value + for i, p in ipairs(s) do + if type(p) == "string" then + if i > 1 then + s[i] = "." .. p + end + else + s[i] = ("[%s]"):format(tostring(p)) + end + end + s.name = table.concat(s) + end -- set every source to passive if there is a dt source local hasDt = false for _, s in ipairs(sources) do - if s[1] == "dt" then hasDt = true break end + if s.name == "dt" then hasDt = true break end end if hasDt then for _, s in ipairs(sources) do - if s[1] ~= "dt" and not s.active then + if s.name ~= "dt" and not s.active then s.passive = true end end @@ -271,25 +513,36 @@ input_mt = { local fn = loadexp(exp, env) -- init sources and bind to source events local boundAfterFilterEvent = {} - local function onAfterFilterEvent(new) self:_update(fn()) end + local function onAfterFilterEvent(new) self:_update{fn()} end for _, s in ipairs(sources) do - local sname = s[1] - ensurePath(self._sourceCache, sname, 0) + local sname = s.name if not self._boundSourceEvents[sname] then if sname:match("^child%.") then - local cname = sname:match("^child%.(.*)$") - assert(self.children[cname], ("input expression refer to %s but this input has no child named %s"):format(sname, cname)) - self._event:bind(self.children[cname].event, "moved", function(new) -- child event -> self._afterFilterEvent link - setPath(self._sourceCache, sname, new) - self._afterFilterEvent:emit(sname, new) - end) + local cname, index = sname:match("^child%.(.*)%[(%d+)%]$") + if not cname then cname = sname:match("^child%.(.*)$") end + local child = self.children[cname] + assert(child, ("input expression refer to %s but this input has no child named %s"):format(sname, cname)) + if child._dimension > 1 then + assert(index, ("input expression refer to %s but this child only has more than one dimension"):format(sname)) + self._event:bind(self.children[cname].event, "moved", function(...) -- child event -> self._afterFilterEvent link + local new = select(tonumber(index), ...) + s.parentCache[s.lastKey] = new + self._afterFilterEvent:emit(sname, new) + end) + else + assert(not index, ("input expression refer to %s but this child only has a single dimension"):format(sname)) + self._event:bind(self.children[cname].event, "moved", function(new) -- child event -> self._afterFilterEvent link + s.parentCache[s.lastKey] = new + self._afterFilterEvent:emit(sname, new) + end) + end else self._event:bind(event, sname, function(new, filter, ...) -- event source -> self._afterFilterEvent link if filter then new = filter(self, new, ...) if not new then return end -- filtered out end - setPath(self._sourceCache, sname, new) + s.parentCache[s.lastKey] = new self._afterFilterEvent:emit(sname, new) end) end @@ -301,11 +554,6 @@ input_mt = { end end end - -- rebind pointer events - if self.config.horizontal and self.config.horizontal then - self._event:bind(self.horizontal.event, "moved", function(new, delta) self.event:emit("pointer moved", new, self.vertical:value(), delta, 0) end) - self._event:bind(self.vertical.event, "moved", function(new, delta) self.event:emit("pointer moved", self.horizontal:value(), new, 0, delta) end) - end end, --- Disable the input and its children, preventing further updates and events. -- The input can be reenabled using `reload`. @@ -316,7 +564,7 @@ input_mt = { self._event:clear() end, - --- Will call fn(source) on the next activated source (including sources not currently used by this input). + --- Will call `fn(source)` on the next activated source (including sources not currently used by this input). -- Typically used to detect an input in your game input binding settings. -- @param fn function that will be called on the next activated source matching the filter -- @param[opt] filter list of string patterns that sources must start with (example `{"button", "key"}` to only get buttons and key sources) @@ -349,7 +597,7 @@ input_mt = { -- A grabbed input will no longer update and instead pass all new update to the subinput. -- This is typically used for contextual action or pause menus: by grabbing the player input, all the direct use of -- this input in the game will stop (can't move caracter, ...) and instead you can use the subinput to handle input in the pause menu. - -- To stop grabbing an input, you will need to `:release` the subinput. + -- To stop grabbing an input, you will need to `release` the subinput. -- -- This will also reset the input to a neutral state. The subinput will share everything with this input, except -- `grabbed`, `grabbing`, `event` (a new event registry is created), and of course its current state. @@ -358,11 +606,19 @@ input_mt = { grabbed = false, grabbing = self, event = signal.new(), - children = {} + children = {}, + _value = {}, + _prevValue = {}, } + for i=1, self._dimension do + g._value[i] = self._value[i] + g._prevValue[i] = self._prevValue[i] + end for _, c in ipairs(self.children) do - g[c.name] = c:grab() - table.insert(g.children, g[c.name]) + local gc = c:grab() + table.insert(g.children, gc) + g.children[c.name] = gc + g[c.name] = gc end self:neutralize() self.grabbed = setmetatable(g, { __index = self }) @@ -380,16 +636,20 @@ input_mt = { self.grabbing = false end, - --- Set the state of this input to a neutral position (i.e. value = 0). + --- Set the state of this input to a neutral position (i.e. value = 0 for every dimension). neutralize = function(self) - self:_update(0) + local zeros = { 0 } + for i=2, self._dimension do zeros[i] = 0 end + self:_update(zeros) self._state = "none" - self._value = 0 - self._prevValue = 0 + for i=1, self._dimension do + self._value[i] = 0 + self._prevValue[i] = 0 + end end, --- Set the joystick associated with this input. - -- The input will ignore every other joystick. + -- This input will then ignore every other joystick. -- Set joystick to `nil` to disable and get input from every connected joystick. -- @param joystick LÖVE jostick object to associate setJoystick = function(self, joystick) @@ -399,51 +659,44 @@ input_mt = { end end, --- Returns the currently selected joystick. + -- @treturn joystick LÖVE jostick object getJoystick = function(self) return self._joystick end, - --- Returns true if the input is currently down. + --- Returns `true` if the input is currently down, `false` otherwise. + -- @treturn boolean if input is down on at least one dimensions down = function(self) return self._state == "down" or self._state == "pressed" end, - --- Returns true if the input has just been pressed. + --- Returns `true` if the input has just been pressed, `false` otherwise. + -- @treturn boolean if input has just been pressed on at least one dimensions pressed = function(self) return self._state == "pressed" end, - --- Returns true if the input has just been released. + --- Returns `true` if the input has just been released, `false` otherwise. + -- @treturn boolean if input has just been released on at least one dimensions released = function(self) return self._state == "released" end, --- Returns the current value of the input. + -- If dimension > 1, this will return several values, one per dimension. + -- @treturn number,... current value of the input for every dimension value = function(self) - return self._value + return unpack(self._value) end, --- Returns the delta value of the input since the last call to `update`. + -- If dimension > 1, this will return several values, one per dimension. + -- @treturn number,... delta of the input for every dimension delta = function(self) - return self._value - self._prevValue - end, - --- If there is a horizontal and vertical children inputs, this returns the horizontal value and the vertical value. - -- Typically used for movement/axes pairs (e.g. to get x,y of a stick or directional pad). - pointer = function(self) - return self.horizontal:value(), self.vertical:value() - end, - --- Same as `pointer`, but normalize the returned vector, i.e. "clamp" the returned x,y coordinates into a circle of radius 1. - -- Typically used to avoid faster movement on diagonals - -- (as if both horizontal and vertical values are 1, the pointer vector has √2 magnitude, higher than the 1 magnitude of a purely vertical or horizontal movement). - clamped = function(self) - local x, y = self:pointer() - local mag = x*x + y*y - if mag > 1 then - local d = sqrt(mag) - return x/d, y/d - else - return x, y + for i=1, self._dimension do + tmp[i] = self._value[i] - self._prevValue[i] end + return unpack(tmp, 1, self._dimension) end, -- Update the state of the input: called at least on every input value change and on :update(). - -- new: new value of the input if it has changed (number, can be anything, but typically in [0-1]) (optional) + -- new: new value of the input if it has changed (list of numbers of size config.dimension, can be anything, but typically in [0-1]) (optional) _update = function(self, new) if self.grabbed then self.grabbed:_update(new) -- pass onto grabber @@ -453,24 +706,33 @@ input_mt = { new = new or self._value local old = self._value self._value = new - -- update state and emit events - local delta = new - old - if delta ~= 0 then - self.event:emit("moved", new, delta) + -- compute delta (in tmp) + for i=1, self._dimension do + tmp[self._dimension + i] = new[i] - old[i] + tmp[i] = new[i] end - if abs(new) >= threshold then - if abs(old) < threshold then - self._state = "pressed" - self.event:emit("pressed") - else - self._state = "down" + -- update state and emit events + for i=self._dimension, self._dimension * 2 do + if tmp[i] ~= 0 then + self.event:emit("moved", unpack(tmp, 1, self._dimension * 2)) + break end - else - if abs(old) >= threshold then - self._state = "released" - self.event:emit("released") + end + for i=1, self._dimension do + if abs(new[i]) >= threshold then + if abs(old[i]) < threshold then + self._state = "pressed" + self.event:emit("pressed", i, unpack(tmp, 1, self._dimension * 2)) + else + self._state = "down" + end else - self._state = "none" + if abs(old[i]) >= threshold then + self._state = "released" + self.event:emit("released", i, unpack(tmp, 1, self._dimension * 2)) + else + self._state = "none" + end end end end @@ -486,4 +748,81 @@ input_mt = { } input_mt.__index = input_mt +--- Input sources. +-- Input sources are the initial source of input data. They are identified by a Lua identifier name. +-- See [expressions](#Input_expressions) on how to use them in expressions. +-- +-- Input sources are provided for common input methods (keyboard, mouse, gamepad) by default; see below for a list of built-in input sources. +-- +-- Additionally, you can define your own input sources, by emitting events in the SignalRegistry returned by `uqt.input.event`. +-- See the file `input/event.lua` to see a description of the events you will need to emit. The file also contains the definition +-- of the built-in input sources that you may use as an example. +-- +-- @section Sources + +--- Keyboard input: 1 if the key X is down, 0 otherwise. +-- X can be any of LÖVE's [KeyConstant](https://love2d.org/wiki/KeyConstant). +-- @field key.X + +--- Keyboard input: 1 if the key with scancode X is down, 0 otherwise. +-- X can be any of LÖVE's [Scancode](https://love2d.org/wiki/Scancode). +-- @field scancode.X + +--- Text input: 1 if the text X was entered, 0 otherwise. +-- X can be any text. +-- @field text.X + +--- Mouse input: 1 if the mouse button is down, 0 otherwise. +-- N is either 1 for the primary mouse button, 2 for secondary or 3 for middle button. +-- @field mouse.N + +--- Mouse input: X position of the mouse cursor. +-- @field mouse.x + +--- Mouse input: Y position of the mouse cursor. +-- @field mouse.y + +--- Mouse input: latest X movement of the mouse cursor. +-- +-- `mouse.dx.p` and `mouse.dx.n` will respectively only report movement in the positive or negative direction and return absolute value. +-- @field mouse.dx + +--- Mouse input: latest Y movement of the mouse cursor. +-- +-- `mouse.dy.p` and `mouse.dy.n` will respectively only report movement in the positive or negative direction and return absolute value. +-- @field mouse.dy + +--- Mouse input: latest X movement of the mouse wheel. +-- +-- `wheel.dx.p` and `wheel.dx.n` will respectively only report movement in the positive or negative direction and return absolute value. +-- @field wheel.dx + +--- Mouse input: latest Y movement of the mouse wheel. +-- +-- `wheel.dy.p` and `wheel.dy.n` will respectively only report movement in the positive or negative direction and return absolute value. +-- @field wheel.dy + +--- Gamepad input: 1 if the button X is down, 0 otherwise. +-- X can be any of LÖVE's [GamepadButton](https://love2d.org/wiki/GamepadButton). +-- @field button.X + +--- Gamepad input: current value of the gamepad axis (between -1 and 1). +-- X can be any of LÖVE's [GamepadAxis](https://love2d.org/wiki/GamepadAxis). +-- +-- `axis.X.p` and `axis.X.n` will respectively only report movement in the positive or negative direction and return absolute value. +-- @field axis.X + +--- On new frame: current delta time value since last frame. Updated on each call to `love.update`. +-- Note that if this input source is present in an expression, the other input sources in the same expression will be set as passive by default. +-- @field dt + +--- Children inputs: current value of a child input of the current input. +-- For child input of dimension 1. +-- Replace X with the name of the child input. +-- +-- If the child input is of dimension at least 2, instead use `child.X[N]`, which gives the +-- current value of the Nth dimension of a child input of the current input. +-- Replace X with the name of the child input, and N with the index of the dimension you want. +-- @field child.X + return make_input diff --git a/ldtk/ldtk.can b/ldtk/ldtk.can index 047579d..3c3ac06 100644 --- a/ldtk/ldtk.can +++ b/ldtk/ldtk.can @@ -43,7 +43,7 @@ -- TODO: handle nineSliceBorders when drawing entities -- TODO: Once stable in LDtk: handle parallax when drawing layers, multiple worlds per file ---- LÖVE wrappers/placeholder +-- LÖVE wrappers/placeholder let lg = (love or {}).graphics let newQuad if lg then @@ -56,7 +56,7 @@ end let cache ---- json helpers +-- json helpers let json_decode do let r, json = pcall(require, "json") @@ -70,7 +70,7 @@ let readJson = (file) return t end ---- color helpers +-- color helpers let parseColor = (str) local r, g, b = str:match("^#(..)(..)(..)") r, g, b = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16)