CTRL
CTRL - Control & Trigger Relays Library - LÖVE INPUT
Simple for basic usage and at the same time robust and advanced for extended use, this library provides all necessary facilities to handle user input, and with enough skill, can be extended to handle even the input devices not native supported by the framework.
Contents
Basic Use
To get started, an instance of CTRL class needs to be created (so if for whatever reason you need multiple different CTRL instances - you can do that), and event handler callback functions need to be defined. They largely work the same way as normal LÖVE input callbacks, except CTRL strips input events of all information that's not relevant, and refines the rest to the most usable state. Then, some input bindings need to be made. And finally, CTRL instance needs to be hooked up to LÖVE input events.
Example Code
local ctrl = require ( 'ctrl' ) ( )
function ctrl:inputpressed ( name, value ) print ( "pressed", name, value ) end
function ctrl:inputreleased ( name, value ) print ( "released", name, value ) end
function ctrl:inputmoved ( name, value ) print ( "moved", name, value ) end
ctrl:bind ( "fire", { "keyboard", "space" } )
ctrl:bind ( "fire", { "joystick", "default", "button", 1 } )
ctrl:bind ( "fire", { "mouse", "left" } )
ctrl:hookup ( )
List of Valid Inputs
"keyboard"
- all standard LÖVE keyboard constants
"mouse"
"x", "y", "wheelx", "wheely", "left", "right", "middle"
- 4, 5, 6, etc.
"gamepad"
"default"
or any existing mapping for a Joystick"axis"
"leftx", "rightx", "lefty", "righty", "triggerleft", "triggerright"
"button"
"a", "b", "x", "y", "back", "guide", "start", "leftstick", "rightstick", "leftshoulder", "rightshoulder"
"hat"
"up", "down", "left", "right"
"joystick"
"default"
or any existing device mapping for a Joystick"axis"
- 1, 2, 3, etc.
"button"
- 1, 2, 3, etc.
"hat"
- 1, 2, 3, etc.
"up", "down", "left", "right"
- 1, 2, 3, etc.
By default all joysticks and gamepads map to "default"
, this is what you want most of the time.
Advanced
CTRL includes substantial assortment of features (hence its hefty size) on top of its basic functionality.
Automatic Input Grab
Most useful for input settings screens. Using grab
function CTRL can be set to automatically bind whichever input user had pressed to a select action, and it can be set to filter out only desired inputs out of all incoming signals. All three arguments are optional, but either callback or autobind name should be present (otherwise it will not do anything useful).
local bindname = "foo"
callback = function ( input )
--cancel input if user have pressed "escape", otherwise bind
if input[ 1 ] == "keyboard" and input[ 2 ] == "escape" then ctrl:grab ( )
else ctrl:bind ( bindname, input ) end
end
ctrl:grab ( callback )
--set CTRL to autobind any next input to "foo" action, and only allow input from keyboard and gamepads buttons
ctrl:grab ( "foo", { { "keyboard" }, { "gamepad", nil, "button" } } )
It would make sense to save acquired input bindings. CTRL has facilities for that, the saveData
and loadData
functions. You can pass a filename in it and it will load/save data to it. If you pass nothing to saveData
, it returns saved data string. You can pass such string into loadData
instead of filename.
Custom Bindings
One of the most important bits of functionality is binding customization. This is achieved by passing third argument into bind
function, the bind options table. It can contain mapper
table that contains information about raw value re-mapping function attached to that binding, filter
table that contains similar information about real-time final value mapping from raw values, and events
table that has a list of events that this binding shall generate.
Mapper table contains two named values:
func
: function that will do the mappingargs
(optional): table of additional arguments that will be passed into that function
The mapper function should be either string name of a registered mapper function (see below) or a function value. When it's called, it's passed raw, last mapped, args, ...
arguments. First argument is a raw value from the input device. Second is the value that it had output the last time it was called. Third is the args
optional table that can contain anything or not exist at all. Finally, a vararg
is passed. It contains all, if any, additional raw event parameters passed into general event handler function. For standard LÖVE events it's empty except for mouse events, then it's dx dy
and isTouch
from the mouse callbacks. You can define custom raw input events that pass variety of raw input information through this.
local fooMapper = function ( raw, last, args, ... )
return raw * args.foo
end
ctrl:bind ( "foo", { "keyboard", "f" }, { mapper = { func = fooMapper, args = { foo = 1 } } } )
Filter table contains two named values:
func
: function that will do the mappingargs
(optional): table of additional arguments that will be passed into that function
As with mapper, filter function should be either string name of a registered filter function (see below) or a function value. When it's called, it's passed dt, mapped or raw, last filtered, args
arguments. First argument is dt
from the update function. Second is the current mapped (or raw, if no mapper set) value. Third is the value that it had output the last time it was called. Fourth is the args
optional table that can contain anything or not exist at all.
local fooFilter = function ( dt, raw, last, args )
return last + raw * dt
end
ctrl:bind ( "foo", { "keyboard", "f" }, { filter = { func = fooFilter } } )
Events table contains any list of event tables. Each of those tables contains three named values:
trigger
: function that will decide whether or not an input event shall be dispatchedhandler
: callback function that will be called to handle the input eventargs
(optional): table of additional arguments that will be passed into trigger function
The trigger function should be either string name of a registered trigger function (see below) or a function value. When it's called, it's passed current, previous, args
arguments. First argument is the current value. Second is the previous value. Third is the args
optional table that can contain anything or not exist at all.
Handler function should be either string name of existing event handler function (simply defined as normal) or a function value. When it's called, it's passed ctrl, name, value
arguments. First argument is the ctrl instance that triggered this event. Second is the bound action name that triggered this event. Third is the current value.
local fooTrigger = function ( curr, last, args )
return curr > last
end
local inputFooBar = function ( ctrl, name, value )
print ( "foobar", name, value )
end
ctrl:bind ( "foo", { "keyboard", "f" }, { events = { foobar = { trigger = fooTrigger, handler = inputFooBar } } } )
Mapper, filter and trigger functions can be "registered" and be referenced by their string name in the binding code. While not immediately beneficial, it allows the data saver to store your bindings exactly, without losing any bits of data: your attached function code cannot be saved to a file, but a string name can be. The event handler callbacks do not need to be explicitly registered, you simply define them on a CTRL instance the same way you define LÖVE input callbacks. The reason to do it that way instead of passing function itself into the binding options is the same as with mappers, filters and triggers.
ctrl:addMapperFunction ( "fooMapper", fooMapper )
ctrl:addFilterFunction ( "fooFilter", fooFilter )
ctrl:addTriggerFunction ( "fooTrigger", fooTrigger )
function ctrl:inputFooBar ( name, value )
print ( "foobar", name, value )
end
CTRL already includes by default a number of mappers, filters and triggers:
deadzone
: clamps lower region of analog input to 0 (args = { deadzone = 0.1 }
)remap
: maps entire analog input region to a different output region (args = { rawmin = 0, rawmax = 1, mapmin = 0, mapmax = 1, min = 0, max = 1 }
)
smooth
: smoothly moves output value towards raw value (args = { speed = 1 }
)ramp
: moves output value up or down depending on raw value (args = { speed = 1, min = 0, max = 1 }
)
pressed
: triggered when value drops under 0.25released
: triggered when value raises over 0.75moved
: triggered when value changes by any amount
Gamepad Mapping
It works the same way as LÖVE's gamepadMapping functionality, except it accepts CTRL-fashion input table, so you can set gamepad mapping using grab
function.
local joystick = love.joystick.getJoysticks[ 1 ]
local inputtype = "start"
local callback = function ( input )
ctrl:setGamepadMapping ( joystick, inputtype, input )
end
--start grab, filter in only joystick buttons
ctrl:grab ( callback, { "joystick", nil, "button" } )
Custom Input Method
This allows to use custom input devices as well as purely virtual devices such as virtual keyboard and virtual joystick commonly found on mobile. There are two ways of doing it. First is piping custom input device events into standard events. Then custom device will be indistinguishable from normal device which it shadows. Second is defining new input type and manually calling general input handler function.
ctrl:addInputs ( { "virtualkeyboard", "A" }, { "w", "s", "a", "d" } )
ctrl:addInputs ( { "virtualkeyboard", "B" }, { "u", "j", "h", "k" } )
ctrl:bind ( "foo", { "virtualkeyboard", "A", "w" } )
ctrl:bind ( "bar", { "virtualkeyboard", "B", "u" } )
. . .
function virtualkeyboard:pressed ( section, key )
ctrl:handleUpdate ( { "virtualkeyboard", section, key }, 1 )
end
function virtualkeyboard:released ( section, key )
ctrl:handleUpdate ( { "virtualkeyboard", section, key }, 0 )
end
Device Mapping
If input type cannot be uniquely and persistently identified (like keyboards and mice), then its ID needs to be produced in run-time. There are many ways to do it and yet none are silver bullet be all end all type. This is why CTRL opts out of automatically trying to identify devices, instead defaulting to mapping them all to "default"
, so you will need to implement your own ID generator function. Simply using defaults is what you need most of the time, but some scenarios, such as local multiplayer, demand differentiation between similar or even identical devices. One common strategy is to simply assign each gamepad their index number, and refer to them by that number. Another strategy involves acquiring GUID or literal name of the gamepad and, if it's not unique, adding a differentiating digit to it. Once you're done producing ID, you can assign joystick to this ID using mapDevice
function:
local id = produceId ( joystick )
ctrl:mapDevice ( joystick, id )
Reference Manual
ctrl ( ), ctrl.new ( ) creates and returns new input handler instance ctrl:hookup ( ) hooks up input handlers to leech off of default löve input callbacks ctrl:bind ( name, input, [options] ) binds specified input to an input name * name - input name to bind to * input - ctrl input address to bind * options (optional) - options table, can contain following fields: * mapper (optional) - table describing a raw value mapper * func - callback mapper function, returns mapped raw value * args (optional) - callback function arguments table * filter (optional) - table describing a value filter * func - callback filter function, returns filtered value * args (optional) - callback function arguments table * events (optional) - table containing list of events description tables. Each table contains: * trigger - callback trigger function, returns true if event should be triggered * handler - callback handler function, called if event is triggered * args (optional) - callback trigger arguments table ctrl:unbind ( [name], [input] ) unbinds specified inputs * name (optional) - input name to unbind * input (optional) - ctrl input address to unbind if called without arguments, unbinds everything if called without input address, unbinds everything bound to a specific name if called without name, unbinds everything bound to a specific address input address can be partial address, then it acts like a filter, unbinds everything that matches ctrl:grab ( [callback], [autobind], [filters] ) starts tracking input devices for any activity, grabs the first one that changes * callback (optional) - function that will be called when activity is detected shall return true if it should ignore current input and track the next one called with the following arguments: * input - ctrl input address table * name - the autobind argument unmodified * autobind (optional) - string input name to which it would automatically bind anything it finds * filters (optional) - list of ctrl input addresses that it will match against during tracking ctrl:getBindings ( [name], [input] ) finds and returns list of all input addresses bound to a specified input name * name (optional) - name to look bindings for * input (optional) - ctrl input path to look in if name is not passed, finds all bindings input can be partial path, then it acts as a filter list entries have the following format: * name - string name of the input * input - ctrl input address table * options - table of input options (mapper, filter, events) ctrl:isUp ( name ) ctrl:wasUp ( name ) returns true if value of an input is under 0.25 * name - input name to look for ctrl:isDown ( name ) ctrl:wasDown ( name ) returns true if value of an input is above 0.75 * name - input name to look for ctrl:getValue ( name ) ctrl:getLastValue ( name ) returns value of an input * name - input name to look for ctrl:resetValues ( ) resets all values to 0 focusing out and in a window combined with some ways of input handling can cause input glitches, resetting all values solves that problem ctrl:mapDevice ( device, value ) maps userdata or table device to a string, number or boolean value * device - the input device userdata, table or any other value by which it's referenced * value - string, number or boolean value by which its input would be addressed should be used with joysticks and gamepads and other devices without persistent ID ctrl:getMapping ( device ) returns previously mapped value for this device, defaults to "default" * device - the input device to look for ctrl:setGamepadMapping ( joy, control, input ) mirros love.joystick.setGamepadMapping, but provides seamless integraiton with the library * joy - löve Joystick object to map * control - löve Gamepad control (button, hat, axis) * input - ctrl joystick input path table ctrl:getGamepadMapping ( joy, control ) mirros love.joystick.getGamepadMapping, but provides seamless integraiton with the library * joy - löve Joystick object to get mapping from * control - löve Gamepad control (button, hat, axis) returns ctrl joystick input address table for selected Gamepad control ctrl:addInputs ( input, new ) adds new values to the valid inputs list * input - ctrl input path to add to * new - list of additional valid inputs, can contain a metatable ctrl:handleUpdate ( input, raw, ... ) processes input event and dispatches callbacks * input - ctrl input address table * raw - raw value * vararg - any additional values, will be passed into raw mapper function ctrl:addTriggerFunction ( name, func ) adds new trigger function to the list * name - string name of new event trigger function * func - callback function, shall return true if event should be triggered called with the following arguments: * current value * previous value * function arguments table ctrl:addFilterFunction ( name, func ) adds new filter function to the list * name - string name of new value filter function * func - callback function, shall return filtered value called with the following arguments: * time elapsed since last call * raw input value * previous filtered value * function arguments table ctrl:addMapperFunction ( name, func ) adds new raw value mapper function to the list * name - string name of new raw value mapper function * func - callback function, shall return mapped raw value called with the following arguments: * raw value * previous mapped value * function arguments table * vararg passed into input event handler function ctrl:saveData ( [filename] ) saves all internal data to a file, returns data string if file is not provided * filename (optional) - filename to store data in ctrl:loadData ( filename ) loads data from a file or a string * filename - filename to load data from, or data string ctrl:handleUpdate ( dt ) ctrl:handleKeypressed ( key, code, repeated ) ctrl:handleKeyreleased ( key, code, repeated ) ctrl:handleMousepressed ( x, y, button, touch ) ctrl:handleMousereleased ( x, y, button, touch ) ctrl:handleMousemoved ( x, y, dx, dy, touch ) ctrl:handleWheelmoved ( x, y ) ctrl:handleGamepadaxis ( joy, axis, value ) ctrl:handleGamepadpressed ( joy, button ) ctrl:handleGamepadreleased ( joy, button ) ctrl:handleJoystickaxis ( joy, axis, value ) ctrl:handleJoystickhat ( joy, hat, dir ) ctrl:handleJoystickpressed ( joy, button ) ctrl:handleJoystickreleased ( joy, button ) ctrl:handleJoystickadded ( joy ) ctrl:handleJoystickremoved ( joy ) LÖVE input event handlers. To be called manually as appropriate if CTRL instance is not "hooked" up. LÖVE input event handlers. To be called manually as appropriate if CTRL instance is not "hooked" up.
Links
- forum thread: https://love2d.org/forums/viewtopic.php?f=5&t=84093
- bitbucket repo: https://bitbucket.org/rcoaxil/ctrl
- demo code/app: https://bitbucket.org/rcoaxil/ctrl/downloads/example.love
Other Languages
Dansk –
Deutsch –
English –
Español –
Français –
Indonesia –
Italiano –
Lietuviškai –
Magyar –
Nederlands –
Polski –
Português –
Română –
Slovenský –
Suomi –
Svenska –
Türkçe –
Česky –
Ελληνικά –
Български –
Русский –
Српски –
Українська –
עברית –
ไทย –
日本語 –
正體中文 –
简体中文 –
Tiếng Việt –
한국어
More info