A game about forced loneliness, made by TACStudios
at master 410 lines 16 kB view raw
1using System; 2using System.Collections.Generic; 3 4namespace UnityEngine.EventSystems 5{ 6 [RequireComponent(typeof(EventSystem))] 7 /// <summary> 8 /// A base module that raises events and sends them to GameObjects. 9 /// </summary> 10 /// <remarks> 11 /// An Input Module is a component of the EventSystem that is responsible for raising events and sending them to GameObjects for handling. The BaseInputModule is a class that all Input Modules in the EventSystem inherit from. Examples of provided modules are TouchInputModule and StandaloneInputModule, if these are inadequate for your project you can create your own by extending from the BaseInputModule. 12 /// </remarks> 13 /// <example> 14 /// <code> 15 /// <![CDATA[ 16 /// using UnityEngine; 17 /// using UnityEngine.EventSystems; 18 /// 19 /// /** 20 /// * Create a module that every tick sends a 'Move' event to 21 /// * the target object 22 /// */ 23 /// public class MyInputModule : BaseInputModule 24 /// { 25 /// public GameObject m_TargetObject; 26 /// 27 /// public override void Process() 28 /// { 29 /// if (m_TargetObject == null) 30 /// return; 31 /// ExecuteEvents.Execute (m_TargetObject, new BaseEventData (eventSystem), ExecuteEvents.moveHandler); 32 /// } 33 /// } 34 /// ]]> 35 ///</code> 36 /// </example> 37 public abstract class BaseInputModule : UIBehaviour 38 { 39 [NonSerialized] 40 protected List<RaycastResult> m_RaycastResultCache = new List<RaycastResult>(); 41 42 /// <summary> 43 /// True if pointer hover events will be sent to the parent 44 /// </summary> 45 [SerializeField] private bool m_SendPointerHoverToParent = true; 46 47 //This is needed for testing 48 /// <summary> 49 /// True if pointer hover events will be sent to the parent 50 /// </summary> 51 protected internal bool sendPointerHoverToParent { get { return m_SendPointerHoverToParent; } set { m_SendPointerHoverToParent = value; } } 52 53 private AxisEventData m_AxisEventData; 54 55 private EventSystem m_EventSystem; 56 private BaseEventData m_BaseEventData; 57 58 protected BaseInput m_InputOverride; 59 private BaseInput m_DefaultInput; 60 61 /// <summary> 62 /// The current BaseInput being used by the input module. 63 /// </summary> 64 public BaseInput input 65 { 66 get 67 { 68 if (m_InputOverride != null) 69 return m_InputOverride; 70 71 if (m_DefaultInput == null) 72 { 73 var inputs = GetComponents<BaseInput>(); 74 foreach (var baseInput in inputs) 75 { 76 // We dont want to use any classes that derrive from BaseInput for default. 77 if (baseInput != null && baseInput.GetType() == typeof(BaseInput)) 78 { 79 m_DefaultInput = baseInput; 80 break; 81 } 82 } 83 84 if (m_DefaultInput == null) 85 m_DefaultInput = gameObject.AddComponent<BaseInput>(); 86 } 87 88 return m_DefaultInput; 89 } 90 } 91 92 /// <summary> 93 /// Used to override the default BaseInput for the input module. 94 /// </summary> 95 /// <remarks> 96 /// With this it is possible to bypass the Input system with your own but still use the same InputModule. For example this can be used to feed fake input into the UI or interface with a different input system. 97 /// </remarks> 98 public BaseInput inputOverride 99 { 100 get { return m_InputOverride; } 101 set { m_InputOverride = value; } 102 } 103 104 protected EventSystem eventSystem 105 { 106 get { return m_EventSystem; } 107 } 108 109 protected override void OnEnable() 110 { 111 base.OnEnable(); 112 m_EventSystem = GetComponent<EventSystem>(); 113 m_EventSystem.UpdateModules(); 114 } 115 116 protected override void OnDisable() 117 { 118 m_EventSystem.UpdateModules(); 119 base.OnDisable(); 120 } 121 122 /// <summary> 123 /// Process the current tick for the module. 124 /// </summary> 125 public abstract void Process(); 126 127 /// <summary> 128 /// Return the first valid RaycastResult. 129 /// </summary> 130 protected static RaycastResult FindFirstRaycast(List<RaycastResult> candidates) 131 { 132 var candidatesCount = candidates.Count; 133 for (var i = 0; i < candidatesCount; ++i) 134 { 135 if (candidates[i].gameObject == null) 136 continue; 137 138 return candidates[i]; 139 } 140 return new RaycastResult(); 141 } 142 143 /// <summary> 144 /// Given an input movement, determine the best MoveDirection. 145 /// </summary> 146 /// <param name="x">X movement.</param> 147 /// <param name="y">Y movement.</param> 148 protected static MoveDirection DetermineMoveDirection(float x, float y) 149 { 150 return DetermineMoveDirection(x, y, 0.6f); 151 } 152 153 /// <summary> 154 /// Given an input movement, determine the best MoveDirection. 155 /// </summary> 156 /// <param name="x">X movement.</param> 157 /// <param name="y">Y movement.</param> 158 /// <param name="deadZone">Dead zone.</param> 159 protected static MoveDirection DetermineMoveDirection(float x, float y, float deadZone) 160 { 161 // if vector is too small... just return 162 if (new Vector2(x, y).sqrMagnitude < deadZone * deadZone) 163 return MoveDirection.None; 164 165 if (Mathf.Abs(x) > Mathf.Abs(y)) 166 { 167 return x > 0 ? MoveDirection.Right : MoveDirection.Left; 168 } 169 170 return y > 0 ? MoveDirection.Up : MoveDirection.Down; 171 } 172 173 /// <summary> 174 /// Given 2 GameObjects, return a common root GameObject (or null). 175 /// </summary> 176 /// <param name="g1">GameObject to compare</param> 177 /// <param name="g2">GameObject to compare</param> 178 /// <returns></returns> 179 protected static GameObject FindCommonRoot(GameObject g1, GameObject g2) 180 { 181 if (g1 == null || g2 == null) 182 return null; 183 184 var t1 = g1.transform; 185 while (t1 != null) 186 { 187 var t2 = g2.transform; 188 while (t2 != null) 189 { 190 if (t1 == t2) 191 return t1.gameObject; 192 t2 = t2.parent; 193 } 194 t1 = t1.parent; 195 } 196 return null; 197 } 198 199 // walk up the tree till a common root between the last entered and the current entered is found 200 // send exit events up to (but not including) the common root. Then send enter events up to 201 // (but not including) the common root. 202 // Send move events before exit, after enter, and on hovered objects when pointer data has changed. 203 protected void HandlePointerExitAndEnter(PointerEventData currentPointerData, GameObject newEnterTarget) 204 { 205 // if we have no target / pointerEnter has been deleted 206 // just send exit events to anything we are tracking 207 // then exit 208 if (newEnterTarget == null || currentPointerData.pointerEnter == null) 209 { 210 var hoveredCount = currentPointerData.hovered.Count; 211 for (var i = 0; i < hoveredCount; ++i) 212 { 213 currentPointerData.fullyExited = true; 214 ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerMoveHandler); 215 ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerExitHandler); 216 } 217 218 currentPointerData.hovered.Clear(); 219 220 if (newEnterTarget == null) 221 { 222 currentPointerData.pointerEnter = null; 223 return; 224 } 225 } 226 227 // if we have not changed hover target 228 if (currentPointerData.pointerEnter == newEnterTarget && newEnterTarget) 229 { 230 if (currentPointerData.IsPointerMoving()) 231 { 232 var hoveredCount = currentPointerData.hovered.Count; 233 for (var i = 0; i < hoveredCount; ++i) 234 ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerMoveHandler); 235 } 236 return; 237 } 238 239 GameObject commonRoot = FindCommonRoot(currentPointerData.pointerEnter, newEnterTarget); 240 GameObject pointerParent = ((Component)newEnterTarget.GetComponentInParent<IPointerExitHandler>())?.gameObject; 241 242 // and we already an entered object from last time 243 if (currentPointerData.pointerEnter != null) 244 { 245 // send exit handler call to all elements in the chain 246 // until we reach the new target, or null! 247 // ** or when !m_SendPointerEnterToParent, stop when meeting a gameobject with an exit event handler 248 Transform t = currentPointerData.pointerEnter.transform; 249 250 while (t != null) 251 { 252 // if we reach the common root break out! 253 if (m_SendPointerHoverToParent && commonRoot != null && commonRoot.transform == t) 254 break; 255 256 // if we reach a PointerExitEvent break out! 257 if (!m_SendPointerHoverToParent && pointerParent == t.gameObject) 258 break; 259 260 currentPointerData.fullyExited = t.gameObject != commonRoot && currentPointerData.pointerEnter != newEnterTarget; 261 ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerMoveHandler); 262 ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerExitHandler); 263 currentPointerData.hovered.Remove(t.gameObject); 264 265 if (m_SendPointerHoverToParent) t = t.parent; 266 267 // if we reach the common root break out! 268 if (commonRoot != null && commonRoot.transform == t) 269 break; 270 271 if (!m_SendPointerHoverToParent) t = t.parent; 272 } 273 } 274 275 // now issue the enter call up to but not including the common root 276 var oldPointerEnter = currentPointerData.pointerEnter; 277 currentPointerData.pointerEnter = newEnterTarget; 278 if (newEnterTarget != null) 279 { 280 Transform t = newEnterTarget.transform; 281 282 while (t != null) 283 { 284 currentPointerData.reentered = t.gameObject == commonRoot && t.gameObject != oldPointerEnter; 285 // if we are sending the event to parent, they are already in hover mode at that point. No need to bubble up the event. 286 if (m_SendPointerHoverToParent && currentPointerData.reentered) 287 break; 288 289 ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerEnterHandler); 290 ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerMoveHandler); 291 currentPointerData.hovered.Add(t.gameObject); 292 293 // stop when encountering an object with the pointerEnterHandler 294 if (!m_SendPointerHoverToParent && t.gameObject.GetComponent<IPointerEnterHandler>() != null) 295 break; 296 297 if (m_SendPointerHoverToParent) t = t.parent; 298 299 // if we reach the common root break out! 300 if (commonRoot != null && commonRoot.transform == t) 301 break; 302 303 if (!m_SendPointerHoverToParent) t = t.parent; 304 } 305 } 306 } 307 308 /// <summary> 309 /// Given some input data generate an AxisEventData that can be used by the event system. 310 /// </summary> 311 /// <param name="x">X movement.</param> 312 /// <param name="y">Y movement.</param> 313 /// <param name="deadZone">Dead zone.</param> 314 protected virtual AxisEventData GetAxisEventData(float x, float y, float moveDeadZone) 315 { 316 if (m_AxisEventData == null) 317 m_AxisEventData = new AxisEventData(eventSystem); 318 319 m_AxisEventData.Reset(); 320 m_AxisEventData.moveVector = new Vector2(x, y); 321 m_AxisEventData.moveDir = DetermineMoveDirection(x, y, moveDeadZone); 322 return m_AxisEventData; 323 } 324 325 /// <summary> 326 /// Generate a BaseEventData that can be used by the EventSystem. 327 /// </summary> 328 protected virtual BaseEventData GetBaseEventData() 329 { 330 if (m_BaseEventData == null) 331 m_BaseEventData = new BaseEventData(eventSystem); 332 333 m_BaseEventData.Reset(); 334 return m_BaseEventData; 335 } 336 337 /// <summary> 338 /// If the module is pointer based, then override this to return true if the pointer is over an event system object. 339 /// </summary> 340 /// <param name="pointerId">Pointer ID</param> 341 /// <returns>Is the given pointer over an event system object?</returns> 342 public virtual bool IsPointerOverGameObject(int pointerId) 343 { 344 return false; 345 } 346 347 /// <summary> 348 /// Should the module be activated. 349 /// </summary> 350 public virtual bool ShouldActivateModule() 351 { 352 return enabled && gameObject.activeInHierarchy; 353 } 354 355 /// <summary> 356 /// Called when the module is deactivated. Override this if you want custom code to execute when you deactivate your module. 357 /// </summary> 358 public virtual void DeactivateModule() 359 {} 360 361 /// <summary> 362 /// Called when the module is activated. Override this if you want custom code to execute when you activate your module. 363 /// </summary> 364 public virtual void ActivateModule() 365 {} 366 367 /// <summary> 368 /// Update the internal state of the Module. 369 /// </summary> 370 public virtual void UpdateModule() 371 {} 372 373 /// <summary> 374 /// Check to see if the module is supported. Override this if you have a platform specific module (eg. TouchInputModule that you do not want to activate on standalone.) 375 /// </summary> 376 /// <returns>Is the module supported.</returns> 377 public virtual bool IsModuleSupported() 378 { 379 return true; 380 } 381 382 /// <summary> 383 /// Returns Id of the pointer following <see cref="UnityEngine.UIElements.PointerId"/> convention. 384 /// </summary> 385 /// <param name="sourcePointerData">PointerEventData whose pointerId will be converted to UI Toolkit pointer convention.</param> 386 /// <seealso cref="UnityEngine.UIElements.IPointerEvent" /> 387 public virtual int ConvertUIToolkitPointerId(PointerEventData sourcePointerData) 388 { 389#if PACKAGE_UITOOLKIT 390 return sourcePointerData.pointerId < 0 ? 391 UIElements.PointerId.mousePointerId : 392 UIElements.PointerId.touchPointerIdBase + sourcePointerData.pointerId; 393#else 394 return -1; 395#endif 396 } 397 398 /// <summary> 399 /// Converts PointerEventData.scrollDelta to corresponding number of ticks of the scroll wheel. 400 /// </summary> 401 /// <remarks> 402 /// Input Module implementations are free to apply a scaling factor to their PointerEventData's scrollDelta. 403 /// This method can be used when a system needs to treat scaling-independent input values. 404 /// </remarks> 405 public virtual Vector2 ConvertPointerEventScrollDeltaToTicks(Vector2 scrollDelta) 406 { 407 return scrollDelta / input.mouseScrollDeltaPerTick; 408 } 409 } 410}