using System; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Serialization; using UnityEngine.UIElements; namespace UnityEngine.EventSystems { /// /// Handles input, raycasting, and sending events. /// /// /// The EventSystem is responsible for processing and handling events in a Unity scene. A scene should only contain one EventSystem. The EventSystem works in conjunction with a number of modules and mostly just holds state and delegates functionality to specific, overrideable components. /// When the EventSystem is started it searches for any BaseInputModules attached to the same GameObject and adds them to an internal list. On update each attached module receives an UpdateModules call, where the module can modify internal state. After each module has been Updated the active module has the Process call executed.This is where custom module processing can take place. /// [AddComponentMenu("Event/Event System")] [DisallowMultipleComponent] [UGUIHelpURL("EventSystem")] public class EventSystem : UIBehaviour { private static readonly List m_EventSystems = new List(); #if UNITY_EDITOR [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] static void ResetStaticsOnLoad() { m_EventSystems.Clear(); } #endif private List m_SystemInputModules = new List(); private BaseInputModule m_CurrentInputModule; /// /// Return the current EventSystem. /// public static EventSystem current { get { return m_EventSystems.Count > 0 ? m_EventSystems[0] : null; } set { int index = m_EventSystems.IndexOf(value); if (index > 0) { m_EventSystems.RemoveAt(index); m_EventSystems.Insert(0, value); } else if (index < 0) { Debug.LogError("Failed setting EventSystem.current to unknown EventSystem " + value); } } } [SerializeField] [FormerlySerializedAs("m_Selected")] private GameObject m_FirstSelected; [SerializeField] private bool m_sendNavigationEvents = true; /// /// Should the EventSystem allow navigation events (move / submit / cancel). /// public bool sendNavigationEvents { get { return m_sendNavigationEvents; } set { m_sendNavigationEvents = value; } } [SerializeField] private int m_DragThreshold = 10; /// /// The soft area for dragging in pixels. /// public int pixelDragThreshold { get { return m_DragThreshold; } set { m_DragThreshold = value; } } private GameObject m_CurrentSelected; /// /// The currently active EventSystems.BaseInputModule. /// public BaseInputModule currentInputModule { get { return m_CurrentInputModule; } } /// /// Only one object can be selected at a time. Think: controller-selected button. /// public GameObject firstSelectedGameObject { get { return m_FirstSelected; } set { m_FirstSelected = value; } } /// /// The GameObject currently considered active by the EventSystem. /// public GameObject currentSelectedGameObject { get { return m_CurrentSelected; } } private bool m_HasFocus = true; /// /// Flag to say whether the EventSystem thinks it should be paused or not based upon focused state. /// /// /// Used to determine inside the individual InputModules if the module should be ticked while the application doesnt have focus. /// public bool isFocused { get { return m_HasFocus; } } /// Protected default constructor. Use to add an EventSystem to a GameObject. protected EventSystem() {} /// /// Recalculate the internal list of BaseInputModules. /// public void UpdateModules() { GetComponents(m_SystemInputModules); var systemInputModulesCount = m_SystemInputModules.Count; for (int i = systemInputModulesCount - 1; i >= 0; i--) { if (m_SystemInputModules[i] && m_SystemInputModules[i].IsActive()) continue; m_SystemInputModules.RemoveAt(i); } } private bool m_SelectionGuard; /// /// Returns true if the EventSystem is already in a SetSelectedGameObject. /// public bool alreadySelecting { get { return m_SelectionGuard; } } /// /// Set the object as selected. Will send an OnDeselect the the old selected object and OnSelect to the new selected object. /// /// GameObject to select. /// Associated EventData. public void SetSelectedGameObject(GameObject selected, BaseEventData pointer) { if (m_SelectionGuard) { Debug.LogError("Attempting to select " + selected + "while already selecting an object."); return; } m_SelectionGuard = true; if (selected == m_CurrentSelected) { m_SelectionGuard = false; return; } // Debug.Log("Selection: new (" + selected + ") old (" + m_CurrentSelected + ")"); ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.deselectHandler); m_CurrentSelected = selected; ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.selectHandler); m_SelectionGuard = false; } private BaseEventData m_DummyData; private BaseEventData baseEventDataCache { get { if (m_DummyData == null) m_DummyData = new BaseEventData(this); return m_DummyData; } } /// /// Set the object as selected. Will send an OnDeselect the the old selected object and OnSelect to the new selected object. /// /// GameObject to select. public void SetSelectedGameObject(GameObject selected) { SetSelectedGameObject(selected, baseEventDataCache); } private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs) { if (lhs.module != rhs.module) { var lhsEventCamera = lhs.module.eventCamera; var rhsEventCamera = rhs.module.eventCamera; if (lhsEventCamera != null && rhsEventCamera != null && lhsEventCamera.depth != rhsEventCamera.depth) { // need to reverse the standard compareTo if (lhsEventCamera.depth < rhsEventCamera.depth) return 1; if (lhsEventCamera.depth == rhsEventCamera.depth) return 0; return -1; } if (lhs.module.sortOrderPriority != rhs.module.sortOrderPriority) return rhs.module.sortOrderPriority.CompareTo(lhs.module.sortOrderPriority); if (lhs.module.renderOrderPriority != rhs.module.renderOrderPriority) return rhs.module.renderOrderPriority.CompareTo(lhs.module.renderOrderPriority); } // Renderer sorting if (lhs.sortingLayer != rhs.sortingLayer) { // Uses the layer value to properly compare the relative order of the layers. var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer); var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer); return rid.CompareTo(lid); } if (lhs.sortingOrder != rhs.sortingOrder) return rhs.sortingOrder.CompareTo(lhs.sortingOrder); // comparing depth only makes sense if the two raycast results have the same root canvas (case 912396) if (lhs.depth != rhs.depth && lhs.module.rootRaycaster == rhs.module.rootRaycaster) return rhs.depth.CompareTo(lhs.depth); if (lhs.distance != rhs.distance) return lhs.distance.CompareTo(rhs.distance); #if PACKAGE_PHYSICS2D // Sorting group if (lhs.sortingGroupID != SortingGroup.invalidSortingGroupID && rhs.sortingGroupID != SortingGroup.invalidSortingGroupID) { if (lhs.sortingGroupID != rhs.sortingGroupID) return lhs.sortingGroupID.CompareTo(rhs.sortingGroupID); if (lhs.sortingGroupOrder != rhs.sortingGroupOrder) return rhs.sortingGroupOrder.CompareTo(lhs.sortingGroupOrder); } #endif return lhs.index.CompareTo(rhs.index); } private static readonly Comparison s_RaycastComparer = RaycastComparer; /// /// Raycast into the scene using all configured BaseRaycasters. /// /// Current pointer data. /// List of 'hits' to populate. public void RaycastAll(PointerEventData eventData, List raycastResults) { raycastResults.Clear(); var modules = RaycasterManager.GetRaycasters(); var modulesCount = modules.Count; for (int i = 0; i < modulesCount; ++i) { var module = modules[i]; if (module == null || !module.IsActive()) continue; module.Raycast(eventData, raycastResults); } raycastResults.Sort(s_RaycastComparer); } /// /// Checks whether the left mouse button is over an EventSystem object. /// /// /// Equivalent to calling with a pointerId of -1. /// /// Returns true if the left mouse button is over an EventSystem object. Otherwise, returns false. public bool IsPointerOverGameObject() { return IsPointerOverGameObject(PointerInputModule.kMouseLeftId); } /// /// Checks whether the pointer with the specified ID is over an EventSystem object. /// /// /// > [!NOTE] /// > If you use `IsPointerOverGameObject()` without a `pointerId`, it defaults to /// > the left mouse button (`pointerId` = -1). For touch input, consider passing a /// > touch ID (such as Input.GetTouch(0).fingerId) to this method when checking /// > during events like Input.GetMouseButtonDown(0) or /// > Input.GetTouch(0).phase == TouchPhase.Began. /// /// The ID of the pointer to check. /// Returns true if the pointer is over an EventSystem object. Otherwise, returns false. /// /// /// /// /// public bool IsPointerOverGameObject(int pointerId) { return m_CurrentInputModule != null && m_CurrentInputModule.IsPointerOverGameObject(pointerId); } // This code is disabled unless the com.unity.modules.uielements module is present. // The UIElements module is always present in the Editor but it can be stripped from a project build if unused. #if !DISABLE_UGUI_UITK_SUPPORT && PACKAGE_UITOOLKIT private UIToolkitInteroperabilityBridge m_UIToolkitInterop = new (); /// /// Use this property to initialize UI Toolkit interoperability with uGUI events. /// internal UIToolkitInteroperabilityBridge uiToolkitInterop => m_UIToolkitInterop; #endif /// /// Returns true if UI Toolkit interoperability is enabled and /// there is some active UI Toolkit content in the scene. /// internal bool isOverridingUIToolkitEvents { get { #if !DISABLE_UGUI_UITK_SUPPORT && PACKAGE_UITOOLKIT return uiToolkitInterop.overrideUIToolkitEvents && IRuntimePanel.uIElementsRuntimeUtility != null && IRuntimePanel.uIElementsRuntimeUtility.HasActiveDocuments(); #else return false; #endif } } /// /// Sets how UI Toolkit runtime panels receive events and handle selection /// when interacting with other objects that use the EventSystem, such as components from the uGUI (Unity UI) package. /// /// /// This method is obsolete. Use the PanelInputConfiguration component instead. /// /// /// The EventSystem used to override UI Toolkit panel events and selection. /// If activeEventSystem is null, UI Toolkit panels will use current enabled EventSystem /// or, if there is none, the default InputManager-based event system will be used. /// /// /// If true, UI Toolkit events will come from this EventSystem /// instead of the default InputManager-based event system. /// /// /// If true, UI Toolkit panels' unassigned selectableGameObject will be automatically initialized /// with children GameObjects of this EventSystem on Start. /// [Obsolete("Use PanelInputConfiguration component instead.", true)] public static void SetUITookitEventSystemOverride(EventSystem activeEventSystem, bool sendEvents = true, bool createPanelGameObjectsOnStart = true) { } /// /// Registers this event system so that it can become the current one. /// protected override void OnEnable() { base.OnEnable(); m_EventSystems.Add(this); #if !DISABLE_UGUI_UITK_SUPPORT && PACKAGE_UITOOLKIT m_UIToolkitInterop.eventSystem = this; m_UIToolkitInterop.OnEnable(); #endif } /// /// Deactivates the current input module and unregisters this event system. /// protected override void OnDisable() { #if !DISABLE_UGUI_UITK_SUPPORT && PACKAGE_UITOOLKIT m_UIToolkitInterop.OnDisable(); #endif if (m_CurrentInputModule != null) { m_CurrentInputModule.DeactivateModule(); m_CurrentInputModule = null; } m_EventSystems.Remove(this); base.OnDisable(); } /// /// Initializes UI Toolkit interoperability. /// protected override void Start() { base.Start(); #if !DISABLE_UGUI_UITK_SUPPORT && PACKAGE_UITOOLKIT m_UIToolkitInterop.Start(); #endif } private void TickModules() { var systemInputModulesCount = m_SystemInputModules.Count; for (var i = 0; i < systemInputModulesCount; i++) { if (m_SystemInputModules[i] != null) m_SystemInputModules[i].UpdateModule(); } } /// /// Records the focus state of the application and updates the input modules when focus is lost. /// /// Whether the application has focus. protected virtual void OnApplicationFocus(bool hasFocus) { m_HasFocus = hasFocus; if (!m_HasFocus) TickModules(); } /// /// Updates the attached input modules, selects the module to use, and processes the current one. /// protected virtual void Update() { #if !DISABLE_UGUI_UITK_SUPPORT && PACKAGE_UITOOLKIT m_UIToolkitInterop.Update(); #endif if (current != this) return; TickModules(); bool changedModule = false; var systemInputModulesCount = m_SystemInputModules.Count; for (var i = 0; i < systemInputModulesCount; i++) { var module = m_SystemInputModules[i]; if (module.IsModuleSupported() && module.ShouldActivateModule()) { if (m_CurrentInputModule != module) { ChangeEventModule(module); changedModule = true; } break; } } // no event module set... set the first valid one... if (m_CurrentInputModule == null) { for (var i = 0; i < systemInputModulesCount; i++) { var module = m_SystemInputModules[i]; if (module.IsModuleSupported()) { ChangeEventModule(module); changedModule = true; break; } } } if (!changedModule && m_CurrentInputModule != null) m_CurrentInputModule.Process(); #if UNITY_EDITOR if (Application.isPlaying) { int eventSystemCount = 0; for (int i = 0; i < m_EventSystems.Count; i++) { if (m_EventSystems[i].GetType() == typeof(EventSystem)) eventSystemCount++; } if (eventSystemCount > 1) Debug.LogWarning("There are " + eventSystemCount + " event systems in the scene. Please ensure there is always exactly one event system in the scene"); } #endif } private void ChangeEventModule(BaseInputModule module) { if (m_CurrentInputModule == module) return; if (m_CurrentInputModule != null) m_CurrentInputModule.DeactivateModule(); if (module != null) module.ActivateModule(); m_CurrentInputModule = module; } /// /// Returns a multi-line string that lists the selected object and the state of the current input module. /// /// The formatted description of the event system state. public override string ToString() { var sb = new StringBuilder(); sb.AppendLine("Selected:" + currentSelectedGameObject); sb.AppendLine(); sb.AppendLine(); sb.AppendLine(m_CurrentInputModule != null ? m_CurrentInputModule.ToString() : "No module"); return sb.ToString(); } } }