415 lines
13 KiB
C#
415 lines
13 KiB
C#
using UnityEngine;
|
|
using Lean.Common;
|
|
|
|
namespace Lean.Touch
|
|
{
|
|
/// <summary>This struct handles the conversion between screen coordinates, and world coordinates.
|
|
/// This conversion is required for many touch interactions, and there are numerous ways it can be performed.</summary>
|
|
[System.Serializable]
|
|
public struct LeanScreenDepth
|
|
{
|
|
public enum ConversionType
|
|
{
|
|
FixedDistance,
|
|
DepthIntercept,
|
|
PhysicsRaycast,
|
|
PlaneIntercept,
|
|
PathClosest,
|
|
AutoDistance,
|
|
HeightIntercept
|
|
}
|
|
|
|
/// <summary>The method used to convert between screen coordinates, and world coordinates.
|
|
/// FixedDistance = A point will be projected out from the camera.
|
|
/// DepthIntercept = A point will be intercepted out from the camera on a surface lying flat on the XY plane.
|
|
/// PhysicsRaycast = A ray will be cast from the camera.
|
|
/// PathClosest = A point will be intercepted out from the camera to the closest point on the specified path.
|
|
/// AutoDistance = A point will be projected out from the camera based on the current Transform depth.
|
|
/// HeightIntercept = A point will be intercepted out from the camera on a surface lying flat on the XZ plane.</summary>
|
|
public ConversionType Conversion;
|
|
|
|
/// <summary>The camera the depth calculations will be done using.
|
|
/// None = MainCamera.</summary>
|
|
public Camera Camera;
|
|
|
|
/// <summary>The plane/path/etc that will be intercepted.</summary>
|
|
public Object Object;
|
|
|
|
/// <summary>The layers used in the raycast.</summary>
|
|
public LayerMask Layers;
|
|
|
|
/// <summary>Tooltips are modified at runtime based on Conversion setting.</summary>
|
|
public float Distance;
|
|
|
|
/// <summary>When performing a ScreenDepth conversion, the converted point can have a normal associated with it. This stores that.</summary>
|
|
public static Vector3 LastWorldNormal = Vector3.forward;
|
|
|
|
private static readonly RaycastHit[] hits = new RaycastHit[128];
|
|
|
|
public LeanScreenDepth(ConversionType newConversion, int newLayers = Physics.DefaultRaycastLayers, float newDistance = 0.0f)
|
|
{
|
|
Conversion = newConversion;
|
|
Camera = null;
|
|
Object = null;
|
|
Layers = newLayers;
|
|
Distance = newDistance;
|
|
}
|
|
|
|
// This will do the actual conversion
|
|
public Vector3 Convert(Vector2 screenPoint, GameObject gameObject = null, Transform ignore = null)
|
|
{
|
|
var position = default(Vector3);
|
|
|
|
TryConvert(ref position, screenPoint, gameObject, ignore);
|
|
|
|
return position;
|
|
}
|
|
|
|
// This will return the delta between two converted screenPoints
|
|
public Vector3 ConvertDelta(Vector2 lastScreenPoint, Vector2 screenPoint, GameObject gameObject = null, Transform ignore = null)
|
|
{
|
|
var lastWorldPoint = Convert(lastScreenPoint, gameObject, ignore);
|
|
var worldPoint = Convert( screenPoint, gameObject, ignore);
|
|
|
|
return worldPoint - lastWorldPoint;
|
|
}
|
|
|
|
// This will do the actual conversion
|
|
public bool TryConvert(ref Vector3 position, Vector2 screenPoint, GameObject gameObject = null, Transform ignore = null)
|
|
{
|
|
var camera = LeanHelper.GetCamera(Camera, gameObject);
|
|
|
|
if (camera != null)
|
|
{
|
|
switch (Conversion)
|
|
{
|
|
case ConversionType.FixedDistance:
|
|
{
|
|
var screenPoint3 = new Vector3(screenPoint.x, screenPoint.y, Distance);
|
|
|
|
position = camera.ScreenToWorldPoint(screenPoint3);
|
|
|
|
LastWorldNormal = -camera.transform.forward;
|
|
|
|
return true;
|
|
}
|
|
|
|
case ConversionType.DepthIntercept:
|
|
{
|
|
var ray = camera.ScreenPointToRay(screenPoint);
|
|
var slope = -ray.direction.z;
|
|
|
|
if (slope != 0.0f)
|
|
{
|
|
var scale = (ray.origin.z - Distance) / slope;
|
|
|
|
position = ray.GetPoint(scale);
|
|
|
|
LastWorldNormal = Vector3.back;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ConversionType.PhysicsRaycast:
|
|
{
|
|
var ray = camera.ScreenPointToRay(screenPoint);
|
|
var hitCount = Physics.RaycastNonAlloc(ray, hits, float.PositiveInfinity, Layers);
|
|
var bestPoint = default(Vector3);
|
|
var bestDist = float.PositiveInfinity;
|
|
|
|
for (var i = hitCount - 1; i >= 0; i--)
|
|
{
|
|
var hit = hits[i];
|
|
var hitDistance = hit.distance;
|
|
|
|
if (hitDistance < bestDist && IsChildOf(hit.transform, ignore) == false)
|
|
{
|
|
bestPoint = hit.point + hit.normal * Distance;
|
|
bestDist = hitDistance;
|
|
|
|
LastWorldNormal = hit.normal;
|
|
}
|
|
}
|
|
|
|
if (bestDist < float.PositiveInfinity)
|
|
{
|
|
position = bestPoint;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ConversionType.PlaneIntercept:
|
|
{
|
|
var plane = default(LeanPlane);
|
|
|
|
if (Exists(gameObject, ref plane) == true)
|
|
{
|
|
var ray = camera.ScreenPointToRay(screenPoint);
|
|
var hit = default(Vector3);
|
|
|
|
if (plane.TryRaycast(ray, ref hit, Distance) == true)
|
|
{
|
|
position = hit;
|
|
|
|
LastWorldNormal = plane.transform.forward;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ConversionType.PathClosest:
|
|
{
|
|
var path = default(LeanPath);
|
|
|
|
if (Exists(gameObject, ref path) == true)
|
|
{
|
|
var ray = camera.ScreenPointToRay(screenPoint);
|
|
|
|
if (path.TryGetClosest(ray, ref position, -1, Distance * Time.deltaTime) == true)
|
|
{
|
|
LastWorldNormal = LeanPath.LastWorldNormal;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ConversionType.AutoDistance:
|
|
{
|
|
if (gameObject != null)
|
|
{
|
|
var depth = camera.WorldToScreenPoint(gameObject.transform.position).z;
|
|
var screenPoint3 = new Vector3(screenPoint.x, screenPoint.y, depth + Distance);
|
|
|
|
position = camera.ScreenToWorldPoint(screenPoint3);
|
|
|
|
LastWorldNormal = -camera.transform.forward;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ConversionType.HeightIntercept:
|
|
{
|
|
var ray = camera.ScreenPointToRay(screenPoint);
|
|
var slope = -ray.direction.y;
|
|
|
|
if (slope != 0.0f)
|
|
{
|
|
var scale = (ray.origin.y - Distance) / slope;
|
|
|
|
position = ray.GetPoint(scale);
|
|
|
|
LastWorldNormal = Vector3.down;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Failed to find camera. Either tag your cameras MainCamera, or set one in this component.", gameObject);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// If the specified object doesn't exist, try and find it in the scene
|
|
private bool Exists<T>(GameObject gameObject, ref T instance)
|
|
where T : Object
|
|
{
|
|
instance = Object as T;
|
|
|
|
// Already exists?
|
|
if (instance != null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Exists in ancestor?
|
|
Object = instance = gameObject.GetComponentInParent<T>();
|
|
|
|
if (instance != null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Exists in scene?
|
|
Object = instance = Object.FindObjectOfType<T>();
|
|
|
|
if (instance != null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Doesn't exist
|
|
return false;
|
|
}
|
|
|
|
// This will return true if current or one of its parents matches the specified gameObject's Transform (current must be non-null)
|
|
private static bool IsChildOf(Transform current, Transform target)
|
|
{
|
|
if (target != null)
|
|
{
|
|
while (true)
|
|
{
|
|
if (current == target)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
current = current.parent;
|
|
|
|
if (current == null)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
namespace Lean.Touch.Editor
|
|
{
|
|
using UnityEditor;
|
|
|
|
[CustomPropertyDrawer(typeof(LeanScreenDepth))]
|
|
public class LeanScreenDepth_Drawer : PropertyDrawer
|
|
{
|
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
|
{
|
|
var conversion = (LeanScreenDepth.ConversionType)property.FindPropertyRelative("Conversion").enumValueIndex;
|
|
var height = base.GetPropertyHeight(property, label);
|
|
var step = height + 2;
|
|
|
|
switch (conversion)
|
|
{
|
|
case LeanScreenDepth.ConversionType.FixedDistance: height += step * 2; break;
|
|
case LeanScreenDepth.ConversionType.DepthIntercept: height += step * 2; break;
|
|
case LeanScreenDepth.ConversionType.PhysicsRaycast: height += step * 3; break;
|
|
case LeanScreenDepth.ConversionType.PlaneIntercept: height += step * 3; break;
|
|
case LeanScreenDepth.ConversionType.PathClosest: height += step * 3; break;
|
|
case LeanScreenDepth.ConversionType.AutoDistance: height += step * 2; break;
|
|
case LeanScreenDepth.ConversionType.HeightIntercept: height += step * 2; break;
|
|
}
|
|
|
|
return height;
|
|
}
|
|
|
|
public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
|
|
{
|
|
var conversion = (LeanScreenDepth.ConversionType)property.FindPropertyRelative("Conversion").enumValueIndex;
|
|
var height = base.GetPropertyHeight(property, label);
|
|
|
|
rect.height = height;
|
|
|
|
DrawProperty(ref rect, property, label, "Conversion", label.text, "The method used to convert between screen coordinates, and world coordinates.\n\nFixedDistance = A point will be projected out from the camera.\n\nDepthIntercept = A point will be intercepted out from the camera on a surface lying flat on the XY plane.\n\nPhysicsRaycast = A ray will be cast from the camera.\n\nPathClosest = A point will be intercepted out from the camera to the closest point on the specified path.\n\nAutoDistance = A point will be projected out from the camera based on the current Transform depth.\n\nHeightIntercept = A point will be intercepted out from the camera on a surface lying flat on the XZ plane.");
|
|
|
|
EditorGUI.indentLevel++;
|
|
{
|
|
DrawProperty(ref rect, property, label, "Camera", null, "The camera the depth calculations will be done using.\n\nNone = MainCamera.");
|
|
|
|
switch (conversion)
|
|
{
|
|
case LeanScreenDepth.ConversionType.FixedDistance:
|
|
{
|
|
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();
|
|
}
|
|
break;
|
|
|
|
case LeanScreenDepth.ConversionType.DepthIntercept:
|
|
{
|
|
DrawProperty(ref rect, property, label, "Distance", "Z =", "The world space point along the Z axis the plane will be placed. For normal 2D scenes this should be 0.");
|
|
}
|
|
break;
|
|
|
|
case LeanScreenDepth.ConversionType.PhysicsRaycast:
|
|
{
|
|
LeanEditor.BeginError(property.FindPropertyRelative("Layers").intValue == 0);
|
|
DrawProperty(ref rect, property, label, "Layers", "The layers used in the raycast.");
|
|
LeanEditor.EndError();
|
|
DrawProperty(ref rect, property, label, "Distance", "Offset", "The world space offset from the raycast hit point.");
|
|
}
|
|
break;
|
|
|
|
case LeanScreenDepth.ConversionType.PlaneIntercept:
|
|
{
|
|
DrawObjectProperty<LeanPlane>(ref rect, property, "Plane", "The plane that will be intercepted.");
|
|
DrawProperty(ref rect, property, label, "Distance", "Offset", "The world space offset from the intercept hit point.");
|
|
}
|
|
break;
|
|
|
|
case LeanScreenDepth.ConversionType.PathClosest:
|
|
{
|
|
DrawObjectProperty<LeanPath>(ref rect, property, "Path", "The path that will be intercepted.");
|
|
DrawProperty(ref rect, property, label, "Distance", "Max Delta", "The maximum amount of segments that can be moved between.");
|
|
}
|
|
break;
|
|
|
|
case LeanScreenDepth.ConversionType.AutoDistance:
|
|
{
|
|
DrawProperty(ref rect, property, label, "Distance", "Offset", "The depth offset from the calculated point.");
|
|
}
|
|
break;
|
|
|
|
case LeanScreenDepth.ConversionType.HeightIntercept:
|
|
{
|
|
DrawProperty(ref rect, property, label, "Distance", "Y =", "The world space point along the Y axis the plane will be placed. For normal top down scenes this should be 0.");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
private void DrawObjectProperty<T>(ref Rect rect, SerializedProperty property, string title, string tooltip)
|
|
where T : Object
|
|
{
|
|
var propertyObject = property.FindPropertyRelative("Object");
|
|
var oldValue = propertyObject.objectReferenceValue as T;
|
|
|
|
var color = GUI.color; if (oldValue == null) GUI.color = Color.red;
|
|
var mixed = EditorGUI.showMixedValue; EditorGUI.showMixedValue = propertyObject.hasMultipleDifferentValues;
|
|
var newValue = EditorGUI.ObjectField(rect, new GUIContent(title, tooltip), oldValue, typeof(T), true);
|
|
EditorGUI.showMixedValue = mixed;
|
|
GUI.color = color;
|
|
|
|
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 |