一、搭建简单场景

  1. 新建一个场景,放置一个plane作为地板,尺寸自定
  2. 创建一个新的material挂在plane上
  3. 再地板上放置一些cube,主要是为了在测试时作为参照物

unity预制第一视角mouselook unity第一人称视角移动脚本_方向键

二、创建主角物体

  1. 创建一个capsule,代表一个角色
  2. 创建一个cube作为capsule的子物体,将立方体放在capsule的“脸”部。挂上材质
  3. 拖曳场景列表中的著摄像机,让著摄像机变成capsule的子物体
  4. 将著摄像机的位置归0,然后微调著摄像机的位置到角色的脸部

unity预制第一视角mouselook unity第一人称视角移动脚本_鼠标指针_02

 三、编写控制脚本——移动部分

  1.  创建脚本FPSCharacter,将其挂载于capsule上
  2. 编写脚本内容。脚本内容是重点,下面分步介绍

 角色的控制分为两大块:角色移动和摄像机旋转

角色移动方面,玩家可以按方向键进行前后左右平移。问题是;玩家按上方向键时,对应哪个方向;玩家按右方向键时,又对应哪个方向。

一般玩家本身具有前方向量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);
        }
    }
}