Search Unity

How would you handle multi-layer GUI.interactions with the new input system

Discussion in 'Input System' started by Whatever560, May 9, 2020.

  1. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    513
    The question is as follow
    Let's say I have a general input map:
    General
    -Cancel
    -Submit
    Game Map
    -Move
    -Jump
    Menu map
    -Quit


    Let's play, now I'm in game with given state.
    All General is enabled
    ONLY Move is enabled

    I want to be able to toggle the menu map and by doing so, toggle the game Map while keeping the game input state and hiding General callbacks

    I also want my Cancel and Submit callbacks to be overridden when I toggle the menu. So that when I'm in game Cancel/Submit interacts with my game callbacks and when I'm in Menu Cancel/Submits works with my menu.

    In a way how does Input inheritance should work? What if I need a nested menu? It also is remapping as it reduces the number of inputs.

    I really'd like to avoid the following
    CanceInGame, SubmitInGame, CancelMenu, SubmitMenu, CancelSubMenu, SubmitSubMenu
     
  2. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    513
    I ended up creating an Input Binding Graph to which I push and pop list of bindings, it works as expected.

    Here is the code, use it if you will.

    Create a "InputBindingGraph" and add "InputBindingCollection" to it.


    Code (CSharp):
    1.     public interface IInputBindingGraph
    2.     {
    3.         /// <summary>
    4.         /// A binding collection to push on the stack
    5.         /// </summary>
    6.         /// <exception cref="InvalidOperationException">if one of the binding was already attached</exception>
    7.         /// <param name="bindings"></param>
    8.  
    9.         void AttachCollection(IInputBindingCollection bindings);
    10.  
    11.         /// <summary>
    12.         /// A binding collection to push on the stack
    13.         /// </summary>
    14.         /// <exception cref="InvalidOperationException">if one of the binding was not attached yet</exception>
    15.         /// <param name="bindings"></param>
    16.         void DetachCollection(IInputBindingCollection bindings);
    17.  
    18.         /// <summary>
    19.         /// A binding collection to pop from the stack
    20.         /// </summary>
    21.         /// <exception cref="InvalidOperationException">if the binding was not attached yet</exception>
    22.         /// <param name="binding"></param>
    23.         void Detach(IInputBinding binding);
    24.  
    25.         /// <summary>
    26.         /// Add an individual input binding
    27.         /// </summary>
    28.         /// <exception cref="InvalidOperationException">if the binding was already attached</exception>
    29.         /// <param name="binding"></param>
    30.         void Attach(IInputBinding binding);
    31.     }
    32.  
    33.     /// <summary>
    34.     /// A binding of an InputAction with another element, could be anything from an interaction triggering the InputAction, or the InputAction being attached a perform callback
    35.     /// </summary>
    36.     public interface IInputBinding
    37.     {
    38.  
    39.         //
    40.         // /// <summary>
    41.         // /// The callback that will trigger on the input action <see cref="GetInput"/>
    42.         // /// </summary>
    43.         // /// <returns></returns>
    44.         // Action<InputAction.CallbackContext> GetCallback();
    45.  
    46.         /// <summary>
    47.         /// Attach the element and the input together
    48.         /// </summary>
    49.         void Attach();
    50.  
    51.         /// <summary>
    52.         /// Detach the element and the input
    53.         /// </summary>
    54.         void Detach();
    55.  
    56.         /// <summary>
    57.         /// Are input bindings the same ? It's a lower form of equals.
    58.         /// </summary>
    59.         /// <param name="binding"></param>
    60.         /// <returns></returns>
    61.         bool IsSame(IInputBinding binding);
    62.  
    63.         /// <summary>
    64.         /// Retrieve the action value of the binding
    65.         /// </summary>
    66.         /// <param name="action"></param>
    67.         /// <returns></returns>
    68.         InputAction GetAction();
    69.  
    70.     }
    71.  
    72.     /// <summary>
    73.     /// An input binding that attach / detach a callback to InputAction.<see cref="InputAction.performed"/> event
    74.     /// </summary>
    75.     public class InputCallback : IInputBinding
    76.     {
    77.         private readonly Action<InputAction.CallbackContext> _callback;
    78.         private readonly InputActionReference _input;
    79.         private bool _enabled;
    80.  
    81.         public override string ToString()
    82.         {
    83.             return _input.name + "=>" + _callback.Method.Name;
    84.         }
    85.  
    86.         public InputCallback(InputActionReference input, Action<InputAction.CallbackContext> callback)
    87.         {
    88.             _input = input;
    89.             _callback = callback;
    90.         }
    91.  
    92.         public InputActionReference GetInput()
    93.         {
    94.             return _input;
    95.         }
    96.  
    97.         public void Attach()
    98.         {
    99.             if(_enabled) return;
    100.             _input.action.performed += _callback;
    101.             _enabled = true;
    102.         }
    103.  
    104.         public void Detach()
    105.         {
    106.             if(_enabled.Not()) return;
    107.             _input.action.performed -= _callback;
    108.             _enabled = false;
    109.         }
    110.  
    111.         public bool IsSame(IInputBinding binding)
    112.         {
    113.             return ReferenceEquals(this, binding);
    114.         }
    115.  
    116.         public InputAction GetAction()
    117.         {
    118.             return _input.action;
    119.         }
    120.  
    121.         public bool GetAction(InputAction action)
    122.         {
    123.             return _input.action.name == action.name;
    124.         }
    125.  
    126.         public bool HasSameAction(IInputBinding binding)
    127.         {
    128.             return false;
    129.         }
    130.  
    131.         public Action<InputAction.CallbackContext> GetCallback()
    132.         {
    133.             return _callback;
    134.         }
    135.     }
    136.  
    137.     public interface IInputBindingCollection
    138.     {
    139.         /// <summary>
    140.         /// Get the list of bindings in the collection
    141.         /// </summary>
    142.         /// <returns></returns>
    143.         IEnumerable<IInputBinding> GetBindings();
    144.  
    145.         /// <summary>
    146.         /// Does the collection detach previous bindings with same InputActions, default is true
    147.         /// </summary>
    148.         /// <returns></returns>
    149.         bool DoesDetachSameInput();
    150.  
    151.         /// <summary>
    152.         /// Bindings to toggle along the same inputs
    153.         /// </summary>
    154.         /// <returns></returns>
    155.         IEnumerable<IInputBinding> GetConflictingBindings();
    156.     }
    157.  
    158.     /// <summary>
    159.     /// A basic input binding collection
    160.     /// </summary>
    161.     public class InputBindingCollection : IInputBindingCollection
    162.     {
    163.         private readonly bool _overrideSame;
    164.         private readonly List<IInputBinding> _bindings;
    165.         private readonly List<IInputBinding> _conflictingBindings;
    166.  
    167.         public InputBindingCollection(IEnumerable<IInputBinding> bindings, IEnumerable<IInputBinding> conflictingBindings, bool overrideSame = true)
    168.         {
    169.             _bindings = new List<IInputBinding>(bindings);
    170.             _conflictingBindings = new List<IInputBinding>(conflictingBindings);
    171.             _overrideSame = overrideSame;
    172.         }
    173.  
    174.         public IEnumerable<IInputBinding> GetBindings()
    175.         {
    176.             return _bindings;
    177.         }
    178.  
    179.         public bool DoesDetachSameInput()
    180.         {
    181.             return _overrideSame;
    182.         }
    183.  
    184.         public IEnumerable<IInputBinding> GetConflictingBindings()
    185.         {
    186.             return _conflictingBindings;
    187.         }
    188.  
    189.     }
    190.  
    191.  
    192.  
    193.     /// <summary>
    194.     /// Contains a hierarchy of input collection,
    195.     /// collections can be enabled as overriding or complement the current binding scheme with push/pop operations.
    196.     /// </summary>
    197.     [Export(typeof(IInputBindingGraph))]
    198.     public class InputBindingGraph : IInputBindingGraph
    199.     {
    200.         private class BindingNode
    201.         {
    202.             public override string ToString()
    203.             {
    204.                 return Binding + (IsLeaf() ? "" : (">"+Child.Binding));
    205.             }
    206.  
    207.             /// <summary>
    208.             /// The parent override binding
    209.             /// </summary>
    210.             public BindingNode Child { get; private set; }
    211.  
    212.             public bool IsLeaf()
    213.             {
    214.                 return Child == null;
    215.             }
    216.  
    217.             /// <summary>
    218.             /// The binding overriding
    219.             /// </summary>
    220.             public IInputBinding Binding;
    221.  
    222.             /// <summary>
    223.             /// Was the binding enabled. Do not change binding state when popping and restoring parent.
    224.             /// </summary>
    225.             public bool WasEnabled;
    226.  
    227.             public BindingNode(IInputBinding binding)
    228.             {
    229.                 Binding = binding;
    230.             }
    231.  
    232.             /// <summary>
    233.             /// Set child and detach
    234.             /// </summary>
    235.             /// <param name="node"></param>
    236.             public void SetChild(BindingNode node)
    237.             {
    238.                 Binding.Detach();
    239.                 Child = node;
    240.  
    241.             }
    242.  
    243.             //Unset child and attach
    244.             public void UnsetChild()
    245.             {
    246.                 Binding.Attach();
    247.                 Child = null;
    248.             }
    249.  
    250.             /// <summary>
    251.             /// Is the current node child binding same as given one
    252.             /// </summary>
    253.             /// <param name="binding"></param>
    254.             /// <returns></returns>
    255.             public bool IsChildBindingSame(IInputBinding binding)
    256.             {
    257.                 return IsLeaf().Not() && Child.Binding.IsSame(binding);
    258.             }
    259.         }
    260.  
    261.  
    262.         private List<BindingNode> _bindingGraph = new List<BindingNode>();
    263.  
    264.         private IEnumerable<BindingNode> Leafs => _bindingGraph.Where(n => n.IsLeaf());
    265.        
    266.         public void AttachCollection(IInputBindingCollection bindings)
    267.         {
    268.             foreach (var conflictingBinding in bindings.GetConflictingBindings())
    269.             {
    270.                 conflictingBinding.Detach();
    271.             }
    272.  
    273.             foreach (var inputBinding in bindings.GetBindings())
    274.             {
    275.                 Attach(inputBinding);
    276.             }
    277.         }
    278.  
    279.         public void DetachCollection(IInputBindingCollection bindings)
    280.         {
    281.             foreach (var conflictingBinding in bindings.GetConflictingBindings())
    282.             {
    283.                 conflictingBinding.Attach();
    284.             }
    285.  
    286.             foreach (var inputBinding in bindings.GetBindings())
    287.             {
    288.                 Detach(inputBinding);
    289.             }
    290.         }
    291.  
    292.         public void Detach(IInputBinding binding)
    293.         {
    294.             //TODO Enhance Multiple iteration, although binding lists are often small and this is not called often
    295.             // can only detach a leaf;
    296.             var nodes = _bindingGraph.Where(l => l.Binding.IsSame(binding)).ToList();
    297.             if(nodes.None()){
    298.                 throw new InvalidOperationException($"Trying to Detach binding for <{binding}> but not present");
    299.             }
    300.             if (nodes.None(l => l.IsLeaf()))
    301.             {
    302.                 throw new InvalidOperationException($"Trying to Detach binding for <{binding}> but none is a leaf in <{nodes.ToDelimitedString(";")}>");
    303.             }
    304.             //Take all leaves of the graph
    305.             //if leaf is same as binding
    306.             var parents = _bindingGraph
    307.                          .Where(n => n.IsChildBindingSame(binding))
    308.                                        .ToList();
    309.            
    310.             binding.Detach();
    311.             _bindingGraph.RemoveAll(b => b.Binding.IsSame(binding));
    312.             parents.ForEach(p => p.UnsetChild());
    313.  
    314.         }
    315.  
    316.         public void Attach(IInputBinding binding)
    317.         {
    318.             var leafs = GetSameLeafBindings(binding).ToList();
    319.  
    320.             var bindingNode = new BindingNode(binding);
    321.             _bindingGraph.Add(bindingNode);
    322.  
    323.             leafs.ForEach(l =>
    324.             {
    325.                 l.SetChild(bindingNode);
    326.             });
    327.  
    328.             binding.Attach();
    329.         }
    330.  
    331.  
    332.         /// <summary>
    333.         /// Get all bindings with the same action, throws an error if the binding is already in the graph
    334.         /// </summary>
    335.         /// <param name="binding"></param>
    336.         /// <exception cref="InvalidOperationException">If the same leaf was already added to the graph</exception>
    337.         /// <returns></returns>
    338.         private IEnumerable<BindingNode> GetSameLeafBindings(IInputBinding binding)
    339.         {
    340.             _bindingGraph.SingleOptional(b => b.Binding.IsSame(binding))
    341.                          .IfPresent(b => throw new InvalidOperationException($"The same binding <{b}> for <{binding}> is already added to the graph. It was not detached yet"));
    342.  
    343.             return Leafs.Where(l => l.Binding.GetAction().name == binding.GetAction().name);
    344.         }
    345.     }