相机是游戏开发的基础。在棋盘应用中展示的游戏场景,或是直接娴熟地控制3A级3D游戏的相机移动来实现电影级特效,基本上制作的所有视频游戏都会用到相机,相机的应用实际上甚至发生在“相机”的说法出现之前。

本文将阐述如何设计2D游戏的相机系统,并说明在Unity中如何实现。

从2D到2.5D:可扩展的相机系统

我们要设计的相机系统是模块化且可扩展的。基本核心由几个含有基本功能的组件构成,还有大量的组件和特效视具体情况选用。

该相机系统针对2D平台游戏,但也很容易扩展为类型的2D、2.5D甚至是3D游戏。

093549mql2szqq124oqr4h.png
跟踪目标

下面的脚本为Main Camera添加基本的跟踪行为。脚本必须作为组件附加到场景中的Main Camera上,它会显示跟踪的目标对象字段。该脚本确保将相机的x和y坐标设置为与跟踪对象一致。这些步骤在Update函数中完成。
[C#] 纯文本查看 复制代码void Update() { transform.position = new Vector3(trackingTarget.position.x, trackingTarget.position.y, transform.position.z); }
将层级视图中的RobotBoy角色拖拽至脚本的“Tracking Target”字段,确保相机跟踪主角。

093549qqqqtktlyza5akir.png
添加offset偏移量

现在还有一处明显限制,就是角色总是位于场景中心位置。角色后方的场景过多,角色前方的视野又太窄,可能不利于游戏进程。

为了解决这个问题,在脚本上添加一些字段,定义相机与目标之间的偏移量。
[C#] 纯文本查看 复制代码float xOffset; float yOffset; // ... void Update() { transform.position = new Vector3(trackingTarget.position.x + xOffset, trackingTarget.position.y + yOffset, transform.position.z); }
2个新字段赋值如下:

093550xmocszqajsoccmct.png
平滑移动

目前相机移动是比较生硬的,频繁移动的游戏环境也会导致部分玩家产生眩晕。可以利用线性插值给相机在跟踪过程中添加一些延迟来解决这个问题,新字段就是角色开始改变移动后,控制相机到达目的地的速度。
[C#] 纯文本查看 复制代码protected float followSpeed; // ... protected override void Update() { float xTarget = trackingTarget.position.x + xOffset; float yTarget = trackingTarget.position.y + yOffset; float xNew = Mathf.Lerp(transform.position.x, xTarget, Time.deltaTime * followSpeed); float yNew = Mathf.Lerp(transform.position.y, yTarget, Time.deltaTime * followSpeed); transform.position = new Vector3(xNew, yNew, transform.position.z); }

093550mb9n22bh2k3bc6ic.png
阻止眩晕:锁定轴

锁定轴来防止大脑因为相机跟随角色上下抖动感到眩晕。也就是只跟踪一个轴。将跟踪代码基于不同的跟踪轴分离开来,并加入锁定标志。
[C#] 纯文本查看 复制代码protected bool isXLocked = false; protected bool isYLocked = false; // ... float xNew = transform.position.x; if (!isXLocked) { xNew = Mathf.Lerp(transform.position.x, xTarget, Time.deltaTime * followSpeed); } float yNew = transform.position.y; if (!isYLocked) { yNew = Mathf.Lerp(transform.position.y, yTarget, Time.deltaTime * followSpeed); }
093550yx8omvx5zpxso5sf.png
路径系统

至此,我们的相机会在水平方向上追踪玩家,相机的观察区域被限制在一个屏幕的高度之内。如果玩家爬上了梯子,或者跳跃到了屏幕上方,应该让相机跟踪这些行为。这里我们使用了一种叫做路径系统(Lane System)的机制来达到这一目的。

想象如下场景:

094029pejf4s9ef9ssuzu4.png
LaneSystem负责根据引用的位置来处理路径之间相机的移动。这里我们再次用到了追踪速度followSpeed,对坐标进行插值处理,这可以避免在切换区域时产生过于突兀的画面切换效果。

[C#] 纯文本查看 复制代码Transform reference; List<Transform> lanes; float followSpeed = 5f; // ... void Update() { float targetYCoord = transform.position.y; if (lanes.Count > 1) { int i = 0; for (i = 0; i < lanes.Count - 1; ++i) { if ((reference.position.y > lanes[i].position.y) (reference.position.y <= lanes[i + 1].position.y)) { targetYCoord = lanes[i].position.y; break; } } if (i == lanes.Count - 1) targetYCoord = lanes[lanes.Count - 1].position.y; } else { targetYCoord = lanes[0].position.y; } float yCoord = Mathf.Lerp(transform.position.y, targetYCoord, Time.deltaTime * followSpeed); transform.position = new Vector3(transform.position.x, yCoord, transform.position.z); }
该实现并非所见即所得,因此我们将它作为练习留给读者。

锁定节点系统

现在已经有了一个不错的相机路径系统,但有时还是需要让相机锁定在场景中的某个物体上面,即游戏场景中的“兴趣点”(POI)。

我们可以在场景中配置一个这样的POI对象,并在上面添加触发器碰撞体(Trigger Collider)来达到这一目的。不管什么时候,只要玩家角色进入了触发体区域,我们就把相机的焦点放在POI上。当角色离开碰撞体区域后,我们再恢复相机的正常行为,例如跟踪。

094052vfe35mnisze00aey.jpg
相机追踪目标的切换通常有两种方法可以实现,一种是简单地进行切换,而另一种是使用栈系统,即将追踪模式push到栈中,再pop恢复。

实现

为了能够对POI锁定点进行配置,只需新建一个对象(可以是空对象,或者和下图一样使用Sprite),然后在该对象上添加一个大的2D圆形碰撞体,这个碰撞体可以指示出POI影响的区域,表示相机会锁定的节点。可以使用任何类型的碰撞体,这里以圆形碰撞体为例。此外再新建一个tag,例如“CameraNode”,将这个tag添加到POI对象上,这样检查起来更加方便。

094110jg3r4g4xeaofjmez.png
在相机的追踪脚本上添加下列字段:
[C#] 纯文本查看 复制代码public Transform TrackingTarget { get { return trackingTarget; } set { trackingTarget = value; } }

然后将下面的脚本添加到玩家对象上,该对象允许相机临时将关注点切换到设定好的POI节点上。该脚本同样还可以记住锁定之前相机的状态,这样当玩家离开触发区域时就可以让相机重新回到之前的状态。如果有需要还可以在此基础上走得更远一点,例如使用栈,不过目前本文并没有重叠多个锁定区域的需求。同样需要注意,还可以调整2D圆形碰撞体的大小,或者替换成其他任意类型的触发碰撞体来触发相机锁定,这里所讲述的仅仅是一个示例。
[C#] 纯文本查看 复制代码public class LockBehavior : MonoBehaviour { #region Public Fields [SerializeField] Camera camera; [SerializeField] string tag; #endregion #region Private private Transform previousTarget; private TrackingBehavior trackingBehavior; private bool isLocked = false; #endregion // Use this for initialization void Start() { trackingBehavior = camera.GetComponent<TrackingBehavior>(); } void OnTriggerEnter2D(Collider2D other) { if (other.tag == tag !isLocked) { isLocked = true; PushTarget(other.transform); } } void OnTriggerExit2D(Collider2D other) { if (other.tag == tag isLocked) { isLocked = false; PopTarget(); } } private void PushTarget(Transform newTarget) { previousTarget = trackingBehavior.TrackingTarget; trackingBehavior.TrackingTarget = newTarget; } private void PopTarget() { trackingBehavior.TrackingTarget = previousTarget; } }

094137mhsroyrrwynjjkdz.png
本期教程先到此为止,下一次我们将为大家介绍相机缩放,屏幕抖动,淡入淡出重叠等等技巧。

原文链接:Mastering 2D Cameras in Unity: A Tutorial for Game Developers
原文作者: Mihai Cozma
感谢Unity官方翻译组成员“巴鲍伯”“E.A.S”对本文翻译所做的贡献。
转载请注明来源:Unity官方中文社区 (forum.china.unity3d.com)。请勿私自更改任何版权说明信息。 Unity, 相机, 教程锐亚教育

锐亚教育 锐亚科技 unity unity教程