Creating an input manager?

So in the fighting game I am trying to make, it has become clear that I need a way to manage all of my inputs coming from various sources.

I need something that can do the following:

  • accept input from 4 or possibly more players on different devices

  • modify the capabilities of the current input system (I need to have logic to detect when an analog stick is moved very quickly to initiate running and things of that sort)

  • detect multiple inputs pressed or held at the same time

  • possibly place inputs into a buffer to be taken out and processed according to what is going on in the game (this is popular in fighting games)

So I’m a bit confused on how to implement this with the Defold-Lua combination, since I am new to both.

Here are my questions:

  • where should this input manager live?
  • should it be a script?
  • should it be instantiated for each character or should it be its own game object?
  • if it is its own game object, what would be the best way to attach it to characters and menu functions?

If anyone has any advice it would really help!

You should do one thing at a time and make several versions of it to learn what is best. Not try to do the complete system at once. Making the complete system all at once will be much more difficult, time consuming, and you’ll probably want to remake it from scratch once you’ve learned more anyway.

Use a single input manager for everything. It should be a script. You can attach it to a single input_manager GO. You should prefer to have the least number of script getting input focus as possible.

The input manager can keep track of the state of input, and send messages to other objects to change their appearance. There are multiple ways to do this… you could also use a Lua module to put input states into from the input manager and allow other scripts to import the Lua module and read the input state flags from. Then your player scripts can import information about what kind of moves their active character can do and listen for those specific combos.

In your on_input you don’t want to do any logic, just updating the state flags. Then at the end of your update you want to clear all of those flags. http://www.defold.com/manuals/application-lifecycle/

This script may be helpful for understanding a direction to take when detecting input queues for handling combos. https://github.com/subsoap/defrs/blob/master/examples/toys/konami_code/konami_code.script For each character’s move you can have a buffered input pattern to search for, and you can add a timer to keep track of start of execution and end of execution of pattern to force doing the combo quickly enough.

Let me stress again that what you are making is complex so don’t try to do everything at once! It is all absolutely possible to make though.

3 Likes

Thank you for the insanely helpful post!

I’m starting to understand a bit better, but I’m still fuzzy in a couple of areas because of my inexperience with Lua and Defold.

How would you achieve this behavior with a Lua module? Would you instantiate a global of the Lua module in the input manager, update its values, and then read them from the player scripts during update() or something?

And also, if you clear the input state information every update, how do you guarantee it happens after all of the player scripts are done using it? The picture you posted says final() is called if a component is to be deleted.

Create a Lua module

input.lua

local M = {}
return M

You can optionally set default values for each thing you want to track like

local M = {}
M.KEY_DOWN_LEFT = false
M.KEY_DOWN_RIGHT = false
return M

Then in a script file you do

local input = require("path/to/input.lua")

Then in your game.project file you need to add the directory the .lua file is in to your custom resources (or only the single .lua file if you don’t want multiple in that directory). You’ll also want to enable shared script state in your game.project file.

Then you can do in your input manager script

input.KEY_LEFT_DOWN = true

And other scripts which also require the module can do

if input.KEY_LEFT_DOWN == true then ...

And also, if you clear the input state information every update, how do you guarantee it happens after all of the player scripts are done using it? The picture you posted says final() is called if a component is to be deleted.

This is more about properly detecting releasing of keys so it would be an internal thing in the manager and not put into the module until the next frame where input would not have been updated to true again. Counting frame numbers might help too. I could try making an example later to work out the exact issues. You may end up needing to catch input once per player to ensure frame perfect input. I can’t give an exact answer right now so the possibilities need to be tested more.

I’m starting to understand a bit better, but I’m still fuzzy in a couple of areas because of my inexperience with Lua and Defold.

Go through the Defoldmine and read source from projects a few times a day. Search Lua on sites like Github with keywords and read any source that is interesting. It will help!

2 Likes

Thank you so much for the advice so far, you have really helped me out!

I’ve been able to get something basic working so far. Everything seems to work properly. Now I need to figure out how to implement deadzones and detection of fast stick movement in an elegant way. If you have any advice on that I would be open to hearing it. What I mean by fast stick movement is: in Super Smash Bros. games, if you smash the analog stick really fast in a direction, you’ll run instead of walk.

I also had to do something extremely annoying in my controller input Lua module to get it working the way I wanted. I used an index for the player number so I don’t have to make duplicate lists for every player number. The only problem is Lua seems to make me have to go through and initialize each level of the structure individually or else it would throw errors. I suppose it isn’t all that bad, it just seems strange like I am doing something wrong, even though it worked.

Here is what I had to do:

[CODE]local max_players = 4

for player_number = 1, max_players do
game_inputs[player_number] = {}

game_inputs[player_number].a = {}
game_inputs[player_number].b = {}
game_inputs[player_number].x = {}
game_inputs[player_number].y = {}

game_inputs[player_number].r = {}
game_inputs[player_number].l = {}
game_inputs[player_number].r_analog = {}
game_inputs[player_number].l_analog = {}

game_inputs[player_number].start = {}
game_inputs[player_number].back = {}

game_inputs[player_number].dpad_left = {}
game_inputs[player_number].dpad_down = {}
game_inputs[player_number].dpad_right = {}
game_inputs[player_number].dpad_up = {}

game_inputs[player_number].lstick_click = {}
game_inputs[player_number].lstick_left = {}
game_inputs[player_number].lstick_down = {}
game_inputs[player_number].lstick_right = {}
game_inputs[player_number].lstick_up = {}

game_inputs[player_number].rstick_click = {}
game_inputs[player_number].rstick_left = {}
game_inputs[player_number].rstick_down = {}
game_inputs[player_number].rstick_right = {}
game_inputs[player_number].rstick_up = {}

game_inputs[player_number].a.pressed = false
game_inputs[player_number].a.released = false
game_inputs[player_number].a.value = 0
game_inputs[player_number].b.pressed = false
game_inputs[player_number].b.released = false
game_inputs[player_number].b.value = 0
game_inputs[player_number].x.pressed = false
game_inputs[player_number].x.released = false
game_inputs[player_number].x.value = 0
game_inputs[player_number].y.pressed = false
game_inputs[player_number].y.released = false
game_inputs[player_number].y.value = 0

game_inputs[player_number].r.pressed = false
game_inputs[player_number].r.released = false
game_inputs[player_number].r.value = 0
game_inputs[player_number].l.pressed = false
game_inputs[player_number].l.released = false
game_inputs[player_number].l.value = 0
game_inputs[player_number].r_analog.pressed = false
game_inputs[player_number].r_analog.released = false
game_inputs[player_number].r_analog.value = 0.0
game_inputs[player_number].l_analog.pressed = false
game_inputs[player_number].l_analog.released = false
game_inputs[player_number].l_analog.value = 0.0

game_inputs[player_number].start.pressed = false
game_inputs[player_number].start.released = false
game_inputs[player_number].start.value = 0
game_inputs[player_number].back.pressed = false
game_inputs[player_number].back.released = false
game_inputs[player_number].back.value = 0

game_inputs[player_number].dpad_left.pressed = false
game_inputs[player_number].dpad_left.released = false
game_inputs[player_number].dpad_left.value = 0.
game_inputs[player_number].dpad_down.pressed = false
game_inputs[player_number].dpad_down.released = false
game_inputs[player_number].dpad_down.value = 0.
game_inputs[player_number].dpad_right.pressed = false
game_inputs[player_number].dpad_right.released = false
game_inputs[player_number].dpad_right.value = 0.
game_inputs[player_number].dpad_up.pressed = false
game_inputs[player_number].dpad_up.released = false
game_inputs[player_number].dpad_up.value = 0.

game_inputs[player_number].lstick_click.pressed = false
game_inputs[player_number].lstick_click.released = false
game_inputs[player_number].lstick_click.value = 0
game_inputs[player_number].lstick_left.pressed = false
game_inputs[player_number].lstick_left.released = false
game_inputs[player_number].lstick_left.value = 0.0
game_inputs[player_number].lstick_down.pressed = false
game_inputs[player_number].lstick_down.released = false
game_inputs[player_number].lstick_down.value = 0.0
game_inputs[player_number].lstick_right.pressed = false
game_inputs[player_number].lstick_right.released = false
game_inputs[player_number].lstick_right.value = 0.0
game_inputs[player_number].lstick_up.pressed = false
game_inputs[player_number].lstick_up.released = false
game_inputs[player_number].lstick_up.value = 0.0

game_inputs[player_number].rstick_click.pressed = false
game_inputs[player_number].rstick_click.released = false
game_inputs[player_number].rstick_click.value = 0
game_inputs[player_number].rstick_left.pressed = false
game_inputs[player_number].rstick_left.released = false
game_inputs[player_number].rstick_left.value = 0.0
game_inputs[player_number].rstick_down.pressed = false
game_inputs[player_number].rstick_down.released = false
game_inputs[player_number].rstick_down.value = 0.0
game_inputs[player_number].rstick_right.pressed = false
game_inputs[player_number].rstick_right.released = false
game_inputs[player_number].rstick_right.value = 0.0
game_inputs[player_number].rstick_up.pressed = false
game_inputs[player_number].rstick_up.released = false
game_inputs[player_number].rstick_up.value = 0.0
end[/CODE]

1 Like

You could shorten that considerably to:

local max_players = 4
local game_inputs = {}
local all_bindings = {
	"b", "x", "y", "r", "l", "r_analog", "l_analog", "start", "back",
	"dpad_left", "dpad_down", "dpad_right", "dpad_up",
	"lstick_click",	"lstick_left", "lstick_down", "lstick_right", "lstick_up",
	"rstick_click",	"rstick_left", "rstick_down", "rstick_right", "rstick_up"
}
for player_number=1, max_players do
	game_inputs[player_number] = {}
	for i, v in ipairs(all_bindings) do
		game_inputs[player_number][v] = {pressed = false, released = false, value = 0}
	end
end

You shouldn’t ever have to write things more than once or twice, just put them in a table and iterate over that.

5 Likes

That makes way more sense and worked perfectly. Thanks so much!

1 Like