首页 > 代码库 > Unity3d学习 相机的跟随

Unity3d学习 相机的跟随

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

技术分享
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class CameraController : MonoBehaviour {
 5 
 6     public GameObject player;
 7 
 8     private Vector3 offset;
 9 
10     void Start ()
11     {
12         offset = transform.position - player.transform.position;
13     }
14     
15     void LateUpdate ()
16     {
17         transform.position = player.transform.position + offset;
18     }
19 }
简单相机移动

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

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

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

技术分享
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class FollowBehavior : MonoBehaviour {
 5 
 6     public Transform trackingTarget;
 7     public float offsetX = 5.0f;
 8     public float offsetY = 4.0f;
 9     public float followSpeed = 1.0f;
10 
11     public bool isXLocked = false;
12     public bool isYLocked = false;
13     // Use this for initialization
14     void Start () {
15         //m_offset = transform.position - trackingTarget.position;
16     }
17     
18     // Update is called once per frame
19     void LateUpdate () {
20         //transform.position = trackingTarget.position + m_offset;
21         float newX = transform.position.x;
22         float targetX = trackingTarget.position.x + offsetX;
23         if (!isXLocked)
24         {
25             newX = Mathf.Lerp(newX, targetX, Time.deltaTime * followSpeed);
26         }
27         float newY = transform.position.y;
28         float targetY = trackingTarget.position.y + offsetY;
29         if (!isYLocked)
30         {
31             newY = Mathf.Lerp(newY, targetY, Time.deltaTime * followSpeed);
32         }
33         transform.position = new Vector3(newX , newY , transform.position.z);
34     }
35 }
平滑的跟踪

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

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

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

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

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

技术分享
 1         void DragCamera()
 2         {
 3             Vector3 nowMousePos = Input.mousePosition;
 4             Vector3 move = nowMousePos - m_originDragPos;
 5             move = Camera.main.ScreenToViewportPoint(move) * DragSpeed * -1;
 6             //平移没有差值运算
 7             transform.Translate(move);
 8             float x = Mathf.Clamp(transform.position.x, minXAndY.x, maxXAndY.x);
 9             float y = Mathf.Clamp(transform.position.y, minXAndY.y, maxXAndY.y);
10             Vector3 pos = new Vector3(x , y , transform.position.z);
11             transform.position = pos;
12             m_originDragPos = nowMousePos;     
13         }
14 
15         // Update is called once per frame
16         void Update()
17         {
18             int mouse = (int)MouseType.LEFT;
19             //记录某一帧时按下的状态(之后的持续按下都返回false,知道下次释放在按下返回true)
20             if (Input.GetMouseButtonDown(mouse))
21             {
22                 m_bIsDrag = true;
23                 //屏幕坐标系
24                 m_originDragPos = Input.mousePosition;
25                 return;
26             }
27             //表示当前的释放
28             if (!Input.GetMouseButton(mouse))
29             {
30                 m_bIsDrag = false;
31                 return;
32             }
33         }
34 
35         void LateUpdate()
36         {
37             if (m_bIsDrag)
38             {
39                 DragCamera();
40             }
41         }
拖拽代码

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

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

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

技术分享
 1 // Update is called once per frame
 2         void Update()
 3         {
 4             int mouse = (int)MouseType.LEFT;
 5             //记录某一帧时按下的状态(之后的持续按下都返回false,知道下次释放在按下返回true)
 6             if (Input.GetMouseButtonDown(mouse))
 7             {
 8                 //不能是UI层
 9                 PointerEventData pointerData = http://www.mamicode.com/new PointerEventData(EventSystem.current);
10                 pointerData.position = Input.mousePosition;
11                 List<RaycastResult> results = new List<RaycastResult>();
12                 EventSystem.current.RaycastAll(pointerData, results);
13 
14                 if (results.Count > 0)
15                 {
16                     if (results[0].gameObject.layer == LayerMask.NameToLayer("UI"))
17                     {
18                         return;
19                     }
20                 }
21                 m_bIsDrag = true;
22                 //屏幕坐标系
23                 m_originDragPos = Input.mousePosition;
24                 return;
25             }
26             //表示当前的释放
27             if (!Input.GetMouseButton(mouse))
28             {
29                 m_bIsDrag = false;
30                 return;
31             }
32         }
屏蔽UI

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

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

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

 

Unity3d学习 相机的跟随