A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3
4////TODO: support ProcessEventsManually
5////TODO: add way to pick by player index
6
7// Some fields assigned through only through serialization.
8#pragma warning disable CS0649
9
10namespace UnityEngine.InputSystem.Samples
11{
12 /// <summary>
13 /// A component for debugging purposes that adds an on-screen display which shows
14 /// activity on an input action over time (<see cref="InputActionVisualizer.Visualization.Interaction"/>)
15 /// or an action's current value (<see cref="InputActionVisualizer.Visualization.Value"/>).
16 /// </summary>
17 /// <seealso cref="InputControlVisualizer"/>
18 [AddComponentMenu("Input/Debug/Input Action Visualizer")]
19 [ExecuteInEditMode]
20 public class InputActionVisualizer : InputVisualizer
21 {
22 /// <summary>
23 /// The action that is being visualized. May be null.
24 /// </summary>
25 public InputAction action => m_Action;
26
27 protected void FixedUpdate()
28 {
29 if (m_Visualization != Visualization.Value || m_Action == null || m_Visualizer == null)
30 return;
31 if (InputSystem.settings.updateMode != InputSettings.UpdateMode.ProcessEventsInFixedUpdate)
32 return;
33 RecordValue(Time.fixedTime);
34 }
35
36 protected void Update()
37 {
38 if (m_Visualization != Visualization.Value || m_Action == null || m_Visualizer == null)
39 return;
40 if (InputSystem.settings.updateMode != InputSettings.UpdateMode.ProcessEventsInDynamicUpdate)
41 return;
42 RecordValue(Time.time);
43 }
44
45 protected new void OnEnable()
46 {
47 if (m_Visualization == Visualization.None)
48 return;
49
50 base.OnEnable();
51
52 ResolveAction();
53 SetupVisualizer();
54
55 if (s_EnabledInstances == null)
56 s_EnabledInstances = new List<InputActionVisualizer>();
57 if (s_EnabledInstances.Count == 0)
58 InputSystem.onActionChange += OnActionChange;
59 s_EnabledInstances.Add(this);
60 }
61
62 protected new void OnDisable()
63 {
64 base.OnDisable();
65
66 if (s_EnabledInstances != null)
67 {
68 s_EnabledInstances.Remove(this);
69 if (s_EnabledInstances.Count == 0)
70 InputSystem.onActionChange -= OnActionChange;
71 }
72
73 if (m_Visualization == Visualization.Interaction && m_Action != null)
74 {
75 m_Action.started -= OnActionTriggered;
76 m_Action.performed -= OnActionTriggered;
77 m_Action.canceled -= OnActionTriggered;
78 }
79 }
80
81 protected new void OnGUI()
82 {
83 if (m_Visualization == Visualization.None)
84 return;
85
86 if (Event.current.type != EventType.Repaint)
87 return;
88
89 base.OnGUI();
90
91 if (m_ShowControlName && m_ActiveControlName != null)
92 VisualizationHelpers.DrawText(m_ActiveControlName, new Vector2(m_Rect.x, m_Rect.yMax),
93 VisualizationHelpers.ValueTextStyle);
94 }
95
96 private void RecordValue(double time)
97 {
98 Debug.Assert(m_Action != null);
99 Debug.Assert(m_Visualizer != null);
100
101 var value = m_Action.ReadValueAsObject();
102 m_Visualizer.AddSample(value, time);
103
104 if (m_ShowControlName)
105 RecordControlName();
106 }
107
108 private void RecordControlName()
109 {
110 var control = m_Action.activeControl;
111 if (control == m_ActiveControl)
112 return;
113
114 m_ActiveControl = control;
115 m_ActiveControlName = control != null ? new GUIContent(control.path) : null;
116 }
117
118 private void ResolveAction()
119 {
120 // If we have a reference to an action, try that first.
121 if (m_ActionReference != null)
122 m_Action = m_ActionReference.action;
123
124 // If we didn't get an action from that but we have an action name,
125 // just search through the currently enabled actions for one that
126 // matches by name.
127 if (m_Action == null && !string.IsNullOrEmpty(m_ActionName))
128 {
129 var slashIndex = m_ActionName.IndexOf('/');
130 var mapName = slashIndex != -1 ? m_ActionName.Substring(0, slashIndex) : null;
131 var actionName = slashIndex != -1 ? m_ActionName.Substring(slashIndex + 1) : m_ActionName;
132
133 var enabledActions = InputSystem.ListEnabledActions();
134 foreach (var action in enabledActions)
135 {
136 if (string.Compare(actionName, action.name, StringComparison.InvariantCultureIgnoreCase) != 0)
137 continue;
138
139 if (mapName != null && action.actionMap != null && string.Compare(mapName, action.actionMap.name,
140 StringComparison.InvariantCultureIgnoreCase) != 0)
141 continue;
142
143 m_Action = action;
144 break;
145 }
146 }
147
148 // If we still don't have an action, there's nothing much for us to do.
149 // The action may show up at a later point.
150 if (m_Action == null)
151 return;
152
153 if (m_Visualization == Visualization.Interaction)
154 {
155 m_Action.performed += OnActionTriggered;
156 m_Action.started += OnActionTriggered;
157 m_Action.canceled += OnActionTriggered;
158 }
159 }
160
161 private void SetupVisualizer()
162 {
163 m_Visualizer = null;
164 if (m_Action == null)
165 return;
166
167 switch (m_Visualization)
168 {
169 case Visualization.Value:
170 switch (m_Action.type)
171 {
172 case InputActionType.Button:
173 m_Visualizer = new VisualizationHelpers.ScalarVisualizer<float>
174 {
175 limitMax = 1
176 };
177 break;
178
179 case InputActionType.Value:
180 case InputActionType.PassThrough:
181 if (!string.IsNullOrEmpty(m_Action.expectedControlType))
182 {
183 var layout = InputSystem.LoadLayout(m_Action.expectedControlType);
184 if (layout != null)
185 {
186 var valueType = layout.GetValueType();
187 if (valueType == typeof(float))
188 m_Visualizer = new VisualizationHelpers.ScalarVisualizer<float>
189 {
190 limitMax = 1
191 };
192 else if (valueType == typeof(int))
193 m_Visualizer = new VisualizationHelpers.ScalarVisualizer<int>
194 {
195 limitMax = 1
196 };
197 else if (valueType == typeof(Vector2))
198 m_Visualizer = new VisualizationHelpers.Vector2Visualizer();
199 }
200 }
201 break;
202 }
203 break;
204
205 case Visualization.Interaction:
206 // We don't really know which interactions are sitting on the action and its bindings
207 // and while we could do and perform work to find out, it's simpler to just wait until
208 // we get input and then whatever interactions we encounter as we go along. Also keeps
209 // the visualization a little less cluttered.
210 m_Visualizer = new VisualizationHelpers.TimelineVisualizer();
211 break;
212 }
213 }
214
215 private void OnActionDisabled()
216 {
217 }
218
219 private void OnActionTriggered(InputAction.CallbackContext context)
220 {
221 Debug.Assert(m_Visualization == Visualization.Interaction);
222
223 var timelineName = "Default";
224 var interaction = context.interaction;
225 if (interaction != null)
226 {
227 timelineName = interaction.GetType().Name;
228 if (timelineName.EndsWith("Interaction"))
229 timelineName = timelineName.Substring(0, timelineName.Length - "Interaction".Length);
230 }
231
232 var visualizer = (VisualizationHelpers.TimelineVisualizer)m_Visualizer;
233 var timelineIndex = visualizer.GetTimeline(timelineName);
234 if (timelineIndex == -1)
235 {
236 Color color;
237 timelineIndex = visualizer.timelineCount;
238 if (timelineIndex < s_InteractionColors.Length)
239 color = s_InteractionColors[timelineIndex];
240 else
241 color = new Color(Random.value, Random.value, Random.value, 1);
242
243 visualizer.AddTimeline(timelineName, color);
244 if (timelineIndex > 0)
245 visualizer.showLegend = true;
246 }
247
248 var time = (float)context.time;
249 switch (context.phase)
250 {
251 case InputActionPhase.Canceled:
252 visualizer.AddSample(timelineIndex, 0f, time);
253 break;
254
255 case InputActionPhase.Performed:
256 visualizer.AddSample(timelineIndex, 1f, time);
257 visualizer.AddSample(timelineIndex, 0f, time);
258 break;
259
260 case InputActionPhase.Started:
261 visualizer.AddSample(timelineIndex, 0.5f, time);
262 break;
263 }
264
265 if (m_ShowControlName)
266 RecordControlName();
267 }
268
269 private static void OnActionChange(object actionOrMap, InputActionChange change)
270 {
271 switch (change)
272 {
273 case InputActionChange.ActionEnabled:
274 case InputActionChange.ActionMapEnabled:
275 for (var i = 0; i < s_EnabledInstances.Count; ++i)
276 if (s_EnabledInstances[i].m_Action == null)
277 {
278 s_EnabledInstances[i].ResolveAction();
279 if (s_EnabledInstances[i].m_Action != null)
280 s_EnabledInstances[i].SetupVisualizer();
281 }
282 break;
283
284 case InputActionChange.ActionDisabled:
285 for (var i = 0; i < s_EnabledInstances.Count; ++i)
286 if (actionOrMap == s_EnabledInstances[i].m_Action)
287 s_EnabledInstances[i].OnActionDisabled();
288 break;
289
290 case InputActionChange.ActionMapDisabled:
291 for (var i = 0; i < s_EnabledInstances.Count; ++i)
292 if (s_EnabledInstances[i].m_Action?.actionMap == actionOrMap)
293 s_EnabledInstances[i].OnActionDisabled();
294 break;
295 }
296 }
297
298 [SerializeField] private Visualization m_Visualization;
299 [SerializeField] private InputActionReference m_ActionReference;
300 [SerializeField] private string m_ActionName;
301 [SerializeField] private bool m_ShowControlName;
302
303 [NonSerialized] private InputAction m_Action;
304 [NonSerialized] private InputControl m_ActiveControl;
305 [NonSerialized] private GUIContent m_ActiveControlName;
306
307 private static List<InputActionVisualizer> s_EnabledInstances;
308 private static readonly Color[] s_InteractionColors =
309 {
310 new Color(1, 0, 0, 1),
311 new Color(0, 0, 1, 1),
312 new Color(1, 1, 0, 1),
313 new Color(1, 0, 1, 1),
314 new Color(0, 1, 1, 1),
315 new Color(0, 1, 0, 1),
316 };
317
318 public enum Visualization
319 {
320 None,
321 Value,
322 Interaction,
323 }
324 }
325}