【案例设计】Free Camera 设计与实现思路

开发平台:Unity 2020
编程平台:Visual Studio 2020

前言

  在游戏世界中,摄像机作为最常见、最常使用的 ComponentGameObject 对象。经常因为项目开发类型,在原基础上进行二次开发。例如 RTS建造游戏、即时战略、模拟现实等需要第三人称摄像机(TP)。枪战对抗、恐怖解密等需要第一人称摄像机(FP)。必须时,如《坦克世界》、《战地风云》等经典游戏需要 TP、FP 两者的结合。本文主要探究 Camera Component 的基础上实现 TP 效果。

注意:Unity 在如今的版本中推广 Ciniemachine 工具包。强化了 Camera 的使用效率、呈现效果。本文不使用该工具包实现。

思考:Transform 与 Vector 的关系与区别


  摄像机的移动 关联 Unity 编辑器中的 Transform 组件信息。该组件用于定义与管理 GameObject 在世空间的位置信息。在游戏中,寄希望于通过不同角度获取虚拟世界中不同角度下的视觉体验。对 Transform 必须有较好的理解与认知。如下示意图所示:
【案例设计】Free Camera 设计与实现思路_第1张图片
  通过上视图,可以总结出以下几点:
   1)世界空间是以 Vector 系组建而成。且方向固定,各轴面互垂直。
   2)Transform 定义的前后左右方向在世界空间下不固定。即以所见方向为前向。

思考:Quaternion 与 EulerAngles 的关系与区别


【案例设计】Free Camera 设计与实现思路_第2张图片

四元数(Quaternion):在 Unity 引擎中,统一使用四元数(Quaternion)实现对 GameObject 的旋转。
特点:四元数具备不受 万向锁 影响的特点。
备注:默认下面板中 Rotation 使用的是 Quaternion数据类型变量。
相关API:Unity 建议使用 *= 方式实现旋转方式。(四元数值累加与 + 无关)

欧拉角(EulerAngle):围绕固定的 X Y Z 轴进行旋转移动。
特点:各轴值变化互不影响。但同时变化多轴值出现 万向锁 情况影响。
备注:默认为面板上具体的数值信息。(非归一化 Quaternion)使用transform.eulerAngles获取关于 X Y Z 轴轴值。
相关API:transform.Rotate() - Unity 封装的基于 EulerAngles 实现的旋转方式。


关于 EulerAngles 轴值限定范围问题的分析与解决方案
  值得注意的是 EulerAngles 的范围区间在 [0, 360]。即超过或低于范围内的数据将转换至范围内的相对数据值。例如 470° = 110°、-60° = 300°等情况。对 Inspector 面板有所了解的会注意到,任意的改变 Rotation 变量值均不会出现被处理至 [0, 360] 区间的范围。即是多少 = 值多少(这里值不为度数)。
  有时候为限制 EulerAngles 中具体某个轴值范围易出现问题,例如 限制 [-60, 60],实际上做不到区间 [-60, 0) 的限制。于是在 Inspector 面板上虽然显示数值 -50。但实际其欧拉角大小为(- 50 + 360)= 310。在实际操作中,其度数的变化是基于X正半轴顺时针开始计算角度值。为实现负数角度的限制,使用以下示例代码逻辑:

public float ClampEulerAngles(float eulerAnglesValue)
{
	var value = eulerAnglesValue <= 180 ? eulerAnglesValue : enlerAnglesValue - 360;
	return value;
}

  使用该方法对EulerAngles数值检测负数范围,再通过Mathf.Clamp()对值域范围进行限制,并重新赋值至EulerAnglesRotation属性上。

思考:Global 与 Local 两种环境下的数据差异


【案例设计】Free Camera 设计与实现思路_第3张图片
全局坐标:基于世界坐标轴(世界正前方、正右方、正上方)
局部坐标:基于自身坐标轴(自身正前方、正右方、正上方)
辅助理解:Transform 与 Vector3 = 世界坐标 与 自身坐标


其他
  在 Unity 中,为区别 Global 与 Local 两个环境下的数据。提供 position/localPositoneularAngles/localEulerAngles等变量加以区别使用。 更多参考 Unity 官方中文文档对 API 的介绍。

思考:固定平面 与 自由平面 的区别


问:什么是固定平面?
:锁定 X Y Z 任意轴组合下的相机自由移动。其锁轴组合方式分为 单锁 与 组合锁 供两种方式。(可理解为在固定平面上移动,任何移动都无法离开限定的平面上)

问:什么是自由平面?
:跨维度变换(2D - 3D)。允许 XYZ 轴值任意组合或单个变换。(无轴平面限制)

public enum LockAxisMode {
	None,
	OnlyX, OnlyY, OnlyZ,
	OnlyXY, OnlyXZ, OnlyYZ,
	All,
}

  在程序设计上,为区别各类型采取 Enum(枚举)作出限定与锁轴设计。用于在执行 Translate / Rotate / Scale 前的判断。即若使用自由平面使用 LockAxisMode.None,若禁用 Free Camera 的使用,选择 LockAxisMode.All 或禁用该逻辑组件即可。

实践:空间移动 Translate


  Free Camera 移动是实现的首要。前后左右上下共计6个方向的位置偏移。根据期望效果方式,其大致的移动效果可总结为3种移动模式。

  • 固定平面移动:例如在同一面上的水平面移动。
    参考游戏:《模拟城市》、《红色警戒》、《王者荣耀》
  • 基于水平面的自由向移动:在固定平面移动基础上,添加垂直平面上的自由移动方式。
    参考游戏:《Battlefiled》、《CSOL》、《CSGO》 中死亡玩家的自由活动相机模式。
  • 全自由度移动:没有固定的平面方向,以自我为中心的前后左右上下。
    参考案例:太空失重环境下的宇航员。


关联参数

  • 按键绑定:前进、后进、左进、右进、上升、下降。
    问题原因
    1)用户操作习惯上不同,需要对按键进行调整。
    2)避免或快速解决 “一绑键,多响应” 的问题。
  • 移动速度:世界空间下 XYZ轴(Vector3) 移动速度
    问题原因:客户需求某一操作的速度变快。
    解决方法:参考 Unity 在 2019版本后提供的 可调节变速 与 过渡移速补值。(描述或不准确)
  • 轴向固定:不期望于部分行为操作可用。例如 仅禁用、禁用部分或全禁用 前后移动 左右移动 上升下降。

固定平面移动

public void DoTranslate()
{
	//transform.position += moveDir * Time.deltaTime;
	transform.Translate(moveDir * Time.deltaTime, Space.World);
}
  • Unity API 提供了transform.Translate(),其原理等价于 transform.position += ...
    唯一区别点在于Space空间不同。上图两行代码的运行效果是相同的。
  • Free Camera 运动方向完全按照世界空间下的 Vector3 前后左右上下方向移动。为避免出现 90°横向 所导致的移动轨迹与按钮反馈的体验度差问题。通常选择基于 Free Camera 朝向进行水平面方向计算,以让用户前后左右的移动与画面呈现相匹配。
  • 在进行水平运动方向的计算中,按照公式:
    前进/后退 方向 = 相机朝向(transform.forward)至 Local 水平坐标系方向前进的公式(该过程称为 “投影”)

  其他说明:在 Unity API 中分别提供 Project()ProjectOnPlane() 两种投影计算方式。其中三维世界中,向量至水平面的投影方向多使用第二种方式。经过 投影 + 归一化 的处理过程。得到 Free Camera 的运动方向。

public Vector3 GetForward(Vector3 forward)
{
	return Vector3.ProjectOnPlane(forward, Vector3.up).normalized;
}

基于水平面的自由向移动

  • 与 固定平面移动 大同小异。其主要不同之处在于 运动方向(前后)是按照自身 transform.forward 方向进行。
    可理解为 头脑转动到任意角度下,双眼正视前方的方向轨迹运动。(这类设计在 FP 上具有相当程度的体现)

全自由移动

  • 无固定平面,其运动方向完全基于自身(transform) 前后左右。仅从移动上,无法体现出其自由度。于是加入Rotation后,其三者的表现就清晰明了。

实践:空间旋转 Rotate


  在 Unity 中,实现物体的旋转通常是依赖于 Inspector 面板直接修改Rotation属性,或程序中调用transform.eulerAnglestransform.rotation进行重赋值行为。转动方向有助于改变 Translate 运动方向。按照其实现思路大致可有以下两种方式:

  • 欧拉角:围绕固定的X Y Z轴进行旋转(该轴可是 世界坐标轴 或是 局部坐标轴)
  • 四元数:按照 X Y Z W 共四个参数值进行计算。Unity 统一使用该类型实现旋转(见前文)。


关联参数

  • 旋转水平面:基于世界水平面 or 自身视线水平面
  • 旋转角度限制:垂直方向上仰角、俯角
  • 旋转灵敏度:即 X Y轴鼠标灵敏度

基于 四元数 的旋转

  四元数按照 d + ai + bj + ck(a、b、c、d 均为常数)存在。

public void DoRotate() => transform.rotation *= Quaternion.Euler(new Vector3(x, y ,z));
  • Quaternion.Euler():将 Vector3 类型转换为 Quaternion 类型。
  • 在 全自由移动 下的表现,是完全基于自身方向进行。如果刻意控制其左右转向围绕水平面方向进行,则其最后需添加transform.rotation = Quaternion.Euler(new Vector3(transform.rotation.x, transform.rotation.y, 0f))进行坐标处理即可。

基于 欧拉角 的旋转

public void DoRotate()
{ 
	//transform.eulerAngles += new Vector3(x, y, z);
	transform.Rotate(new Vector3(x, y, z));
}
  • 实质上该方法即 Unity API 中transform.Rotate()方法原理。即可使用transform.Rotate(new Vector3(x, y, z))进行表示。
  • 同理于 四元数 实现左右转向围绕水平面方向进行,应使用transform.enlerAngles = new Vector3(tranform.eulerAngles.x, transform.eulerAngles.y, 0f);进行角度重置。即使传入的值为负数,在 EulerAngles 中会处理至[0, 360]值范围内。

特别的transform.enlerAngles.x 这类具体到特定值上,在开发过程中无法直接修改该值以实现角度限制。则应追溯至上一层级进行数据类型赋值。

实践:视距缩放 Scale


  实现画面的放大与缩小效果,类似放大镜的作用。在 Unity 中这类实现方法无疑是将 Free Camera 沿 transform.forward 方向进行偏移。 当然,在一定范围内,使用 Camera.fieldOfView 属性进行值修改可达到同样的效果。其原理即是降低视野范围。

关联参数

  • 缩放阻尼:过渡效果(在移动、旋转中 亦可考虑添加)
    关联词Lerp()
  • 缩放速率:同距离下的最快移动速度。

基于 fieldOfView 的缩放

public void DoScale()
{
	var valueEnd = Input.mouseScrollDelta.y >=0f ? 20f : 60f;
	_Camera.fielfOfView = Mathf.Lerp(_Camera.fieldOfView, valueEnd, 1f);
}
  • Mathf.Lerp():插值过程,此处描述为1s内完成当前值至valueEnd值的变化。
  • 弊端:当值变化过于极端的情况下,画面视野扭曲程度较高,与实际放大缩小充满违和感。

基于 位置变化 的缩放

public void DoScale()
{
	var moveDir = transform.forward * Input.mouseScrollDelta.y * Time.deltaTime;
	_Camera.transfrom.position += moveDir;
	//_Camera.transform.Translate(moveDir);
}

其他实现层面:

  • 添加 Aim 模式,修改缩放策略。有时候期望于仅放大观察物体表面内容,则可使用 Physics.RayCasst() 物理射线检测对象(或由点击行为绑定的 GameObject 信息)。
    按照 (目标 至 主摄像机 的距离)* 0.5f 进行移动效果(或使用插值),以达到无限接近目标位置。但未穿过目标。

小结


  Free Camera 的应用环境广泛,也是入门级学习的典型案例。理解与深析是认识三维虚拟世界的重要点。


可优化措施

  1. 使用 ScriptableObject 创建配置参数。
  2. 推荐配合 Cinemachine 工具包拓展 Free Camera 功能设计。
  3. 代码结构上,划分 主逻辑(移动 旋转 缩放)、助理(转换方法、枚举)、可配置(持久化数据)。切忌整合在同一脚本下,增加阅读难度。
  4. 扩展设计 Free Camera 的三维空间运动范围,限制其在某些区域内的移动。

你可能感兴趣的:(Unity,经典案例实现,unity)