Hi,
I want to code a input system for a multiplayer fighting game, and I need to handle complicated stuffs about combos, timed actions, buffered inputs, short/long press etc..
So my idea is to have a global input_controller.script which will handle all keys and timings and players, store the result in state table tracking gameplay actions. This global state would then be accessed to other scripts in the game. Or the controller can use defold-event to trigger an event for each action. I also like to have dynamic bindings and “input profiles” to define different sets of actions (like “gameplay_fight”, “gameplay_car”, “ui_menus”, “ui_debug” etc.) that can be enable/disable.
I already used the greate defold-input extension, which provide a really good basis but I want to go farther.
So I have questions about the application lifecycle of Defold, regarding Defold application lifecycle manual and Defold application lifecycle manual.
- When there is multiple inputs in the same frame, does ALL on_input() function be called before the message dispatch phase to happen, or is each message dispatched after each on_input() ?
- When we are in the message-dispatch phase, and we repost a message, does it work “rounds after rounds” like "Every messages of round 1 are called (on_message functions) for ALL objects, THEN every reposts messages are called on ALL objects (round2), THEN every re-reposts etc..) or does it work like “immediat dispatch” as like "a message of round 1 is executed, if it repost and it is added to queue but the repost could be executed before another round1 message in another gameobject) ?
I want to do a thing like that, but I want to know if it is reliable before coding many things…
I did write some fake code to get the big picture:
--- ----------- GENERIC GLOBAL input_controller.script
local inputs = require('libs.inputs') -- module to handle global state of inputs and some usefull functions
-- actions of this particular game - define mappings between keys, time of bufferings, combinations of keys, profiles etc.
-- this module use the libs.inputs module to build the mappings.
-- ultimately, actions are also defold-event events, so we can register to them in other scripts
local myactions = require('game.actions.lua')
---@package
---@param self inputs_controller.script
function init(self)
self.compute_actions = false
self.action_mapper = inputs.configureActions(myactions) -- object to track input events and compute actions states
self.actions_state = action_mapper.init() -- will store resulting actions states
end
---@package
---@param self inputs_controller.script
---@param action_id hash
---@param action on_input.action
---@return boolean|nil
function on_input(self, action_id, action)
local time = socket.gettime()
if action_id then
self.action_mapper:track_trigger(action_id, action, time) -- track input events
else
self.action_mapper:track_mouse(action, time} -- track mouse and touch events
end
-- post message to compute actions only one time per frame, after ALL on_input but BEFORE fixed_update
if not self.compute_actions then
self.compute_actions = true
msg.post('#', 'compute_actions')
end
end
---@package
---@param self inputs_controller.script
---@param message_id hash
---@param message table
---@param sender url
function on_message(self, message_id, message, sender)
if message_id == hash('compute_actions') then -- executes after ALL on_input but BEFORE fixed_update
-- here we have all input triggers and mouses states --> compute actions
self.actions_state = self.action_mapper:computeNewActionsState(self.actions_state) -- some complicated calculations about combos, timing, actions states etc.
-- now we have the new actions state --> we can post "action" messages to objects or trigger events via defold-events. They will be executed BEFORE fixed_update
for action_id, action_state in pairs(self.actions_state) do
action_state.event:trigger() -- use defold-event to trigger system wide
-- or suppose we have a property to set target object, juste post to it, this object will handle routing
msg.post(target_object,'action',{action_id=action_id, action_state = action_state})
end
elseif message_id == hash('reset_states') then -- a message to reset triggers after each frame
self.action_mapper:reset()
end
end
function on_late_update(self,dt)
msg.post('#','reset_states') -- post msg to clear all triggers states at the end of all update phase
end
--- -------------------------------------------------------------------------------------
---- in a script with on_message handling:
local myactions = require('game.actions.lua')
---@package
---@param self inputs_controller.script
---@param message_id hash
---@param message table
---@param sender url
function on_message(self, message_id, message, sender)
if message_id == hash('action') then
if message.action_id == myactions.JUMP then
-- do things for jump
end
end
--- -----------------------------------------------------------------------------------
---- OR in any script with Event registering -> our actions are events!
local myactions = require('game.actions.lua')
function init(self)
myactions.JUMP:subscribe(function(self, data)
-- do jump things here
self.direction.y = 1
end)
myactions.MOVE_LEFT:subscribe(function(self, data)
-- do move left
self.direction.x = -1
end)
end
function fixed_update(self, dt)
-- here actions event have already been executed
self.direction = vmath.normalize(self.direction)
self.position = self.position + self.direction * self.speed
--- etc..
end
----------------------------------
So if somebody understands what I want to do, is it a good idea? will it work and stand the passing of time and Defold engine updates?