Unity实现第三人称视角

最近耍了一波巫师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是我在测试时使用的两个参数,为了避免摄像机贴着地面移动  及  在射线检测到地面的边界处出现突变导致摄像机进入地表中,可以根据需要改动。


版权声明:本文为qq_37394426原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。