·序言“你最早喜欢上的游戏类型是什么”如果询问每一位游戏爱好者和游戏开发者那么FPSFirst-Person Shooting,第一人称射击类型游戏必定是一个高频次出现的答案。甚至说若要首次感受到游戏的乐趣甚至不必去体验“第一人称射击”的纷飞战火。当你在屏幕前移动鼠标旋转视角向周围的世界好奇张望的时候游戏的乐趣和精彩已经足以在你心中埋下梦的种子。【自由旋转视角】是一项十分常见的游戏功能这项功能允许玩家移动鼠标来使视角转向不同的方向以此支持各类游戏中的观望、射击和行进能力。这项功能描述起来只需寥寥数语理解起来也非常容易——因为这项功能的体验本身就与我们的生物本能高度一致。”自由视角“是游戏沉浸感最主要的来源之一。然而对于Unity游戏开发新手来说这项功能却时常成为初学阶段的第一个”拦路虎“甚至是”劝退“级别的高难度障碍。究其原因一方面是Unity老生常谈的问题缺乏”轮子“和蓝图另一方面则是这项功能所涉及的一些3D数学知识确实难度较大且相对生僻难解。至于说为什么一项在数学上不那么容易理解的功能反而是我们的生物本能那......只能感慨自然造物的神奇了呀。一.似易实难的数学大坑玩家在游戏中旋转视角的过程就是代表玩家的摄像机(Camera)不断发生姿态改变的过程。要想在游戏中模拟出自由视角的效果就必须设法用数学语言描述出玩家的姿态和姿态变化并根据玩家的操作进行适当的计算。那么什么是姿态当玩家正在望向某个方向时如何描述摄像机在空间中的姿态呢这是一个似易实难的问题——很多萌新都会产生一种典型的错误理解我们来看一下问题是如何产生的。如图是一个朴实无华的空间直角坐标系x轴指向右边y轴指向上方Z轴指向远离自己的方向。此坐标系即为Unity使用的坐标系假设玩家(自己)处于该坐标空间中且面朝Z轴正方向那么此时玩家正在注视的方向可以用空间向量表示为(0,0,1)。于是玩家在空间中的姿态可以用空间向量(0,0,1)来唯一确定。这样说对吗答案是不对。想象一架在空中正常向前飞行的飞机。飞行员在座舱中向前观望可以看到上方是天空下方是地面如图——然后假设飞行员开始做“倒飞”特技动作让飞机上下颠倒过来继续飞行。此时飞行员视野中的景象应该是地面在上天空在下。此时飞机飞行的方向没有改变两种情况下的飞行方向的空间向量表示完全相同但飞机的姿态和飞行员所看到的场景都是不同的。这两种姿态显然不能认为是同一种姿态假设玩家在游戏中想要正常向前走动屏幕上却显示出了地面在上、天空在下的画面那玩家肯定会抓狂的。到这里问题就体现出了它的严重性——要准确地对玩家的姿态进行数学描述并不是那么容易的事情高中和大学基础课程中的空间向量在这个问题中派不上用场。二.偏转、俯仰和桶滚那么怎样正确地描述物体的空间姿态和姿态变化呢我们需要引入一些全新的3D数学概念来解决这个问题。一个物体在三维空间中姿态的改变可以被描述为3种基本行为【偏转(yaw)】【俯仰(pitch)】和【桶滚(roll)】。任何形式的姿态改变都可以被拆分为这3种行为的适当组合。物体绕着竖直轴的旋转行为称为【偏转】。物体绕着水平(左右方向)轴的旋转行为称为【俯仰】物体绕着前后方向轴的旋转行为称为【桶滚】。这些行为的含义是非常容易理解的因为它们都可以在现实生活中找到典型的例子。以下行为属于【偏转】:·军训时的“向左转”“向右转”指令·汽车的左转弯和右转弯·水平放置的指南针在被拨动后重新指向北方偏转示意图以下行为属于【俯仰】·飞机的向上爬升和向下俯冲·驾驶汽车上坡和下坡·运动员投掷标枪后标枪上升和下落时的姿态变化俯仰示意图以下行为属于【桶滚】·飞机由正常飞行切换为倒飞姿态的过程·转动插入锁孔中的钥匙·子弹在发射时在枪管内膛线的约束下作螺旋运动。桶滚示意图完成对以上三种行为的定义后我们不难理解玩家的各种姿态变化都可以由这三种基本行为组合而成。例如玩家瞄准当前视野内左上方的敌人这个过程中的姿态变化可以分解为玩家向左旋转一定角度偏转玩家抬高枪口以指向高处的敌人。俯仰于是我们可以尝试总结更加一般的情况将游戏中”自由视角镜头“的姿态变化转化为数学语言。根据前面情境的经验我们容易知道玩家在正常走动和观望时玩家摄像机发生的姿态变化由【偏转】和【俯仰】组成而不会发生【桶滚】的变化。当玩家人物的姿态发生桶滚变换时游戏中的天空不再位于人物视野的上方地面不再位于人物视野的下方这种情况多发生在基于游戏规则的特殊情境中例如《守望先锋》中卢西奥的贴墙跑就是典型的【桶滚】情形。你只要找一张床并以侧卧姿势躺下就可以在现实中模拟这个情形此时在你的视野中天花板和地板必然分别位于左右两侧。实际上《守望先锋》中的镜头并不会真的跟随卢西奥的眼睛进行【桶滚】变换而且大多数类似游戏的处理都是如此。究其原因许多玩家的平衡感根本无法承受这样的画面可能导致呕吐或癫痫发作。少了【桶滚】这个维度那么玩家镜头的姿态变化就可以用【偏转】和【俯仰】两个角度来描述。而玩家的鼠标支持前、后、左、右移动的行为恰好也可以提供两个不同维度的正值、负值和0这就为鼠标移动视角提供了数学上的可行性。你可以打开任意一款FPS游戏来感受这个过程。不难发现鼠标的向左、向右移动行为对应了玩家姿态变化中的【偏转】而向前、向后移动行为对应的则是玩家的【俯仰】。具体地说自由视角镜头对于玩家鼠标行为的响应规则是这样的当玩家的鼠标有向前移动趋势时进行向上的俯仰当玩家的鼠标有向后移动趋势时进行向下的俯仰当玩家的鼠标有向左移动趋势时进行向左(逆时针)的偏转当玩家的鼠标有向右移动趋势时进行向右(顺时针)的偏转。三.在Unity中实现·获取鼠标的移动趋势Unity为我们提供了【输入轴】的功能它使我们能够便捷地获取玩家鼠标在每一帧中的移动趋势。例如当玩家向前推动鼠标一段距离再停下时输入轴MouseY的值会在这段时间内从0平缓地增加到某个最大值然后平缓地回落到0。通过读取输入轴的值我们就可以编写镜头控制代码使得玩家摄像机的姿态对玩家的鼠标行为作出正确的响应从而实现自由视角的功能。·在Unity中访问物体的旋转角度Unity使用Transform类来描述物体的位置、旋转和缩放信息其中“旋转”有两种不同的表现形式分别是四元数(Quaternion)和欧拉角(EulerAngles)。【欧拉角】是较易理解的一种旋转表示方法它通过物体的【俯仰】【偏转】【桶滚】角度共3个角度值来描述物体的旋转姿态。在Unity中Transform的第二组数据称为Transform.EulerAngles也就是物体旋转状态的欧拉角表示形式Transform.EulerAngles包含x,y,z三项数值分别表示物体绕下图所示坐标系中x轴、y轴、z轴的旋转角即俯仰角、偏转角和桶滚角。·编写代码现在我们终于有了足够的知识储备可以编写代码来实现【自由视角】功能啦。创建脚本SimpleFreelook.cs内容如下:using UnityEngine; public class SimpleFreelook : MonoBehaviour { public Transform Camera;//应绑定为主摄像机 public float yawSensitivity;//鼠标的偏转敏感度 public float pitchSensitivity;//鼠标的俯仰敏感度 public float pitchLimit 75;//俯仰的最大角度 private void Update() { float yaw Input.GetAxis(Mouse X) * yawSensitivity;//计算当前帧的偏转值 float pitch -Input.GetAxis(Mouse Y) * pitchSensitivity;//计算当前帧的俯仰值 Vector3 rot new Vector3(pitch, yaw, 0);//计算出当前帧中摄像机的欧拉角应当发生的改变量 Vector3 afterRot;//开始计算摄像机在应用改变量后欧拉角的预期值 afterRot.x Mathf.Clamp(ClampAngle(Camera.localEulerAngles.x) rot.x, -pitchLimit, pitchLimit);//俯仰角度需要进行约束避免过度仰视/俯视而导致操作困难 afterRot.y Camera.localEulerAngles.y rot.y;//偏转角度不需要设限 afterRot.z 0;//桶滚角度始终为0 Camera.localEulerAngles afterRot;//将摄像机的欧拉角设定为预期值 } //在访问Unity提供的欧拉角数值时需要先消除360°角周期的影响将每个角度的数值约束在[-180,180]范围内否则会对数学运算造成干扰。 public float ClampAngle(float angle) { if(angle 180 angle -180) { return angle; } while(angle 180) { angle - 360; } while(angle -180) { angle 360; } return angle; } }将脚本挂载到任意物体上并将Camera字段绑定为主摄像机即可实现第一人称自由视角的功能。Tips上面的代码所表示的算法大致与前面的理论知识一致但还有两点重要的细节需要讲解。(1) pitchLimit变量限制了玩家的最大俯仰角度为75度。一方面这能够避免玩家因过度仰视和俯视而迷失方向另一方面更可以避免俯仰角度超过90°导致玩家处于异常姿态。例如当玩家在90°仰视天空的状态下继续前推鼠标进行仰视则玩家的视野会转向后方并且是地面在上天空在下此时玩家人物的实际姿势相当于舞蹈动作中的“下腰”状态。在绝大多数游戏中这个姿势显然是不可能被允许的。(2)在访问物体欧拉角的x/y/z数值时Unity返回的读出值具有周期上的随机性例如对于俯仰角x 0Transform.EulerAngle.x的读出值可能是-3600360中的一个。为了克服这个问题上面的代码段中引入了ClampAngle方法来将这样的角度值统一归拢到[-180,180]区间内。否则将会产生运算错误。以下为难度较高的拓展内容大家可以选择性了解四. 欧拉角和万向节锁【四元数】和【欧拉角】这两项概念是非常难以在短时间内讲清的对于很大一部分Unity开发者来说甚至永远都不必理解。【四元数】是一种超复数Unity中的四元数采用x,y,z,w四个数值来描述物体的旋转姿态。在实际应用中这四个数值几乎不会表示任何几何意义因此我们很少会在代码中直接使用四元数而是多在Unity辅助运算的基础上将四元数用于旋转过程的平滑插值【欧拉角】比四元数要简单许多但它的缺点是会受到万向节锁(Gimbal lock)的影响这会使得物体在面朝方向连续变化的过程中经历不连续的欧拉角取值。#万向节锁欧拉角的固有缺陷万向节锁是一项不易理解的3D数学概念但在这里Victor可以给出一个例子由大家去体会。打开任意一款FPS游戏让人物面朝北方向前推动鼠标直到人物视角竖直指向天空。将天空看成一个高度为1的平面则此时你瞄准的天空坐标是(0,1,0)。在这个时候想象一架可疑飞机M从你瞄准的位置出发向西南方飞行你需要一直保持对它的瞄准。尝试移动鼠标来使瞄准点向天空的西南方向移动或者说将瞄准点调整为M-m,1,-m(m≥0)。你会发现无论怎样移动鼠标都无法使瞄准方向立即朝天空中的西南方移动。想要向点M进行瞄准你必须向后移动鼠标来使视角下降同时再向左移动鼠标进行一个135°的逆时针大角度偏转才能完成瞄准。可疑飞机M(-m,1,-m)出发后m值会从0开始平滑地增加而人物瞄准动点M(-m,1,-m)的过程在观感上是一段平滑的旋转过程。然而在这段过程中m值从0变化到εEpsilon,即接近0的小正数的瞬间人物的偏转角会发生135°的突变。这就是万向节锁的一个例子它意味着物体在面朝方向连续变化的过程中可能经历不连续的欧拉角取值。通过这个示例或许你还能对前面的代码加深理解——为什么第一人称镜头的俯仰角度通常需要有限制上面的例子其实已经展示了一个事实即一个物体在俯仰角度达到90°时在欧拉角的体系下会丢失部分旋转维度。也正是这一点才导致玩家在俯仰角度过大时很容易“晕头转向”难以控制镜头。以上的缺陷使得欧拉角在表示旋转时存在一定的局限性。当物体需要执行某些平滑旋转过程时此类过程有时难以通过对欧拉角的控制来实现。要实现类似的功能可以改用四元数来控制物体的旋转也可以使用DOTween等动画插件。五.刨根问底网上关于前一部分内容的讲解很多但讲解程度几乎全都会止步于此并且最终认为【万向节锁】是一项深奥难解并且在实际应用中为害不小的隐患。但事实上万向节锁是一个完全可防可控的问题而不是什么玄学和不稳定性。笑为什么用欧拉角旋转物体会遇到欧拉角数值突然发生非平滑改变的情况其实大学阶段的高等数学教材已经给了我们答案——欧拉角体系可以看成是球面坐标系的变种。在数学里球坐标系Spherical coordinate system是一种利用球坐标(rθφ)表示一个点 p 在三维空间的位置的三维正交坐标系。下图显示了球坐标的几何意义原点到 P 点的距离 r 原点到点 P 的连线与 z轴正方向之间的天顶角仰角θ以及原点到点 P 的连线在 xOy平面的投影线与 x轴正方向之间的方位角φ。是不是和欧拉角极为相似不难看出球坐标系定义中的距离r与我们研究的旋转问题无关而与天顶角θ类似的是EulerAngles.x都表示俯仰维度与方位角φ对应的则是EulerAngles.y表示偏航维度。这两个维度可以确定一个物体的面朝方向也就是上图中的向量O--P。在以上两个维度的基础上欧拉角多出了一个维度【桶滚(EulerAngles.z)】这个维度的含义是物体在面朝方向确定前提下的自身旋转量。而传统数学中的射线O--P是没有自身旋转之说的故这个维度自然不会在球面坐标体系中出现。对于一个球心在原点半径为1的球球面上的绝大多数点都拥有一个确定的球坐标(这里忽略角的周期性)但有两个点例外这两个点正是球的上下两极。当天顶角θ 0或θ π时无论方位角φ怎么改变点P对应的都是球的上下两个极点。而当点P从某个极点恰好移开时P的方位角φ取值会从任意值突然变为确定值。很明显欧拉角中的EulerAngles.y也会发生相同的情况。这就是万向节锁或者说欧拉角的数值突变现象的真正来源。到这里我们就揭开了第4节中那个135°诡异旋转的谜团。当玩家的人物竖直望向天空时其方位角(脚尖指向的方向)是任意的而当玩家的视角不再竖直向上的瞬间其方位角立刻被限制成了一个固定值脚尖必须朝着西南方才能正常瞄准。玩家开始瞄准飞机之前其脚尖的偏转角EulerAngles.y是初始值0即脚尖朝北而开始注视飞机之后的偏转角将被强制约束到-135°即脚尖朝着西南方。因此玩家开始瞄准飞机的瞬间必须完成135度的偏转。这样我们终于可以得到使用欧拉角旋转物体时的最终结论对于一个Unity物体的某段旋转过程如果其面朝的方向在某一瞬间将会是【正上方】或【正下方】则在这一瞬间的前后该物体的欧拉角的y值可能会发生数值突变。其它情况下不会发生上述事件。大功告成通过这一结论和前面的内容你应当可以准确地判断出游戏中的某个旋转过程是否适合使用欧拉角进行运算以及需要的话如何运算。
Unity游戏功能(2)—第一人称镜头
·序言“你最早喜欢上的游戏类型是什么”如果询问每一位游戏爱好者和游戏开发者那么FPSFirst-Person Shooting,第一人称射击类型游戏必定是一个高频次出现的答案。甚至说若要首次感受到游戏的乐趣甚至不必去体验“第一人称射击”的纷飞战火。当你在屏幕前移动鼠标旋转视角向周围的世界好奇张望的时候游戏的乐趣和精彩已经足以在你心中埋下梦的种子。【自由旋转视角】是一项十分常见的游戏功能这项功能允许玩家移动鼠标来使视角转向不同的方向以此支持各类游戏中的观望、射击和行进能力。这项功能描述起来只需寥寥数语理解起来也非常容易——因为这项功能的体验本身就与我们的生物本能高度一致。”自由视角“是游戏沉浸感最主要的来源之一。然而对于Unity游戏开发新手来说这项功能却时常成为初学阶段的第一个”拦路虎“甚至是”劝退“级别的高难度障碍。究其原因一方面是Unity老生常谈的问题缺乏”轮子“和蓝图另一方面则是这项功能所涉及的一些3D数学知识确实难度较大且相对生僻难解。至于说为什么一项在数学上不那么容易理解的功能反而是我们的生物本能那......只能感慨自然造物的神奇了呀。一.似易实难的数学大坑玩家在游戏中旋转视角的过程就是代表玩家的摄像机(Camera)不断发生姿态改变的过程。要想在游戏中模拟出自由视角的效果就必须设法用数学语言描述出玩家的姿态和姿态变化并根据玩家的操作进行适当的计算。那么什么是姿态当玩家正在望向某个方向时如何描述摄像机在空间中的姿态呢这是一个似易实难的问题——很多萌新都会产生一种典型的错误理解我们来看一下问题是如何产生的。如图是一个朴实无华的空间直角坐标系x轴指向右边y轴指向上方Z轴指向远离自己的方向。此坐标系即为Unity使用的坐标系假设玩家(自己)处于该坐标空间中且面朝Z轴正方向那么此时玩家正在注视的方向可以用空间向量表示为(0,0,1)。于是玩家在空间中的姿态可以用空间向量(0,0,1)来唯一确定。这样说对吗答案是不对。想象一架在空中正常向前飞行的飞机。飞行员在座舱中向前观望可以看到上方是天空下方是地面如图——然后假设飞行员开始做“倒飞”特技动作让飞机上下颠倒过来继续飞行。此时飞行员视野中的景象应该是地面在上天空在下。此时飞机飞行的方向没有改变两种情况下的飞行方向的空间向量表示完全相同但飞机的姿态和飞行员所看到的场景都是不同的。这两种姿态显然不能认为是同一种姿态假设玩家在游戏中想要正常向前走动屏幕上却显示出了地面在上、天空在下的画面那玩家肯定会抓狂的。到这里问题就体现出了它的严重性——要准确地对玩家的姿态进行数学描述并不是那么容易的事情高中和大学基础课程中的空间向量在这个问题中派不上用场。二.偏转、俯仰和桶滚那么怎样正确地描述物体的空间姿态和姿态变化呢我们需要引入一些全新的3D数学概念来解决这个问题。一个物体在三维空间中姿态的改变可以被描述为3种基本行为【偏转(yaw)】【俯仰(pitch)】和【桶滚(roll)】。任何形式的姿态改变都可以被拆分为这3种行为的适当组合。物体绕着竖直轴的旋转行为称为【偏转】。物体绕着水平(左右方向)轴的旋转行为称为【俯仰】物体绕着前后方向轴的旋转行为称为【桶滚】。这些行为的含义是非常容易理解的因为它们都可以在现实生活中找到典型的例子。以下行为属于【偏转】:·军训时的“向左转”“向右转”指令·汽车的左转弯和右转弯·水平放置的指南针在被拨动后重新指向北方偏转示意图以下行为属于【俯仰】·飞机的向上爬升和向下俯冲·驾驶汽车上坡和下坡·运动员投掷标枪后标枪上升和下落时的姿态变化俯仰示意图以下行为属于【桶滚】·飞机由正常飞行切换为倒飞姿态的过程·转动插入锁孔中的钥匙·子弹在发射时在枪管内膛线的约束下作螺旋运动。桶滚示意图完成对以上三种行为的定义后我们不难理解玩家的各种姿态变化都可以由这三种基本行为组合而成。例如玩家瞄准当前视野内左上方的敌人这个过程中的姿态变化可以分解为玩家向左旋转一定角度偏转玩家抬高枪口以指向高处的敌人。俯仰于是我们可以尝试总结更加一般的情况将游戏中”自由视角镜头“的姿态变化转化为数学语言。根据前面情境的经验我们容易知道玩家在正常走动和观望时玩家摄像机发生的姿态变化由【偏转】和【俯仰】组成而不会发生【桶滚】的变化。当玩家人物的姿态发生桶滚变换时游戏中的天空不再位于人物视野的上方地面不再位于人物视野的下方这种情况多发生在基于游戏规则的特殊情境中例如《守望先锋》中卢西奥的贴墙跑就是典型的【桶滚】情形。你只要找一张床并以侧卧姿势躺下就可以在现实中模拟这个情形此时在你的视野中天花板和地板必然分别位于左右两侧。实际上《守望先锋》中的镜头并不会真的跟随卢西奥的眼睛进行【桶滚】变换而且大多数类似游戏的处理都是如此。究其原因许多玩家的平衡感根本无法承受这样的画面可能导致呕吐或癫痫发作。少了【桶滚】这个维度那么玩家镜头的姿态变化就可以用【偏转】和【俯仰】两个角度来描述。而玩家的鼠标支持前、后、左、右移动的行为恰好也可以提供两个不同维度的正值、负值和0这就为鼠标移动视角提供了数学上的可行性。你可以打开任意一款FPS游戏来感受这个过程。不难发现鼠标的向左、向右移动行为对应了玩家姿态变化中的【偏转】而向前、向后移动行为对应的则是玩家的【俯仰】。具体地说自由视角镜头对于玩家鼠标行为的响应规则是这样的当玩家的鼠标有向前移动趋势时进行向上的俯仰当玩家的鼠标有向后移动趋势时进行向下的俯仰当玩家的鼠标有向左移动趋势时进行向左(逆时针)的偏转当玩家的鼠标有向右移动趋势时进行向右(顺时针)的偏转。三.在Unity中实现·获取鼠标的移动趋势Unity为我们提供了【输入轴】的功能它使我们能够便捷地获取玩家鼠标在每一帧中的移动趋势。例如当玩家向前推动鼠标一段距离再停下时输入轴MouseY的值会在这段时间内从0平缓地增加到某个最大值然后平缓地回落到0。通过读取输入轴的值我们就可以编写镜头控制代码使得玩家摄像机的姿态对玩家的鼠标行为作出正确的响应从而实现自由视角的功能。·在Unity中访问物体的旋转角度Unity使用Transform类来描述物体的位置、旋转和缩放信息其中“旋转”有两种不同的表现形式分别是四元数(Quaternion)和欧拉角(EulerAngles)。【欧拉角】是较易理解的一种旋转表示方法它通过物体的【俯仰】【偏转】【桶滚】角度共3个角度值来描述物体的旋转姿态。在Unity中Transform的第二组数据称为Transform.EulerAngles也就是物体旋转状态的欧拉角表示形式Transform.EulerAngles包含x,y,z三项数值分别表示物体绕下图所示坐标系中x轴、y轴、z轴的旋转角即俯仰角、偏转角和桶滚角。·编写代码现在我们终于有了足够的知识储备可以编写代码来实现【自由视角】功能啦。创建脚本SimpleFreelook.cs内容如下:using UnityEngine; public class SimpleFreelook : MonoBehaviour { public Transform Camera;//应绑定为主摄像机 public float yawSensitivity;//鼠标的偏转敏感度 public float pitchSensitivity;//鼠标的俯仰敏感度 public float pitchLimit 75;//俯仰的最大角度 private void Update() { float yaw Input.GetAxis(Mouse X) * yawSensitivity;//计算当前帧的偏转值 float pitch -Input.GetAxis(Mouse Y) * pitchSensitivity;//计算当前帧的俯仰值 Vector3 rot new Vector3(pitch, yaw, 0);//计算出当前帧中摄像机的欧拉角应当发生的改变量 Vector3 afterRot;//开始计算摄像机在应用改变量后欧拉角的预期值 afterRot.x Mathf.Clamp(ClampAngle(Camera.localEulerAngles.x) rot.x, -pitchLimit, pitchLimit);//俯仰角度需要进行约束避免过度仰视/俯视而导致操作困难 afterRot.y Camera.localEulerAngles.y rot.y;//偏转角度不需要设限 afterRot.z 0;//桶滚角度始终为0 Camera.localEulerAngles afterRot;//将摄像机的欧拉角设定为预期值 } //在访问Unity提供的欧拉角数值时需要先消除360°角周期的影响将每个角度的数值约束在[-180,180]范围内否则会对数学运算造成干扰。 public float ClampAngle(float angle) { if(angle 180 angle -180) { return angle; } while(angle 180) { angle - 360; } while(angle -180) { angle 360; } return angle; } }将脚本挂载到任意物体上并将Camera字段绑定为主摄像机即可实现第一人称自由视角的功能。Tips上面的代码所表示的算法大致与前面的理论知识一致但还有两点重要的细节需要讲解。(1) pitchLimit变量限制了玩家的最大俯仰角度为75度。一方面这能够避免玩家因过度仰视和俯视而迷失方向另一方面更可以避免俯仰角度超过90°导致玩家处于异常姿态。例如当玩家在90°仰视天空的状态下继续前推鼠标进行仰视则玩家的视野会转向后方并且是地面在上天空在下此时玩家人物的实际姿势相当于舞蹈动作中的“下腰”状态。在绝大多数游戏中这个姿势显然是不可能被允许的。(2)在访问物体欧拉角的x/y/z数值时Unity返回的读出值具有周期上的随机性例如对于俯仰角x 0Transform.EulerAngle.x的读出值可能是-3600360中的一个。为了克服这个问题上面的代码段中引入了ClampAngle方法来将这样的角度值统一归拢到[-180,180]区间内。否则将会产生运算错误。以下为难度较高的拓展内容大家可以选择性了解四. 欧拉角和万向节锁【四元数】和【欧拉角】这两项概念是非常难以在短时间内讲清的对于很大一部分Unity开发者来说甚至永远都不必理解。【四元数】是一种超复数Unity中的四元数采用x,y,z,w四个数值来描述物体的旋转姿态。在实际应用中这四个数值几乎不会表示任何几何意义因此我们很少会在代码中直接使用四元数而是多在Unity辅助运算的基础上将四元数用于旋转过程的平滑插值【欧拉角】比四元数要简单许多但它的缺点是会受到万向节锁(Gimbal lock)的影响这会使得物体在面朝方向连续变化的过程中经历不连续的欧拉角取值。#万向节锁欧拉角的固有缺陷万向节锁是一项不易理解的3D数学概念但在这里Victor可以给出一个例子由大家去体会。打开任意一款FPS游戏让人物面朝北方向前推动鼠标直到人物视角竖直指向天空。将天空看成一个高度为1的平面则此时你瞄准的天空坐标是(0,1,0)。在这个时候想象一架可疑飞机M从你瞄准的位置出发向西南方飞行你需要一直保持对它的瞄准。尝试移动鼠标来使瞄准点向天空的西南方向移动或者说将瞄准点调整为M-m,1,-m(m≥0)。你会发现无论怎样移动鼠标都无法使瞄准方向立即朝天空中的西南方移动。想要向点M进行瞄准你必须向后移动鼠标来使视角下降同时再向左移动鼠标进行一个135°的逆时针大角度偏转才能完成瞄准。可疑飞机M(-m,1,-m)出发后m值会从0开始平滑地增加而人物瞄准动点M(-m,1,-m)的过程在观感上是一段平滑的旋转过程。然而在这段过程中m值从0变化到εEpsilon,即接近0的小正数的瞬间人物的偏转角会发生135°的突变。这就是万向节锁的一个例子它意味着物体在面朝方向连续变化的过程中可能经历不连续的欧拉角取值。通过这个示例或许你还能对前面的代码加深理解——为什么第一人称镜头的俯仰角度通常需要有限制上面的例子其实已经展示了一个事实即一个物体在俯仰角度达到90°时在欧拉角的体系下会丢失部分旋转维度。也正是这一点才导致玩家在俯仰角度过大时很容易“晕头转向”难以控制镜头。以上的缺陷使得欧拉角在表示旋转时存在一定的局限性。当物体需要执行某些平滑旋转过程时此类过程有时难以通过对欧拉角的控制来实现。要实现类似的功能可以改用四元数来控制物体的旋转也可以使用DOTween等动画插件。五.刨根问底网上关于前一部分内容的讲解很多但讲解程度几乎全都会止步于此并且最终认为【万向节锁】是一项深奥难解并且在实际应用中为害不小的隐患。但事实上万向节锁是一个完全可防可控的问题而不是什么玄学和不稳定性。笑为什么用欧拉角旋转物体会遇到欧拉角数值突然发生非平滑改变的情况其实大学阶段的高等数学教材已经给了我们答案——欧拉角体系可以看成是球面坐标系的变种。在数学里球坐标系Spherical coordinate system是一种利用球坐标(rθφ)表示一个点 p 在三维空间的位置的三维正交坐标系。下图显示了球坐标的几何意义原点到 P 点的距离 r 原点到点 P 的连线与 z轴正方向之间的天顶角仰角θ以及原点到点 P 的连线在 xOy平面的投影线与 x轴正方向之间的方位角φ。是不是和欧拉角极为相似不难看出球坐标系定义中的距离r与我们研究的旋转问题无关而与天顶角θ类似的是EulerAngles.x都表示俯仰维度与方位角φ对应的则是EulerAngles.y表示偏航维度。这两个维度可以确定一个物体的面朝方向也就是上图中的向量O--P。在以上两个维度的基础上欧拉角多出了一个维度【桶滚(EulerAngles.z)】这个维度的含义是物体在面朝方向确定前提下的自身旋转量。而传统数学中的射线O--P是没有自身旋转之说的故这个维度自然不会在球面坐标体系中出现。对于一个球心在原点半径为1的球球面上的绝大多数点都拥有一个确定的球坐标(这里忽略角的周期性)但有两个点例外这两个点正是球的上下两极。当天顶角θ 0或θ π时无论方位角φ怎么改变点P对应的都是球的上下两个极点。而当点P从某个极点恰好移开时P的方位角φ取值会从任意值突然变为确定值。很明显欧拉角中的EulerAngles.y也会发生相同的情况。这就是万向节锁或者说欧拉角的数值突变现象的真正来源。到这里我们就揭开了第4节中那个135°诡异旋转的谜团。当玩家的人物竖直望向天空时其方位角(脚尖指向的方向)是任意的而当玩家的视角不再竖直向上的瞬间其方位角立刻被限制成了一个固定值脚尖必须朝着西南方才能正常瞄准。玩家开始瞄准飞机之前其脚尖的偏转角EulerAngles.y是初始值0即脚尖朝北而开始注视飞机之后的偏转角将被强制约束到-135°即脚尖朝着西南方。因此玩家开始瞄准飞机的瞬间必须完成135度的偏转。这样我们终于可以得到使用欧拉角旋转物体时的最终结论对于一个Unity物体的某段旋转过程如果其面朝的方向在某一瞬间将会是【正上方】或【正下方】则在这一瞬间的前后该物体的欧拉角的y值可能会发生数值突变。其它情况下不会发生上述事件。大功告成通过这一结论和前面的内容你应当可以准确地判断出游戏中的某个旋转过程是否适合使用欧拉角进行运算以及需要的话如何运算。