在Unity RunTime中实现UnityEditor的双击聚焦物体功能

直接上代码

核心工具代码:

using UnityEngine;

namespace Extension
{
    public static class CameraFocusExtension
    {
        /// <summary>
        /// 通过Render获取包围盒
        /// </summary>
        /// <param name="component"></param>
        /// <returns></returns>
        public static Bounds GetBoundsWithRender(this Component component)
        {
            var bounds = new Bounds(component.transform.position, Vector3.zero);
        
            foreach (Renderer renderer in component.GetComponentsInChildren<Renderer>())
            {
                bounds.Encapsulate(renderer.bounds);
            }

            return bounds;
        }

        /// <summary>
        /// 直接聚焦目标
        /// </summary>
        /// <param name="mainCamera">摄像机</param>
        /// <param name="distanceOffset">摄像机位移偏差</param>
        /// <param name="bounds">目标包围盒</param>
        /// <param name="isLockRotation">锁定旋转</param>
        public static void FocusBounds(this Camera mainCamera, Bounds bounds,float distanceOffset = 0,bool isLockRotation = false)
        {
            mainCamera.GetInfoOnFocusBounds(bounds,out var cameraPosition,out Quaternion cameraRotation,distanceOffset,isLockRotation);
            mainCamera.transform.position = cameraPosition;
            mainCamera.transform.rotation = cameraRotation;
        }
        /// <summary>
        /// 聚焦目标,并让聚焦物体位于RectTransform内
        /// 注意,此方法会锁定物体旋转
        /// </summary>
        /// <param name="mainCamera"></param>
        /// <param name="bounds"></param>
        /// <param name="rect"></param>
        /// <param name="distanceOffset"></param>
        public static void FocusBounds(this Camera mainCamera, Bounds bounds,RectTransform rect, float distanceOffset = 0
            )
        {
            //计算比例
            mainCamera.GetInfoOnFocusBounds(bounds,rect,out var cameraPosition,out Quaternion cameraRotation,distanceOffset);
            mainCamera.transform.position = cameraPosition;
            mainCamera.transform.rotation = cameraRotation;
        }

        /// <summary>
        /// 获得聚焦时摄像机应该处于的坐标,不会控制Camera移动和旋转
        /// 注意,此坐标已经默认摄像机朝向物体,不需要应用后又旋转摄像机
        /// </summary>
        /// <param name="mainCamera">摄像机</param>
        /// <param name="bounds">目标包围盒</param>
        /// <param name="cameraRotation">摄像机旋转角度</param>
        /// <param name="distanceOffset">摄像机位移偏差</param>
        /// <param name="cameraPosition">摄像机坐标</param>
        /// <param name="isLockRotation">是否锁定旋转,锁定下返回的是摄像机当前的旋转值</param>
        /// <returns></returns>
        public static void GetInfoOnFocusBounds(this Camera mainCamera, Bounds bounds,out Vector3 cameraPosition,out Quaternion cameraRotation,float distanceOffset = 0,bool isLockRotation = false)
        {
            Vector3[] vertices = new Vector3[8];
            var cameraTransform = mainCamera.transform;
            var targetCenter = bounds.center;

            if (isLockRotation)
            {
                cameraPosition = bounds.center - cameraTransform.forward*20;
                cameraRotation = cameraTransform.rotation;
                
            }
            else
            {
                cameraPosition = cameraTransform.position;
                cameraRotation = Quaternion.LookRotation(targetCenter-cameraPosition);
            }
         
            var centerDirection = targetCenter - cameraPosition;
        
            //计算Bounds盒八个点中,距离轴心最远的点
            var res =GetMaxOutPointAndOffset(bounds,centerDirection, cameraPosition,targetCenter,ref vertices);
        
            var outPoint = res.Item1;
        
            var viewAngle = GetCameraViewPortAngle(mainCamera, cameraTransform.forward);
        
            var centerDistance = Vector3.Distance(targetCenter, cameraPosition);
        
            var pointDistance = GetDistanceToLine(outPoint, cameraPosition, centerDirection);

            var d = Mathf.Tan(viewAngle* Mathf.Deg2Rad) * centerDistance;
        
        
            d = (pointDistance / d) * centerDistance;
            d += res.Item2; 
            cameraPosition = targetCenter + (-centerDirection.normalized * (d * (1+distanceOffset)));
        
        }
        public static void GetInfoOnFocusBounds(this Camera mainCamera, Bounds bounds,RectTransform rectTransform,out Vector3 cameraPosition,out Quaternion cameraRotation,float distanceOffset = 0)
        {
            Vector3[] vertices = new Vector3[8];
            var screenRect = Screen.safeArea;
            var rect = rectTransform.rect;
            rect.width *= rectTransform.localScale.x;
            rect.height *= rectTransform.localScale.y;
            
            
            var cameraTransform = mainCamera.transform;
            var targetCenter = bounds.center;
            var mul = new Vector2(rect.width / screenRect.width, rect.height / screenRect.height);
            Vector3 v1 = rectTransform.position;
            Vector3 v2 = screenRect.center;
            v1.z = 10;
            v2.z = 10;
            var focusOffsetToScreen =mainCamera.ScreenToWorldPoint(v1) - mainCamera.ScreenToWorldPoint(v2);
            cameraPosition = bounds.center - cameraTransform.forward*10 - focusOffsetToScreen;
            cameraRotation = cameraTransform.rotation;
                
          
            var centerDirection = targetCenter - cameraPosition;
        
            //计算Bounds盒八个点中,距离轴心最远的点
            var res =GetMaxOutPointAndOffset(bounds,centerDirection, cameraPosition,targetCenter,ref vertices);
        
            var outPoint = res.Item1;
        
            var viewAngle = GetCameraViewPortAngle(mainCamera, cameraTransform.forward) *Mathf.Min(mul.x,mul.y);
            
            var centerDistance = Vector3.Distance(targetCenter, cameraPosition);
        
            var pointDistance = GetDistanceToLine(outPoint, cameraPosition, centerDirection);

            var d = Mathf.Tan(viewAngle* Mathf.Deg2Rad) * centerDistance;
        
        
            d = (pointDistance / d) * centerDistance;
            d += res.Item2;
            
            cameraPosition = targetCenter + (-centerDirection.normalized * (d * (1+distanceOffset)));
            
          
        }
        static (Vector3,float) GetMaxOutPointAndOffset(Bounds targetBounds,Vector3 centerDirection,Vector3 currentPosition,Vector3 targetCenter,ref Vector3[] vertices)
        {
            float maxDistance = 0;
            float distanceOffset = 0;

            Vector3 point = default;
            Vector3 min = targetBounds.min;
            Vector3 max = targetBounds.max;
            vertices[0] = new Vector3(min.x, min.y, min.z);
            vertices[1] = new Vector3(min.x, min.y, max.z);
            vertices[2] = new Vector3(min.x, max.y, min.z);
            vertices[3] = new Vector3(min.x, max.y, max.z);
            vertices[4] = new Vector3(max.x, min.y, min.z);
            vertices[5] = new Vector3(max.x, min.y, max.z);
            vertices[6] = new Vector3(max.x, max.y, min.z);
            vertices[7] = new Vector3(max.x, max.y, max.z);
            
            
            foreach (var v in vertices)
            {
                var dis = GetSqrDistanceToLine(v, currentPosition, centerDirection);
                if (dis > maxDistance)
                {
                    maxDistance = dis;
                    point = v;
                }
                //计算点到中心的向量
                var dir = v - targetCenter;
                distanceOffset += Vector3.Project(dir, centerDirection).magnitude;
            }

            distanceOffset /= 8;
            return (point,distanceOffset);
        }
        static float GetSqrDistanceToLine(Vector3 point, Vector3 point2,Vector3 direction)
        {
            var pointDir = point - point2;
            var f = Vector3.Project(pointDir, direction) + point2;
            var dis = Vector3.SqrMagnitude(point- f);
            return dis;
        }
        static float GetDistanceToLine(Vector3 point, Vector3 point2,Vector3 direction)
        {
            var pointDir = point - point2;
            var f = Vector3.Project(pointDir, direction) + point2;
            var dis = Vector3.Distance(point, f);
            return dis;
        }
        static float GetCameraViewPortAngle(Camera camera,Vector3 centerDirection)
        {
            float minAngle = 0;
            var planes = GeometryUtility.CalculateFrustumPlanes(camera);
            for (int i = 0; i < 4; i++)
            {
                var plane = planes[i];
                //计算投影
                var p = Vector3.ProjectOnPlane(centerDirection, plane.normal);
                var angle = Vector3.Angle(p, centerDirection);
            
                if (minAngle == 0)
                    minAngle = angle;
                else if(angle < minAngle)
                    minAngle = angle;
            }

            return minAngle;

        }
    }
}

然后是示例代码:

#if UNITY_EDITOR

using Extension;
using UnityEngine;
using UnityEngine.InputSystem;

[ExecuteInEditMode]
public class CameraFocusSample : MonoBehaviour
{
    public Transform targetTransform;
    public Bounds targetBounds;
    public RectTransform rectTransform;
    public bool useRect = false;
    public bool updateBuildBounds = true;
    public bool updateFocus = false;
    
    /// <summary>
    /// 虽然场景里会出现锁定时但能旋转不能位移
    /// 但这是正常现象,因为这个参数是决定聚焦时是否改变摄像机旋转值的
    /// </summary>
    public bool isLockRotation= false;
    public float distanceOffset = 0;

    public Camera mainCamera;
    private void Update()
    {

        if (updateBuildBounds && targetTransform != null)
        {
            targetBounds = targetTransform.GetBoundsWithRender();
        }

        if (mainCamera == null)
            mainCamera = Camera.main;
        else if(updateFocus)
        {
            if (useRect)
                mainCamera.FocusBounds(targetBounds,rectTransform,distanceOffset);
            else
                mainCamera.FocusBounds(targetBounds,distanceOffset,isLockRotation);
        }
    }
    [ContextMenu("Focus")]
    private void Focus()
    {
        if (mainCamera == null)
            mainCamera = Camera.main;
        mainCamera.GetInfoOnFocusBounds(targetBounds,out var position,out var rotation,distanceOffset,isLockRotation);
        mainCamera.transform.position = position;
        mainCamera.transform.rotation = rotation;
    }

    private void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.cyan;
        Gizmos.DrawWireCube(targetBounds.center,targetBounds.size);
        var focusRect = rectTransform.rect;
        var z = Vector3.Distance(Camera.main.transform.position, targetTransform.position);
        var screenRect = Screen.safeArea;
        Vector3 v1 = rectTransform.position;
        v1.z = z;
        Vector3 v2 = screenRect.center;
        v2.z = z;
        v1 = mainCamera.ScreenToWorldPoint(v1);
        v2 = mainCamera.ScreenToWorldPoint(v2);
        Gizmos.DrawCube(v1,Vector3.one);
        Gizmos.DrawCube(v2,Vector3.one);
        Gizmos.color = Color.red;

        Gizmos.DrawLine(v1,v2);

        var focusOffsetToScreen =v1 -v2;
        Gizmos.DrawCube(Camera.main.transform.position - focusOffsetToScreen,Vector3.one);
    }
}


#endif

示例图:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值