Unity6是傻逼

参考&插件

使用package:Cinemachine

参考教程:THIRD PERSON MOVEMENT in Unity 在油管上

B站链接:https://www.bilibili.com/video/BV1mp4y1S7jm/

摄像机类型:FreeLock Camera(Cinemachine内置的,装了package才能用,通过GameObject-Cinemachine-FreeLock Camera添加,这样会自动给Main Camera添加CinemachineBrain脚本,让它能自动选择激活的Cinemachine Camera)

实操

第三人称摄像机部分

FreeLock Camera三个轨道设置,跟着视频做就完了,拉相机的轨道高度和半径,调整Y轴高度看Camera进行调试

跟着视频做,基本都是拖滑条啥的

踩坑

FreeLock CameraStatus在启动后一直是StandbyMain Camera自动选择了TopRig摄像机(三个摄像机轨道之一),正确的应该是选择自己建立的FreeLock Camera

社区讨论:https://discussions.unity.com/t/cinemachinebrain-live-camera-is-top-rig-instead-of-free-look-camera/867843

手动点击FreeLock CameraStatus solo按钮可以正常运行

修改脚本参数properties,把优先级priority改成100就好了

初版WASD移动逻辑

public class ThirdPersonMovement : MonoBehaviour
{
    public CharacterController controller;
    public float moveSpeed = 5f;
    void Start()
    {
        controller = GetComponent<CharacterController>();

    }

    void Update()
    {
        float Horizontal = Input.GetAxisRaw("Horizontal");
        float Vertical = Input.GetAxisRaw("Vertical");
        Vector3 direction = new Vector3(Horizontal, 0, Vertical).normalized;// 归一化方向向量,确保移动方向的长度恒为 1,从而避免斜向移动速度比直线更快的问题。
        if (direction.magnitude >= 0.1f)//如果方向向量的长度大于等于 0.1,表示玩家正在输入移动方向。不能使用direction.magnitude!=0,因为direction是浮点数,这样做在开发中不安全
        {
            controller.Move(direction * moveSpeed * Time.deltaTime);//使用角色控制器移动
        }
    }
}

计算方向角

初版不能实现随移动方向改变角色方向,需要引入方向角

        if (direction.magnitude >= 0.1f)//如果方向向量的长度大于等于 0.1,表示玩家正在输入移动方向。不能使用direction.magnitude!=0,因为direction是浮点数,这样做在开发中不安全
        {
            float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;// 计算目标角度,使用反正切函数计算出方向向量的角度
            transform.rotation = Quaternion.Euler(0, targetAngle, 0);//将角色的旋转设置为目标角度
            controller.Move(direction * moveSpeed * Time.deltaTime);//使用角色控制器移动
        }

添加转向平滑

现在主角可以根据移动方向改变头部朝向了,但角度改变是瞬时的,添加平滑

public float turnSmoothTime = 0.1f; // 旋转平滑时间 增加一个变量
    void Update()
    {
        float Horizontal = Input.GetAxisRaw("Horizontal");
        float Vertical = Input.GetAxisRaw("Vertical");
        Vector3 direction = new Vector3(Horizontal, 0, Vertical).normalized;// 归一化方向向量,确保移动方向的长度恒为 1,从而避免斜向移动速度比直线更快的问题。
        if (direction.magnitude >= 0.1f)// 如果方向向量的长度大于等于 0.1,表示玩家正在输入移动方向。不能使用direction.magnitude!=0,因为direction是浮点数,这样做在开发中不安全
        {
            float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;// 计算目标角度,使用反正切函数计算出方向向量的角度
            float angle = Mathf.SmoothDampAngle(
                transform.eulerAngles.y,   // 当前角度
                targetAngle,               // 目标角度
                ref turnSmoothVelocity,    // 当前“速度”参考值(由方法内部更新)
                turnSmoothTime             // 平滑时间
            );

            transform.rotation = Quaternion.Euler(0, angle, 0);// 将角色的y轴旋转设置为目标角度
            controller.Move(direction * moveSpeed * Time.deltaTime);// 使用角色控制器移动
        }
    }

ref turnSmoothVelocity讲解

帧数当前角度目标角度turnSmoothVelocity说明
第1帧30°90°0.0开始旋转
第2帧45°90°15.0正在加速转向
第3帧65°90°10.0慢慢减速
第4帧80°90°5.0快到目标
第5帧90°90°0.0到达目标,速度归零

相对摄像机方向改变移动方向

现在实现了平滑转动,但WASD的移动方向是以模型为基准的,摄像机转180°后操作会变成相反的,因此要加入摄像机方向。

public Transform cam;//引入摄像头

记得把Main Camera拖进Cam,不然运行会报错

拖成了·FreeLock Camera会让后面获取的cam旋转角度一直为0

模型旋转部分

float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg + cam.eulerAngles.y;// 计算目标角度,使用反正切函数计算出方向向量的角度,再加上摄像头的y轴旋转角度,得到角色需要朝向的目标角度。

模型移动部分

Vector3 moveDir = Quaternion.Euler(0, targetAngle, 0) * Vector3.forward;// 计算移动方向,使用四元数旋转将目标角度转换为一个新的方向向量
controller.Move(moveDir.normalized * moveSpeed * Time.deltaTime);// 使用角色控制器移动

Quaternion.Euler(0, targetAngle, 0)是四元数,是旋转量, 与Vector3.forward 相乘,转换为向量。

moveDir.normalized 同样归一化,确保移动方向是个单位向量

添加防穿模

CinemachineFreeLock脚本最下方找到Add Extension

添加Cinemachine Collider

添加对Ground类型Layer的碰撞检测(提前给场景内的模型添加好Layer类型标记)

添加对Player标签的过滤,否则摄像头会卡在Player模型内部,同样提前给玩家模型添加Player标记

装逼让你飞起来

添加跳跃

添加变量

    // --- 2. 跳跃部分 ---
    public float jumpHeight = 2f; // 跳跃高度
    public float gravity = -9.81f; // 重力
    private float verticalVelocity; // 垂直速度
    Vector3 moveDir; // 移动方向
    public float acceleration = 10f; // 平滑减速

update()中添加

        // --- 2. 跳跃部分 ---
        if (controller.isGrounded)
        {
            if (verticalVelocity < 0) verticalVelocity = -2f; // 稍微压住贴地,避免在地面上抖动
            if (Input.GetButtonDown("Jump"))
            {
                verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity); // 设置初始跳跃速度
            }
        }
        else
        {
            verticalVelocity += gravity * Time.deltaTime; // 累加重力(加速度)
        }

合并水平和竖直速度

试了试分开写两个脚本,会因为它们都在用 CharacterController.Move(),但不会自动合并彼此的移动向量。导致移动的时候没法跳跃。为了避免以后开发变得麻烦,现在就合进去。

        // --- 2. 跳跃部分 ---
        if (controller.isGrounded)
        {
            if (verticalVelocity < 0) verticalVelocity = -2f; // 稍微压住贴地,避免在地面上抖动
            if (Input.GetButtonDown("Jump"))
            {
                verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity); // 设置初始跳跃速度
            }
        }
        else
        {
            verticalVelocity += gravity * Time.deltaTime; // 累加重力(加速度)
        }

        // --- 3. 合并水平+垂直移动 ---
        Vector3 finalMove = moveDir * moveSpeed;
        finalMove.y = verticalVelocity;
        controller.Move(finalMove * Time.deltaTime);

踩坑

为了最后合并,把水平移动部分的moveDir从局部变量提到了类里,但导致moveDir会保持上一帧的状态,按下按键就一直移动。

需要给if (direction.magnitude >= 0.1f)添加一个else逻辑,没有按下水平方向键时让水平速度快速衰减到0

        else
        {
            // 缓慢停止
            moveDir = Vector3.Lerp(moveDir, Vector3.zero, acceleration * Time.deltaTime);
        }

moveDir需要在创建的时候就归一化,而不是合并时归一化,因为衰减过程中moveDir的长度不应该为1

            moveDir = Quaternion.Euler(0, targetAngle, 0) * Vector3.forward;// 计算移动方向,使用四元数旋转将目标角度转换为一个新的方向向量
            moveDir = moveDir.normalized; // 归一化移动方向向量,确保其长度恒为 1

controller.isGrounded地面检测失灵

当把主体换为了导入的猫娘模型后,出现了跳跃键失灵的问题,通过Debug.log(controller.isGrounded)发现地面检测存在问题

Character ControllerMin Move Distance从0.001改为0得到解决

完整代码

完整的ThirdPersonMovement.cs


using UnityEngine;

public class ThirdPersonMovement : MonoBehaviour
{
    // --- 1. 水平部分 ---
    public CharacterController controller;
    public float moveSpeed = 5f;
    public float turnSmoothTime = 0.05f; // 旋转平滑时间
    float turnSmoothVelocity; // 旋转平滑速度
    public Transform cam; // 引入摄像头


    // --- 2. 跳跃部分 ---
    public float jumpHeight = 2f; // 跳跃高度
    public float gravity = -9.81f; // 重力
    private float verticalVelocity; // 垂直速度
    private Vector3 moveDir; // 移动方向
    public float acceleration = 10f; // 平滑减速
    void Awake()
    {
        controller = GetComponent<CharacterController>();
        Cursor.lockState = CursorLockMode.Locked;// 默认锁定鼠标
        Cursor.visible = false;// 默认光标隐藏

    }
    void Update()
    {
        // --- 1. 水平部分 ---
        float Horizontal = Input.GetAxisRaw("Horizontal");
        float Vertical = Input.GetAxisRaw("Vertical");
        Vector3 direction = new Vector3(Horizontal, 0, Vertical).normalized;// 归一化方向向量,确保移动方向的长度恒为 1,从而避免斜向移动速度比直线更快的问题。
        if (direction.magnitude >= 0.1f)// 如果方向向量的长度大于等于 0.1,表示玩家正在输入移动方向。不能使用direction.magnitude!=0,因为direction是浮点数,这样做在开发中不安全
        {
            float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg + cam.eulerAngles.y;// 计算目标角度,使用反正切函数计算出方向向量的角度,再加上摄像头的y轴旋转角度,得到角色需要朝向的目标角度。
            float angle = Mathf.SmoothDampAngle(
                transform.eulerAngles.y,   // 当前角度
                targetAngle,               // 目标角度
                ref turnSmoothVelocity,    // 当前“速度”参考值(由方法内部更新)
                turnSmoothTime             // 平滑时间
            );

            transform.rotation = Quaternion.Euler(0, angle, 0);// 将角色的y轴旋转设置为目标角度
            moveDir = Quaternion.Euler(0, targetAngle, 0) * Vector3.forward;// 计算移动方向,使用四元数旋转将目标角度转换为一个新的方向向量
            moveDir = moveDir.normalized; // 归一化移动方向向量,确保其长度恒为 1
        }
        else
        {
            // 缓慢停止
            moveDir = Vector3.Lerp(moveDir, Vector3.zero, acceleration * Time.deltaTime);
        }
        // --- 2. 跳跃部分 ---
        if (controller.isGrounded)
        {
            if (verticalVelocity < 0) verticalVelocity = -2f; // 稍微压住贴地,避免在地面上抖动
            if (Input.GetButtonDown("Jump"))
            {
                verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity); // 设置初始跳跃速度
            }
        }
        else
        {
            verticalVelocity += gravity * Time.deltaTime; // 累加重力(加速度)
        }

        // --- 3. 合并水平+垂直移动 ---
        Vector3 finalMove = moveDir * moveSpeed;
        finalMove.y = verticalVelocity;
        controller.Move(finalMove * Time.deltaTime);

        // ESC 切换鼠标锁定
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            Cursor.lockState = Cursor.lockState == CursorLockMode.Locked ? CursorLockMode.None : CursorLockMode.Locked;
            Cursor.visible = !Cursor.visible;
        }
    }
}

模型导入

画的什么玩意,崩的要死(

答案是进入Material的Inspector面板,修改Outline Thickness参数,拉小点就行了

调整后

幾重にも辛酸を舐め、七難八苦を越え、艱難辛苦の果て、満願成就に至れ
最后更新于 2025-04-14