Unity3d学习 相机的跟随

时间:2020-12-02 04:20:10

  最近在写关于相机跟随的逻辑,其实最早接触相机跟随是在Unity官网的一个叫Roll-a-ball tutorial上,其中简单的涉及了关于相机如何跟随物体的移动而移动,如下代码:

 using UnityEngine;
using System.Collections; public class CameraController : MonoBehaviour { public GameObject player; private Vector3 offset; void Start ()
{
offset = transform.position - player.transform.position;
} void LateUpdate ()
{
transform.position = player.transform.position + offset;
}
}

简单相机移动

  可以很容易的理解上述的代码: 在初始化时计算与对应物体的向量差值,然后在LateUpdate中对相机位置进行及时更新,至于为什么要放在LateUpdate,因为LateUpdate是等所有脚本的Update跑完之后

在更新自己的逻辑,这样相机得到物体的位置往往是最新的 。具体可以看 Unity关于脚本生命周期 中有提到。

  上述的代码 , 相机是实时跟踪的, 其实相机的跟踪可以变的跟平滑一点,可以利用Unity中的Mathf.Lerp,在每一帧做一个线性的差值,这样的话可以使相机跟随变的更平滑一点,如下优化的代码:

 using UnityEngine;
using System.Collections; public class FollowBehavior : MonoBehaviour { public Transform trackingTarget;
public float offsetX = 5.0f;
public float offsetY = 4.0f;
public float followSpeed = 1.0f; public bool isXLocked = false;
public bool isYLocked = false;
// Use this for initialization
void Start () {
//m_offset = transform.position - trackingTarget.position;
} // Update is called once per frame
void LateUpdate () {
//transform.position = trackingTarget.position + m_offset;
float newX = transform.position.x;
float targetX = trackingTarget.position.x + offsetX;
if (!isXLocked)
{
newX = Mathf.Lerp(newX, targetX, Time.deltaTime * followSpeed);
}
float newY = transform.position.y;
float targetY = trackingTarget.position.y + offsetY;
if (!isYLocked)
{
newY = Mathf.Lerp(newY, targetY, Time.deltaTime * followSpeed);
}
transform.position = new Vector3(newX , newY , transform.position.z);
}
}

平滑的跟踪

  上述代码能满足大多数情况,但是如果一个场景里有多个焦点呢? 比如现在要满足的业务条件是:    

  • 当鼠标对屏幕进行拖拽时,需要移动相机
  • 当有多个焦点时,如何更好的切换

  我们先来实现第一个需求,先讲讲现在具备哪些条件:

  • Input.GetMouseButtonDown(0) : 这个表示在某一帧按下鼠标左键,会返回true,如果你一直按着不放(返回的是false),直到你松开再按下(才会再次返回true) 可以参考文档
  • Input.GetMouseButton(0): 这个表示当前是鼠标左键按下,会返回true 可以参考文档

  通过上述接口,我们可以实现拖拽了,思路的话就不细说,看代码就行:

         void DragCamera()
{
Vector3 nowMousePos = Input.mousePosition;
Vector3 move = nowMousePos - m_originDragPos;
move = Camera.main.ScreenToViewportPoint(move) * DragSpeed * -;
//平移没有差值运算
transform.Translate(move);
float x = Mathf.Clamp(transform.position.x, minXAndY.x, maxXAndY.x);
float y = Mathf.Clamp(transform.position.y, minXAndY.y, maxXAndY.y);
Vector3 pos = new Vector3(x , y , transform.position.z);
transform.position = pos;
m_originDragPos = nowMousePos;
} // Update is called once per frame
void Update()
{
int mouse = (int)MouseType.LEFT;
//记录某一帧时按下的状态(之后的持续按下都返回false,知道下次释放在按下返回true)
if (Input.GetMouseButtonDown(mouse))
{
m_bIsDrag = true;
//屏幕坐标系
m_originDragPos = Input.mousePosition;
return;
}
//表示当前的释放
if (!Input.GetMouseButton(mouse))
{
m_bIsDrag = false;
return;
}
} void LateUpdate()
{
if (m_bIsDrag)
{
DragCamera();
}
}

拖拽代码

 这边提一下在DragCamera函数中如果OriginDragPos不及时更新,屏幕在鼠标移动时会一直移动,因为在计算是产生的move向量一直有值,所以会不断偏移,这边看需求吧。

  上述的代码已经可以实现相机的拖拽了,但是如果你的屏幕上有UI结构,按下UI结构时,点击UI结构 ,其实也会调用 Input.GetMouseButtonDown(0),就会调用拖拽函数,但是

往往这种情况下,是不需要将m_bIsDrag设为true,所以如何优化屏蔽呢? 看如下代码:

 // Update is called once per frame
void Update()
{
int mouse = (int)MouseType.LEFT;
//记录某一帧时按下的状态(之后的持续按下都返回false,知道下次释放在按下返回true)
if (Input.GetMouseButtonDown(mouse))
{
//不能是UI层
PointerEventData pointerData = new PointerEventData(EventSystem.current);
pointerData.position = Input.mousePosition;
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(pointerData, results); if (results.Count > )
{
if (results[].gameObject.layer == LayerMask.NameToLayer("UI"))
{
return;
}
}
m_bIsDrag = true;
//屏幕坐标系
m_originDragPos = Input.mousePosition;
return;
}
//表示当前的释放
if (!Input.GetMouseButton(mouse))
{
m_bIsDrag = false;
return;
}
}

屏蔽UI

  这边要提一下关于Unity5.X中GUI的事件系统 确定事件产生到接收 流程是 输入模块产生事件数据 PointerEventData ,通过投影模块(射线)确定具体UI , 最终到具体UI来接收数据,

由于这不是本篇的重点,可以看一下 关于事件系统的博文我们这里模仿了前两步骤,确定当前鼠标输入的点是否UI有就直接return.

  关于焦点确定,其实算是优化项吧 ,我这边采样的是委托/事件方式来发送对应的Tranform,当然也可以直接接口。

   相机移动的例子