using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; namespace HighlightPlus { public enum TriggerMode { ColliderEventsOnlyOnThisObject = 0, RaycastOnThisObjectAndChildren = 1, Volume = 2 } public enum RayCastSource { MousePosition = 0, CameraDirection = 1 } [RequireComponent(typeof(HighlightEffect))] [ExecuteInEditMode] [HelpURL("https://kronnect.com/guides/highlight-plus-introduction/")] public class HighlightTrigger : MonoBehaviour { [Tooltip("Enables highlight when pointer is over this object.")] public bool highlightOnHover = true; [Tooltip("Used to trigger automatic highlighting including children objects.")] #if ENABLE_INPUT_SYSTEM public TriggerMode triggerMode = TriggerMode.RaycastOnThisObjectAndChildren; #else public TriggerMode triggerMode = TriggerMode.ColliderEventsOnlyOnThisObject; #endif public Camera raycastCamera; public RayCastSource raycastSource = RayCastSource.MousePosition; public LayerMask raycastLayerMask = -1; [Tooltip("Minimum distance for target.")] public float minDistance; [Tooltip("Maximum distance for target. 0 = infinity")] public float maxDistance; [Tooltip("Blocks interaction if pointer is over an UI element")] public bool respectUI = true; public LayerMask volumeLayerMask; const int MAX_RAYCAST_HITS = 100; [Tooltip("If the object will be selected by clicking with mouse or tapping on it.")] public bool selectOnClick; [Tooltip("Profile to use when object is selected by clicking on it.")] public HighlightProfile selectedProfile; [Tooltip("Profile to use whtn object is selected and highlighted.")] public HighlightProfile selectedAndHighlightedProfile; [Tooltip("Automatically deselects any other selected object prior selecting this one")] public bool singleSelection; [Tooltip("Toggles selection on/off when clicking object")] public bool toggle; [Tooltip("Keeps current selection when clicking outside of any selectable object")] public bool keepSelection = true; [NonSerialized] public Collider[] colliders; [NonSerialized] public Collider2D[] colliders2D; public bool hasColliders => (colliders != null && colliders.Length > 0); public bool hasColliders2D => (colliders2D != null && colliders2D.Length > 0); UnityEngine.Object currentCollider; static RaycastHit[] hits; static RaycastHit2D[] hits2D; HighlightEffect hb; public HighlightEffect highlightEffect { get { return hb; } } public event OnObjectSelectionEvent OnObjectSelected; public event OnObjectSelectionEvent OnObjectUnSelected; public event OnObjectHighlightEvent OnObjectHighlightStart; public event OnObjectHighlightEvent OnObjectHighlightEnd; TriggerMode currentTriggerMode; [RuntimeInitializeOnLoadMethod] static void DomainReloadDisabledSupport() { HighlightManager.selectedObjects.Clear(); } void OnEnable() { Init(); } private void OnValidate() { if (currentTriggerMode != triggerMode) { UpdateTriggers(); } } void UpdateTriggers() { currentTriggerMode = triggerMode; if (currentTriggerMode == TriggerMode.RaycastOnThisObjectAndChildren) { colliders = GetComponentsInChildren(); colliders2D = GetComponentsInChildren(); if (hits == null || hits.Length != MAX_RAYCAST_HITS) { hits = new RaycastHit[MAX_RAYCAST_HITS]; } if (hits2D == null || hits2D.Length != MAX_RAYCAST_HITS) { hits2D = new RaycastHit2D[MAX_RAYCAST_HITS]; } if (Application.isPlaying) { StopAllCoroutines(); if (gameObject.activeInHierarchy) { StartCoroutine(DoRayCast()); } } } } public void Init() { if (raycastCamera == null) { raycastCamera = HighlightManager.GetCamera(); } UpdateTriggers(); if (hb == null) { hb = GetComponent(); } InputProxy.Init(); } void Start() { UpdateTriggers(); if (triggerMode == TriggerMode.RaycastOnThisObjectAndChildren) { if (raycastCamera == null) { raycastCamera = HighlightManager.GetCamera(); if (raycastCamera == null) { Debug.LogError("Highlight Trigger on " + gameObject.name + ": no camera found!"); } } } else { Collider collider = GetComponent(); if (collider == null) { if (GetComponent() != null) { gameObject.AddComponent(); } } } } IEnumerator DoRayCast() { yield return null; WaitForEndOfFrame w = new WaitForEndOfFrame(); while (triggerMode == TriggerMode.RaycastOnThisObjectAndChildren) { if (raycastCamera == null) { yield return null; continue; } int hitCount; bool hit = false; #if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER if (respectUI) { EventSystem es = EventSystem.current; if (es == null) { es = CreateEventSystem(); } List raycastResults = new List(); PointerEventData eventData = new PointerEventData(es); Vector3 cameraPos = raycastCamera.transform.position; if (raycastSource == RayCastSource.MousePosition) { eventData.position = InputProxy.mousePosition; } else { eventData.position = new Vector2(raycastCamera.pixelWidth * 0.5f, raycastCamera.pixelHeight * 0.5f); } es.RaycastAll(eventData, raycastResults); hitCount = raycastResults.Count; // check UI blocker bool blocked = false; for (int k = 0; k < hitCount; k++) { RaycastResult rr = raycastResults[k]; if (rr.module is UnityEngine.UI.GraphicRaycaster) { blocked = true; break; } } if (blocked) { yield return null; continue; } // look for our gameobject for (int k = 0; k < hitCount; k++) { RaycastResult rr = raycastResults[k]; float distance = Vector3.Distance(rr.worldPosition, cameraPos); if (distance < minDistance || (maxDistance > 0 && distance > maxDistance)) continue; GameObject theGameObject = rr.gameObject; for (int c = 0; c < colliders.Length; c++) { if (colliders[c].gameObject == theGameObject) { Collider theCollider = colliders[c]; hit = true; if (selectOnClick && InputProxy.GetMouseButtonDown(0)) { ToggleSelection(); break; } else if (theCollider != currentCollider) { SwitchCollider(theCollider); k = hitCount; break; } } } } } // if not blocked by UI and no hit found, fallback to raycast (required if no PhysicsRaycaster is present on the camera) #endif Ray ray; if (raycastSource == RayCastSource.MousePosition) { #if !ENABLE_INPUT_SYSTEM if (!CanInteract()) { yield return null; continue; } #endif ray = raycastCamera.ScreenPointToRay(InputProxy.mousePosition); } else { ray = new Ray(raycastCamera.transform.position, raycastCamera.transform.forward); } bool isMouseButonDown = InputProxy.GetMouseButtonDown(0); if (hasColliders2D) { if (maxDistance > 0) { hitCount = Physics2D.GetRayIntersectionNonAlloc(ray, hits2D, maxDistance, raycastLayerMask); } else { hitCount = Physics2D.GetRayIntersectionNonAlloc(ray, hits2D, float.MaxValue, raycastLayerMask); } for (int k = 0; k < hitCount; k++) { if (Vector3.Distance(hits2D[k].point, ray.origin) < minDistance) continue; Collider2D theCollider = hits2D[k].collider; int colliders2DCount = colliders2D.Length; for (int c = 0; c < colliders2DCount; c++) { if (colliders2D[c] == theCollider) { hit = true; if (selectOnClick && isMouseButonDown) { ToggleSelection(); break; } else if (theCollider != currentCollider) { SwitchCollider(theCollider); k = hitCount; break; } } } } } if (hasColliders) { if (maxDistance > 0) { hitCount = Physics.RaycastNonAlloc(ray, hits, maxDistance, raycastLayerMask); } else { hitCount = Physics.RaycastNonAlloc(ray, hits, float.MaxValue, raycastLayerMask); } for (int k = 0; k < hitCount; k++) { if (Vector3.Distance(hits[k].point, ray.origin) < minDistance) continue; Collider theCollider = hits[k].collider; int collidersCount = colliders.Length; for (int c = 0; c < collidersCount; c++) { if (colliders[c] == theCollider) { hit = true; if (selectOnClick && isMouseButonDown) { ToggleSelection(); break; } else if (theCollider != currentCollider) { SwitchCollider(theCollider); k = hitCount; break; } } } } } if (!hit && currentCollider != null) { SwitchCollider(null); } if (selectOnClick && isMouseButonDown && !keepSelection && !hit) { yield return w; // wait for other potential triggers to act if (HighlightManager.lastTriggerFrame < Time.frameCount) { HighlightManager.DeselectAll(); } } yield return null; } } #if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER EventSystem CreateEventSystem() { GameObject eo = new GameObject("Event System created by Highlight Plus", typeof(EventSystem), typeof(UnityEngine.InputSystem.UI.InputSystemUIInputModule)); return eo.GetComponent(); } #endif void SwitchCollider(UnityEngine.Object newCollider) { if (!highlightOnHover && !hb.isSelected) return; currentCollider = newCollider; if (currentCollider != null) { Highlight(true); } else { Highlight(false); } } bool CanInteract() { if (!respectUI) return true; EventSystem es = EventSystem.current; if (es == null) return true; if (Application.isMobilePlatform && InputProxy.touchCount > 0 && es.IsPointerOverGameObject(InputProxy.GetFingerIdFromTouch(0))) { return false; } else if (es.IsPointerOverGameObject(-1)) return false; return true; } void OnMouseDown() { if (isActiveAndEnabled && triggerMode == TriggerMode.ColliderEventsOnlyOnThisObject) { if (!CanInteract()) return; if (selectOnClick && InputProxy.GetMouseButtonDown(0)) { ToggleSelection(); return; } Highlight(true); } } void OnMouseEnter() { if (isActiveAndEnabled && triggerMode == TriggerMode.ColliderEventsOnlyOnThisObject) { if (!CanInteract()) return; Highlight(true); } } void OnMouseExit() { if (isActiveAndEnabled && triggerMode == TriggerMode.ColliderEventsOnlyOnThisObject) { if (!CanInteract()) return; Highlight(false); } } void Highlight(bool state) { if (state) { if (!hb.highlighted) { if (OnObjectHighlightStart != null && hb.target != null) { if (!OnObjectHighlightStart(hb.target.gameObject)) return; } } } else { if (hb.highlighted) { if (OnObjectHighlightEnd != null && hb.target != null) { OnObjectHighlightEnd(hb.target.gameObject); } } } if (selectOnClick || hb.isSelected) { if (hb.isSelected) { if (state && selectedAndHighlightedProfile != null) { selectedAndHighlightedProfile.Load(hb); } else if (selectedProfile != null) { selectedProfile.Load(hb); } else { hb.previousSettings.Load(hb); } if (hb.highlighted) { hb.UpdateMaterialProperties(); } else { hb.SetHighlighted(true); } return; } else if (!highlightOnHover) { hb.SetHighlighted(false); return; } } hb.SetHighlighted(state); } void ToggleSelection() { HighlightManager.lastTriggerFrame = Time.frameCount; bool newState = toggle ? !hb.isSelected : true; if (newState) { if (OnObjectSelected != null && !OnObjectSelected(gameObject)) return; } else { if (OnObjectUnSelected != null && !OnObjectUnSelected(gameObject)) return; } if (singleSelection && newState) { HighlightManager.DeselectAll(); } hb.isSelected = newState; if (newState && !HighlightManager.selectedObjects.Contains(hb)) { HighlightManager.selectedObjects.Add(hb); } else if (!newState && HighlightManager.selectedObjects.Contains(hb)) { HighlightManager.selectedObjects.Remove(hb); } if (hb.isSelected) { if (hb.previousSettings == null) { hb.previousSettings = ScriptableObject.CreateInstance(); } hb.previousSettings.Save(hb); } else { hb.RestorePreviousHighlightEffectSettings(); } Highlight(true); } public void OnTriggerEnter(Collider other) { if (triggerMode == TriggerMode.Volume) { if ((volumeLayerMask & (1 << other.gameObject.layer)) != 0) { Highlight(true); } } } public void OnTriggerExit(Collider other) { if (triggerMode == TriggerMode.Volume) { if ((volumeLayerMask & (1 << other.gameObject.layer)) != 0) { Highlight(false); } } } } }