完美解决Unity中拖拽相机视图的跟手问题
在转载的上一篇博客中,可以看到拖拽相机跟手已经非常完美。但是最近策划同学又提出在相机跟手后,地图地面相对于相机的高度不变。在转载的博客中,因为是根据相机与地图世界坐标的距离计算实现的相机视图跟手,在使用透视相机(近大远小)下不可避免的会出现地图也在上下移动。要实现这个需求有两种方式(附带完整代码):
1、在移动过程中,每帧都通过射线获取当前屏幕点击点对应的地图的世界坐标,然后在根据地图的世界坐标在保持相机高度不变的情况下,反向计算出相机的位置。每帧通过射线计算,消耗比较大。(不推荐)
using UnityEngine; [RequireComponent(typeof(Camera))] //按住鼠标右键旋转,同时按wasd移动,按住shift加速移动,按住中键拖拽视图 public class FreeCamera : MonoBehaviour { //相机旋转速度 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_mouseMoveBegin = 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(1)) { //跟手拖拽的关键 Ray ray = m_cam.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit, Mathf.Infinity)) { Vector3 vec_cam2hitPoint = hit.point - transform.position; this.m_camHitDistance = Vector3.Dot(vec_cam2hitPoint, transform.forward); } m_mouseMoveBegin = m_cam.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, m_camHitDistance)); } else if (Input.GetMouseButton(1)) { //跟手拖拽的关键 Ray ray = m_cam.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit, Mathf.Infinity)) { Vector3 vec_cam2hitPoint = hit.point - transform.position; this.m_camHitDistance = Vector3.Dot(vec_cam2hitPoint, transform.forward); } var movePos = m_cam.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, m_camHitDistance)); var offset = movePos - m_mouseMoveBegin; // 计算相机的移动方向 相反 m_cam.transform.position = m_cam.transform.position - offset; } } float ClampAngle(float angle, float minAngle, float maxAgnle) { if (angle <= -360) angle += 360; if (angle >= 360) angle -= 360; return Mathf.Clamp(angle, minAngle, maxAgnle); } }
2、通过向量的计算,求出当前屏幕点击对应的地图世界坐标,然后根据向量计算的坐标反推出相机的坐标。(推荐使用)
using UnityEngine; [RequireComponent(typeof(Camera))] //按住鼠标右键旋转,同时按wasd移动,按住shift加速移动,按住中键拖拽视图 public class FreeCamera : MonoBehaviour { //相机旋转速度 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_mouseMoveBegin = 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.GetMouseButtonDown(1)) { //跟手拖拽的关键 var screenPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 50); m_mouseMoveBegin = ScreenToWorldPoint(screenPos, 0.0f); } else if (Input.GetMouseButton(1)) { var screenPOs = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 50); var movePos = ScreenToWorldPoint(screenPOs, 0.0f); //相机的移动方向相反 var offset = m_mouseMoveBegin - movePos; offset.y = 0; m_cam.transform.position += offset; } } public Vector3 ScreenToWorldPoint(Vector3 screenPos, float yPlane) { var position = transform.position; var point = m_cam.ScreenToWorldPoint(screenPos); var forward = (point - position).normalized; float scalar = 0.0f; if (Mathf.Abs(forward.y) < float.Epsilon) scalar = 1.0f; else scalar = (yPlane - position.y) / forward.y; var target = new Vector3(position.x + forward.x * scalar, yPlane, position.z + forward.z * scalar); return target; } //根据地图的世界坐标反向推算出当前相机的位置 (保持地图和相机高度不变的情况下) private Vector3 FoceOn(Vector3 position) { var forward = transform.forward; var fixedY = transform.position.y; var rate = (fixedY - position.y) / forward.y; var target = new Vector3(rate * forward.x + position.x, fixedY, rate * forward.z + position.z); return target; } float ClampAngle(float angle, float minAngle, float maxAgnle) { if (angle <= -360) angle += 360; if (angle >= 360) angle -= 360; return Mathf.Clamp(angle, minAngle, maxAgnle); } }