Skip to content

Commit e8abd1c

Browse files
authored
FIX: ISXB-1524, ISXB-1588, ISXB-1593 Rebinding and rebinding sample related bug fixes and updated sample (#2168)
- CHANGED: Expanded `RebindingUISample` to include a "game mode" state and a "menu state" to be more similar to a real game. ISX-2325 - NEW: Added a new fluent API `WithSuppressedActionPropagation()` to `UnityEngine.InputSystem.InputActionRebindingExtensions` that allows suppressing actions from firing during interactive rebinding while allowing state updates to avoid actions triggering after state event suppression (default). ISXB-1546. - NEW: Added a new Monobehavior `InputActionLabel` to rebinding sample to allow dynamic text showing relevant binding for an `InputAction`. - FIX: Fixed an issue in `RebindingUISample` that fired actions bound to the same control as the target control in a rebinding process. ISXB-1524. - FIX: Fixed an issue in `RebindActionUI` which resulted in active binding not being shown after a scene reload. ISXB-1588. - FIX: Fixed an issue in `GamepadIconExample` which resulted in icons for left and right triggers not being displayed after a rebind to the exact same controls. ISXB-1593.
1 parent 775282e commit e8abd1c

File tree

71 files changed

+20836
-1359
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+20836
-1359
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using UnityEngine.Events;
4+
using UnityEngine.UI;
5+
6+
namespace UnityEngine.InputSystem.Samples.RebindUI
7+
{
8+
public class ActionLabel : MonoBehaviour
9+
{
10+
[Tooltip("Reference to action that is to be rebound from the UI.")]
11+
[SerializeField]
12+
private InputActionReference m_Action;
13+
14+
[SerializeField]
15+
private string m_BindingId;
16+
17+
[SerializeField]
18+
private InputBinding.DisplayStringOptions m_DisplayStringOptions;
19+
20+
[Tooltip("Text label that will receive the current, formatted binding string.")]
21+
[SerializeField]
22+
private Text m_BindingText;
23+
24+
[Tooltip("Event that is triggered when the way the binding is display should be updated. This allows displaying "
25+
+ "bindings in custom ways, e.g. using images instead of text.")]
26+
[SerializeField]
27+
private UpdateBindingUIEvent m_UpdateBindingUIEvent;
28+
29+
private static List<ActionLabel> s_InputActionUIs;
30+
31+
/// <summary>
32+
/// Reference to the action that is to be rebound.
33+
/// </summary>
34+
public InputActionReference actionReference
35+
{
36+
get => m_Action;
37+
set
38+
{
39+
m_Action = value;
40+
UpdateBindingDisplay();
41+
}
42+
}
43+
44+
/// <summary>
45+
/// ID (in string form) of the binding that is to be rebound on the action.
46+
/// </summary>
47+
/// <seealso cref="InputBinding.id"/>
48+
public string bindingId
49+
{
50+
get => m_BindingId;
51+
set
52+
{
53+
m_BindingId = value;
54+
UpdateBindingDisplay();
55+
}
56+
}
57+
58+
/// <summary>
59+
/// Text component that receives the display string of the binding. Can be <c>null</c> in which
60+
/// case the component entirely relies on <see cref="updateBindingUIEvent"/>.
61+
/// </summary>
62+
public Text bindingText
63+
{
64+
get => m_BindingText;
65+
set
66+
{
67+
m_BindingText = value;
68+
UpdateBindingDisplay();
69+
}
70+
}
71+
72+
/// <summary>
73+
/// Display options for the binding.
74+
/// </summary>
75+
public InputBinding.DisplayStringOptions displayStringOptions
76+
{
77+
get => m_DisplayStringOptions;
78+
set
79+
{
80+
m_DisplayStringOptions = value;
81+
UpdateBindingDisplay();
82+
}
83+
}
84+
85+
/// <summary>
86+
/// Trigger a refresh of the currently displayed binding.
87+
/// </summary>
88+
public void UpdateBindingDisplay()
89+
{
90+
var displayString = string.Empty;
91+
var deviceLayoutName = default(string);
92+
var controlPath = default(string);
93+
94+
// Get display string from action.
95+
var action = m_Action?.action;
96+
if (action != null)
97+
{
98+
var bindingIndex = action.bindings.IndexOf(x => x.id.ToString() == m_BindingId);
99+
if (bindingIndex != -1)
100+
displayString = action.GetBindingDisplayString(bindingIndex, out deviceLayoutName, out controlPath, displayStringOptions);
101+
}
102+
103+
// Set on label (if any).
104+
if (m_BindingText != null)
105+
m_BindingText.text = displayString;
106+
107+
// Give listeners a chance to configure UI in response.
108+
m_UpdateBindingUIEvent?.Invoke(this, displayString, deviceLayoutName, controlPath);
109+
}
110+
111+
protected void OnEnable()
112+
{
113+
if (s_InputActionUIs == null)
114+
s_InputActionUIs = new List<ActionLabel>();
115+
s_InputActionUIs.Add(this);
116+
if (s_InputActionUIs.Count == 1)
117+
InputSystem.onActionChange += OnActionChange;
118+
UpdateBindingDisplay();
119+
}
120+
121+
protected void OnDisable()
122+
{
123+
s_InputActionUIs.Remove(this);
124+
if (s_InputActionUIs.Count == 0)
125+
{
126+
s_InputActionUIs = null;
127+
InputSystem.onActionChange -= OnActionChange;
128+
}
129+
}
130+
131+
// When the action system re-resolves bindings, we want to update our UI in response
132+
// to show the currently relevant binding.
133+
private static void OnActionChange(object obj, InputActionChange change)
134+
{
135+
if (change != InputActionChange.BoundControlsChanged)
136+
return;
137+
138+
var action = obj as InputAction;
139+
var actionMap = action?.actionMap ?? obj as InputActionMap;
140+
var actionAsset = actionMap?.asset ?? obj as InputActionAsset;
141+
142+
for (var i = 0; i < s_InputActionUIs.Count; ++i)
143+
{
144+
var component = s_InputActionUIs[i];
145+
var referencedAction = component.actionReference?.action;
146+
if (referencedAction == null)
147+
continue;
148+
149+
if (referencedAction == action ||
150+
referencedAction.actionMap == actionMap ||
151+
referencedAction.actionMap?.asset == actionAsset)
152+
component.UpdateBindingDisplay();
153+
}
154+
}
155+
}
156+
157+
[Serializable]
158+
public class UpdateBindingUIEvent : UnityEvent<ActionLabel, string, string, string>
159+
{
160+
}
161+
}

Assets/Samples/RebindingUI/ActionLabel.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#if UNITY_EDITOR
2+
using System.Linq;
3+
using UnityEditor;
4+
5+
////TODO: support multi-object editing
6+
7+
namespace UnityEngine.InputSystem.Samples.RebindUI
8+
{
9+
/// <summary>
10+
/// A custom inspector for <see cref="RebindActionUI"/> which provides a more convenient way for
11+
/// picking the binding which to rebind.
12+
/// </summary>
13+
[CustomEditor(typeof(ActionLabel))]
14+
public class ActionLabelEditor : UnityEditor.Editor
15+
{
16+
protected void OnEnable()
17+
{
18+
m_ActionProperty = serializedObject.FindProperty("m_Action");
19+
m_BindingIdProperty = serializedObject.FindProperty("m_BindingId");
20+
m_BindingTextProperty = serializedObject.FindProperty("m_BindingText");
21+
m_DisplayStringOptionsProperty = serializedObject.FindProperty("m_DisplayStringOptions");
22+
m_UpdateBindingUIEventProperty = serializedObject.FindProperty("m_UpdateBindingUIEvent");
23+
24+
RefreshBindingOptions();
25+
}
26+
27+
public override void OnInspectorGUI()
28+
{
29+
EditorGUI.BeginChangeCheck();
30+
31+
// Binding section.
32+
EditorGUILayout.LabelField(m_BindingLabel, Styles.boldLabel);
33+
using (new EditorGUI.IndentLevelScope())
34+
{
35+
EditorGUILayout.PropertyField(m_ActionProperty);
36+
37+
var newSelectedBinding = EditorGUILayout.Popup(m_BindingLabel, m_SelectedBindingOption, m_BindingOptions);
38+
if (newSelectedBinding != m_SelectedBindingOption)
39+
{
40+
var bindingId = m_BindingOptionValues[newSelectedBinding];
41+
m_BindingIdProperty.stringValue = bindingId;
42+
m_SelectedBindingOption = newSelectedBinding;
43+
}
44+
45+
var optionsOld = (InputBinding.DisplayStringOptions)m_DisplayStringOptionsProperty.intValue;
46+
var optionsNew = (InputBinding.DisplayStringOptions)EditorGUILayout.EnumFlagsField(m_DisplayOptionsLabel, optionsOld);
47+
if (optionsOld != optionsNew)
48+
m_DisplayStringOptionsProperty.intValue = (int)optionsNew;
49+
}
50+
51+
// UI section.
52+
EditorGUILayout.Space();
53+
EditorGUILayout.LabelField(m_UILabel, Styles.boldLabel);
54+
using (new EditorGUI.IndentLevelScope())
55+
{
56+
EditorGUILayout.PropertyField(m_BindingTextProperty);
57+
}
58+
59+
// Events section.
60+
EditorGUILayout.Space();
61+
EditorGUILayout.LabelField(m_EventsLabel, Styles.boldLabel);
62+
using (new EditorGUI.IndentLevelScope())
63+
{
64+
EditorGUILayout.PropertyField(m_UpdateBindingUIEventProperty);
65+
}
66+
67+
if (EditorGUI.EndChangeCheck())
68+
{
69+
serializedObject.ApplyModifiedProperties();
70+
RefreshBindingOptions();
71+
}
72+
}
73+
74+
protected void RefreshBindingOptions()
75+
{
76+
var actionReference = (InputActionReference)m_ActionProperty.objectReferenceValue;
77+
var action = actionReference?.action;
78+
79+
if (action == null)
80+
{
81+
m_BindingOptions = new GUIContent[0];
82+
m_BindingOptionValues = new string[0];
83+
m_SelectedBindingOption = -1;
84+
return;
85+
}
86+
87+
var bindings = action.bindings;
88+
var bindingCount = bindings.Count;
89+
90+
m_BindingOptions = new GUIContent[bindingCount];
91+
m_BindingOptionValues = new string[bindingCount];
92+
m_SelectedBindingOption = -1;
93+
94+
var currentBindingId = m_BindingIdProperty.stringValue;
95+
for (var i = 0; i < bindingCount; ++i)
96+
{
97+
var binding = bindings[i];
98+
var bindingId = binding.id.ToString();
99+
var haveBindingGroups = !string.IsNullOrEmpty(binding.groups);
100+
101+
// If we don't have a binding groups (control schemes), show the device that if there are, for example,
102+
// there are two bindings with the display string "A", the user can see that one is for the keyboard
103+
// and the other for the gamepad.
104+
var displayOptions =
105+
InputBinding.DisplayStringOptions.DontUseShortDisplayNames | InputBinding.DisplayStringOptions.IgnoreBindingOverrides;
106+
if (!haveBindingGroups)
107+
displayOptions |= InputBinding.DisplayStringOptions.DontOmitDevice;
108+
109+
// Create display string.
110+
var displayString = action.GetBindingDisplayString(i, displayOptions);
111+
112+
// If binding is part of a composite, include the part name.
113+
if (binding.isPartOfComposite)
114+
displayString = $"{ObjectNames.NicifyVariableName(binding.name)}: {displayString}";
115+
116+
// Some composites use '/' as a separator. When used in popup, this will lead to to submenus. Prevent
117+
// by instead using a backlash.
118+
displayString = displayString.Replace('/', '\\');
119+
120+
// If the binding is part of control schemes, mention them.
121+
if (haveBindingGroups)
122+
{
123+
var asset = action.actionMap?.asset;
124+
if (asset != null)
125+
{
126+
var controlSchemes = string.Join(", ",
127+
binding.groups.Split(InputBinding.Separator)
128+
.Select(x => asset.controlSchemes.FirstOrDefault(c => c.bindingGroup == x).name));
129+
130+
displayString = $"{displayString} ({controlSchemes})";
131+
}
132+
}
133+
134+
m_BindingOptions[i] = new GUIContent(displayString);
135+
m_BindingOptionValues[i] = bindingId;
136+
137+
if (currentBindingId == bindingId)
138+
m_SelectedBindingOption = i;
139+
}
140+
}
141+
142+
private SerializedProperty m_ActionProperty;
143+
private SerializedProperty m_BindingIdProperty;
144+
private SerializedProperty m_BindingTextProperty;
145+
private SerializedProperty m_UpdateBindingUIEventProperty;
146+
private SerializedProperty m_DisplayStringOptionsProperty;
147+
148+
private GUIContent m_BindingLabel = new GUIContent("Binding");
149+
private GUIContent m_DisplayOptionsLabel = new GUIContent("Display Options");
150+
private GUIContent m_UILabel = new GUIContent("UI");
151+
private GUIContent m_EventsLabel = new GUIContent("Events");
152+
private GUIContent[] m_BindingOptions;
153+
private string[] m_BindingOptionValues;
154+
private int m_SelectedBindingOption;
155+
156+
private static class Styles
157+
{
158+
public static GUIStyle boldLabel = new GUIStyle("MiniBoldLabel");
159+
}
160+
}
161+
}
162+
#endif

Assets/Samples/RebindingUI/ActionLabelEditor.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using UnityEngine.EventSystems;
2+
3+
namespace UnityEngine.InputSystem.Samples.RebindUI
4+
{
5+
/// <summary>
6+
/// Simple utility that modifies a referenced CanvasGroup while being active.
7+
/// </summary>
8+
public class CanvasGroupModifier : MonoBehaviour
9+
{
10+
[Tooltip("The Canvas Group to be modified while this component is active")]
11+
public CanvasGroup canvasGroup;
12+
13+
[Tooltip("The interactable setting to use for the Canvas Group while this component is active")]
14+
public bool interactable = false;
15+
16+
private bool m_SavedInteractable;
17+
private GameObject m_SelectedObject;
18+
19+
private void OnEnable()
20+
{
21+
if (canvasGroup != null)
22+
{
23+
// Store selection to make sure it is not changed when switching "windows".
24+
m_SelectedObject = EventSystem.current.currentSelectedGameObject;
25+
26+
// Save current setting and override
27+
m_SavedInteractable = canvasGroup.interactable;
28+
canvasGroup.interactable = interactable;
29+
}
30+
}
31+
32+
private void OnDisable()
33+
{
34+
if (canvasGroup != null)
35+
{
36+
// Restore previous setting.
37+
canvasGroup.interactable = m_SavedInteractable;
38+
39+
// Restore previous selection.
40+
var eventSystem = EventSystem.current;
41+
if (eventSystem != null)
42+
{
43+
if (m_SelectedObject != null)
44+
eventSystem.SetSelectedGameObject(m_SelectedObject);
45+
else if (EventSystem.current.currentSelectedGameObject == null)
46+
eventSystem.SetSelectedGameObject(eventSystem.firstSelectedGameObject);
47+
}
48+
}
49+
}
50+
}
51+
}

Assets/Samples/RebindingUI/CanvasGroupModifier.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Samples/RebindingUI/Game.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)