Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two concepts for reworking input handling #343

Open
markusobi opened this issue May 17, 2018 · 13 comments
Open

Two concepts for reworking input handling #343

markusobi opened this issue May 17, 2018 · 13 comments
Assignees

Comments

@markusobi
Copy link
Contributor

To make our Input system more maintainable/extendable I have worked out two concepts, which might help in this regard.

Concept 1: Views handle Input.

InputActions will be handled by Views ONLY. The topmost View on the screen will receive all input. Each View can decide to handle the InputAction and/or consume it independently.

  • Consumption: If a View consumes an InputAction, it will NOT be passed to the next View below. To figure out the View draw order, one can simply traverse the already exisiting Viewtree.
  • Handling: Handling an InputActions means firing a binding. Bindings are now stored in View and can be registered per View. Generally we want handled InputActions to be consumed by default, to avoid making it overly complicated.

A View can decide to handle+consume only specifiy InputActions and let all others through.
Whether or not a View will consume InputActions will in most cases depend on it's visibility.
I.e. when the Loading Screen is visible no binding shall be fired, except for the console (which is on top of the Loading Screen).
If there is no associated View for a binding, one can add a empty Pseudo-View at the right position in the View-Tree.
i.e. to be able to open the stats screen while the inventory is open, an invisible view can be inserted before the inventory view.

Advantage: Implicit order of InputAction handling/consumption through View draw order.
Disadvantages: bound to View/Vieworder. Visibility of Pseudoviews has to be set somewhere.

Concept 2: Passing Input through a separate Chain of InputHandler.

This Concept is very similar to Concept 1, but in this case we are not using the View-Tree, but a separate chain of InputActing handling objects.
Analog to Concept 1, an InputAction is passed through the Chain, where each object can decide whether to consume and/or handle it.
A draft of how such chain could look like is found here:keyhandling.dot.pdf.

Advantages: Independent of View-Tree.

Goal

The goal behind these concepts is to make it easier to add new input consuming Views without having to add isViewXYVisible all over the place (currently all over the place in different InputAction handlers).

Feedback is welcome.

@frabert
Copy link
Contributor

frabert commented May 18, 2018

I personally prefer the first approach, makes implementing Views easier and puts less responsibility on them

@aaeberharter
Copy link
Collaborator

aaeberharter commented May 19, 2018

I am not sure whether the Views are suitable for this. I thought about a hierarchy of context classes. If the parent context gets enabled (e.g. menu shows up) all child contexts receive no more input. This is more or less the same as your suggestion but uses a separate class. Then if a view (or any other gameplay component) requires input it would activate its context. Keep in mind that not all views actually listen to user input (e.g. health bar). Furthermore one could store variables in the context, for example the this pointer of the PlayerController.

Since my original implementation of the callback system is often not used as intended I would like to propose a change. I would split up RegisterAction into several functions:
RegisterTrigger(actionType, void(bool pressed, int slope)) only gets called when pressed changes it's state. slope tells you whether the button was pressed (+1) or released (-1). This resembles more or less the regular button.
RegisterTriggerContinuous(actionType, void(bool pressed, int slope)) gets called in every frame. slope is 0 if button state did not change.
RegisterAxis(actionType, void(float state, float slope)) Represents an axis e.g. a locked mouse or a joystick. This function gets called every frame because there is little sense in determining whether a float value has changed.
RegisterPointer(actionType, void(float2 state, float2 slope)) x and y coordinates of a free mouse or the Nintendo Wii thing or whatever.
RegisterText(actionType, void(unicode character) Still have to think about how exactly you would do this.

In each of the function you could access it's globally declared context and use this information to determine which character has to move etc.

Just a suggestion. @\all Please get involved and tell us what you think about it.

@markusobi
Copy link
Contributor Author

markusobi commented May 21, 2018

Keep in mind that not all views actually listen to user input (e.g. health bar).

Such Views would just pass all input through. It would be possible to define default behaviors via enums like:

  • consume_handled: Consume all the InputActions, that was registered on it. (probably default behaviour)
  • consume_all_input: let's nothing through (i.e. for Loading Screen)

In case of the Health Bar: consume_handled + No binding is registered on it
→ passthrough of all input.

Furthermore one could store variables in the context, for example the this pointer of the PlayerController.

Is it easier to update the context than to update the View, when the current controlled NPC changes or gets invalidated?
What is the advantage of keeping the context up to date compared to a View?
Where do the context objects live? Inside a ContextTree/ContextList?
Also: You will still need to rebuild the whole ViewTree as ContextTree in order to get the order of Views right.

I would split up RegisterAction into several functions

I think this is a good idea. Luckily this change would be largely independent of the proposed change. So it should not be easier/harder to implement it with the current system than with the proposed system.

@aaeberharter
Copy link
Collaborator

Okay I'm in favor of your solution because you can avoid the redundancy of defining the view tree twice. I just wonder how you would handle the 3D scene. Somehow put it into a view too or rather create a dummy view which does the input handling for the 3D scene?

@markusobi
Copy link
Contributor Author

Somehow put it into a view too or rather create a dummy view which does the input handling for the 3D scene?

I plan to use a dummy view for the player/camera controls.

@ataulien
Copy link
Collaborator

@markusobi I would not call it "dummy view", but rather see the main 3d-scene as an UI-View on it's own: The UI-View which shows the game, if that makes sense.

@markusobi
Copy link
Contributor Author

@ataulien Should this 3d-Scene View actually draw the world? Should it directly call GameEngine::drawFrame ?

@ataulien
Copy link
Collaborator

@markusobi not sure about that. It probably could though! It would allow for picture-in-picture and all such fancy things.

@markusobi
Copy link
Contributor Author

It would allow for picture-in-picture and all such fancy things.

Fancy things like mirrors :-)

@frabert
Copy link
Contributor

frabert commented May 30, 2018

And portals!!!

@superdaveman
Copy link

I sense a Gothic Portal Gun Mod. :D

@tomedi
Copy link

tomedi commented Jun 8, 2018

I'm all for portals and mirrors! So, I'm also for this 3d- Scene View- thing :D (is this Option 2, the separate Chain of InputHandler?)

@ataulien
Copy link
Collaborator

ataulien commented Jun 9, 2018

Well, mirrors, portals and other 3d-stuff is usually handled differently. This should not be the reason to chose any of the two options over the other one.

@markusobi markusobi self-assigned this Jun 13, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants