document the code

baton's code, while not incredibly complicated, isn't exactly self-explanatory, and i've already made different mistakes multiple times throughout baton's development process, so i think the code deserves some detailed comments.
This commit is contained in:
Andrew Minnich
2019-02-20 02:05:48 -05:00
parent 72a2bd3014
commit 6753632615

View File

@@ -27,41 +27,75 @@ local baton = {
]] ]]
} }
-- string parsing functions --
-- splits a source definition into type and value
-- example: 'button:a' -> 'button', 'a'
local function parseSource(source) local function parseSource(source)
return source:match '(.+):(.+)' return source:match '(.+):(.+)'
end end
-- splits an axis value into axis and direction
-- example: 'leftx-' -> 'leftx', '-'
local function parseAxis(value) local function parseAxis(value)
return value:match '(.+)([%+%-])' return value:match '(.+)([%+%-])'
end end
-- splits a joystick hat value into hat number and direction
-- example: '2rd' -> '2', 'rd'
local function parseHat(value) local function parseHat(value)
return value:match '(%d)(.+)' return value:match '(%d)(.+)'
end end
--[[
-- source functions --
each source function checks the state of one type of input
and returns a value from 0 to 1. for binary controls, such
as keyboard keys and gamepad buttons, they return 1 if the
input is held down and 0 if not. for analog controls, such
as "leftx+" (the left analog stick held to the right), they
return a number from 0 to 1.
source functions are split into keyboard/mouse functions
and joystick/gamepad functions. baton treats these two
categories slightly differently.
]]
local sourceFunction = {keyboardMouse = {}, joystick = {}} local sourceFunction = {keyboardMouse = {}, joystick = {}}
-- checks whether a keyboard key is down or not
function sourceFunction.keyboardMouse.key(key) function sourceFunction.keyboardMouse.key(key)
return love.keyboard.isDown(key) and 1 or 0 return love.keyboard.isDown(key) and 1 or 0
end end
-- checks whether a keyboard key is down or not,
-- but it takes a scancode as an input
function sourceFunction.keyboardMouse.sc(sc) function sourceFunction.keyboardMouse.sc(sc)
return love.keyboard.isScancodeDown(sc) and 1 or 0 return love.keyboard.isScancodeDown(sc) and 1 or 0
end end
-- checks whether a mouse buttons is down or not.
-- note that baton doesn't detect mouse movement, just the buttons
function sourceFunction.keyboardMouse.mouse(button) function sourceFunction.keyboardMouse.mouse(button)
return love.mouse.isDown(tonumber(button)) and 1 or 0 return love.mouse.isDown(tonumber(button)) and 1 or 0
end end
-- checks the position of a joystick axis
function sourceFunction.joystick.axis(joystick, value) function sourceFunction.joystick.axis(joystick, value)
local axis, direction = parseAxis(value) local axis, direction = parseAxis(value)
-- "a and b or c" is ok here because b will never be boolean
value = tonumber(axis) and joystick:getAxis(tonumber(axis)) value = tonumber(axis) and joystick:getAxis(tonumber(axis))
or joystick:getGamepadAxis(axis) or joystick:getGamepadAxis(axis)
if direction == '-' then value = -value end if direction == '-' then value = -value end
return value > 0 and value or 0 return value > 0 and value or 0
end end
-- checks whether a joystick button is held down or not
-- can take a number or a GamepadButton string
function sourceFunction.joystick.button(joystick, button) function sourceFunction.joystick.button(joystick, button)
-- i'm intentionally not using the "a and b or c" idiom here
-- because joystick.isDown returns a boolean
if tonumber(button) then if tonumber(button) then
return joystick:isDown(tonumber(button)) and 1 or 0 return joystick:isDown(tonumber(button)) and 1 or 0
else else
@@ -69,14 +103,29 @@ function sourceFunction.joystick.button(joystick, button)
end end
end end
-- checks the direction of a joystick hat
function sourceFunction.joystick.hat(joystick, value) function sourceFunction.joystick.hat(joystick, value)
local hat, direction = parseHat(value) local hat, direction = parseHat(value)
return joystick:getHat(hat) == direction and 1 or 0 return joystick:getHat(hat) == direction and 1 or 0
end end
--[[
-- player class --
the player object takes a configuration table and handles input
accordingly. it's called a "player" because it makes sense to use
multiple of these for each player in a multiplayer game, but
you can use separate player objects to organize inputs
however you want.
]]
local Player = {} local Player = {}
Player.__index = Player Player.__index = Player
-- internal functions --
-- sets the player's config to a user-defined config table
-- and sets some defaults if they're not already defined
function Player:_loadConfig(config) function Player:_loadConfig(config)
if not config then if not config then
error('No config table provided', 4) error('No config table provided', 4)
@@ -90,6 +139,7 @@ function Player:_loadConfig(config)
self.config = config self.config = config
end end
-- initializes a control object for each control defined in the config
function Player:_initControls() function Player:_initControls()
self._controls = {} self._controls = {}
for controlName, sources in pairs(self.config.controls) do for controlName, sources in pairs(self.config.controls) do
@@ -105,6 +155,7 @@ function Player:_initControls()
end end
end end
-- initializes an axis pair object for each axis pair defined in the config
function Player:_initPairs() function Player:_initPairs()
self._pairs = {} self._pairs = {}
for pairName, controls in pairs(self.config.pairs) do for pairName, controls in pairs(self.config.pairs) do
@@ -129,6 +180,16 @@ function Player:_init(config)
self._activeDevice = 'none' self._activeDevice = 'none'
end end
--[[
detects the active device (keyboard/mouse or joystick).
if the keyboard or mouse is currently being used, joystick
inputs will be ignored. this is to prevent slight axis movements
from adding errant inputs when someone's using the keyboard.
the active device is saved to player._activeDevice, which is then
used throughout the rest of the update loop to check only
keyboard or joystick inputs.
]]
function Player:_setActiveDevice() function Player:_setActiveDevice()
for _, control in pairs(self._controls) do for _, control in pairs(self._controls) do
for _, source in ipairs(control.sources) do for _, source in ipairs(control.sources) do
@@ -147,6 +208,10 @@ function Player:_setActiveDevice()
end end
end end
--[[
gets the value of a control by running the appropriate source functions
for all of its sources. does not apply deadzone.
]]
function Player:_getControlRawValue(control) function Player:_getControlRawValue(control)
local rawValue = 0 local rawValue = 0
for _, source in ipairs(control.sources) do for _, source in ipairs(control.sources) do
@@ -165,6 +230,10 @@ function Player:_getControlRawValue(control)
return rawValue return rawValue
end end
--[[
updates each control in a player. saves the value with and without deadzone
and the down/pressed/released state.
]]
function Player:_updateControls() function Player:_updateControls()
for _, control in pairs(self._controls) do for _, control in pairs(self._controls) do
control.rawValue = self:_getControlRawValue(control) control.rawValue = self:_getControlRawValue(control)
@@ -176,6 +245,10 @@ function Player:_updateControls()
end end
end end
--[[
updates each axis pair in a player. saves the value with and without deadzone
and the down/pressed/released state.
]]
function Player:_updatePairs() function Player:_updatePairs()
for _, pair in pairs(self._pairs) do for _, pair in pairs(self._pairs) do
-- get raw x and y -- get raw x and y
@@ -208,12 +281,16 @@ function Player:_updatePairs()
end end
end end
-- public API --
-- checks for changes in inputs
function Player:update() function Player:update()
self:_setActiveDevice() self:_setActiveDevice()
self:_updateControls() self:_updateControls()
self:_updatePairs() self:_updatePairs()
end end
-- gets the value of a control or axis pair without deadzone applied
function Player:getRaw(name) function Player:getRaw(name)
if self._pairs[name] then if self._pairs[name] then
return self._pairs[name].rawX, self._pairs[name].rawY return self._pairs[name].rawX, self._pairs[name].rawY
@@ -224,6 +301,7 @@ function Player:getRaw(name)
end end
end end
-- gets the value of a control or axis pair with deadzone applied
function Player:get(name) function Player:get(name)
if self._pairs[name] then if self._pairs[name] then
return self._pairs[name].x, self._pairs[name].y return self._pairs[name].x, self._pairs[name].y
@@ -234,6 +312,7 @@ function Player:get(name)
end end
end end
-- gets whether a control or axis pair is "held down"
function Player:down(name) function Player:down(name)
if self._pairs[name] then if self._pairs[name] then
return self._pairs[name].down return self._pairs[name].down
@@ -244,6 +323,7 @@ function Player:down(name)
end end
end end
-- gets whether a control or axis pair was pressed this frame
function Player:pressed(name) function Player:pressed(name)
if self._pairs[name] then if self._pairs[name] then
return self._pairs[name].pressed return self._pairs[name].pressed
@@ -254,6 +334,7 @@ function Player:pressed(name)
end end
end end
-- gets whether a control or axis pair was released this frame
function Player:released(name) function Player:released(name)
if self._pairs[name] then if self._pairs[name] then
return self._pairs[name].released return self._pairs[name].released
@@ -264,10 +345,20 @@ function Player:released(name)
end end
end end
--[[
gets the currently active device (either "kbm", "joy", or "none").
this is useful for displaying instructional text. you may have
a menu that says "press ENTER to confirm" or "press A to confirm"
depending on whether the player is using their keyboard or gamepad.
this function allows you to detect which they used most recently.
]]
function Player:getActiveDevice() function Player:getActiveDevice()
return self._activeDevice return self._activeDevice
end end
-- main functions --
-- creates a new player with the user-provided config table
function baton.new(config) function baton.new(config)
local player = setmetatable({}, Player) local player = setmetatable({}, Player)
player:_init(config) player:_init(config)