InputActionVisualizer.cs 12 KB

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