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; public LeanScreenQuery(MethodType newMethod) { Method = newMethod; Search = SearchType.GetComponentInParent; RequiredTag = null; Camera = null; Layers = Physics.DefaultRaycastLayers; Distance = 50.0f; } public T Query(GameObject gameObject, Vector2 screenPosition, ref Vector3 worldPosition) where T : Component { var camera = LeanHelper.GetCamera(Camera, gameObject); var bestResult = default(Component); var bestDistance = float.PositiveInfinity; var bestPosition = default(Vector3); if (camera != null) { if (camera.pixelRect.Contains(screenPosition) == true) { DoRaycast3D(camera, screenPosition, ref bestResult, ref bestDistance, ref bestPosition); DoRaycast2D(camera, screenPosition, ref bestResult, ref bestDistance, ref bestPosition); DoRaycastUI(screenPosition, ref bestResult, ref bestDistance, ref bestPosition); } } if (bestResult != null) { worldPosition = bestPosition; return Result(bestResult); } return null; } private T Result(Component bestResult) where T : Component { var result = default(T); if (bestResult != null) { switch (Search) { case SearchType.GetComponent: result = bestResult.GetComponent (); break; case SearchType.GetComponentInParent: result = bestResult.GetComponentInParent (); break; case SearchType.GetComponentInChildren: result = bestResult.GetComponentInChildren(); break; } // Discard if tag doesn't match if (result != null && string.IsNullOrEmpty(RequiredTag) == false && result.tag != RequiredTag) { result = null; } } return result; } 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