For Pongbat I created a static class that handles all gamepad input. I wanted this class to be able to notify other classes in various situations, namely:

When looking on the internet, I could not find the right solution for my requirements. I didn’t want to add any dependencies to my game controller manager, since I might use this gamepad input controller in future projects. I decided to look into hump.signal and create something similar.

The solution proved to be simple, but since I did have a little bit of trouble to find it on the internet, I will share my approach here.

The gamepad input controller maintains a table that stores a list of objects and callback functions per notification (message). I’ve added a public subscribe function, so other classes can register to receive notifications. Likewise I added an unsubscribe function, so other classes can stop receiving notifications. Finally, the game controller manager internally calls a notify function to invoke a notification.

The end result is as such (simplified):

local gamepadInputController = {}

-- a private notification registry, here I support 3 notifications
local registry = { 
	['release'] = {}, 
	['add'] = {},
	['remove'] = {},
}

-- this private method used to invoke the notification 
-- on all objects registered to receive a message
local function notify(message, ...)
	for _, fn in pairs(registry[message]) do fn(...) end
end

-- a public method to add a joystick to the manager
local function joystickAdded(self, joystick)
	local id, _ = joystick:getID()

	-- invoke 'add' message on all registered callers
	notify('add', id)

	if p1 == nil and p2 ~= joystick then p1 = joystick end
	if p2 == nil and p1 ~= joystick then p2 = joystick end
end

-- public method used to subscribe for a message
local function subscribe(self, message, caller, fn)
	registry[message][caller] = fn
end

-- public method used to unsubscribe for a message
local function unsubscribe(self, message, caller)
	registry[message][caller] = nil
end

return {
	joystickAdded = joystickAdded,
	subscribe = subscribe,
	unsubscribe = unsubscribe,
}

I make use of hump.gamestate to manage scene transitions. hump.gamestate makes use of a stack to manage scenes. When pushing new scenes on the stack, it’s important for the previous scene to stop receiving game controller notifications. After all, we don’t want multiple scenes from receiving game controller input at the same time. Only the front most scene should receive gamepad input notifications.

So in order to achieve this, the base scene class registers and unregisters from notifications as such (simplified):

local scene = Class {} -- based on hump.class

-- a private callback function that is invoked on receiving the 'add' notification
local function onGamepadAdded(id)
    setInputMode('gamepad')
end

-- the default initializer for hump.class
function scene:init(title, background)
    self._background = background
    self._title = title
end

-- this function is called by hump.gamestate upon entering a scene
function scene:enter(previous, ...)  
    if previous then
        GamepadInputController:unsubscribe('add', previous) 
    end

    GamepadInputController:subscribe('add', self, function(id) onGamepadAdded(self, id) end)
end

-- this function is called by hump.gamestate upon popping to the previous scene
function scene:resume()  
    GamepadInputController:subscribe('add', self, function(id) onGamepadAdded(self, id) end)    
end

-- this function is called by hump.gamestate upon replacing the current scene
function scene:leave()
    GamepadInputController:unsubscribe('add', self) 
end

return scene

The above approach seems sufficient for my needs so far.

Right now I am unsure if I should consider using baton in the future for handling mouse, keyboard & gamepad input. One problem I forsee when using baton, is that some gamepads will likely not be supported.

When working on my own gamepad handling code, I noticed my 8BitDo SN30 and 8BitDo Zero 2 controllers are not recognized properly by the LÖVE framework. When controllers are not properly recognized by LÖVE, the button handling code becomes more complicated. I believe these gamepads are not recognized properly due to their simpler layouts. I do however want to support these simpler gamepads, as they are nice for novice gamers (like my 4 year old daughter).

If baton indeed doesn’t properly support these simpler gamepads, I might end up needing to use my custom gamepad input controller in future LÖVE projects. Time will tell …