目录
一、前言
二、Transform基础
1、几种坐标系
2、position和localPosition属性
3、rotation属性
三、摄像机的平移
1、键盘控制平移
2、鼠标控制平移
3、整合
四、摄像机的旋转
1、绕自身旋转
2、绕目标物体旋转
3、整合
五、优化功能
1、调整速率
2、切换目标物体
3、设置常用摄像机观察点
一、前言
在做虚拟仿真或数字孪生等项目中,常常会遇到需要自由移动视角的场景。最近在用unity制作一个有关3D打印机的数字孪生项目时遇到了这种情况,本文将结合unity和blender的视角移动功能,介绍一个类似这两个软件的视角移动的方法。
二、Transform基础
Unity3D中的Transform组件是每个GameObject对象都有的组件,它控制着对象的位置、旋转和缩放。 在介绍视角移动前,本文将先介绍transform的position、rotation两个属性和unity中的坐标系。
1、几种坐标系
在unity中常见的有两种坐标系,分别是全局坐标(World Space)和局部坐标(Local Space),在unity场景窗口(Scene)中可以切换这两种坐标系,如图所示:
全局坐标:也称为世界坐标,表示物体在场景中的绝对位置和方向。全局坐标是相对于场景的原点(0,0,0)而言的。
局部坐标:也称为相对坐标,表示物体相对于其父物体的位置和方向。局部坐标是相对于父物体的原点(0,0,0)而言的。
2、position和localPosition属性
position和localPosition都是三维向量(Vector3),前者用于描述物体在场景中的绝对位置,即相对于世界原点的位置;后者描述物体相对于其父级对象的位置,以父物体为原点。
在没有设置父物体时,Transform.position和Transform.localPosition的值相同,都等于物体的全局坐标。
当物体P被设为物体Q的子物体时,物体P的Transform.localPosition表示与父物体Q的相对位置,当只改变父物体Q的Transform.position时,子物体P将跟随父物体Q移动,因此子物体P的Transform.position随物体Q改变,然而子物体P相对于父物体Q的相对位置不变,所以子物体P的Transform.localPosition保持不变。
值得一提的是,在unity的检查器(Inspector)面板的Transform组件中,所显示的position为localPosition,对于无父物体的物体而言,其代表全局(世界)坐标,对于有父物体的物体而言,则表示两者的相对位置。
3、rotation属性
和描述位置的属性一样,描述旋转的属性也分为rotation和localRotation。它是一个四元数(Quaternion)。rotation与localRotation的关系和position与localPosition一样,这里就不多做赘述。不过值得注意的一点是,在unity中文文档中提到:
“请勿尝试编辑/修改 rotation。”
“要旋转 Transform,请使用 Transform.Rotate,它将使用欧拉角。”
三、摄像机的平移
在unity场景窗口中我们可以按住鼠标滚轮进行平移,而在blender中我们可以按住左Shift+鼠标滚轮平移视角,除此之外,在unity中也可以通过键盘上下左右键控制视角移动。而在项目中,我们将尝试控制摄像机平移模仿上述效果。
1、键盘控制平移
键盘控制平移的代码非常简单,不过在此之前应该在unity的项目设置(Project Setting)中设置好输入管理器(Input Manager)。如下图所示:
在脚本分别调用Input.GetAxis("Vertical")和Input.GetAxis("Horizontal")将返回一个float值,由于类型设置为“Key or Mouse Button”,则返回-1、0、1三个值。
当按下键盘W或者↑键时Input.GetAxis("Vertical")将返回1,按下S或者↓键时将返回-1,不按任何键将返回0;同理当按下D或→键时Input.GetAxis("Horizontal")将返回1,按下A或者←键时将返回-1,不按则返回0。
根据这个属性可得到控制平移的代码:
float moveZ = Input.GetAxis("Vertical") * moveSpeedWithKeyBoard * Time.deltaTime;
float moveX = Input.GetAxis("Horizontal") * moveSpeedWithKeyBoard * Time.deltaTime;
transform.position += transform.forward * moveZ + transform.right * moveX;
2、鼠标控制平移
我将用按下滚轮拖动鼠标的方式控制摄像机上下左右平移,滚动滚轮控制前后平移。
首先是前后移动,确保你的输入管理器(Input Manager)设置如图所示:
通过调用Input.GetAxis("Mouse ScrollWheel")获取前后移动的值,同样这个函数将返回一个float值,滚轮向前滚为正,向后为负。代码如下:
float MouseScroll = Input.GetAxis("Mouse ScrollWheel") * moveSpeedWithScroll * Time.deltaTime;
transform.position += transform.forward * MouseScroll;
然后是上下左右移动 ,当鼠标滚轮按下时获取鼠标在xy轴移动的值:
if (Input.GetMouseButton(2))
{
float moveX = -Input.GetAxis("Mouse X") * moveSpeedWithMouse * Time.deltaTime;
float moveY = -Input.GetAxis("Mouse Y") * moveSpeedWithMouse * Time.deltaTime;
transform.position += transform.right * moveX + transform.up * moveY;
}
3、整合
综合上述,不难发现这几段代码的相似之处:接收来自鼠标或键盘的输入并算出摄像机在forward、right、up三个方向上的移动值,再乘以对应的方向向量并加到相机的位置上,因此可以整合成以下函数:
/// <summary>
/// 相机移动
/// </summary>
public void Move()
{
float moveX = 0;
float moveY = 0;
float moveZ = 0;
// 接收键盘输入
moveZ = Input.GetAxis("Vertical") * moveSpeedWithKeyBoard * Time.deltaTime;
moveX = Input.GetAxis("Horizontal") * moveSpeedWithKeyBoard * Time.deltaTime;
// 接收滚轮输入
moveZ = Input.GetAxis("Mouse ScrollWheel") * moveSpeedWithScroll * Time.deltaTime;
// 接收鼠标输入
if (Input.GetMouseButton(2))
{
moveX = -Input.GetAxis("Mouse X") * moveSpeedWithMouse * Time.deltaTime;
moveY = -Input.GetAxis("Mouse Y") * moveSpeedWithMouse * Time.deltaTime;
}
// 控制移动
transform.position += transform.forward * moveZ + transform.right * moveX + transform.up * moveY;
}
四、摄像机的旋转
常见的摄像机旋转有两种,即摄像机自身旋转和摄像机绕目标物体旋转。在unity的场景窗口中按住鼠标滚轮为绕自身旋转,按住左Alt+鼠标左键时绕选中物体旋转。下面我们将尝试做出上述效果。
1、绕自身旋转
绕自身旋转的思路很简单,只需要获取鼠标输入并更改摄像机自身的旋转。代码如下:
if (Input.GetMouseButton(0))
{
float rotateX = Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime;
float rotateY = Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime;
Vector3 eulerAngles = transform.eulerAngles;
eulerAngles.y += rotateX;
eulerAngles.x -= rotateY;
transform.eulerAngles = eulerAngles;
}
2、绕目标物体旋转
绕目标物体转动较为复杂,需要同时改变position和rotation的值。首先确定目标物体,再改变position值,这个过程和上文的鼠标控制平移一样,接下来需要改变rotation的值确保摄像机始终面向目标物体,只需调用transform.LookAt()函数即可。
当然使用上述方法可以粗略实现围绕旋转,但是依旧有个小问题,就是摄像机会在旋转的过程中逐渐远离目标物体,原因是我们每次改变position的值都将增加一点二者之间的距离(根据三角形的斜边大于直角边的原理),而后面的步骤没有抵消掉这个距离变化。
所有在以上步骤之后还应矫正一下距离,将摄像机的位置在目标物体的方向上移动至距离不变的位置,最终的代码如下:
if (Input.GetMouseButton(1))
{
float rotateX = Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime;
float rotateY = Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime;
float distance = Vector3.Distance(target.transform.position, transform.position);
transform.position += transform.right * -rotateX + transform.up * -rotateY;
transform.LookAt(target.transform);
transform.position = target.transform.position + -transform.forward * distance;
}
3、整合
同样的,我们将以上两段代码整合封装成函数,可以得到以下代码:
/// <summary>
/// 相机旋转
/// </summary>
public void Rotate()
{
//接收输入
float rotateX = Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime;
float rotateY = Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime;
// 绕自身旋转
if (Input.GetMouseButton(0))
{
Vector3 eulerAngles = transform.eulerAngles;
eulerAngles.y += rotateX;
eulerAngles.x -= rotateY;
transform.eulerAngles = eulerAngles;
}
// 绕喷头旋转
if (Input.GetMouseButton(1))
{
float distance = Vector3.Distance(target.transform.position, transform.position);
transform.position += transform.right * -rotateX + transform.up * -rotateY;
transform.LookAt(target.transform);
transform.position = target.transform.position + -transform.forward * distance;
}
}
五、优化功能
1、调整速率
上述方法中,移动的速率是不变的,这样可能会导致当摄像机离物体很远时会显得移动很慢,离物体很近时会显得太快,可以写一个函数,根据距离目标物体的远近自动调整速率或许可以获得更好的手感。
2、切换目标物体
上述方法中没有提到目标物体的切换与选择,可以添加一个功能,通过鼠标点击选中游戏中的对象作为目标物体围绕旋转,切换目标只需选择其他对象即可。
3、设置常用摄像机观察点
在实际项目中,可设置几个常用的摄像机机位,用户可通过快捷键快速切换到该位置观察。例如在3D打印机的仿真中,可以在打印机窗口前设置一个观察点来模拟实际中用户观察3D打印机的视角。