暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

OnScreenControl.cs 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. using System;
  2. using Unity.Collections;
  3. using UnityEngine.InputSystem.Layouts;
  4. using UnityEngine.InputSystem.LowLevel;
  5. using UnityEngine.InputSystem.Utilities;
  6. ////REVIEW: should we make this ExecuteInEditMode?
  7. ////TODO: handle display strings for this in some form; shouldn't display generic gamepad binding strings, for example, for OSCs
  8. ////TODO: give more control over when an OSC creates a new devices; going simply by name of layout only is inflexible
  9. ////TODO: make this survive domain reloads
  10. ////TODO: allow feeding into more than one control
  11. namespace UnityEngine.InputSystem.OnScreen
  12. {
  13. /// <summary>
  14. /// Base class for on-screen controls.
  15. /// </summary>
  16. /// <remarks>
  17. /// The set of on-screen controls together forms a device. A control layout
  18. /// is automatically generated from the set and a device using the layout is
  19. /// added to the system when the on-screen controls are enabled.
  20. ///
  21. /// The layout that the generated layout is based on is determined by the
  22. /// control paths chosen for each on-screen control. If, for example, an
  23. /// on-screen control chooses the 'a' key from the "Keyboard" layout as its
  24. /// path, a device layout is generated that is based on the "Keyboard" layout
  25. /// and the on-screen control becomes the 'a' key in that layout.
  26. ///
  27. /// If a <see cref="GameObject"/> has multiple on-screen controls that reference different
  28. /// types of device layouts (e.g. one control references 'buttonWest' on
  29. /// a gamepad and another references 'leftButton' on a mouse), then a device
  30. /// is created for each type referenced by the setup.
  31. /// </remarks>
  32. public abstract class OnScreenControl : MonoBehaviour
  33. {
  34. /// <summary>
  35. /// The control path (see <see cref="InputControlPath"/>) for the control that the on-screen
  36. /// control will feed input into.
  37. /// </summary>
  38. /// <remarks>
  39. /// A device will be created from the device layout referenced by the control path (see
  40. /// <see cref="InputControlPath.TryGetDeviceLayout"/>). The path is then used to look up
  41. /// <see cref="control"/> on the device. The resulting control will be fed values from
  42. /// the on-screen control.
  43. ///
  44. /// Multiple on-screen controls sharing the same device layout will together create a single
  45. /// virtual device. If, for example, one component uses <c>"&lt;Gamepad&gt;/buttonSouth"</c>
  46. /// and another uses <c>"&lt;Gamepad&gt;/leftStick"</c> as the control path, a single
  47. /// <see cref="Gamepad"/> will be created and the first component will feed data to
  48. /// <see cref="Gamepad.buttonSouth"/> and the second component will feed data to
  49. /// <see cref="Gamepad.leftStick"/>.
  50. /// </remarks>
  51. /// <seealso cref="InputControlPath"/>
  52. public string controlPath
  53. {
  54. get => controlPathInternal;
  55. set
  56. {
  57. controlPathInternal = value;
  58. if (isActiveAndEnabled)
  59. SetupInputControl();
  60. }
  61. }
  62. /// <summary>
  63. /// The actual control that is fed input from the on-screen control.
  64. /// </summary>
  65. /// <remarks>
  66. /// This is only valid while the on-screen control is enabled. Otherwise, it is <c>null</c>. Also,
  67. /// if no <see cref="controlPath"/> has been set, this will remain <c>null</c> even if the component is enabled.
  68. /// </remarks>
  69. public InputControl control => m_Control;
  70. private InputControl m_Control;
  71. private OnScreenControl m_NextControlOnDevice;
  72. private InputEventPtr m_InputEventPtr;
  73. /// <summary>
  74. /// Accessor for the <see cref="controlPath"/> of the component. Must be implemented by subclasses.
  75. /// </summary>
  76. /// <remarks>
  77. /// Moving the definition of how the control path is stored into subclasses allows them to
  78. /// apply their own <see cref="InputControlAttribute"/> attributes to them and thus set their
  79. /// own layout filters.
  80. /// </remarks>
  81. protected abstract string controlPathInternal { get; set; }
  82. private void SetupInputControl()
  83. {
  84. Debug.Assert(m_Control == null, "InputControl already initialized");
  85. Debug.Assert(m_NextControlOnDevice == null, "Previous InputControl has not been properly uninitialized (m_NextControlOnDevice still set)");
  86. Debug.Assert(!m_InputEventPtr.valid, "Previous InputControl has not been properly uninitialized (m_InputEventPtr still set)");
  87. // Nothing to do if we don't have a control path.
  88. var path = controlPathInternal;
  89. if (string.IsNullOrEmpty(path))
  90. return;
  91. // Determine what type of device to work with.
  92. var layoutName = InputControlPath.TryGetDeviceLayout(path);
  93. if (layoutName == null)
  94. {
  95. Debug.LogError(
  96. $"Cannot determine device layout to use based on control path '{path}' used in {GetType().Name} component",
  97. this);
  98. return;
  99. }
  100. // Try to find existing on-screen device that matches.
  101. var internedLayoutName = new InternedString(layoutName);
  102. var deviceInfoIndex = -1;
  103. for (var i = 0; i < s_OnScreenDevices.length; ++i)
  104. {
  105. ////FIXME: this does not take things such as different device usages into account
  106. if (s_OnScreenDevices[i].device.m_Layout == internedLayoutName)
  107. {
  108. deviceInfoIndex = i;
  109. break;
  110. }
  111. }
  112. // If we don't have a matching one, create a new one.
  113. InputDevice device;
  114. if (deviceInfoIndex == -1)
  115. {
  116. // Try to create device.
  117. try
  118. {
  119. device = InputSystem.AddDevice(layoutName);
  120. }
  121. catch (Exception exception)
  122. {
  123. Debug.LogError(
  124. $"Could not create device with layout '{layoutName}' used in '{GetType().Name}' component");
  125. Debug.LogException(exception);
  126. return;
  127. }
  128. InputSystem.AddDeviceUsage(device, "OnScreen");
  129. // Create event buffer.
  130. var buffer = StateEvent.From(device, out var eventPtr, Allocator.Persistent);
  131. // Add to list.
  132. deviceInfoIndex = s_OnScreenDevices.Append(new OnScreenDeviceInfo
  133. {
  134. eventPtr = eventPtr,
  135. buffer = buffer,
  136. device = device,
  137. });
  138. }
  139. else
  140. {
  141. device = s_OnScreenDevices[deviceInfoIndex].device;
  142. }
  143. // Try to find control on device.
  144. m_Control = InputControlPath.TryFindControl(device, path);
  145. if (m_Control == null)
  146. {
  147. Debug.LogError(
  148. $"Cannot find control with path '{path}' on device of type '{layoutName}' referenced by component '{GetType().Name}'",
  149. this);
  150. // Remove the device, if we just created one.
  151. if (s_OnScreenDevices[deviceInfoIndex].firstControl == null)
  152. {
  153. s_OnScreenDevices[deviceInfoIndex].Destroy();
  154. s_OnScreenDevices.RemoveAt(deviceInfoIndex);
  155. }
  156. return;
  157. }
  158. m_InputEventPtr = s_OnScreenDevices[deviceInfoIndex].eventPtr;
  159. // We have all we need. Permanently add us.
  160. s_OnScreenDevices[deviceInfoIndex] =
  161. s_OnScreenDevices[deviceInfoIndex].AddControl(this);
  162. }
  163. protected void SendValueToControl<TValue>(TValue value)
  164. where TValue : struct
  165. {
  166. if (m_Control == null)
  167. return;
  168. if (!(m_Control is InputControl<TValue> control))
  169. throw new ArgumentException(
  170. $"The control path {controlPath} yields a control of type {m_Control.GetType().Name} which is not an InputControl with value type {typeof(TValue).Name}", nameof(value));
  171. ////FIXME: this gives us a one-frame lag (use InputState.Change instead?)
  172. m_InputEventPtr.internalTime = InputRuntime.s_Instance.currentTime;
  173. control.WriteValueIntoEvent(value, m_InputEventPtr);
  174. InputSystem.QueueEvent(m_InputEventPtr);
  175. }
  176. protected void SentDefaultValueToControl()
  177. {
  178. if (m_Control == null)
  179. return;
  180. ////FIXME: this gives us a one-frame lag (use InputState.Change instead?)
  181. m_InputEventPtr.internalTime = InputRuntime.s_Instance.currentTime;
  182. m_Control.ResetToDefaultStateInEvent(m_InputEventPtr);
  183. InputSystem.QueueEvent(m_InputEventPtr);
  184. }
  185. protected virtual void OnEnable()
  186. {
  187. SetupInputControl();
  188. }
  189. protected virtual void OnDisable()
  190. {
  191. if (m_Control == null)
  192. return;
  193. var device = m_Control.device;
  194. for (var i = 0; i < s_OnScreenDevices.length; ++i)
  195. {
  196. if (s_OnScreenDevices[i].device != device)
  197. continue;
  198. var deviceInfo = s_OnScreenDevices[i].RemoveControl(this);
  199. if (deviceInfo.firstControl == null)
  200. {
  201. // We're the last on-screen control on this device. Remove the device.
  202. s_OnScreenDevices[i].Destroy();
  203. s_OnScreenDevices.RemoveAt(i);
  204. }
  205. else
  206. {
  207. s_OnScreenDevices[i] = deviceInfo;
  208. // We're keeping the device but we're disabling the on-screen representation
  209. // for one of its controls. If the control isn't in default state, reset it
  210. // to that now. This is what ensures that if, for example, OnScreenButton is
  211. // disabled after OnPointerDown, we reset its button control to zero even
  212. // though we will not see an OnPointerUp.
  213. if (!m_Control.CheckStateIsAtDefault())
  214. SentDefaultValueToControl();
  215. }
  216. m_Control = null;
  217. m_InputEventPtr = new InputEventPtr();
  218. Debug.Assert(m_NextControlOnDevice == null);
  219. break;
  220. }
  221. }
  222. private struct OnScreenDeviceInfo
  223. {
  224. public InputEventPtr eventPtr;
  225. public NativeArray<byte> buffer;
  226. public InputDevice device;
  227. public OnScreenControl firstControl;
  228. public OnScreenDeviceInfo AddControl(OnScreenControl control)
  229. {
  230. control.m_NextControlOnDevice = firstControl;
  231. firstControl = control;
  232. return this;
  233. }
  234. public OnScreenDeviceInfo RemoveControl(OnScreenControl control)
  235. {
  236. if (firstControl == control)
  237. firstControl = control.m_NextControlOnDevice;
  238. else
  239. {
  240. for (OnScreenControl current = firstControl.m_NextControlOnDevice, previous = firstControl;
  241. current != null; previous = current, current = current.m_NextControlOnDevice)
  242. {
  243. if (current != control)
  244. continue;
  245. previous.m_NextControlOnDevice = current.m_NextControlOnDevice;
  246. break;
  247. }
  248. }
  249. control.m_NextControlOnDevice = null;
  250. return this;
  251. }
  252. public void Destroy()
  253. {
  254. if (buffer.IsCreated)
  255. buffer.Dispose();
  256. if (device != null)
  257. InputSystem.RemoveDevice(device);
  258. device = null;
  259. buffer = new NativeArray<byte>();
  260. }
  261. }
  262. private static InlinedArray<OnScreenDeviceInfo> s_OnScreenDevices;
  263. }
  264. }