最近耍了一波巫师3,突然想到能不能做个简单的第三人称视角的摄像机控制。不过这个摄像机控制目前还没有被卡视角的功能(就是那种根据地形调整摄像机距离)准备在后面几天实现卡视角功能。不过先来做个简单的第三人称视角控制吧。开冲!
在场景中用Cube搭个简易的小人(插个标志标明小人的前方向),以一个Plane为地面,用一个Cube与一个Sphere作为标识,容易辨别视角移动。
新建一个C#脚本CameraMove,拖动到MainCamera上
在脚本中定义如下变量:
//player infoprivate GameObject player;private Transform playerTF;//distance vector between player and cameraprivate Vector3 dirVector;public float distance;//mouse moveprivate float fMouseX;private float fMouseY;public float speed;public float bottomLimitAngle;//the limit angleprivate float bottomLimit;//the cos value
其中player就是玩家人物需要拖动赋值,playerTF是通过player得到的transform。dirVector是从player指向camera的一个方向向量。distance是指player与camera之间的距离,在Inspector中赋值。fMouseX与fMouseY是鼠标的移动量,speed是视角灵敏度(鼠标移动时,视角的变化速度),bottomLimitAngle是用于限制camera可以向下移动到什么程度,bottomLimit是通过bottomLimit得到的Cos值。
void Start () {//**System settings**Cursor.visible = false;Cursor.lockState = CursorLockMode.Locked;//**assignment initial**//player assignmentplayer = GameObject.FindGameObjectWithTag ("Player");playerTF = player.transform;//**value initial**//distanceVector initialdirVector = Vector3.Normalize(playerTF.position - transform.position);transform.position = playerTF.position + distance * (-dirVector);//mouse move initialfMouseX = 0;fMouseY = 0;bottomLimit = Mathf.Cos (bottomLimitAngle / 180 * Mathf.PI);}
初始化就不需要多说了,很容易理解,不过要注意把Player的Tag改为“Player”。
下面介绍Update部分的核心代码:
1.保持摄像机看向玩家。
//Update Cameratransform.LookAt (playerTF);
2.获取鼠标变化量,并根据鼠标移动来改变视角角度。在视角移动之前先监测是否已经到达的最下方,如果已经到达,且用户仍试图向下移动视角(fMouse>0),则令fMouseY为0。下一步要进行视角移动与旋转,注意!视角的水平旋转与垂直旋转是不一样的!(下面会介绍),最后更新一下方向向量。
//Camera MovefMouseX = Input.GetAxis ("Mouse X");fMouseY = Input.GetAxis ("Mouse Y");//avoid ditheringif (Vector3.Dot (-dirVector.normalized, -playerTF.up.normalized) > bottomLimit) {if (fMouseY > 0) {fMouseY = 0;};}//two types of parameters;//(axis,value)is rotate around the axis of the transform's position;// (position, axis, value)is rotate around the axis of the specific position;//Rotate Horizontaltransform.RotateAround(playerTF.position ,playerTF.up, speed * fMouseX);//Rotate Verticaltransform.RotateAround (playerTF.position, -VerticalRotateAxis(dirVector),speed * fMouseY);//distance ControldirVector = Vector3.Normalize(playerTF.position - transform.position);
那么视角的水平旋转与垂直旋转到底有什么不同呢?如下图。水平旋转是绕着playerTF.up即玩家的向上的轴旋转的。而垂直旋转是绕着玩家的中心旋转的,如果垂直旋转也像水平旋转一样操作,比如绕着playerTF.right来旋转,则会造成一种错误的现象:当我们不停地下移鼠标时,视角并没有移下去,而是一直在绕一个小圈。所以对于垂直旋转,还需要计算一个法线向量。
下面介绍计算垂直旋转的法线向量的方法。如上图中我们画出的垂直旋转的圆,它与xz平面相交处的切线一定是垂直于xz平面的,所以我们可以确定,这个法线向量一定是在xz平面中的。设为normal(a,0,c);与旋转另一个相关的属性是dirVector,因为旋转面就是由 dirVector绕normal 旋转一圈 形成的。设它为dirVector(x,y,z)。
则由normal与dirVector的点乘积为0得出ax+cz=0,得出a =(-z/x)*c,令a^2+c^2=1得出a^2 = z^2 / (x^2 + z^2);c^2 = x^2 / (x^2 + z^2)。
这样就确定了normal的a与c的绝对值是多少,那么怎么来确定其正负值呢,我们一致设定 在水平轴为x,纵轴为z的二维坐标系中(如下图) dirVector在normal的顺时针旋转90度的位置。由此得出:以下情况:
dir:x>0,z>0在第一象限。 normal:a<0,c>0在第二象限
dir:x>0,z<0在第四象限。 normal:a>0,c>0在第一象限
dir:x<0,z<0在第三象限。 normal:a>0,c<0在第四象限
dir:x<0,z>0在第二象限。 normal:a<0,c<0在第一象限
代码实现如下:
Vector3 VerticalRotateAxis(Vector3 dirVector){Vector3 player2Camera = -dirVector.normalized;float x = player2Camera.x;float z = player2Camera.z;Vector3 rotateAxis = Vector3.zero;rotateAxis.z = Mathf.Sqrt (x * x / (x * x + z * z));rotateAxis.x = Mathf.Sqrt (z * z / (x * x + z * z));if (x >= 0) {if (z >= 0) {rotateAxis.x = -rotateAxis.x;}} else {if (z >= 0) {rotateAxis.x = -rotateAxis.x;rotateAxis.z = -rotateAxis.z;} else {rotateAxis.z = -rotateAxis.z;}}Debug.Log (rotateAxis);return rotateAxis;}
这样我们就实现了鼠标移动控制第三人称视角的移动。源码加场景的unity包在这里下载:/download/qq_37394426/10914613
过几天后 小菜鸡我会更新根据地形变化来改变摄像机距离,希望和各位道友一起努力!!!
现在该来实现摄像机距离根据地形变化了,话不多说,上代码:
//distance ControldirVector = Vector3.Normalize(playerTF.position - transform.position);Ray cameraRay = new Ray(playerTF.position, -dirVector);RaycastHit hitinfo;if (Physics.Raycast (cameraRay, out hitinfo, distance*10/9, LayerMask.GetMask("Terrain"))) {actualDistance = hitinfo.distance * 9/10;transform.position = playerTF.position + actualDistance * (-dirVector);} else {actualDistance = distance;transform.position = playerTF.position + actualDistance * (-dirVector);}
更新Update中的distance Control模块中的代码,从玩家中心发射一条长度为 预设摄像机距离* 10/9 的射线,然后根据射线是否检测带地表,更新摄像机与玩家之间的实际距离actualDistance,乘10/9与乘9/10是我在测试时使用的两个参数,为了避免摄像机贴着地面移动 及 在射线检测到地面的边界处出现突变导致摄像机进入地表中,可以根据需要改动。