Нет описания
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. using System;
  2. using Unity.Collections.LowLevel.Unsafe;
  3. using UnityEngine.InputSystem.Controls;
  4. using UnityEngine.InputSystem.LowLevel;
  5. using UnityEngine.InputSystem.Utilities;
  6. #if UNITY_EDITOR
  7. using UnityEditor;
  8. using UnityEngine.InputSystem.Editor;
  9. #endif
  10. ////TODO: add pressure support
  11. ////REVIEW: extend this beyond simulating from Pointers only? theoretically, we could simulate from any means of generating positions and presses
  12. ////REVIEW: I think this is a workable first attempt but overall, not a sufficient take on input simulation. ATM this uses InputState.Change
  13. //// to shove input directly into Touchscreen. Also, it uses state change notifications to set off the simulation. The latter leads
  14. //// to touch input potentially changing multiple times in response to a single pointer event. And the former leads to the simulated
  15. //// touch input not being visible at the event level -- which leaves Touch and Finger slightly unhappy, for example.
  16. //// I think being able to cycle simulated input fully through the event loop would result in a setup that is both simpler and more robust.
  17. //// Also, it would allow *disabling* the source devices as long as we don't disable them in the backend, too.
  18. //// Finally, the fact that we spin off input *from* events here and feed that into InputState.Change() by passing the event along
  19. //// means that places that make sure we process input only once (e.g. binding composites which will remember the event ID they have
  20. //// been triggered from) may reject the simulated input when they have already seen the non-simulated input (which may be okay
  21. //// behavior).
  22. namespace UnityEngine.InputSystem.EnhancedTouch
  23. {
  24. /// <summary>
  25. /// Adds a <see cref="Touchscreen"/> with input simulated from other types of <see cref="Pointer"/> devices (e.g. <see cref="Mouse"/>
  26. /// or <see cref="Pen"/>).
  27. /// </summary>
  28. [AddComponentMenu("Input/Debug/Touch Simulation")]
  29. [ExecuteInEditMode]
  30. [HelpURL(InputSystem.kDocUrl + "/manual/Touch.html#touch-simulation")]
  31. #if UNITY_EDITOR
  32. [InitializeOnLoad]
  33. #endif
  34. public class TouchSimulation : MonoBehaviour, IInputStateChangeMonitor
  35. {
  36. public Touchscreen simulatedTouchscreen { get; private set; }
  37. public static TouchSimulation instance => s_Instance;
  38. public static void Enable()
  39. {
  40. if (instance == null)
  41. {
  42. ////TODO: find instance
  43. var hiddenGO = new GameObject();
  44. hiddenGO.SetActive(false);
  45. hiddenGO.hideFlags = HideFlags.HideAndDontSave;
  46. s_Instance = hiddenGO.AddComponent<TouchSimulation>();
  47. instance.gameObject.SetActive(true);
  48. }
  49. instance.enabled = true;
  50. }
  51. public static void Disable()
  52. {
  53. if (instance != null)
  54. instance.enabled = false;
  55. }
  56. public static void Destroy()
  57. {
  58. Disable();
  59. if (s_Instance != null)
  60. {
  61. Destroy(s_Instance.gameObject);
  62. s_Instance = null;
  63. }
  64. }
  65. protected void AddPointer(Pointer pointer)
  66. {
  67. if (pointer == null)
  68. throw new ArgumentNullException(nameof(pointer));
  69. // Ignore if already added.
  70. if (m_Pointers.ContainsReference(m_NumPointers, pointer))
  71. return;
  72. // Add to list.
  73. ArrayHelpers.AppendWithCapacity(ref m_Pointers, ref m_NumPointers, pointer);
  74. ArrayHelpers.Append(ref m_CurrentPositions, default(Vector2));
  75. ArrayHelpers.Append(ref m_CurrentDisplayIndices, default(int));
  76. InputSystem.DisableDevice(pointer, keepSendingEvents: true);
  77. }
  78. protected void RemovePointer(Pointer pointer)
  79. {
  80. if (pointer == null)
  81. throw new ArgumentNullException(nameof(pointer));
  82. // Ignore if not added.
  83. var pointerIndex = m_Pointers.IndexOfReference(pointer, m_NumPointers);
  84. if (pointerIndex == -1)
  85. return;
  86. // Cancel all ongoing touches from the pointer.
  87. for (var i = 0; i < m_Touches.Length; ++i)
  88. {
  89. var button = m_Touches[i];
  90. if (button != null && button.device != pointer)
  91. continue;
  92. UpdateTouch(i, pointerIndex, TouchPhase.Canceled);
  93. }
  94. // Remove from list.
  95. m_Pointers.EraseAtWithCapacity(ref m_NumPointers, pointerIndex);
  96. ArrayHelpers.EraseAt(ref m_CurrentPositions, pointerIndex);
  97. ArrayHelpers.EraseAt(ref m_CurrentDisplayIndices, pointerIndex);
  98. // Re-enable the device (only in case it's still added to the system).
  99. if (pointer.added)
  100. InputSystem.EnableDevice(pointer);
  101. }
  102. private unsafe void OnEvent(InputEventPtr eventPtr, InputDevice device)
  103. {
  104. var pointerIndex = m_Pointers.IndexOfReference(device, m_NumPointers);
  105. if (pointerIndex < 0)
  106. return;
  107. var eventType = eventPtr.type;
  108. if (eventType != StateEvent.Type && eventType != DeltaStateEvent.Type)
  109. return;
  110. ////TODO: this can be simplified if we use events instead of InputState.Change() but doing so requires work on buffering events while processing; also
  111. //// needs extra handling to not lag into the next frame
  112. ////REVIEW: should we have specialized paths for MouseState and PenState here? (probably can only use for StateEvents)
  113. Pointer pointer = m_Pointers[pointerIndex];
  114. // Read pointer position.
  115. var positionControl = pointer.position;
  116. var positionStatePtr = positionControl.GetStatePtrFromStateEventUnchecked(eventPtr, eventType);
  117. if (positionStatePtr != null)
  118. m_CurrentPositions[pointerIndex] = positionControl.ReadValueFromState(positionStatePtr);
  119. // Read display index.
  120. var displayIndexControl = pointer.displayIndex;
  121. var displayIndexStatePtr = displayIndexControl.GetStatePtrFromStateEventUnchecked(eventPtr, eventType);
  122. if (displayIndexStatePtr != null)
  123. m_CurrentDisplayIndices[pointerIndex] = displayIndexControl.ReadValueFromState(displayIndexStatePtr);
  124. // End touches for which buttons are no longer pressed.
  125. ////REVIEW: There must be a better way to do this
  126. for (var i = 0; i < m_Touches.Length; ++i)
  127. {
  128. var button = m_Touches[i];
  129. if (button == null || button.device != device)
  130. continue;
  131. var buttonStatePtr = button.GetStatePtrFromStateEventUnchecked(eventPtr, eventType);
  132. if (buttonStatePtr == null)
  133. {
  134. // Button is not contained in event. If we do have a position update, issue
  135. // a move on the button's corresponding touch. This makes us deal with delta
  136. // events that only update pointer positions.
  137. if (positionStatePtr != null)
  138. UpdateTouch(i, pointerIndex, TouchPhase.Moved, eventPtr);
  139. }
  140. else if (button.ReadValueFromState(buttonStatePtr) < (ButtonControl.s_GlobalDefaultButtonPressPoint * ButtonControl.s_GlobalDefaultButtonReleaseThreshold))
  141. UpdateTouch(i, pointerIndex, TouchPhase.Ended, eventPtr);
  142. }
  143. // Add/update touches for buttons that are pressed.
  144. foreach (var control in eventPtr.EnumerateControls(InputControlExtensions.Enumerate.IgnoreControlsInDefaultState, device))
  145. {
  146. if (!control.isButton)
  147. continue;
  148. // Check if it's pressed.
  149. var buttonStatePtr = control.GetStatePtrFromStateEventUnchecked(eventPtr, eventType);
  150. Debug.Assert(buttonStatePtr != null, "Button returned from EnumerateControls() must be found in event");
  151. var value = 0f;
  152. control.ReadValueFromStateIntoBuffer(buttonStatePtr, UnsafeUtility.AddressOf(ref value), 4);
  153. if (value <= ButtonControl.s_GlobalDefaultButtonPressPoint)
  154. continue; // Not in default state but also not pressed.
  155. // See if we have an ongoing touch for the button.
  156. var touchIndex = m_Touches.IndexOfReference(control);
  157. if (touchIndex < 0)
  158. {
  159. // No, so add it.
  160. touchIndex = m_Touches.IndexOfReference((ButtonControl)null);
  161. if (touchIndex >= 0) // If negative, we're at max touch count and can't add more.
  162. {
  163. m_Touches[touchIndex] = (ButtonControl)control;
  164. UpdateTouch(touchIndex, pointerIndex, TouchPhase.Began, eventPtr);
  165. }
  166. }
  167. else
  168. {
  169. // Yes, so update it.
  170. UpdateTouch(touchIndex, pointerIndex, TouchPhase.Moved, eventPtr);
  171. }
  172. }
  173. eventPtr.handled = true;
  174. }
  175. private void OnDeviceChange(InputDevice device, InputDeviceChange change)
  176. {
  177. // If someone removed our simulated touchscreen, disable touch simulation.
  178. if (device == simulatedTouchscreen && change == InputDeviceChange.Removed)
  179. {
  180. Disable();
  181. return;
  182. }
  183. switch (change)
  184. {
  185. case InputDeviceChange.Added:
  186. {
  187. if (device is Pointer pointer)
  188. {
  189. if (device is Touchscreen)
  190. return; ////TODO: decide what to do
  191. AddPointer(pointer);
  192. }
  193. break;
  194. }
  195. case InputDeviceChange.Removed:
  196. {
  197. if (device is Pointer pointer)
  198. RemovePointer(pointer);
  199. break;
  200. }
  201. }
  202. }
  203. protected void OnEnable()
  204. {
  205. if (simulatedTouchscreen != null)
  206. {
  207. if (!simulatedTouchscreen.added)
  208. InputSystem.AddDevice(simulatedTouchscreen);
  209. }
  210. else
  211. {
  212. simulatedTouchscreen = InputSystem.GetDevice("Simulated Touchscreen") as Touchscreen;
  213. if (simulatedTouchscreen == null)
  214. simulatedTouchscreen = InputSystem.AddDevice<Touchscreen>("Simulated Touchscreen");
  215. }
  216. if (m_Touches == null)
  217. m_Touches = new ButtonControl[simulatedTouchscreen.touches.Count];
  218. foreach (var device in InputSystem.devices)
  219. OnDeviceChange(device, InputDeviceChange.Added);
  220. if (m_OnDeviceChange == null)
  221. m_OnDeviceChange = OnDeviceChange;
  222. if (m_OnEvent == null)
  223. m_OnEvent = OnEvent;
  224. InputSystem.onDeviceChange += m_OnDeviceChange;
  225. InputSystem.onEvent += m_OnEvent;
  226. }
  227. protected void OnDisable()
  228. {
  229. if (simulatedTouchscreen != null && simulatedTouchscreen.added)
  230. InputSystem.RemoveDevice(simulatedTouchscreen);
  231. // Re-enable all pointers we disabled.
  232. for (var i = 0; i < m_NumPointers; ++i)
  233. InputSystem.EnableDevice(m_Pointers[i]);
  234. m_Pointers.Clear(m_NumPointers);
  235. m_Touches.Clear();
  236. m_NumPointers = 0;
  237. m_LastTouchId = 0;
  238. m_PrimaryTouchIndex = -1;
  239. InputSystem.onDeviceChange -= m_OnDeviceChange;
  240. InputSystem.onEvent -= m_OnEvent;
  241. }
  242. private unsafe void UpdateTouch(int touchIndex, int pointerIndex, TouchPhase phase, InputEventPtr eventPtr = default)
  243. {
  244. Vector2 position = m_CurrentPositions[pointerIndex];
  245. Debug.Assert(m_CurrentDisplayIndices[pointerIndex] <= byte.MaxValue, "Display index was larger than expected");
  246. byte displayIndex = (byte)m_CurrentDisplayIndices[pointerIndex];
  247. var touch = new TouchState
  248. {
  249. phase = phase,
  250. position = position,
  251. displayIndex = displayIndex
  252. };
  253. var time = eventPtr.valid ? eventPtr.time : InputState.currentTime;
  254. var oldTouchState = (TouchState*)((byte*)simulatedTouchscreen.currentStatePtr +
  255. simulatedTouchscreen.touches[touchIndex].stateBlock.byteOffset);
  256. if (phase == TouchPhase.Began)
  257. {
  258. touch.isPrimaryTouch = m_PrimaryTouchIndex < 0;
  259. touch.startTime = time;
  260. touch.startPosition = position;
  261. touch.touchId = ++m_LastTouchId;
  262. touch.tapCount = oldTouchState->tapCount; // Get reset automatically by Touchscreen.
  263. if (touch.isPrimaryTouch)
  264. m_PrimaryTouchIndex = touchIndex;
  265. }
  266. else
  267. {
  268. touch.touchId = oldTouchState->touchId;
  269. touch.isPrimaryTouch = m_PrimaryTouchIndex == touchIndex;
  270. touch.delta = position - oldTouchState->position;
  271. touch.startPosition = oldTouchState->startPosition;
  272. touch.startTime = oldTouchState->startTime;
  273. touch.tapCount = oldTouchState->tapCount;
  274. if (phase == TouchPhase.Ended)
  275. {
  276. touch.isTap = time - oldTouchState->startTime <= Touchscreen.s_TapTime &&
  277. (position - oldTouchState->startPosition).sqrMagnitude <= Touchscreen.s_TapRadiusSquared;
  278. if (touch.isTap)
  279. ++touch.tapCount;
  280. }
  281. }
  282. if (touch.isPrimaryTouch)
  283. InputState.Change(simulatedTouchscreen.primaryTouch, touch, eventPtr: eventPtr);
  284. InputState.Change(simulatedTouchscreen.touches[touchIndex], touch, eventPtr: eventPtr);
  285. if (phase.IsEndedOrCanceled())
  286. {
  287. m_Touches[touchIndex] = null;
  288. if (m_PrimaryTouchIndex == touchIndex)
  289. m_PrimaryTouchIndex = -1;
  290. }
  291. }
  292. [NonSerialized] private int m_NumPointers;
  293. [NonSerialized] private Pointer[] m_Pointers;
  294. [NonSerialized] private Vector2[] m_CurrentPositions;
  295. [NonSerialized] private int[] m_CurrentDisplayIndices;
  296. [NonSerialized] private ButtonControl[] m_Touches;
  297. [NonSerialized] private int m_LastTouchId;
  298. [NonSerialized] private int m_PrimaryTouchIndex = -1;
  299. [NonSerialized] private Action<InputDevice, InputDeviceChange> m_OnDeviceChange;
  300. [NonSerialized] private Action<InputEventPtr, InputDevice> m_OnEvent;
  301. internal static TouchSimulation s_Instance;
  302. #if UNITY_EDITOR
  303. static TouchSimulation()
  304. {
  305. // We're a MonoBehaviour so our cctor may get called as part of the MonoBehaviour being
  306. // created. We don't want to trigger InputSystem initialization from there so delay-execute
  307. // the code here.
  308. EditorApplication.delayCall +=
  309. () =>
  310. {
  311. InputSystem.onSettingsChange += OnSettingsChanged;
  312. InputSystem.onBeforeUpdate += ReEnableAfterDomainReload;
  313. };
  314. }
  315. private static void ReEnableAfterDomainReload()
  316. {
  317. OnSettingsChanged();
  318. InputSystem.onBeforeUpdate -= ReEnableAfterDomainReload;
  319. }
  320. private static void OnSettingsChanged()
  321. {
  322. if (InputEditorUserSettings.simulateTouch)
  323. Enable();
  324. else
  325. Disable();
  326. }
  327. #endif
  328. ////TODO: Remove IInputStateChangeMonitor from this class when we can break the API
  329. void IInputStateChangeMonitor.NotifyControlStateChanged(InputControl control, double time, InputEventPtr eventPtr, long monitorIndex)
  330. {
  331. }
  332. void IInputStateChangeMonitor.NotifyTimerExpired(InputControl control, double time, long monitorIndex, int timerIndex)
  333. {
  334. }
  335. // Disable warnings about unused parameters.
  336. #pragma warning disable CA1801
  337. ////TODO: [Obsolete]
  338. protected void InstallStateChangeMonitors(int startIndex = 0)
  339. {
  340. }
  341. ////TODO: [Obsolete]
  342. protected void OnSourceControlChangedValue(InputControl control, double time, InputEventPtr eventPtr,
  343. long sourceDeviceAndButtonIndex)
  344. {
  345. }
  346. ////TODO: [Obsolete]
  347. protected void UninstallStateChangeMonitors(int startIndex = 0)
  348. {
  349. }
  350. }
  351. }