Mitigating Code Bloat with Input Handling

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
CakeJamble
Prole
Posts: 2
Joined: Fri Apr 04, 2025 8:19 pm

Mitigating Code Bloat with Input Handling

Post by CakeJamble »

I want to provide controller support, so I added a gamepadpressed function to my file. As the game grows in size, I am essentially doubling the lines of code for handling input. How do you mitigate this kind of code bloat? In order to keep things focused rn, we can assume that I don't really care that users can use both forms of input at once to cause unintended behavior.

Ex:

Code: Select all

function character_select:keypressed(key)
  if key == 'right' then
    character_select:set_right()
  end
  statPreview = character_select:setStatPreview()
end;
  
function character_select:gamepadpressed(joystick, button)
  if button == 'dpright' then
    character_select:set_right()
  end
  statPreview = character_select:setStatPreview()
end;
As I add game states and classes, this seems like it can quickly get out of hand:

Code: Select all

--[[ Hierarchy of keypressed chain:
  1. 
    combat:keypressed -> characterTeam:keypressed
      characterTeam:keypressed -> each character:keypressed
        character:keypressed -> offenseState:keypressed OR defenseState:keypressed
  
  2. combat:keypressed -> actionUI:keypressed
]]
function combat:keypressed(key)
  self.characterTeam:keypressed(key)
  
  if self.actionUI then
    self.actionUI:keypressed(key)
  end

end;
How do you usually organize your input handling when dealing with OOP and multiple gamestates? Or do you usually not even sweat this kind of stuff?
MrFariator
Party member
Posts: 578
Joined: Wed Oct 05, 2016 11:53 am

Re: Mitigating Code Bloat with Input Handling

Post by MrFariator »

Effectively, the way I like to do it:
1) love.keypressed, love.gamepadpressed and similar callbacks take note of the button pressed, and put it in a table. Use a time-stamp or something too, if you want to know how long ago a button was pressed (for example, to implement "repeating" inputs after enough time).
2) Have some configuration that maps each game input ("attack", "jump", "accept", "cancel", "pause", "left", "right", etc) to specific buttons. Use separate mappings for keyboard and controller; probably a good idea to allow players to tweak these mappings (either in-game, or directly messing with a configuration file). For keyboards, it's better to use scancodes than the key literals (because of different keyboard layouts like QWERTY and AZERTY).
3) During love.update, have some code that goes through those button mappings. For each detected event (press, release), update the status of these game inputs (press, hold, repeat, release). You can minimize this by making your code only check the last input device (like if keyboard was last used, only check keyboard mappings).
4) Provide some interface for the rest of your game's code to check the status of a given button. You could for example either do this with an observer pattern (your input code says "hey, this button was pressed on this frame", and whatever object listening in responds to it), or have some set of functions that returns the status of buttons as determined in step 3 (like "isAttackPressed", "isJumpPressed", "isJumpReleased", etc).

After that setup is done, your player class, menus or other things you can interact with would just check the status through whatever method you chose in 4. The idea is basically to have those love callbacks (love.keypressed, love.gamepadpressed, etc) do as little work as possible, more or less detecting the events and pushing them to be handled on next available frame. Your input handling code as such doesn't really care what other places might be tracking its or various buttons' status.

This of course assumes a single-player game. You'll likely have more considerations for multiplayer (like keeping track of multiple input devices separately).

As far state management goes, I usually let each state define a callback for a given received input. For example, player class might check if "attack" is pressed (while looping through all the relevant buttons in a way or another), and if yes, it will call the corresponding "attack" function in the current state. The state is then free to implement if it should or should not act upon that button being pressed. If you transition from one state to another, you may optionally choose to drop all other inputs (to prevent certain actions from happening on the same frame, like maybe rolling and attacking).

This may duplicate some code in the player class, but that's usually not that big of a deal if the amount of potential states or state transitions is small enough. If in doubt, can always setup a common function that certain states then use for that specific input, if appropriate. Ultimately this part is wildly going to depend on how your player class might control (are states exclusive? can you be in multiple states at once? etc).

For menus you may consider having a "stack" hierarchy, where only the top-most active menu has control, and then anything below it sits idle. This could be achieved with a simple table keeping track of currently active menus, but only checking inputs inside the top-most menu. Relinquishing control as such is akin to popping and pushing whatever menus within the stack/table. This way each menu can implement its own input logic as necessary, if not use a common one.
Last edited by MrFariator on Sat Apr 05, 2025 3:03 am, edited 16 times in total.
User avatar
dusoft
Party member
Posts: 792
Joined: Fri Nov 08, 2013 12:07 am
Location: Europe usually
Contact:

Re: Mitigating Code Bloat with Input Handling

Post by dusoft »

Yup, abstraction here is the only way to go. You might like to check how this library handles it (see the simple movement definitions), it's well done:
https://github.com/tesselode/baton?tab= ... g-controls
CakeJamble
Prole
Posts: 2
Joined: Fri Apr 04, 2025 8:19 pm

Re: Mitigating Code Bloat with Input Handling

Post by CakeJamble »

MrFariator wrote: Fri Apr 04, 2025 8:48 pm Effectively, the way I like to do it:
1) love.keypressed, love.gamepadpressed and similar callbacks take note of the button pressed, and put it in a table. Use a time-stamp or something too, if you want to know how long ago a button was pressed (for example, to implement "repeating" inputs after enough time).
2) Have some configuration that maps each game input ("attack", "jump", "accept", "cancel", "pause", "left", "right", etc) to specific buttons. Use separate mappings for keyboard and controller; probably a good idea to allow players to tweak these mappings (either in-game, or directly messing with a configuration file). For keyboards, it's better to use scancodes than the key literals (because of different keyboard layouts like QWERTY and AZERTY).
3) During love.update, have some code that goes through those button mappings. For each detected event (press, release), update the status of these game inputs (press, hold, repeat, release). You can minimize this by making your code only check the last input device (like if keyboard was last used, only check keyboard mappings).
4) Provide some interface for the rest of your game's code to check the status of a given button. You could for example either do this with an observer pattern (your input code says "hey, this button was pressed on this frame", and whatever object listening in responds to it), or have some set of functions that returns the status of buttons as determined in step 3 (like "isAttackPressed", "isJumpPressed", "isJumpReleased", etc).

After that setup is done, your player class, menus or other things you can interact with would just check the status through whatever method you chose in 4. The idea is basically to have those love callbacks (love.keypressed, love.gamepadpressed, etc) do as little work as possible, more or less detecting the events and pushing them to be handled on next available frame. Your input handling code as such doesn't really care what other places might be tracking its or various buttons' status.

This of course assumes a single-player game. You'll likely have more considerations for multiplayer (like keeping track of multiple input devices separately).

As far state management goes, I usually let each state define a callback for a given received input. For example, player class might check if "attack" is pressed (while looping through all the relevant buttons in a way or another), and if yes, it will call the corresponding "attack" function in the current state. The state is then free to implement if it should or should not act upon that button being pressed. If you transition from one state to another, you may optionally choose to drop all other inputs (to prevent certain actions from happening on the same frame, like maybe rolling and attacking).

This may duplicate some code in the player class, but that's usually not that big of a deal if the amount of potential states or state transitions is small enough. If in doubt, can always setup a common function that certain states then use for that specific input, if appropriate. Ultimately this part is wildly going to depend on how your player class might control (are states exclusive? can you be in multiple states at once? etc).

For menus you may consider having a "stack" hierarchy, where only the top-most active menu has control, and then anything below it sits idle. This could be achieved with a simple table keeping track of currently active menus, but only checking inputs inside the top-most menu. Relinquishing control as such is akin to popping and pushing whatever menus within the stack/table. This way each menu can implement its own input logic as necessary, if not use a common one.
Thanks! This is super helpful, and I appreciate you even coming back to add more context to your explanations.
For implementing that Observer design pattern, I'm still unsure exactly how that would look like. Is it similar to Event Listeners? I have some experience setting that up in Unity, where it's simple, but I'm unsure how I would register an event listener to a function/var/state change in Love.

Right now, I do have implemented character states that are defined as their own classes which are aggregated in my Character class. The character class has a Movement, Offense, and Defense state that have exclusive control over a Character's control configuration. In the Observer pattern, would the Character states be subscribed to the controller configuration class that you described? Based on your explanation, I'm kind of imagining this, and I've removed some of the functionality detailed for it to be more readable:

https://imgur.com/a/0UHAsoA
Fallback incase image doesn't load properly:
https://www.plantuml.com/plantuml/png/b ... 8yRi2gPAO_

Then intended outcome is to have a turn-based combat system that listens for inputs differently in each state so that a turn-based QTE system can be replicated, similar to the Mario RPG games (SNES, N64, GC, GBA, DS).

Edit: Sorry, there are some mistakes in the UML diagram for the function arguments. If they make it difficult to understand, I'll come back to fix those so anyone can give some feedback
MrFariator
Party member
Posts: 578
Joined: Wed Oct 05, 2016 11:53 am

Re: Mitigating Code Bloat with Input Handling

Post by MrFariator »

CakeJamble wrote: Sun Apr 06, 2025 11:13 pm For implementing that Observer design pattern, I'm still unsure exactly how that would look like. I have some experience setting that up in Unity, where it's simple, but I'm unsure how I would register an event listener to a function/var/state change in Love.

Right now, I do have implemented character states that are defined as their own classes which are aggregated in my Character class. The character class has a Movement, Offense, and Defense state that have exclusive control over a Character's control configuration. In the Observer pattern, would the Character states be subscribed to the controller configuration class that you described? Based on your explanation, I'm kind of imagining this, and I've removed some of the functionality detailed for it to be more readable:
With the Observer Pattern mention, I mostly meant to use that as an example on how the rest of your codebase might be reading or receiving game inputs. Personally, I find the pattern useful for cases where you might be interested in certain sequences of button presses, or if you'd rather prefer to make things react to these events, rather than having to explicitly call some "checkInputs" function in a given menu, class or the like every frame.

For a (decently) menu-driven game, such as the Mario RPGs, it could be a decent choice if you make each particular submenu start/stop listening to commands whenever they are brought up or closed. However, this can get decently complex, so you can always achieve this with the stack based approach, too, where the "checkInputs" function is only checked from the top-most menu; rest are ignored for that frame.

As for implementing the observer pattern in lua, if you want to go for it, you can more or less implement it like so: Make a table that holds your events, each event will have its own table, containing all the objects listening in (these could be function values for callbacks, or an object that is expected to contain a specific function). Whenever an event is sent out, loop over the event's table and invoke the functions/objects contained therein. The events themselves could be signified by strings, numbers, or even tables; all up to you on that front. To make an object stop listening to events, you just remove it from the corresponding table. If you want an example library that implements the pattern, see something like beholder.
CakeJamble wrote: Sun Apr 06, 2025 11:13 pm Is it similar to Event Listeners?
Functionally, the pattern is indeed similar to Unity's Event Listeners.
CakeJamble wrote: Sun Apr 06, 2025 11:13 pm Right now, I do have implemented character states that are defined as their own classes which are aggregated in my Character class. The character class has a Movement, Offense, and Defense state that have exclusive control over a Character's control configuration. In the Observer pattern, would the Character states be subscribed to the controller configuration class that you described? Based on your explanation, I'm kind of imagining this, and I've removed some of the functionality detailed for it to be more readable:

https://imgur.com/a/0UHAsoA
Fallback incase image doesn't load properly:
https://www.plantuml.com/plantuml/png/b ... 8yRi2gPAO_
Personally I've had the player object have one set of listeners taking in inputs, and passing those onto the current state. I am pretty rusty on my UML charts (I haven't actively been using them since my university days), but seems like you're about on the same track.
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Amazon [Bot], Google [Bot] and 7 guests