
2.3 相机跟随
相机是场景中不可缺少的元素,它就像是人的眼睛,三维场景的呈现方式,最后还是要通过相机来确定。图2-9展示了相机的视野范围。

图2-9 相机及其视野范围
通常第一人称或第三人称的游戏,相机会跟随角色移动,故而要实现下面3个功能。
1)相机跟随坦克移动。
2)鼠标控制相机的角度。
3)鼠标滚轮调整相机与坦克的距离。
下面实现的是一套通用的第三人称相机组件,除了可以用于坦克游戏中,还可以把它抽出来,用在更多的游戏中。
2.3.1 数学原理
复习三角函数:因为本节会涉及sin、cos等三角函数,如果遗忘了这些知识,可以参阅相关资料(如百度百科的“三角函数公式”词条)。在图2-10所示的三角形中,角A的角度为θ,假设已知边AB的长度,那么由公式AC=AB·cosθ, BC=AB·sinθ即可求出边AC和BC的长度。

图2-10 三角函数示意图
要想让相机跟随坦克移动,就要明白在一定角度下相机与坦克的位置关系。如图2-11所示,设相机与坦克的距离为distance,相机与xz平面的角度为roll。根据三角关系即可求得映射在xz平面的距离d为distance·cos(roll),相机高度为distance·sin(roll)。

图2-11 在yz平面,相机与坦克的位置关系
在图2-12所示的xz平面中,设相机与坦克的距离为d(即图2-11中distance映射在xz平面的长度),相机的旋转角度为rot。由图2-12可知,相机与坦克的连线与x轴的角度为rot-180。根据三角函数,即可得出x轴的位移为d·sin(rot), z轴的位移为d·cos(rot)。所以,只要用坦克的坐标减去相对位移,便能求出相机的坐标。

图2-12 相机与坦克的位置关系
2.3.2 跟随算法
新建名为CameraFollow.cs的文件,在CameraFollow类中编写相机的跟随功能,代码如下所示。

程序解释如下所示。
1)定义3个变量distance、rot、roll分别代表距离、横向角度和纵向角度(参见图2-11和图2-12)。由于Mathf.Sin和Mathf.Cos使用弧度作为单位,因此这里的角度都用弧度来表示。根据“弧度=角度*2π/360”可以得知,30f*Mathf.PI*2/360便是30度所对应的弧度值。
2)定义变量target表示相机要跟随的物体。然后在Start()方法中通过Game-Object.Find找到场景中的坦克,赋值给target(或通过2.3.3节实现的SetTarget方法)。注意Find方法的参数要与场景中的坦克名相同。
GameObject.Find(名字)会根据参数所指定的名字,在场景中查找物体。如果场景中存在对应名字的物体,那么它将会返回该游戏对象,否则返回null。层次面板中显示了场景中所有游戏物体的名字,读者可以右键该物体,选择Rename菜单项来修改名字(如图2-13所示)。

图2-13 修改游戏对象的名字
3)在LateUpdate()中通过上文得出的位置关系计算出相机的新位置,最后使用transform. LookAt方法使相机对准目标。读者还记得前面提及的Update方法吗?Unity3D会在每一帧中调用它,那么LateUpdate又是什么呢?这里将涉及Unity3D的生命周期。
图2-14描述了Unity3D组件的生命周期。当组件被创建时(进入场景后,场景里的所有游戏对象和组件都会被创建), Unity3D会依次调用它们的Awake和Start方法,然后在每一帧中依次调用Update和LateUpdate方法。也就是说Unity3D会在调用所有组件的Update方法后再调用LateUpdate。通过Update和LateUpdate可以控制脚本的执行顺序,例如在Update里编写移动物体的代表,在LateUpdate中实现跟随物体的相机。

图2-14 简化版的组件生命周期
4) Camera.main表示场景中的主相机,它是第一个启用的被标记为“Main-Camera”的相机。只需要给Camera.main.transform.position赋值即可设置相机位置。下列几行代码将根据2.3.1节中所叙述的数学原理,计算相机的位置,并保存到Vector3类型的cameraPos中。
Vector3 cameraPos; float d = distance *Mathf.Cos (roll); float height = distance * Mathf.Sin(roll); cameraPos.x = targetPos.x +d * Mathf.Cos(rot); cameraPos.z = targetPos.z + d * Mathf.Sin(rot); cameraPos.y = targetPos.y + height;
5)Camera.main.transform.LookAt()使相机旋转,对准它所跟随的物体。图2-15 (左)展示了相机与立方体的初始位置关系,在调用LookAt方法后,相机的旋转角度如图2-15(右)所示,对准了立方体。

图2-15 LookAt方法示意图
现在,将CameraFollow脚本拉到相机身上,调整CameraFollow组件的距离和初始角度(如图2-16所示)。然后绘制一块地形,使玩家能够感受到坦克的移动过程。运行游戏,即可看到相机跟随在坦克后面(如图2-17所示)。

图2-16 相机跟随组件的属性

图2-17 相机跟随在坦克后面
2.3.3 设置跟随目标
在CameraFollow类中添加SetTarget方法,设置相机对准的目标。不同的三维模型其中心点会有所不同,图2-18展示的是相机对准不同中心点的情况。中心点不同,玩家所看到的视角也就不同(如图2-20所示)。

图2-18 相机对准不同的中心点

图2-19 添加一个名为cameraPoint的方块,精确控制相机对准的点

图2-20 cameraPoint在不同位置时,相机的视角变化
为了能够指定相机对准的中心点,特制定如下规则。
1)如果对准的物体带有名为cameraPoint的子物体,那么相机对准cameraPoint子物体。
2)如果物体不含名为cameraPoint的子物体,则对准物体中心点。
下面是相应的代码。

可以在炮塔上方添加一个名为cameraPoint的方块(作为Tank的子物体),精确控制相机对准的中心点(如图2-19所示)。
图2-20展示了cameraPoint在不同位置时,相机的视角的变化。
2.3.4 横向旋转相机
本节将实现通过鼠标来控制相机旋转的功能,当鼠标向左移动时,相机随之“左转”;当鼠标向右移动时,相机随之“右转”。这样,玩家便可以从各个方向查看坦克模型(如图2-21所示)。

图2-21 相机随着鼠标的移动而旋转,玩家可以从各个方向查看模型
Unity3D的输入轴Mouse X和Mouse Y代表着鼠标的移动增量,也就是说当鼠标向左移动时,Input.GetAxis ("Mouse X")的值会增大,向右则减小。只要让旋转角度rot与Mouse X成正比关系,便能够通过鼠标控制相机的角度。
在CameraFollow类中新增变量rotSpeed,表示相机旋转的速度。然后编写Rotate()方法,使相机的横向角度rot随着Input.GetAxis ("Mouse X")的改变而改变,代码如下所示。

最后在LateUpdate ()中调用Rotate()。运行游戏后,镜头将随着鼠标的移动而转动,玩家便可以从各个角度观察坦克了。
void LateUpdate ()
{
//一些判断
if (target == null)
return;
if (Camera.main == null)
return;
//横向旋转
Rotate();
……
Unity3D的输入轴请参见InputManager面板(可通过Edit→Project Settings→Input打开,面板如图2-22所示),默认包含Mouse X、Mouse Y、Mouse ScrollWheel(鼠标滚轮)、Horizontal(水平轴)、Vertical(垂直轴)等多个参数项。我们会在使用到具体的输入轴时再做说明。

图2-22 Unity3D的输入项
2.3.5 纵向旋转相机
除了操控相机的横向角度,玩家还可以调整相机的高度。下面的代码通过maxRoll和minRoll定义了相机的纵向旋转范围(以弧度表示),通过rollSpeed给出旋转的速度。
//纵向角度范围 private float maxRoll = 70f * Mathf.PI * 2 / 360; private float minRoll = -10f * Mathf.PI * 2 / 360; //纵向旋转速度 private float rollSpeed = 0.2f;
在CameraFollow类中编写Roll方法,使相机纵向角度roll随着Input.GetAxis ("Mouse Y")的改变而改变。最后在LateUpdate()里调用它,代码如下所示。

运行游戏,即可在各个角度观察坦克(如图2-23所示)。

图2-23 在不同角度观察坦克
2.3.6 滚轮调节距离
本节将会实现用鼠标滚轮调节相机与坦克之间距离的功能。输入轴Mouse Scroll-Wheel代表鼠标滚轮,即通过Input.GetAxis ("Mouse ScrollWheel")可以获取鼠标滚轮的增量,当滚轮向上滚动时该值减少,向下滚动时该值增加。添加maxDistance、minDistance和zoomSpeed这3个调整距离的变量,其中maxDistance和minDistance表示距离的范围,zoomSpeed表示缩放的速度,代码如下所示。
//距离范围 public float maxDistance = 22f; public float minDistance = 5f; //距离变化速度 public float zoomSpeed = 0.2f;
在CameraFollow类中添加Zoom方法实现距离缩放,并在LateUpdate ()里调用它,代码如下所示。

运行游戏,滚动鼠标滚轮,相机与坦克的距离就会随之改变(如图2-24所示)。

图2-24 在不同距离下观察坦克