最近耍了一波巫师3,突然想到能不能做个简单的第三人称视角的摄像机控制。不过这个摄像机控制目前还没有被卡视角的功能(就是那种根据地形调整摄像机距离)准备在后面几天实现卡视角功能。不过先来做个简单的第三人称视角控制吧。开冲!

在场景中用Cube搭个简易的小人(插个标志标明小人的前方向),以一个Plane为地面,用一个Cube与一个Sphere作为标识,容易辨别视角移动。

新建一个C#脚本CameraMove,拖动到MainCamera上
在脚本中定义如下变量:
//player info
private GameObject player;
private Transform playerTF;
//distance vector between player and camera
private Vector3 dirVector;
public float distance;
//mouse move
private float fMouseX;
private float fMouseY;
public float speed;
public float bottomLimitAngle;//the limit angle
private 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 assignment
player = GameObject.FindGameObjectWithTag ("Player");
playerTF = player.transform;
//**value initial**
//distanceVector initial
dirVector = Vector3.Normalize(playerTF.position - transform.position);
transform.position = playerTF.position + distance * (-dirVector);
//mouse move initial
fMouseX = 0;
fMouseY = 0;
bottomLimit = Mathf.Cos (bottomLimitAngle / 180 * Mathf.PI);
}初始化就不需要多说了,很容易理解,不过要注意把Player的Tag改为“Player”。
下面介绍Update部分的核心代码:
1.保持摄像机看向玩家。
//Update Camera
transform.LookAt (playerTF);2.获取鼠标变化量,并根据鼠标移动来改变视角角度。在视角移动之前先监测是否已经到达的最下方,如果已经到达,且用户仍试图向下移动视角(fMouse>0),则令fMouseY为0。下一步要进行视角移动与旋转,注意!视角的水平旋转与垂直旋转是不一样的!(下面会介绍),最后更新一下方向向量。
//Camera Move
fMouseX = Input.GetAxis ("Mouse X");
fMouseY = Input.GetAxis ("Mouse Y");
//avoid dithering
if (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 Horizontal
transform.RotateAround(playerTF.position ,playerTF.up, speed * fMouseX);
//Rotate Vertical
transform.RotateAround (playerTF.position, -VerticalRotateAxis(dirVector),speed * fMouseY);
//distance Control
dirVector = 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包在这里下载:https://download.csdn.net/download/qq_37394426/10914613
过几天后 小菜鸡我会更新根据地形变化来改变摄像机距离,希望和各位道友一起努力!!!
现在该来实现摄像机距离根据地形变化了,话不多说,上代码:
//distance Control
dirVector = 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是我在测试时使用的两个参数,为了避免摄像机贴着地面移动 及 在射线检测到地面的边界处出现突变导致摄像机进入地表中,可以根据需要改动。