Input APIs are difficult to work with

ContextActionService and GUI-related input APIs are very sensitive to input sinking, and this makes it difficult to write robust input code which properly handles all situations.

We are forced to use to more generic APIs such as UserInputService and InputObjects to properly handle all situations, but we still must use the sensitive APIs because they have features the generic APIs do not (such as being able to sink input or control the order in which inputs happen or only trigger in the context of a particular GUI object).

So we have to always use both of these different APIs at the same time, and that just adds more work and complexity. I find working with user input one of the biggest pain points in the engine because of stuff like this – I often have to spend quite a bit of time debugging and carefully reviewing how different parts of game systems deal with input.

Here are some common examples where this kind of problem is encountered:

  1. Detecting touch input changes/ends. To achieve this properly, you need to listen/poll the InputObject’s UserInputState changed signal or UserInputService.InputChanged/Ended or UserInputService.TouchChanged/Ended signals. You cannot use ContextActionService for this because if the touch is over an active gui object it won’t be sent to ContextActionService callbacks.
    The PlayerModule’s DynamicThumbstick had this problem and fixed it with the “UserDynamicThumbstickMoveOverButtons2” FFlag. I was debugging an issue like this today in my own project, and I was confused about how the DynamicThumbstick was able to avoid this problem until I figured out that this FFlag was changing the behavior such that the ContextActionService binding wasn’t handling Changed events and instead that was being handled by other UserInputService parts of the code which do not suffer from the sinking problem.

  2. Implementing custom button code (e.g. which correctly handles situations like players clicking on a button, moving their mouse off of it, then moving it back on to the button, then releasing their click). The GuiObject input signals only provide information when the input is happening directly over the object, and will stop being fired if the input moves off the object. So to implement these features robustly you need to start using the UserInputService / InputObject stuff.

  3. General robustness / dealing with input overrides. If an input is in progress and being consumed by some piece of code, and then it gets overridden by a consumer of higher priority, I want the original consumer to still be able to receive information needed to clean up the input when it finishes (or detect that the input was overriden and clean up state even before the input finishes). This would “soft fix” some of the input issues I encounter frequently, making them less likely to break the game when they happen (e.g. preventing players from starting a new input because the code failed to clean up the old input because the input end signal was consumed by a different piece of code).

The consequences of these kinds of issues is that the most robust input architecture tends to follow this structure (in my experience):

  1. ContextActionService/GuiObject.InputBegan/etc. to detect the beginning of an input.
  2. UserInputService / InputObjects to actually process input changes and detect when input ends.
  3. ContextActionService to sink known inputs (as that is the special thing that ContextActionService provides).

If you’re doing anything non-trivial with touch/mouse input you’re probably going to need to expand all your code out into this form at some point to get it working 100% correctly.

I believe all of this could be avoided if sinking and other idiosyncratic concerns (e.g. mouse has been moved off gui object so we’re gonna stop processing GuiObject.Input____ events) did not completely prevent callback execution, and instead that these additional pieces of information were provided as arguments to the callback functions. Then we could achieve the function of all structural pieces using only a single callback / API.

11 Likes

:point_up_2:
as someone who makes stuff using mixes of UserInputService alongside ContextActionService, it would be perfect if the best parts of both were made into one service. It doesn’t help how ROBLOX doesn’t let you use what would be amazing features such as ContextActionService:CallFunction. (Especially how they literally recommend it to you in the docs, lol.) It would be perfect for making custom gui buttons, but still using ContextActionService as a base, but nooooooooo only roblox themselves can use it i guess.
seriously??? why can’t we just have one, perfect, unified service for input instead of spreading it out over at least 3!

2 Likes