开发平台:Unity 2020
编程平台:Visual Studio 2020
在游戏世界中,摄像机作为最常见、最常使用的 Component 或 GameObject 对象。经常因为项目开发类型,在原基础上进行二次开发。例如 RTS建造游戏、即时战略、模拟现实等需要第三人称摄像机(TP)。枪战对抗、恐怖解密等需要第一人称摄像机(FP)。必须时,如《坦克世界》、《战地风云》等经典游戏需要 TP、FP 两者的结合。本文主要探究 Camera Component 的基础上实现 TP 效果。
注意:Unity 在如今的版本中推广 Ciniemachine 工具包。强化了 Camera 的使用效率、呈现效果。本文不使用该工具包实现。
摄像机的移动 关联 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 <= 180 ? eulerAnglesValue : enlerAnglesValue - 360;
return value;
}
使用该方法对EulerAngles
数值检测负数范围,再通过Mathf.Clamp()
对值域范围进行限制,并重新赋值至EulerAngles
或Rotation
属性上。
全局坐标:基于世界坐标轴(世界正前方、正右方、正上方)
局部坐标:基于自身坐标轴(自身正前方、正右方、正上方)
辅助理解:Transform 与 Vector3 = 世界坐标 与 自身坐标
◼ 其他
在 Unity 中,为区别 Global 与 Local 两个环境下的数据。提供 position
/localPositon
、eularAngles
/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
或禁用该逻辑组件即可。
Free Camera 移动是实现的首要。前后左右上下共计6个方向的位置偏移。根据期望效果方式,其大致的移动效果可总结为3种移动模式。
◼ 关联参数
◼ 固定平面移动
public void DoTranslate()
{
//transform.position += moveDir * Time.deltaTime;
transform.Translate(moveDir * Time.deltaTime, Space.World);
}
transform.Translate()
,其原理等价于 transform.position += ...
。Space
空间不同。上图两行代码的运行效果是相同的。transform.forward
)至 Local
水平坐标系方向前进的公式(该过程称为 “投影”) 其他说明:在 Unity API 中分别提供 Project()
、ProjectOnPlane()
两种投影计算方式。其中三维世界中,向量至水平面的投影方向多使用第二种方式。经过 投影 + 归一化 的处理过程。得到 Free Camera 的运动方向。
public Vector3 GetForward(Vector3 forward)
{
return Vector3.ProjectOnPlane(forward, Vector3.up).normalized;
}
◼ 基于水平面的自由向移动
transform.forward
方向进行。◼ 全自由移动
transform
) 前后左右。仅从移动上,无法体现出其自由度。于是加入Rotation
后,其三者的表现就清晰明了。
在 Unity 中,实现物体的旋转通常是依赖于 Inspector 面板直接修改Rotation
属性,或程序中调用transform.eulerAngles
或transform.rotation
进行重赋值行为。转动方向有助于改变 Translate 运动方向。按照其实现思路大致可有以下两种方式:
◼ 关联参数
◼ 基于 四元数 的旋转
四元数按照 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));
}
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
这类具体到特定值上,在开发过程中无法直接修改该值以实现角度限制。则应追溯至上一层级进行数据类型赋值。
实现画面的放大与缩小效果,类似放大镜的作用。在 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 信息)。
Free Camera 的应用环境广泛,也是入门级学习的典型案例。理解与深析是认识三维虚拟世界的重要点。
◼ 可优化措施
ScriptableObject
创建配置参数。