第一人称角色控制器

最近在折腾Unity的第一人称角色控制器,所谓第一人称角色控制器就是指像FPS游戏那样使用第一人称视角,锁定鼠标控制头的转动观察周围

preview.png

FPCC.gif

推荐使用CharacterController而不是Rigidbody,虽然刚体组件创建起来更简单,但是CharacterController拥有更多实用的功能,比如处理步距落差,斜坡行走,防卡墙里等等优点。

开始

我们准备两个组件,一个负责眼睛的转动(视野的旋转)、一个负责身体的物理运算(移动、跳跃等)

首先是控制画面旋转的脚本 MouseLook.cs

using UnityEngine;

public class MouseLook : MonoBehaviour
{
    public float mouseSensitivity = 1f;

    // Update is called once per frame
    void Update()
    {
        RotateView();
    }

    private void RotateView()
    {
        // 首先获取鼠标的deltaX 和 deltaY,表示相对于上一帧移动了多少
        float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime * 10;
        float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime * 10;

        Transform character = transform; // 身体的Transform组件
        Transform camera = Camera.main.transform; // 眼睛的Transform组件
        
        // 将鼠标deltaX和deltaY转换成对应的旋转值并对各个物体进行旋转
        // (注:两个Quaternion相乘表示旋转角度相加)
        // 这里需要注意当进行上下旋转(pitch)时,由相机(眼睛)来承担旋转任务
        // 左右旋转(yaw)时由身体来承担旋转,这和FPS游戏中是一样的
        // y轴旋转量由character承担,x轴旋转量由camera承担
        character.localRotation *= Quaternion.Euler(0f, mouseX, 0f);
        camera.localRotation *= Quaternion.Euler(-mouseY, 0f, 0f);

        // 最后限制一下pitch旋转角,避免超过-90和+90,如果超过了会出现下图的情形
        camera.localRotation = ClampRotationAroundXAxis(camera.localRotation);
    }

    // 这里是参考了Unity官方的一个例子,这里他使用四元数运算把x轴角度限制在-90, 90之间
    // 避免过度旋转导致可以看到身后的东西,具体的原理不做讲解,只需要知道方法的功能即可
    // Unity内部使用四元数表示旋转,四元数相比欧拉角有许多优点,但是运算起来也更复杂
    Quaternion ClampRotationAroundXAxis(Quaternion q)
    {
        q.x /= q.w;
        q.y /= q.w;
        q.z /= q.w;
        q.w = 1.0f;

        float angleX = 2.0f * Mathf.Rad2Deg * Mathf.Atan (q.x);

        angleX = Mathf.Clamp (angleX, -90, 90);

        q.x = Mathf.Tan (0.5f * Mathf.Deg2Rad * angleX);

        return q;
    }
}

接着是控制物理运动的脚本Move.cs


using UnityEngine;

public class Move : MonoBehaviour
{
    public float moveSpeed = 1f;
    public float jumpHeight = 1;
    public float gravityMultiplier = 1f;

    private CharacterController characterController;
    private bool jump = false;
    private Vector3 velocity;

    void Start()
    {
        characterController = GetComponent<CharacterController>();
    }

    void Update()
    {
        // 当检测到Space键按下时,设置jump为true
        if (!jump && Input.GetKeyDown(KeyCode.Space))
        {
            jump = true;
        }
    }

    void FixedUpdate()
    {
        // 获取前后左右的delta值,按住wasd触发
        // GetAxis()会返回-1, 0 , 1表示按下了左,没有按任何键,右这三种情况
        // 上下方向也是一致的  上键: 1, 不按键:0, 下键:-1
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");

        // 计算出移动的方向,再乘以速度
        Vector3 move = transform.forward * z + transform.right * x;
        velocity.x = move.x * moveSpeed;
        velocity.z = move.z * moveSpeed;

        // 模拟重力,如果物体不在地上,则会每帧加上一点重力加速度,使其下落速度不断变快
        // 知道于地面接触
        if (!characterController.isGrounded)
        {
            // 这里的Physics.gravity会返回(0, -9.81, 0)这个值
            // 这里参考了重力公式
            velocity += Physics.gravity / 2 * Time.deltaTime * Time.deltaTime * gravityMultiplier;
        }else{
            // 当在地面上时给一个很小的力保持与地面的紧密接触
            velocity.y = -0.01f;
        }

        // 跳跃,当检测到Space键被按下时
        if (jump)
        {
            jump = false;
            
            // 给一个向上的力(只能给一帧),重力参考了跳跃公式
            velocity.y = Mathf.Sqrt(jumpHeight * -2 * -Physics.gravity.magnitude);
        }

        // 最后将这个deltaVelocity交给characterController进行移动
        characterController.Move(velocity);
    }
}

重力

这里使用物体自由落体瞬时速度公式来模拟瞬时重力(写在FixedUpdate里对应到每一帧)

deltaY表示瞬时下落的速度,g取-9.81,t表示下落的持续时间

ΔY=12g×t2\Delta Y=\frac{1}{2}g \times t^2

跳跃

这里使用物体竖直上抛位移速度公式变换而来

v=h×2×gv=\sqrt[]{h \times -2 \times g}

v表示跳跃到h高度所需要的速度,g取-9.81,最后加上角色的其它运动量即可

最后

很多思路参考了视频 https://www.bilibili.com/video/BV1nE411b7X8