using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; namespace Lean.Common { /// This struct stores information about and allows you to search the scene for a specific object on screen space. [System.Serializable] public struct LeanScreenQuery { public enum MethodType { Raycast } public enum SearchType { GetComponent, GetComponentInParent, GetComponentInChildren } /// The method used to search the scene based on a screen position. /// Raycast = 3D, 2D, and EventSystem raycast. public MethodType Method; /// The scene will be queried (e.g. Raycast) against these layers. public LayerMask Layers; /// When the query hits a GameObject, how should the desired component be searched for relative to it? public SearchType Search; /// The component found from the search must have this tag. public string RequiredTag; /// The camera used to perform the search. /// None = MainCamera. public Camera Camera; public float Distance; private static RaycastHit[] raycastHits = new RaycastHit[1024]; private static RaycastHit2D[] raycastHit2Ds = new RaycastHit2D[1024]; // Used to find if the GUI is in use private static List tempRaycastResults = new List(10); // Used by RaycastGui private static PointerEventData tempPointerEventData; // Used by RaycastGui private static EventSystem tempEventSystem; private static List> tempLayers = new List>(); public LeanScreenQuery(MethodType newMethod) { Method = newMethod; Search = SearchType.GetComponentInParent; RequiredTag = null; Camera = null; Layers = Physics.DefaultRaycastLayers; Distance = 50.0f; } public static void ChangeLayers(GameObject root, bool ancestors, bool children) { tempLayers.Add(new KeyValuePair(root, root.layer)); root.layer = 2; // Ignore raycast if (ancestors == true && root.transform.parent != null) { ChangeLayers(root.transform.parent.gameObject, true, false); } if (children == true) { for (var i = root.transform.childCount - 1; i >= 0; i--) { ChangeLayers(root.transform.GetChild(i).gameObject, false, true); } } } public static void RevertLayers() { foreach (var tempLayer in tempLayers) { if (tempLayer.Key != null) { tempLayer.Key.layer = tempLayer.Value; } } tempLayers.Clear(); } public T Query(GameObject gameObject, Vector2 screenPosition) { var result = default(T); var root = default(Component); var worldPosition = default(Vector3); if (TryQuery(gameObject, screenPosition, ref result, ref root, ref worldPosition) == true) { return result; } return default(T); } public bool TryQuery(GameObject gameObject, Vector2 screenPosition, ref T result, ref Component root, ref Vector3 worldPosition) { var camera = LeanHelper.GetCamera(Camera, gameObject); var bestHit = default(Component); var bestDistance = float.PositiveInfinity; var bestPosition = default(Vector3); if (camera != null) { if (camera.pixelRect.Contains(screenPosition) == true) { DoRaycast3D(camera, screenPosition, ref bestHit, ref bestDistance, ref bestPosition); DoRaycast2D(camera, screenPosition, ref bestHit, ref bestDistance, ref bestPosition); DoRaycastUI(screenPosition, ref bestHit, ref bestDistance, ref bestPosition); } } if (bestHit != null) { worldPosition = bestPosition; return TryResult(bestHit, ref result, ref root); } return false; } private bool TryResult(Component hit, ref T result, ref Component component) { if (hit != null) { switch (Search) { case SearchType.GetComponent: if (TryGetComponent (hit, ref result, ref component) == false) return false; break; case SearchType.GetComponentInParent: if (TryGetComponentInParent (hit, ref result, ref component) == false) return false; break; case SearchType.GetComponentInChildren: if (TryGetComponentInChildren(hit, ref result, ref component) == false) return false; break; } // Discard if tag doesn't match if (result != null && string.IsNullOrEmpty(RequiredTag) == false && component.tag != RequiredTag) { return false; } return true; } return false; } private bool TryGetComponent(Component hit, ref T result, ref Component component) { result = hit.GetComponent(); if (result != null) { component = hit; return true; } return false; } private bool TryGetComponentInParent(Component hit, ref T result, ref Component component) { if (TryGetComponent(hit, ref result, ref component) == true) { return true; } if (hit.transform.parent != null) { return TryGetComponentInParent(hit.transform.parent, ref result, ref component); } return false; } private bool TryGetComponentInChildren(Component hit, ref T result, ref Component component) { if (TryGetComponent(hit, ref result, ref component) == true) { return true; } for (var i = 0; i < hit.transform.childCount; i++) { if (TryGetComponentInParent(hit.transform.GetChild(i), ref result, ref component) == true) { return true; } } return false; } private static int GetClosestRaycastHitsIndex(int count) { var closestIndex = -1; var closestDistance = float.PositiveInfinity; for (var i = 0; i < count; i++) { var distance = raycastHits[i].distance; if (distance < closestDistance) { closestIndex = i; closestDistance = distance; } } return closestIndex; } private void DoRaycast3D(Camera camera, Vector2 screenPosition, ref Component bestResult, ref float bestDistance, ref Vector3 bestPosition) { var ray = camera.ScreenPointToRay(screenPosition); var count = Physics.RaycastNonAlloc(ray, raycastHits, float.PositiveInfinity, Layers); if (count > 0) { var closestHit = raycastHits[GetClosestRaycastHitsIndex(count)]; var distance = closestHit.distance; if (distance < bestDistance) { bestDistance = distance; bestResult = closestHit.collider; bestPosition = closestHit.point; } } } private void DoRaycast2D(Camera camera, Vector2 screenPosition, ref Component bestResult, ref float bestDistance, ref Vector3 bestPosition) { var ray = camera.ScreenPointToRay(screenPosition); var count = Physics2D.GetRayIntersectionNonAlloc(ray, raycastHit2Ds, float.PositiveInfinity, Layers); if (count > 0) { var closestHit = raycastHit2Ds[0]; var distance = closestHit.distance; if (distance < bestDistance) { bestDistance = distance; bestResult = closestHit.transform; bestPosition = closestHit.point; } } } private void DoRaycastUI(Vector2 screenPosition, ref Component bestResult, ref float bestDistance, ref Vector3 bestPosition) { var currentEventSystem = EventSystem.current; if (currentEventSystem == null) { currentEventSystem = Object.FindObjectOfType(); } if (currentEventSystem != null) { // Create point event data for this event system? if (currentEventSystem != tempEventSystem) { tempEventSystem = currentEventSystem; if (tempPointerEventData == null) { tempPointerEventData = new PointerEventData(tempEventSystem); } else { tempPointerEventData.Reset(); } } // Raycast event system at the specified point tempPointerEventData.position = screenPosition; currentEventSystem.RaycastAll(tempPointerEventData, tempRaycastResults); foreach (var result in tempRaycastResults) { var resultLayer = 1 << result.gameObject.layer; if ((resultLayer & Layers) != 0) { var distance = result.distance; if (distance < bestDistance) { bestDistance = distance; bestResult = result.gameObject.transform; bestPosition = result.worldPosition; } break; } } } } } } #if UNITY_EDITOR namespace Lean.Common.Editor { using UnityEditor; [CustomPropertyDrawer(typeof(LeanScreenQuery))] public class LeanScreenQuery_Drawer : PropertyDrawer { public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { var method = (LeanScreenQuery.MethodType)property.FindPropertyRelative("Method").enumValueIndex; var height = base.GetPropertyHeight(property, label); var step = height + 2; switch (method) { case LeanScreenQuery.MethodType.Raycast: height += step * 4; break; } return height; } public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label) { var method = (LeanScreenQuery.MethodType)property.FindPropertyRelative("Method").enumValueIndex; var height = base.GetPropertyHeight(property, label); rect.height = height; DrawProperty(ref rect, property, label, "Method", label.text, "The method used to search the scene based on a screen position.\n\nRaycast = 3D, 2D, and EventSystem raycast."); EditorGUI.indentLevel++; { DrawProperty(ref rect, property, label, "Camera", null, "The camera the depth calculations will be done using.\n\nNone = MainCamera."); switch (method) { case LeanScreenQuery.MethodType.Raycast: { //LeanEditor.BeginError(property.FindPropertyRelative("Distance").floatValue == 0.0f); // DrawProperty(ref rect, property, label, "Distance", "Distance", "The world space distance from the camera the point will be placed. This should be greater than 0."); //LeanEditor.EndError(); LeanEditor.BeginError(property.FindPropertyRelative("Layers").intValue == 0.0f); DrawProperty(ref rect, property, label, "Layers", "Layers", "The scene will be queried (e.g. Raycast) against these layers."); LeanEditor.EndError(); DrawProperty(ref rect, property, label, "Search", "Search", "When the query hits a GameObject, how should the desired component be searched for relative to it?"); DrawProperty(ref rect, property, label, "RequiredTag", "RequiredTag", "The component found from the search must have this tag."); } break; } } EditorGUI.indentLevel--; } private void DrawObjectProperty(ref Rect rect, SerializedProperty property, string title, string tooltip) where T : Object { var propertyObject = property.FindPropertyRelative("Object"); var oldValue = propertyObject.objectReferenceValue as T; LeanEditor.BeginError(oldValue == null); var mixed = EditorGUI.showMixedValue; EditorGUI.showMixedValue = propertyObject.hasMultipleDifferentValues; var newValue = EditorGUI.ObjectField(rect, new GUIContent(title, tooltip), oldValue, typeof(T), true); EditorGUI.showMixedValue = mixed; LeanEditor.EndError(); if (oldValue != newValue) { propertyObject.objectReferenceValue = newValue; } rect.y += rect.height; } private void DrawProperty(ref Rect rect, SerializedProperty property, GUIContent label, string childName, string overrideName = null, string overrideTooltip = null) { var childProperty = property.FindPropertyRelative(childName); label.text = string.IsNullOrEmpty(overrideName) == false ? overrideName : childProperty.displayName; label.tooltip = string.IsNullOrEmpty(overrideTooltip) == false ? overrideTooltip : childProperty.tooltip; EditorGUI.PropertyField(rect, childProperty, label); rect.y += rect.height + 2; } } } #endif