戏做顿 发表于 2020-12-29 10:28

一个简单操作解决Unity中拖拽相机视图的跟手问题

今天需要写个类似Unity场景视图中控制相机运动的功能。其它功能都好弄,唯独这个拖拽视图的功能有点问题:拖拽视图时,跟手的效果不好。就像下图,这个是Unity场景视图的效果(当然这个应该时Unity故意这样做的):
跟手效果不好,立方体相对于鼠标位置偏移太多了。
于是我又打开Blender看了下,它的跟手效果就比较完美。
Blender拖拽场景视图
接下来来分析下如何解决这个问题。
首先拖拽视图的原理时:当我往一个方向拖拽相机视图时,物体的位置是不会变的。真正动的是相机,相机会朝拖拽的反方向运动,要想实现好的效果,关键是这个相机移动的速度问题。
首先想到的是通过暴露相机移动速度这个变量,手动调,不过这个不仅麻烦而且视野当中的物体距离相机的距离会对效果造成很大的影响(远的物体移动的很快,近的物体又太慢)。
我后面想到的解决办法是用Camera.ScreenToWorldPoint这个方法。通过鼠标在屏幕上的单位时间位移   c反推出世界坐标 w。详细说明参考:
unity中Camera.ScreenToWorldPoint_游戏_天行九歌-CSDN博客
从前面博客中的说明可以知道,要想相机移动与视图匹配,只需要
Camera.ScreenToWorldPoint(new Vector3(x,y,z))中参数的z分量等于上图中的s长度即可(相机视野为视锥体,物体近大远小,同样的屏幕位移,不同的z值反推到世界坐标位移是不相同的(越近越小))。
而s的长度我们可以简单的通过射线检测得到,完整逻辑。
实现结果如下:
比较好的跟手效果
下面是完整代码,方便各位参考:

using UnityEngine;


//按住鼠标右键旋转,同时按wasd移动,按住shift加速移动,按住中键拖拽视图
public class FreeCamera : MonoBehaviour <span class="p">{

    //相机旋转速度
    public float rotateSpeed = 5f;
    //相机缩放速度
    public float scaleSpeed = 10f;

    //旋转变量
    private float m_deltX = 0f;
    private float m_deltY = 0f;

    //移动变量
    float m_camNormalMoveSpeed = 0.2f;
    float m_camFastMoveSpeed = 2f;
    private Vector3 m_mouseMovePos = Vector3.zero;
    private Vector3 m_targetPos;
    Camera m_cam;
    float m_distance;
    float m_camHitDistance = 10;
    Quaternion m_camBeginRotation;

    void Start () {
      m_cam = GetComponent<Camera> ();
      m_camBeginRotation = m_cam.transform.rotation;
    }

    void Update () {

      if (Input.GetMouseButton (1)) {
            //鼠标右键点下控制相机旋转;
            m_deltX += Input.GetAxis ("Mouse X") * rotateSpeed;
            m_deltY -= Input.GetAxis ("Mouse Y") * rotateSpeed;
            m_deltX = ClampAngle (m_deltX, -360, 360);
            m_deltY = ClampAngle (m_deltY, -70, 70);
            m_cam.transform.rotation = m_camBeginRotation * Quaternion.Euler (m_deltY, m_deltX, 0);

            //鼠标右键按住时控制相机移动
            float _inputX = Input.GetAxis ("Horizontal");
            float _inputY = Input.GetAxis ("Vertical");
            float _camMoveSpeed = Input.GetKey(KeyCode.LeftShift)?m_camFastMoveSpeed:m_camNormalMoveSpeed;
            m_targetPos = transform.position + transform.forward * _camMoveSpeed * _inputY + transform.right * _camMoveSpeed * _inputX;
            transform.position = Vector3.Lerp (transform.position, m_targetPos, 0.5f);

      }

      //鼠标中键点下场景缩放
      if (Input.GetAxis ("Mouse ScrollWheel") != 0) {
            m_distance = Input.GetAxis ("Mouse ScrollWheel") * scaleSpeed;
            m_targetPos = m_cam.transform.position + m_cam.transform.forward * m_distance;
            m_cam.transform.position = Vector3.Lerp (m_cam.transform.position, m_targetPos, 0.5f);
      }

      //鼠标拖拽视野
      if (Input.GetMouseButtonDown (2)) {
            //跟手拖拽的关键
            Ray ray = m_cam.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if(Physics.Raycast(ray,out hit,Mathf.Infinity)){
                Vector3 _offset = hit.point - transform.position;
                this.m_camHitDistance = Vector3.Dot(_offset,transform.forward);
            }
            m_mouseMovePos = m_cam.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, m_camHitDistance));
      } else if (Input.GetMouseButton (2)) {
            Vector3 _worldPoint = Vector3.zero;
            _worldPoint = m_cam.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, m_camHitDistance)) - m_mouseMovePos;
            m_cam.transform.position = m_cam.transform.position - _worldPoint;
      }
    }

    float ClampAngle (float angle, float minAngle, float maxAgnle) {
      if (angle <= -360)
            angle += 360;
      if (angle >= 360)
            angle -= 360;

      return Mathf.Clamp (angle, minAngle, maxAgnle);
    }
}
页: [1]
查看完整版本: 一个简单操作解决Unity中拖拽相机视图的跟手问题