开发平台:Unity 2020 编程平台:Visual Studio 2020
前言在游戏世界中,摄像机作为最常见、最常使用的 Component 或 GameObject 对象。经常因为项目开发类型,在原基础上进行二次开发。例如 RTS建造游戏、即时战略、模拟现实等需要第三人称摄像机(TP)。枪战对抗、恐怖解密等需要第一人称摄像机(FP)。必须时,如《坦克世界》、《战地风云》等经典游戏需要 TP、FP 两者的结合。本文主要探究 Camera Component 的基础上实现 TP 效果。
注意:Unity 在如今的版本中推广 Ciniemachine 工具包。强化了 Camera 的使用效率、呈现效果。本文不使用该工具包实现。
思考:Transform 与 Vector 的关系与区别 摄像机的移动 关联 Unity 编辑器中的 Transform 组件信息。该组件用于定义与管理 GameObject 在世空间的位置信息。在游戏中,寄希望于通过不同角度获取虚拟世界中不同角度下的视觉体验。对 Transform 必须有较好的理解与认知。如下示意图所示: 通过上视图,可以总结出以下几点: 1)世界空间是以 Vector 系组建而成。且方向固定,各轴面互垂直。 2)Transform 定义的前后左右方向在世界空间下不固定。即以所见方向为前向。
四元数(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 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 的应用环境广泛,也是入门级学习的典型案例。理解与深析是认识三维虚拟世界的重要点。
◼ 可优化措施
- 使用
ScriptableObject
创建配置参数。 - 推荐配合 Cinemachine 工具包拓展 Free Camera 功能设计。
- 代码结构上,划分 主逻辑(移动 旋转 缩放)、助理(转换方法、枚举)、可配置(持久化数据)。切忌整合在同一脚本下,增加阅读难度。
- 扩展设计 Free Camera 的三维空间运动范围,限制其在某些区域内的移动。