Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Analysing Layout Hierarchy

Discussion in 'Input System' started by a_weber, Feb 18, 2020.

  1. a_weber

    a_weber

    Joined:
    May 2, 2018
    Posts:
    4
    Hello,
    I am currently implementing a menu for rebinding. This menu restricts what kind of controls are accepted for rebinding, for example if I am in the "Gamepad" tab, only inputs from gamepads should be accepted. This is mostly working as intended using WithControlsExcluding("<Gamepad>") for the "Keyboard&Mouse" tab. Ideally I'd use something like WithControlsHavingToMatchPath("<Keyboard>|<Mouse">) or WithControls("Keyboard").WithControls("Mouse") but I don't think that functionality exists - The way I do it now, someone could bind an XRController button to my keyboard map. I do a sanity check later that rejects invalid bindings, but they still cancel the rebinding operation. Is there a better alternative here?
    Thanks for your time.
     
  2. Rene-Damm

    Rene-Damm

    Unity Technologies

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Yup can call WithControlsHavingToMatchPath repeatedly with cumulative effect.

    Code (CSharp):
    1. action.PerformInteractiveRebinding()
    2.     .WithControlsHavingToMatchPath("<Keyboard>")
    3.     .WithControlsHavingToMatchPath("<Mouse>");
    Note that PerformInteractiveRebinding() will do that automatically for every device require in the control scheme. E.g. if you have a keyboard+mouse control scheme, the rebind will automatically be restricted to keyboard and mouse.
     
    a_weber likes this.
  3. a_weber

    a_weber

    Joined:
    May 2, 2018
    Posts:
    4
    Ahhh I was so close, just didn't know about the cumulative effect of WithControlsHavingToMatchPath! Thank you very much.

    I don't actually use PerformInteractiveRebinding, instead I create a new RebindingOperation and use the callbacks to go through my own abstraction layer. This is so I can detect if someone is trying to bind a button to a 2DVector in which case I automatically create a composite. Additionally, I use it to store rebinding information on disk (Maybe there is a smarter way of doing this, too? Does a mechanism to store overrides and either add/remove bindings or override their enabled state already exist in the API?)

    One issue I can see is that pressing a button on the dpad currently sets the path to "<Gamepad>/dpad" instead of "<Gamepad>/dpad/up" etc. Is there a function of RebindingOperation that changes this behaviour? (Maybe WithExpectedControlType<float>()?)

    Also, is there a smarter way to check if a path matches a certain layout? Currently I am using
    Code (CSharp):
    1. InputControlPath.MatchesPrefix("<Gamepad>", InputSystem.FindControl(binding.effectivePath /*"<DualShockPS4HID>/touchpadButton"*/));
    This works fine but only while a PS4 controller is connected. Is it possible to compare two string paths directly?
    I would also be happy to work with groups, but I don't really want to write the string comparison code myself, also they seem very intransparent/lacking documentation and prone to mistakes.

    Additionally, is it possible to find out of what type a path is, ideally even when the corresponding device isn't plugged in? E.g. that <Gamepad>/leftStick is 2DVector.

    Thanks again for your help!
     
  4. Rene-Damm

    Rene-Damm

    Unity Technologies

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Not yet. Simple 1-call overrides->JSON and JSON->overrides APIs are on the list for after 1.0.

    Yup, it goes by control type. If you set it on the action, it should automatically take effect (though setting it with WithExpectedControlType is just as fine, of course).////EDIT: Nevermind, that's only for PerformInteractiveRebinding().

    Without one it will consider any control but favor non-synthetic over synthetic ones (which is why it likes "dpad" more than "dpad/up"). ////EDIT: actually, ignore that for this case... "dpad/up" isn't synthetic (unlike, say, "leftStick/up"); so here it's probably just a matter of "dpad" coming before "dpad/up" on the device and thus getting picked by default because either match has the same score.

    Code (CSharp):
    1. var deviceLayout = InputControlPath.TryGetDeviceLayout(path);
    2. if (!string.IsNullOrEmpty(deviceLayout) &&
    3.     InputSystem.IsFirstLayoutBasedOnSecond(deviceLayout, "DualShockGamepad"))
    4.     Debug.Log("Path is a binding for a DualShock gamepad");
    Code (CSharp):
    1. var layoutName = InputControlPath.TryGetControlLayout(path);
    2. if (!string.IsNullOrEmpty(layoutName))
    3.     Debug.Log($"Path '{path}' is a {layoutName}");
    For "<Gamepad>/leftStick" that should give you "Stick". Unfortunately, looks like we don't have a public API for getting the control type for a layout ATM. You can load the layout, though, and query its type.

    Code (CSharp):
    1. var layout = InputSystem.LoadLayout(layoutName);
    2. var valueType = layout.GetValueType();
    3. Debug.Log($"{layout} is a {valueType}");
    For the "Stick" layout, that should give you Vector2.

    ////EDIT: Bah, looks like your post is awaiting moderator approval so isn't publicly visible yet. Anyway, should pop up soon.
     
    Last edited: Feb 19, 2020
  5. a_weber

    a_weber

    Joined:
    May 2, 2018
    Posts:
    4
    Wonderful, you're a legend! Those have all been super helpful (and very difficult to find in the documentation) and make me much more confident in my code (It feels much cleaner using the layout API).

    I just have a couple more questions about bindings, if you don't mind me asking - Composite bindings generally consist of 3 (5) separate bindings, right? How are these linked?

    It seems to me like it always follows a certain pattern: if
    binding[i].isComposite
    , then
    binding[i + 1/2(/3/4)].isPartOfComposite
    is true and these bindings are part of the composite binding i, so these groups of bindings always need to stay together as they are apparently not linked through other means, correct?

    If
    binding[i].isComposite
    is true, then
    binding[i].GetNameOfComposite()
    will return 2DVector or 1DAxis and this is the only way to determine the type, correct?

    If
    binding[i + 1/2(/3/4)].isPartOfComposite
    is true,
    binding[i].name
    will be positive/negative or up/down/left/right, this is the only way to determine which binding in the composite points in which direction, correct?

    How does one work with a binding's "Use in control scheme"? Is that just what
    binding[i].groups
    is? Are groups always guaranteed to be one of the 5 defined?

    Lastly, is there a way to disable (or remove) a binding from an action at runtime? I basically need to change the type of a binding from/to composite.

    Once again, thank you very much for your help!
     
    Last edited: Feb 23, 2020