在Unity中掌握2D摄像机(游戏开发人员教程)

本文概述

  • 从2D到2.5D:可扩展的摄像头系统
  • 入门
  • 追踪目标
  • 增加偏移
  • 平滑事情
  • 停止头晕:轴锁定
  • 车道系统
  • 锁节点系统
  • 相机变焦
  • 屏幕震动
  • 淡入淡出和叠加
  • 本文总结
对于开发人员而言, 相机是游戏开发过程的基石之一。从仅在国际象棋应用程序中显示游戏视图, 到完全指导3D AAA游戏中的摄像机运动以获取电影效果, 摄像机实际上已被用于任何制作过的视频游戏中, 甚至在真正被称为” 摄像机” 之前也是如此。
在本文中, 我将解释如何设计2D游戏的摄像头系统, 并且还将解释如何在目前最流行的游戏引擎之一Unity中实现它的一些要点。
从2D到2.5D:可扩展的摄像头系统 我们将一起设计的摄像头系统是模块化且可扩展的。它具有一个基本核心, 该核心由几个可确保基本功能的组件组成, 然后可根据当前情况选择使用各种组件/效果。
我们在此处构建的摄像头系统针对2D平台游戏, 但可以轻松扩展到其他类型的2D游戏, 2.5D游戏甚至3D游戏。
在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
在Unity中掌握2D相机:游戏开发人员教程
鸣叫
我将相机功能分为两个主要组:相机跟踪和相机效果。
追踪
我们将在此处进行的大多数摄像机移动将基于跟踪。这就是对象(在本例中为摄像机)跟踪其他对象在游戏场景中移动时的能力。我们将要实施的跟踪类型将解决2d平台游戏中遇到的一些常见情况, 但是可以通过针对你可能具有的其他特定情况的新型跟踪类型来扩展它们。
特效
【在Unity中掌握2D摄像机(游戏开发人员教程)】我们将实现一些很酷的效果, 例如相机抖动, 相机缩放, 相机褪色和色彩叠加。
入门 在Unity中创建一个新的2D项目并导入标准资产, 尤其是RobotBoy角色。接下来, 创建一个地面框并添加一个角色实例。你应该能够在当前场景中与角色一起行走和跳跃。确保将相机设置为” 正交” 模式(默认情况下设置为” 透视” )。
在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
追踪目标 以下脚本将向我们的主摄像机添加基本的跟踪行为。该脚本必须作为组件附加到场景中的主摄像头, 并且必须公开用于分配要跟踪的目标对象的字段。然后, 脚本确保相机的x和y坐标与其跟踪的对象相同。所有这些处理都在更新步骤中完成。
[SerializeField] protected Transform trackingTarget; // ...void Update() { transform.position = new Vector3(trackingTarget.position.x, trackingTarget.position.y, transform.position.z); }

将RobotBoy角色从场景层次中拖到我们的以下行为暴露的” Tracking Target” 字段上, 以便能够跟踪主要角色。
在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
增加偏移 一切都很好, 但是我们可以立即看到局限性:角色始终处于场景的中心。我们可以在角色后面看到很多东西, 这些通常是我们不感兴趣的东西, 而且我们看到角色前面的东西太少了, 这可能对游戏玩法有害。
为了解决这个问题, 我们在脚本中添加了一些新字段, 以允许将摄像机定位在距其目标偏移的位置。
[SerializeField] float xOffset; [SerializeField] float yOffset; // ...void Update() { transform.position = new Vector3(trackingTarget.position.x + xOffset, trackingTarget.position.y + yOffset, transform.position.z); }

在下面, 你可以看到两个新字段的可能配置:
在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
平滑事情 摄像机的运动非常僵硬, 并且由于不断观察到的环境运动, 还会使某些玩家感到头晕。为了解决这个问题, 我们将在使用线性插值的摄像头跟踪中添加一些延迟, 并增加一个新字段来控制角色开始改变位置后摄像头到位的速度。
[SerializeField] 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); }

在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
停止头晕:轴锁定 由于你的大脑一直不停地观看摄像机和角色一起上下移动的感觉, 因此我们引入了轴锁定功能。这意味着我们可以将跟踪限制为仅一个轴。然后, 将我们的跟踪代码分为与轴无关的跟踪, 并考虑新的锁定标志。
[SerializeField] protected bool isXLocked = false; [SerializeField] 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); }

在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
车道系统 现在, 相机仅可水平跟踪播放器, 因此我们只能使用一个屏幕的高度。如果角色爬上某个梯子或跳得更高, 我们必须遵循。我们这样做的方法是使用车道系统。
想象以下情况:
在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
角色最初位于下车道。当角色保持在该车道的边界内时, 摄像机将仅在我们可以设置的车道特定高度偏移上水平移动。
一旦角色进入另一个车道, 摄像机将过渡到该车道并继续从那里开始水平移动, 直到发生下一个车道改变为止。
为了防止在跳跃等动作中快速切换车道, 必须谨慎设计车道, 这会给玩家造成混乱。仅当玩家的角色将在其上停留一段时间时, 才应更改车道。
车道的级别可以根据设计人员的特定需求在整个游戏级别中进行更改, 或者可以完全打断, 其他摄像头跟踪系统可以代替它们。因此, 我们需要一些限制器来指定车道区域。
实作
一种可能的实现是将车道添加为场景中的简单对象。我们将在上面的跟踪脚本中将它们的Y位置坐标与Y偏移配对使用, 以实现系统。因此, 它们在X和Z坐标上的位置无关紧要。
将LaneSystem类以及跟踪类添加到摄像机, 然后将车道对象分配给提供的数组。还将玩家角色分配给” 参考” 字段。由于参考位于一条车道与另一条车道之间, 因此将使用两者中较低的一条来定位摄像机。
在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
LaneSystem类负责根据参考位置在通道之间移动摄像机。 followSpeed在这里再次用于位置插值, 以防止车道切换太突然:
[SerializeField] Transform reference; [SerializeField] List< Transform> lanes; [SerializeField] 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并将触发器对撞机附加到它们上来实现。每当角色进入触发对撞机时, 我们都会移动相机并停留在POI上。当角色移动然后离开POI的触发对撞机时, 我们回到另一种跟踪方式, 通常是标准的跟随行为。
在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
摄像机跟踪到锁定节点的切换可以通过简单的开关或堆栈系统来完成, 在堆栈系统上推入并弹出跟踪模式。
实作
为了配置锁定节点, 只需创建一个对象(可以为空, 或者在下面的屏幕快照中为sprite), 然后将一个较大的Circle Collider 2D组件附加到该对象上, 这样就可以标记玩家在摄像机将要进入的区域聚焦节点。你可以选择任何类型的对撞机, 此处以” Circle” 为例。另外, 创建一个你可以轻松检查的标签, 例如” CameraNode” , 并将其分配给该对象。
在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
将以下属性添加到相机的跟踪脚本中:
public Transform TrackingTarget { get { return trackingTarget; } set { trackingTarget = value; } }

然后将以下脚本附加到播放器, 这将使它可以将摄像机的目标临时切换到你设置的锁定节点。该脚本还将记住其先前的目标, 因此当播放器超出触发区域时, 它可以返回该目标。如果需要, 你可以继续进行完整的堆栈转换, 但是出于我们的目的, 因为我们不会重叠多个锁定节点, 所以可以这样做。另外请注意, 你可以调整Circle Collider 2D的位置, 或者再次添加任何其他种类的对撞机来触发相机锁定, 这仅是一个示例。
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; }}

在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
相机变焦 当我们要聚焦于POI或关卡内更狭窄的区域时, 可以根据用户输入或动画来执行相机缩放。
通过操纵相机的orthographicSize, 可以在Unity 3D中实现2D相机缩放。将下一个脚本作为组件附加到相机并使用SetZoom方法更改缩放系数将产生所需的效果。 1.0表示不缩放, 0.5表示放大两次, 2表示缩小两次, 依此类推。
[SerializeField] float zoomFactor = 1.0f; [SerializeField] float zoomSpeed = 5.0f; private float originalSize = 0f; private Camera thisCamera; // Use this for initialization void Start() { thisCamera = GetComponent< Camera> (); originalSize = thisCamera.orthographicSize; }// Update is called once per frame void Update() { float targetSize = originalSize * zoomFactor; if (targetSize != thisCamera.orthographicSize) { thisCamera.orthographicSize = Mathf.Lerp(thisCamera.orthographicSize, targetSize, Time.deltaTime * zoomSpeed); } }void SetZoom(float zoomFactor) { this.zoomFactor = zoomFactor; }

屏幕震动 每当我们需要在游戏中展示地震, 爆炸或任何其他效果时, 相机抖动效果都会派上用场。
在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
GitHub上提供了有关如何执行此操作的示例实现:gist.github.com/ftvs/5822103。实现非常简单。与我们到目前为止介绍的其他效果不同, 它依赖于一点随机性。
淡入淡出和叠加 当我们的水平开始或结束时, 淡入或淡出效果就不错。我们可以通过在整个屏幕上延伸的面板中添加不可交互的UI纹理来实现此目的。最初是透明的, 我们可以用任何颜色和不透明度填充该图像, 也可以对其进行动画处理以获得所需的效果。
这是该配置的示例, 请注意将UI面板对象分配给” 主摄像机” 对象的” 摄像机叠加” 子级。 Camera Overlay公开了一个名为Overlay的脚本, 该脚本具有以下功能:
[SerializeField] Image overlay; // ...public void SetOverlayColor(Color color) { overlay.color = color; }

在Unity中掌握2D摄像机(游戏开发人员教程)

文章图片
为了产生淡入效果, 请像在下一个脚本中那样, 通过在使用SetOverlayColor设置的目标颜色上添加插值来更改Overlay脚本, 然后将Panel的初始颜色设置为Black(或White), 并将目标颜色设置为到叠加层的最终颜色。你可以将fadeSpeed更改为适合你需要的任何值, 我认为0.8对初学者来说是一个很好的选择。 fadeSpeed的值用作时间修改器。 1.0表示它将在多个帧中发生, 但会在1秒钟的时间内发生。 0.8表示实际上将需要1 / 0.8 = 1.25秒才能完成。
public class Overlay : MonoBehaviour { #region Fields[SerializeField] Image overlay; [SerializeField] float fadeSpeed = 5f; [SerializeField] Color targetColor; #endregionvoid Update() { if (overlay.color != targetColor) { overlay.color = Color.Lerp(overlay.color, targetColor, Time.deltaTime * fadeSpeed); } }#region Publicpublic void SetOverlayColor(Color color) { targetColor = color; }#endregion }

本文总结 在本文中, 我试图演示了为游戏安装模块化2D摄像头系统所需的基本组件, 以及设计该组件所需的思维方式。当然, 所有游戏都有其特定的需求, 但是通过此处介绍的基本跟踪和简单效果, 你可以走很长一段路, 并且还具有实现自己的效果的蓝图。然后, 你可以走得更远, 并将所有内容打包到可重用的Unity 3D程序包中, 也可以将其传输到其他项目。
摄像机系统对于为玩家传达合适的氛围非常重要。当你想到古典戏剧和电影之间的区别时, 我想使用一个很好的比较。相机和胶卷本身为场景带来了无限可能, 最终最终演变为一种艺术, 因此, 如果你不打算实施另一款” 乒乓” 游戏, 那么高级相机应该是你选择的任何游戏项目中的工具。从现在开始。
相关:与MVC的统一:如何升级游戏开发

    推荐阅读