Unity3D相机操控(完整模拟Scene视图操作)


1. 需求

  一直想把Scene视图相机的操作复制到Game视图来,之前工作实现了一部分,但是不完善,前几天晚上抽空重新写了个。
  在写的过程中遇到一些问题,这里记录一下。
  Scene视图的操作总结如下:
  1. 正交/透视视图的缓动切换
   2. 滚动鼠标滚轮能够拉近、缩远
  3. 按住鼠标中键能够上下左右拖拽相机
  4. ALT+鼠标左键 或 按住鼠标右键 可360查看场景
  5. 正视图、左视图、俯视图等
  6. 按住F能够聚焦物体
  7. ALT+鼠标右键 拖动鼠标能够放大缩小视角
  8. 选中物体后,按住Shift+F,相机会一直跟随物体
  这里实现了1~7,8没实现。

2. 效果

先看看效果吧。
正交/透视切换。
正交透视切换
360°查看。
360°查看
聚焦和各种视图。
聚焦及各种视图
中键拖拽(物体始终保持在鼠标下方)。
在这里插入图片描述
缩放视角。
缩放视角

3. 逻辑梳理

3.1 正交/透视视图的缓动切换

  实现相机正交视图和透视视图缓动切换的核心是对相机的投影矩阵进行插值(从相机当前的投影矩阵插值到正交投影矩阵或者透视投影矩阵)
  分两个步骤:

  1. 根据相机的参数算出相机对应的透视投影矩阵和正交投影矩阵,如fieldOfView/aspect/orthographicSize/farClipPlane/nearClipPlane这些参数。
    计算透视投影矩阵的公式如下。
    透视摄像机的参数对透视投影视锥体的影响
    透视投影矩阵计算公式
 // 透视投影矩阵.
 Matrix4x4 defaultPersProjMat = new Matrix4x4();
 //defaultPersProjMat = Matrix4x4.Perspective(fov, aspect, near, far);           // 透视矩阵也可直接调用这个来计算.
 defaultPersProjMat.SetRow(0, new Vector4(1.0f / (tan * aspect), 0, 0, 0));
 defaultPersProjMat.SetRow(1, new Vector4(0, 1.0f / tan, 0, 0));
 defaultPersProjMat.SetRow(2, new Vector4(0, 0, -(far + near) / (far - near), -2 * far * near / (far - near)));
 defaultPersProjMat.SetRow(3, new Vector4(0, 0, -1, 0));

  计算正交投影矩阵的公式如下。
正交摄像机的参数对正交投影视锥体的影响
正交投影矩阵计算公式

// 正交投影矩阵.
Matrix4x4 defaultOrthProjMat = new Matrix4x4();
defaultOrthProjMat.SetRow(0, new Vector4(1.0f / (aspect * size), 0, 0, 0));
defaultOrthProjMat.SetRow(1, new Vector4(0, 1.0f / size, 0, 0));
defaultOrthProjMat.SetRow(2, new Vector4(0, 0, -2f / (far - near), -(far + near) / (far - near)));
defaultOrthProjMat.SetRow(3, new Vector4(0, 0, 0, 1));
  1. 对投影矩阵进行插值
    对矩阵插值其实也就是对矩阵中的各行进行插值。
public static Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float t)
{
    t = Mathf.Clamp01(t);
    Matrix4x4 lerpMatrix = new Matrix4x4();
    for (int i = 0; i < 4; i++)
    {
        lerpMatrix.SetRow(i, Vector4.Lerp(from.GetRow(i), to.GetRow(i), t));
    }
    return lerpMatrix;
}

  有个特别重要的点。
  相机的fieldOfView和orthographicSize这两个参数必须同时修改,其中一个参数变了,另外一个参数也要跟着变。
  相机的fieldOfView和orthographicSize这两个参数必须同时修改,其中一个参数变了,另外一个参数也要跟着变。
  相机的fieldOfView和orthographicSize这两个参数必须同时修改,其中一个参数变了,另外一个参数也要跟着变。
  否则,透视/正交视图切换的时候会发现两个视角看到的物体的大小不一样。
  那么透视相机的fieldOfView和正交相机的orthographicSize对应的关系是什么呢?要把这个东西用文字表达清楚还真不是件容易的事,所以我画了张图,不知道大家能不能理解。如果没明白,知道公式是这样就行了。
fov与相机的关系
公式如下。

/// <summary>
/// 根据正交相机的size设置对应透视相机的fov
/// </summary>
/// <param name="size">正交相机高度的一半,即orthographicSize</param>
private float SetFovBySize(float size)
{
    float fov = 2.0f * Mathf.Atan(size / distance) * Mathf.Rad2Deg;
    m_Camera.fieldOfView = fov;
    return fov;
}

/// <summary>
/// 根据透视相机的fov设置对应正交相机的即orthographicSize
/// </summary>
/// <param name="fov">透视相机的fov</param>
private float SetSizeByFov(float fov)
{
    float size = distance * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
    m_Camera.orthographicSize = size;
    return size;
}

3.2 拉近缩远

  核心就是更改相机到观察物体的距离。
  注意,距离修改之后需要重新计算相机的透视投影矩阵和正交投影矩阵,从而保证正交/透视视图切换的正确性。
  具体看源码中的Zoom方法。

3.3 上下左右拖拽相机

  细心的读者可能会发现,上面的效果展示图中,无论我们鼠标如何移动,物体都跟随着鼠标
  看了好多网上给的代码,发现他们用的拖动系数都是固定的,如果我们相机拉远或者拉近就会出现拖动鼠标,物体会移动得老远或者移动得很慢的情况,如下图这种样子。
错误移动示例
  所以我做了修改,根据相机的远近动态去调整拖动系数,以达到物体始终在鼠标下的效果。
  原理就是根据鼠标移动的像素换算成相机移动的距离,分析见3.1节画的那张图。我急着去吃饭,就不多说了哈。看代码还不明白的话,留言就对了。逃。

    /// <summary>
    /// 计算拖拽距离.
    /// </summary>
    /// <param name="camera">相机</param>
    /// <param name="mouseDelta">鼠标移动距离</param>
    /// <param name="distance">相机距离观察物体的距离</param>
    private Vector3 CalcDragLength(Camera camera, Vector2 mouseDelta, float distance)
    {
        float rectHeight = -1;
        float rectWidth = -1;
        if (camera.orthographic)
        {
            rectHeight = 2 * camera.orthographicSize;
            //rectWidth = rectHeight / camera.aspect;
        }
        else
        {
            rectHeight = 2 * distance * Mathf.Tan(camera.fieldOfView * 0.5f * Mathf.Deg2Rad);
        }
        rectWidth = Screen.width * rectHeight / Screen.height;
        Vector3 moveDir = -rectWidth / Screen.width * mouseDelta.x * camera.transform.right - rectHeight / Screen.height * mouseDelta.y * camera.transform.up;

        return moveDir;
    }

4 源码

  把代码挂在相机上就能够使用了。两个类CameraController和Utils.Math。
  项目链接。
  链接:https://pan.baidu.com/s/1qBsaHJgBg5OFqkBiE8lv5Q
  提取码:mi6m。

using UnityEngine;
using System.Collections;

public class CameraController : MonoBehaviour
{
    /// <summary>
    /// 视角类型.
    /// </summary>
    private enum ViewType
    {
        _2D = 0,
        _3D
    }

    /// <summary>
    /// 鼠标按键.
    /// </summary>
    private enum MouseButton
    {
        /// <summary>
        /// 鼠标左键.
        /// </summary>
        LEFT = 0,
        /// <summary>
        /// 鼠标右键.
        /// </summary> 
        RIGHT,
        /// <summary>
        /// 鼠标中键.
        /// </summary>
        MIDDLE
    }

    /// <summary>
    /// 指针样式.
    /// </summary>
    private enum CursorType
    {
        /// <summary>
        /// 默认样式.
        /// </summary>
        DEFAULT = 0,
        /// <summary>
        /// 小手.
        /// </summary>
        HAND,
        /// <summary>
        /// 眼睛.
        /// </summary>
        EYE,
        /// <summary>
        /// 放大镜.
        /// </summary>
        MAGNIFIER
    }

    #region 字段
    private const string MOUSESCROLLWHEEL = "Mouse ScrollWheel";        // 鼠标滚轮.
    private const string MOUSEX = "Mouse X";
    private const string MOUSEY = "Mouse Y";

    #region 输入

    private bool leftAltKeyDown;                                        // 左Alt键-按下.
    private bool rightAltKeyDown;                                       // 右Alt键-按下.
    private bool leftAltKey;                                            // 左Alt键-长按.
    private bool rightAltKey;                                           // 右Alt键-长按.
    private bool leftAltKeyUp;                                          // 左Alt键-抬起.
    private bool rightAltKeyUp;                                         // 右Alt键-抬起.
    private bool leftMouseButtonDown;                                   // 鼠标左键-按下.
    private bool rightMouseButtonDown;                                  // 鼠标右键-按下.
    private bool leftMouseButton;                                       // 鼠标左键-长按.
    private bool rightMouseButton;                                      // 鼠标右键-长按.
    private bool rightMouseButtonUp;                                    // 鼠标右键-抬起.
    private bool middleMouseButton;                                     // 鼠标中键-长按.
    private bool middleMouseButtonUp;                                   // 鼠标中键-抬起.

    #endregion

    private Camera m_Camera;                                            // 相机.
    private Texture2D handCursorTexture;                                // 小手.
    private Texture2D eyeCursorTexture;                                 // 眼睛.
    private Texture2D magnifierCursorTexture;                           // 放大镜.

    private Vector2 hotSpot = Vector2.zero;
    private CursorMode cursorMode = CursorMode.Auto;

    private float angle_X;                                              // 水平方向的角度值,绕Transform的y轴旋转(左右)
    private float angle_Y;                                              // 竖直方向的角度值,绕Transform的x轴旋转(上下)
    private float angle_Z;

    // 切换视角
    private Matrix4x4 defaultOrthProjMat;                               // 相机默认的正交投影矩阵.
    private Matrix4x4 defaultPersProjMat;                               // 相机默认的透视投影矩阵.
    private Matrix4x4 currentProjMat;                                   // 相机当前的投影矩阵.
    private bool isChangingViewType = false;                            // 正在切换视角.
    private float viewTypeLerpTime = 0f;                                // 插值时间.
    private ViewType viewType;                                          // 当前视角.

    [Header("Zoom")]
    // 相机拉近拉远 Zoom
    [SerializeField]
    private float distance = 20f;                      // 相机与lookAroudPos点的距离.
    [SerializeField, Range(1f, 20f)]
    private float zoomSpeed = 10f;                                      // 滚轮缩放速度.
    [SerializeField]
    private float maxDistance = 100f;                  // 相机最远距离.
    [SerializeField]
    private float minDistance = 1f;                    // 相机最近距离.

    [Header("360° Rotate")]
    // Alt+鼠标拖动, 360度观察
    [SerializeField, Range(2f, 10f)]
    private float rotateSensitivity = 5f;                               // 旋转灵敏度.
    private Vector3 lookAroundPos = Vector3.zero;

    // Alt+鼠标右键 放大缩小
    [Header("Field Of View")]
    private float minFov = 20f;                                         // 最小视角.
    private float maxFov = 135f;                                        // 最大视角.
    private Vector3 lastMousePosFov = Vector3.zero;
    [SerializeField, Range(0.01f, 0.1f)]
    private float fovSensitivity = 0.05f;                               // 视角灵敏度.

    // 选中物体聚焦
    private GameObject selectedObj = null;                              // 选中的物体.
    private float raycastMaxDistance = 1000f;
    private LayerMask raycastLayerMask;
    private RaycastHit raycastHit;

    [Header("Focus")]
    [SerializeField, Range(0.1f, 5f)]
    private float focusSpeed = 3.0f;                                    // 聚焦的速度.
    [SerializeField]
    private float focusDistance = 3f;                                   // 聚焦时与物体的距离.
    private bool isFocusing = false;                                    // 相机正看向物体中.
    private float focusTime = 0f;                                       // 聚焦时间.
    private Vector3 cameraInitForward;                                  // 相机最初的前方.
    private Vector3 focusTargetForward;                                 // 聚焦目标前方.
    private Vector3 focusInitPos;                                       // 聚焦前相机初始位置.
    private Vector3 focusTargetPos;                                     // 聚焦目标位置.
    #endregion

    #region Unity_Method

    private void Start()
    {
        Init();
    }

    private void LateUpdate()
    {
        // 更新输入.
        UpdateInput();
        // 切换视角.
        SwitchViewType();
        // 拉近拉远.
        Zoom();
        // 拖动相机.
        Drag();
        // 360°查看.
        LookAround();
        // 放大缩小. 和切换视角共同使用时有Bug.
        Magnifier();
        // 聚焦.
        Focus();
    }

    private void OnGUI()
    {
        if (GUI.Button(new Rect(Vector2.zero, new Vector2(100, 50)), "正交"))
        {
            SetViewType(ViewType._2D);
        }

        if (GUI.Button(new Rect(Vector2.up * 60, new Vector2(100, 50)), "透视"))
        {
            SetViewType(ViewType._3D);
        }

        if (GUI.Button(new Rect(Vector2.up * 120, new Vector2(100, 50)), "正视图"))
        {
            Front();
        }

        if (GUI.Button(new Rect(Vector2.up * 180, new Vector2(100, 50)), "后视图"))
        {
            Back();
        }

        if (GUI.Button(new Rect(Vector2.up * 240, new Vector2(100, 50)), "左视图"))
        {
            Left();
        }

        if (GUI.Button(new Rect(Vector2.up * 300, new Vector2(100, 50)), "右视图"))
        {
            Right();
        }

        if (GUI.Button(new Rect(Vector2.up * 360, new Vector2(100, 50)), "上视图"))
        {
            Top();
        }

        if (GUI.Button(new Rect(Vector2.up * 420, new Vector2(100, 50)), "下视图"))
        {
            Down();
        }
    }

    #endregion

    #region 初始化相关

    /// <summary>
    /// 初始化.
    /// </summary>
    private void Init()
    {
        m_Camera = GetComponent<Camera>();
        raycastLayerMask = 1 << LayerMask.NameToLayer("Default");

        RecalcDistance();
        CalculateProjMatrix();                  // 获取相机默认矩阵.
        ResetLookAroundPos();                   // 重置观察中心.
        LoadCursorTexture();                    // 加载鼠标图标.
    }

    /// <summary>
    /// 重置观察中心(观察中心在相机的前方).
    /// </summary>
    private void ResetLookAroundPos()
    {
        lookAroundPos = m_Camera.transform.position + m_Camera.transform.rotation * (Vector3.forward * distance);
    }

    /// <summary>
    /// 加载鼠标指针图标.
    /// </summary>
    private void LoadCursorTexture()
    {
        handCursorTexture = Resources.Load<Texture2D>("Textures/Hand");
        eyeCursorTexture = Resources.Load<Texture2D>("Textures/Eye");
        magnifierCursorTexture = Resources.Load<Texture2D>("Textures/Magnifier");
    }

    /// <summary>
    /// 设置鼠标样式.
    /// </summary>
    private void SetCursor(CursorType cursorType)
    {
        switch (cursorType)
        {
            case CursorType.DEFAULT:
                Cursor.SetCursor(null, hotSpot, cursorMode);
                break;
            case CursorType.HAND:
                Cursor.SetCursor(handCursorTexture, hotSpot, cursorMode);
                break;
            case CursorType.EYE:
                Cursor.SetCursor(eyeCursorTexture, hotSpot, cursorMode);
                break;
            case CursorType.MAGNIFIER:
                Cursor.SetCursor(magnifierCursorTexture, hotSpot, cursorMode);
                break;
            default:
                Debug.LogError("未知指针类型.");
                break;
        }
    }

    /// <summary>
    /// 更新输入.
    /// </summary>
    private void UpdateInput()
    {
        leftAltKeyDown = Input.GetKeyDown(KeyCode.LeftAlt);                            // 左Alt键-按下.
        rightAltKeyDown = Input.GetKeyDown(KeyCode.RightAlt);                          // 右Alt键-按下.
        leftAltKey = Input.GetKey(KeyCode.LeftAlt);                                    // 左Alt键-长按.
        rightAltKey = Input.GetKey(KeyCode.RightAlt);                                  // 右Alt键-长按.
        leftAltKeyUp = Input.GetKeyUp(KeyCode.LeftAlt);                                // 左Alt键-抬起.
        rightAltKeyUp = Input.GetKeyUp(KeyCode.RightAlt);                              // 右Alt键-抬起.
        rightMouseButtonDown = Input.GetMouseButtonDown((int)MouseButton.RIGHT);       // 鼠标右键-按下.
        leftMouseButtonDown = Input.GetMouseButtonDown((int)MouseButton.LEFT);         // 鼠标左键-按下.
        leftMouseButton = Input.GetMouseButton((int)MouseButton.LEFT);                 // 鼠标左键-长按.
        rightMouseButton = Input.GetMouseButton((int)MouseButton.RIGHT);               // 鼠标右键-长按.
        rightMouseButtonUp = Input.GetMouseButtonUp((int)MouseButton.RIGHT);           // 鼠标右键-抬起.

        middleMouseButton = Input.GetMouseButton((int)MouseButton.MIDDLE);             // 鼠标中键-按下.  
        middleMouseButtonUp = Input.GetMouseButtonUp((int)MouseButton.MIDDLE);         // 鼠标中键-抬起.
    }

    /// <summary>
    /// 计算距离.
    /// </summary>
    private void RecalcDistance()
    {
        Vector3 checkFarPos = m_Camera.transform.position + m_Camera.transform.rotation * (Vector3.forward * 1000);
        if (Physics.Linecast(m_Camera.transform.position, checkFarPos, out raycastHit))
        {
            // 如果相机中心有物体,则以物体到相机为距离
            distance = raycastHit.distance;
            if (selectedObj == null)
            {
                selectedObj = raycastHit.collider.gameObject;
            }
        }
    }

    #endregion

    #region 切换视角

    /// <summary>
    /// 计算投影矩阵.
    /// </summary>
    private void CalculateProjMatrix()
    {
        currentProjMat = m_Camera.projectionMatrix;

        float aspect = m_Camera.aspect;
        float fov = m_Camera.fieldOfView;
        float size = m_Camera.orthographicSize;
        float far = m_Camera.farClipPlane;
        float near = m_Camera.nearClipPlane;
        float tan = Mathf.Tan(Mathf.Deg2Rad * fov * 0.5f);

        if (m_Camera.orthographic)
        {
            // 启动时相机是正交的
            viewType = ViewType._2D;
            fov = SetFovBySize(size);
        }
        else
        {
            // 启动时相机是透视的
            viewType = ViewType._3D;
            size = SetSizeByFov(fov);
        }

        // 透视投影矩阵.
        defaultPersProjMat = new Matrix4x4();
        //defaultPersProjMat = Matrix4x4.Perspective(fov, aspect, near, far);           // 透视矩阵也可直接调用这个来计算.
        defaultPersProjMat.SetRow(0, new Vector4(1.0f / (tan * aspect), 0, 0, 0));
        defaultPersProjMat.SetRow(1, new Vector4(0, 1.0f / tan, 0, 0));
        defaultPersProjMat.SetRow(2, new Vector4(0, 0, -(far + near) / (far - near), -2 * far * near / (far - near)));
        defaultPersProjMat.SetRow(3, new Vector4(0, 0, -1, 0));

        // 正交投影矩阵.
        defaultOrthProjMat = new Matrix4x4();
        defaultOrthProjMat.SetRow(0, new Vector4(1.0f / (aspect * size), 0, 0, 0));
        defaultOrthProjMat.SetRow(1, new Vector4(0, 1.0f / size, 0, 0));
        defaultOrthProjMat.SetRow(2, new Vector4(0, 0, -2f / (far - near), -(far + near) / (far - near)));
        defaultOrthProjMat.SetRow(3, new Vector4(0, 0, 0, 1));
    }

    /// <summary>
    /// 切换视角.
    /// </summary>
    private void SwitchViewType()
    {
        if (isChangingViewType)
        {
            viewTypeLerpTime += Time.deltaTime * 2.0f;
            if (viewType == ViewType._2D)
            {
                // 切换到正交视图.
                currentProjMat = Utils.Math.MatrixLerp(currentProjMat, defaultOrthProjMat, viewTypeLerpTime);
            }
            else
            {
                // 切换到透视视图.
                currentProjMat = Utils.Math.MatrixLerp(currentProjMat, defaultPersProjMat, viewTypeLerpTime);
            }
            m_Camera.projectionMatrix = currentProjMat;
            if (viewTypeLerpTime >= 1.0f)
            {
                isChangingViewType = false;
                viewTypeLerpTime = 0f;

                if (viewType == ViewType._2D)
                {
                    m_Camera.orthographic = true;
                }
                else
                {
                    m_Camera.orthographic = false;
                }
                m_Camera.ResetProjectionMatrix();
            }
        }
    }

    /// <summary>
    /// 设置将要切换的视角.
    /// </summary>
    /// <param name="targetViewType">目标视角.</param>
    private void SetViewType(ViewType targetViewType)
    {
        if (viewType != targetViewType)
        {
            viewType = targetViewType;
            isChangingViewType = true;
            viewTypeLerpTime = 0f;
        }
    }

    /// <summary>
    /// 根据正交相机的size设置对应透视相机的fov
    /// </summary>
    /// <param name="size">正交相机高度的一半,即orthographicSize</param>
    private float SetFovBySize(float size)
    {
        float fov = 2.0f * Mathf.Atan(size / distance) * Mathf.Rad2Deg;
        m_Camera.fieldOfView = fov;
        return fov;
    }

    /// <summary>
    /// 根据透视相机的fov设置对应正交相机的即orthographicSize
    /// </summary>
    /// <param name="fov">透视相机的fov</param>
    private float SetSizeByFov(float fov)
    {
        float size = distance * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
        m_Camera.orthographicSize = size;
        return size;
    }

    #endregion

    #region 拉近拉远

    /// <summary>
    /// 拉近拉远.
    /// </summary>
    /// <remarks>鼠标滚轮滚动,向上滚动拉近相机,向下滚动拉远相机</remarks>
    private void Zoom()
    {
        float scrollWheelValue = Input.GetAxis(MOUSESCROLLWHEEL);
        if (!Utils.Math.IsEqual(scrollWheelValue, 0f))
        {
            // scrollWheelValue:滚轮向上为+ 向下为-
            // 同时滚轮向上,拉近相机;滚轮向下,拉远相机
            distance -= scrollWheelValue * zoomSpeed;
            distance = Mathf.Clamp(distance, minDistance, maxDistance);

            m_Camera.transform.position = lookAroundPos - m_Camera.transform.rotation * (Vector3.forward * distance);

            // 相机距离改变后需同时修改fov或orthographicSize以及投影矩阵,保证正交/透视视图切换效果
            CalculateProjMatrix();
        }
    }

    #endregion

    #region 拖动

    private bool isDraging = false;
    private Vector3 lastMousePos;

    /// <summary>
    /// 拖动.
    /// </summary>
    /// <remark>长按鼠标中键拖动</remark>
    private void Drag()
    {
        // 鼠标中键拖动相机.
        if (middleMouseButton)
        {
            if (isDraging == false)
            {
                isDraging = true;
                // 设置小手指针.
                SetCursor(CursorType.HAND);

                lastMousePos = Input.mousePosition;
            }
            else
            {
                Vector3 newMousePos = Input.mousePosition;
                Vector3 delta = newMousePos - lastMousePos;
                m_Camera.transform.position += CalcDragLength(m_Camera, delta, distance);
                lastMousePos = newMousePos;

                // 相机移动,重新设置目标位置.
                ResetLookAroundPos();
            }
        }
        if (middleMouseButtonUp)
        {
            isDraging = false;
            // 恢复默认指针.
            SetCursor(CursorType.DEFAULT);
        }
    }

    /// <summary>
    /// 计算拖拽距离.
    /// </summary>
    /// <param name="camera">相机</param>
    /// <param name="mouseDelta">鼠标移动距离</param>
    /// <param name="distance">相机距离观察物体的距离</param>
    private Vector3 CalcDragLength(Camera camera, Vector2 mouseDelta, float distance)
    {
        float rectHeight = -1;
        float rectWidth = -1;
        if (camera.orthographic)
        {
            rectHeight = 2 * camera.orthographicSize;
            //rectWidth = rectHeight / camera.aspect;
        }
        else
        {
            rectHeight = 2 * distance * Mathf.Tan(camera.fieldOfView * 0.5f * Mathf.Deg2Rad);
        }
        rectWidth = Screen.width * rectHeight / Screen.height;
        Vector3 moveDir = -rectWidth / Screen.width * mouseDelta.x * camera.transform.right - rectHeight / Screen.height * mouseDelta.y * camera.transform.up;

        return moveDir;
    }

    #endregion

    #region 360°旋转观察

    /// <summary>
    /// 360°旋转查看.
    /// </summary>
    /// <remark>Alt+鼠标左键 或 鼠标右键</remark>
    private void LookAround()
    {
        if (leftAltKeyDown || rightAltKeyDown || rightMouseButtonDown)
        {
            SetCursor(CursorType.EYE);

            angle_X = m_Camera.transform.eulerAngles.y;
            angle_Y = m_Camera.transform.eulerAngles.x;
            angle_Z = m_Camera.transform.eulerAngles.z;
        }

        if ((leftAltKey && leftMouseButton)
            || (rightAltKey && leftMouseButton)
            || (!leftAltKey && !rightAltKey && rightMouseButton))
        {
            float deltaX = Input.GetAxis(MOUSEX);
            float deltaY = Input.GetAxis(MOUSEY);

            // 相机朝前和朝后,与鼠标的滑动方向相反
            if ((angle_Y > 90f && angle_Y < 270f) || (angle_Y < -90 && angle_Y > -270f))
            {
                angle_X -= deltaX * rotateSensitivity;
            }
            else
            {
                angle_X += deltaX * rotateSensitivity;
            }
            angle_Y -= deltaY * rotateSensitivity;

            angle_X = Utils.Math.ClampAngle(angle_X, -365, 365);
            angle_Y = Utils.Math.ClampAngle(angle_Y, -365, 365);

            SetCameraPos();
        }

        if (leftAltKeyUp || rightAltKeyUp || rightMouseButtonUp)
        {
            SetCursor(CursorType.DEFAULT);
        }
    }

    private void SetCameraPos()
    {
        Quaternion rotation = Quaternion.Euler(angle_Y, angle_X, angle_Z);
        Vector3 dir = Vector3.forward * -distance;
        m_Camera.transform.rotation = rotation;
        m_Camera.transform.position = lookAroundPos + rotation * dir;
    }

    #endregion

    #region 聚焦选中物体

    /// <summary>
    /// 聚焦选中物体.
    /// </summary>
    private void Focus()
    {
        if (leftMouseButtonDown)
        {
            Ray ray = m_Camera.ScreenPointToRay(Input.mousePosition);

            // Scene视图绘制射线方便测试.
            //Debug.DrawLine(ray.origin, ray.origin + ray.direction * raycastMaxDistance, Color.red, 3f);

            if (Physics.Raycast(ray, out raycastHit, raycastMaxDistance, raycastLayerMask))
            {
                selectedObj = raycastHit.collider.gameObject;
            }
            else
            {
                selectedObj = null;
                ResetLookAroundPos();
            }
        }

        if (selectedObj != null)
        {
            if (Input.GetKeyDown(KeyCode.F))
            {
                isFocusing = true;
                focusTime = 0f;
                cameraInitForward = m_Camera.transform.forward;
                focusTargetForward = Vector3.Normalize(selectedObj.transform.position - m_Camera.transform.position);

                focusInitPos = m_Camera.transform.position;
                distance = focusDistance;
                focusTargetPos = selectedObj.transform.position - focusTargetForward * distance;
            }
        }

        if (isFocusing)
        {
            focusTime += Time.deltaTime * focusSpeed;
            Vector3 forward = Vector3.Lerp(cameraInitForward, focusTargetForward, focusTime);
            m_Camera.transform.rotation = Quaternion.LookRotation(forward);

            m_Camera.transform.position = Vector3.Lerp(focusInitPos, focusTargetPos, focusTime);

            if (focusTime >= 1f)
            {
                // 聚焦完毕.
                focusTime = 0f;
                isFocusing = false;
                m_Camera.transform.rotation = Quaternion.LookRotation(focusTargetForward);
                m_Camera.transform.position = focusTargetPos;

                // 设置观察中心.
                lookAroundPos = selectedObj.transform.position;
            }
        }
    }

    #endregion

    #region 各种视图

    /// <summary>
    /// 正视图.
    /// </summary>
    private void Front()
    {
        angle_X = 180;
        angle_Y = 0;

        SetCameraPos();
    }

    /// <summary>
    /// 后视图.
    /// </summary>
    private void Back()
    {
        angle_X = 0;
        angle_Y = 0;

        SetCameraPos();
    }

    /// <summary>
    /// 右视图.
    /// </summary>
    private void Right()
    {
        angle_X = -90;
        angle_Y = 0;

        SetCameraPos();
    }

    /// <summary>
    /// 左视图.
    /// </summary>
    private void Left()
    {
        angle_X = 90;
        angle_Y = 0;

        SetCameraPos();
    }

    /// <summary>
    /// 上视图(俯视图).
    /// </summary>
    private void Top()
    {
        angle_X = 0;
        angle_Y = 90;

        SetCameraPos();
    }

    /// <summary>
    /// 下视图.
    /// </summary>
    private void Down()
    {
        angle_X = 0;
        angle_Y = -90;

        SetCameraPos();
    }

    #endregion

    #region 放大/缩小

    /// <summary>
    /// 放大缩小.
    /// </summary>
    private void Magnifier()
    {
        if ((leftAltKey || rightAltKey) && rightMouseButton)
        {
            if (lastMousePosFov.Equals(Vector3.zero))
            {
                lastMousePosFov = Input.mousePosition;
                SetCursor(CursorType.MAGNIFIER);
            }

            float deltaX = lastMousePosFov.x - Input.mousePosition.x;

            if (viewType == ViewType._2D)
            {
                // 透视视图改size
                float size = m_Camera.orthographicSize + deltaX * 0.01f;
                size = Mathf.Clamp(size, 1.0f, 8f);
                m_Camera.orthographicSize = size;

                // 调节size需同时修改相机透视模式下的fov
                SetFovBySize(size);
            }
            else
            {
                // 透视视图改fov
                float fov = m_Camera.fieldOfView + deltaX * fovSensitivity;
                fov = Mathf.Clamp(fov, minFov, maxFov);
                m_Camera.fieldOfView = fov;

                // 调节了fov需同时修改相机正交模式下的size
                SetSizeByFov(fov);
            }

            lastMousePosFov = Input.mousePosition;
        }

        if (rightMouseButtonUp)
        {
            SetCursor(CursorType.DEFAULT);
            lastMousePosFov = Vector3.zero;

            // 重新计算投影矩阵.
            CalculateProjMatrix();
        }
    }

    #endregion
}

  辅助类。

using UnityEngine;

namespace Utils
{
    public static class Math
    {
        public static bool IsEqual(float a, float b)
        {
            if(Mathf.Abs(a - b) < 0.000001f)
            {
                return true;
            }
            return false;
        }

        public static Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float t)
        {
            t = Mathf.Clamp01(t);
            Matrix4x4 lerpMatrix = new Matrix4x4();
            for (int i = 0; i < 4; i++)
            {
                lerpMatrix.SetRow(i, Vector4.Lerp(from.GetRow(i), to.GetRow(i), t));
            }
            return lerpMatrix;
        }

        /// <summary>
        /// 对角度进行限制.
        /// </summary>
        public static float ClampAngle(float angle, float min, float max)
        {
            if (angle < -360)
                angle += 360;
            if (angle > 360)
                angle -= 360;
            return Mathf.Clamp(angle, min, max);
        }
    }
}
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值