一、搭建简单场景
- 新建一个场景,放置一个plane作为地板,尺寸自定
- 创建一个新的material挂在plane上
- 再地板上放置一些cube,主要是为了在测试时作为参照物
二、创建主角物体
- 创建一个capsule,代表一个角色
- 创建一个cube作为capsule的子物体,将立方体放在capsule的“脸”部。挂上材质
- 拖曳场景列表中的著摄像机,让著摄像机变成capsule的子物体
- 将著摄像机的位置归0,然后微调著摄像机的位置到角色的脸部
三、编写控制脚本——移动部分
- 创建脚本FPSCharacter,将其挂载于capsule上
- 编写脚本内容。脚本内容是重点,下面分步介绍
角色的控制分为两大块:角色移动和摄像机旋转
角色移动方面,玩家可以按方向键进行前后左右平移。问题是;玩家按上方向键时,对应哪个方向;玩家按右方向键时,又对应哪个方向。
一般玩家本身具有前方向量transform.forward和右方向量transform.right。右方向量与右方向的移动直接对应;前方向量比较麻烦,因为存在抬头,低头的情况。如果玩家在抬头看天时直接向前方走,就会得到向天上飞的结果。
解决方案是得到前方向量后,略加修改,去除前方向量的y轴分量,这样就让前方向量保持水平了。整段代码在文章末尾,计算移动的部分代码如下
void Move()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 fwd = transform.forward;
Vector3 f = new Vector3(fwd.x, 0, fwd.z).normalized;
Vector3 r = transform.right;
Vector3 move = f * z + r * x;
transform.position += move * speed * Time.deltaTime;
}
四、编写控制脚本——旋转部分
接下来是旋转镜头部分。由于著摄像机已经挂在角色身上,因此直接旋转角色,镜头就会跟着旋转。其旋转的关键代码如下
void MouseLook()
{
float mx = Input.GetAxis("Mouse X");
float my = -Input.GetAxis("Mouse Y");
Quaternion qx = Quaternion.Euler(0, mx, 0);
Quaternion qy = Quaternion.Euler(my, 0, 0);
transform.rotation = qx * transform.rotation;
transform.rotation = transform.rotation*qy;
}
鼠标移动旋转物体的思路并不简单,但是巧妙利用四元数之后,代码出乎意料的简短。首先,“Mouse X”和“Mouse Y”是两个特殊的输入轴,分别对应的是鼠标的横向移动和纵向移动(不是鼠标位置,而是鼠标位置的变化量)
鼠标横向移动对应的是镜头水平旋转,即沿y轴旋转;鼠标纵向移动对应的是镜头俯仰旋转。因此可以将鼠标的横向、纵向移动当作欧拉角,并转化成四元数,这样就得到了两个”小旋转“,分别叫做qx和qy。
之后只要用四元数乘法将小旋转应用于角色当前朝向,问题就解决了。难点在于,镜头水品那个旋转实际上是沿世界坐标系的y轴旋转,而镜头俯仰旋转则是围绕局部坐标系的x轴旋转。
最后,因为人们在低头、抬头动作时,不能超过90°看到自己的后面,所以要对俯仰角度作出限制,因此需要在MouseLook()函数尾部补上以下一段逻辑。
float angle = transform.eulerAngles.x;
if (angle > 180) { angle -= 360; }
if (angle <- 180) { angle+= 360; }
if(angle>80)
{
Debug.Log("A" + transform.eulerAngles.x);
transform.eulerAngles = new Vector3(80, transform.eulerAngles.y, 0);
}
if (angle <-80)
{
Debug.Log("A" + transform.eulerAngles.x);
transform.eulerAngles = new Vector3(-80, transform.eulerAngles.y, 0);
}
五、隐藏并锁定鼠标指针
测试时,会发现鼠标指针会影响游戏体验,如果单击到Game窗口之外的区域,Game窗口就会失去焦点。解决方法是隐藏鼠标指针,并把鼠标指针锁定到屏幕中央,代码如下
void Start()
{
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
}
在Unity编译器中,只需要按下Esc键就会显示出鼠标指针了,而在实际游戏开发者能够,别忘了在合适的时机将鼠标指针崇信县属出来,并取消锁定。如原神中就是摁Alt键取消锁定的。
六、完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FPSCharachter : MonoBehaviour
{
public float speed = 5f;
void Start()
{
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
}
void Update()
{
Move();
MouseLook();
}
void Move()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 fwd = transform.forward;
Vector3 f = new Vector3(fwd.x, 0, fwd.z).normalized;
Vector3 r = transform.right;
Vector3 move = f * z + r * x;
transform.position += move * speed * Time.deltaTime;
}
void MouseLook()
{
float mx = Input.GetAxis("Mouse X");
float my = -Input.GetAxis("Mouse Y");
Quaternion qx = Quaternion.Euler(0, mx, 0);
Quaternion qy = Quaternion.Euler(my, 0, 0);
transform.rotation = qx * transform.rotation;
transform.rotation = transform.rotation * qy;
float angle = transform.eulerAngles.x;
if (angle > 180) { angle -= 360; }
if (angle <- 180) { angle+= 360; }
if(angle>80)
{
Debug.Log("A" + transform.eulerAngles.x);
transform.eulerAngles = new Vector3(80, transform.eulerAngles.y, 0);
}
if (angle <-80)
{
Debug.Log("A" + transform.eulerAngles.x);
transform.eulerAngles = new Vector3(-80, transform.eulerAngles.y, 0);
}
}
}