【unity实战】InputSystem+Cinemachine+Animator实现一个第三人称控制器,类PUBG吃鸡游戏的角色控制器

最终效果

在这里插入图片描述

前言

本文我将带大家从零手搓一个带动画的第一人称和第三人称unity人物控制器,不借助任何外在的第三方插件。主要使用到了新输入系统InputSystem+虚拟相机Cinemachine+Animator

其实第一人称和第三人称unity人物控制器我之前做过不少,但是都没有带动画效果。

注意本文不是新手向的,如果你还不了解如何使用unity可以参考【unity游戏开发入门到精通——通用篇】,可以等有一定基础了再来看本篇文章。

一、前期准备

1、创建项目

如果你不知道如何下载unity6可以参考:【unity小技巧】国内Unity6下载安装和一些Unity6新功能使用介绍

这里我直接使用最新的Unity6版本,当然,如果你想使用其他版本也是可以的。
在这里插入图片描述

2、选择在进入播放模式时启动哪些重新加载选项

为了更快地进入播放模式,节约我们的时间,我们可以禁用 scene 或域重新加载。这里我在项目设置里将进入播放模式设置设置成Reload Scene Only (仅重新加载场景)

域重新加载是指编辑器在开始播放模式之前重置脚本状态。场景重新加载是指编辑器在播放模式开始之前销毁所有场景 GameObjects 并从磁盘重新加载场景。具体介绍可以参考官方文档说明:禁用域和场景重新加载的详细信息
在这里插入图片描述

3、搭建基本场景原型

如果你还不知道如何使用ProBuilder,可以参考:【推荐100个unity插件之28】在unity中建模,使用Unity制作基础模型,搭建场景原型——ProBuilder的使用

这里我使用unity自带的ProBuilder插件搭建基本场景原型,效果就大家自由发挥。当然你也可以自己使用其他方式,比如使用不同的3D模型进行拼接。
在这里插入图片描述

4、拖入3D角色模型

这里随便找个3D人物模型即可,我这里导入的是:そふぃーら
在这里插入图片描述

如果你不知道vrm模型如何在unity中使用,可以参考:【推荐100个unity插件之25】在unity中直接使用VRM模型——URPUniVrm插件的使用

这个模型其实不算特别好,衣服头发经常穿模,VRM插件也经常爆警告,看着很难受。所以到后面开发我更换成了模型:【善良米塔/瘋狂米塔 vroid版本/vrm】,如果中途发现我的突然换了隔模型,千万不用惊讶。
在这里插入图片描述

这里推荐另一种方式使用VRM模型,也就是将VRM模型转成FBX模型再到unity中使用,可以参考:【blender小技巧】使用Blender将VRM或者其他模型转化为FBX模型,并在unity使用,导出带贴图的FBX模型,贴图材质问题修复

二、真实开始

1、输入控制

对InputSystem还不熟悉的小伙伴可以先看:【unity游戏开发——InputSystem】

using UnityEngine;
using UnityEngine.InputSystem;

public class InputController : MonoBehaviour {
    PlayerInput playerInput;
    public Vector2 moveVector2;
    public bool isSprint;
    public bool isCrouch;
    public bool isJump;
    void Awake()
    {
        playerInput = GetComponent<PlayerInput>();
    }

    private void OnEnable() {
        // 输入触发事件,任何输入都会触发该事件
        playerInput.onActionTriggered += OnActionTrigger;
    }

    void OnDisable() {
        playerInput.onActionTriggered -= OnActionTrigger;
    }

    private void OnActionTrigger(InputAction.CallbackContext context)
    {
        switch (context.action.name)
        {
            case "Move":
                // 获取输入的方向值(Vector2类型)
                moveVector2 = context.ReadValue<Vector2>();
                // Debug.Log("Move: " + moveVector2);//打印
                break;
            case "Sprint":
                //按下返回true,松开返回false
                isSprint = context.action.IsPressed();
                break;
            case "Crouch":
                isCrouch = !isCrouch;
                break;
            case "Jump":
                isJump = context.action.IsPressed();
                break;
        }
    }
}

挂载脚本
在这里插入图片描述
结果,键盘wasd输入打印值
在这里插入图片描述

2、使用Character Controller实现人物移动

这里我们使用Character Controller实现第三人称控制,如果你还不知道具体如何使用CharacterController,可以参考:

给角色添加Character Controller,并配置合适的参数
在这里插入图片描述

新增PlayerController脚本,控制角色移动

using UnityEngine;

[RequireComponent(typeof(Animator), typeof(CharacterController))]
public class PlayerController : MonoBehaviour
{
    CharacterController characterController;

    [Header("输入")]
    InputController inputController;
    Vector3 direction;//输入创建移动方向向量
    
    [Header("移动")]
    public float speed = 5f; // 玩家移动的速度
    Vector3 moveDirection;//角色的移动方向

    void Awake()
    {
        characterController = GetComponent<CharacterController>();
        inputController = GetComponent<InputController>();
    }

    void Update()
    {
        SetPlayerMove();
    }

    //处理角色移动
    void SetPlayerMove(){
        // 根据输入创建移动方向向量
        direction = new Vector3(inputController.moveVector2.x, 0, inputController.moveVector2.y);
        
        //将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向
        moveDirection  = transform.TransformDirection(direction);
        characterController.Move(moveDirection.normalized * speed * Time.deltaTime);
    }
}

挂载脚本
在这里插入图片描述

效果
在这里插入图片描述

3、添加虚拟相机Cinemachine跟随

如果你还不知道如何使用Cinemachine,可以参考:【推荐100个unity插件之10】Unity最全的最详细的Cinemachine(虚拟相机系统)介绍,详细案例讲解,快速上手

新增虚拟相机
在这里插入图片描述
配置参数
在这里插入图片描述

效果
在这里插入图片描述

4、人物跟随相机旋转

因为我们要做的是FPS第三人称视角,所以我们需要实现人物一直面向相机

代码如下

// 处理角色旋转
void SetPlayerRotation()
{
    // 获取相机的旋转(只考虑 Y 轴的旋转)
    float yRotation = mainCamera.transform.eulerAngles.y;
    // 计算角色的目标旋转
    Quaternion targetRotation = Quaternion.Euler(0, yRotation, 0);
    //进行平滑过渡到目标旋转(如果需要平滑效果)
    transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turnSpeed * Time.deltaTime);
}

效果
在这里插入图片描述

5、导入动画

这里所有的动画,都可以在mixamo网站免费获取,如果你还不懂如何使用它,可以参考:【游戏资源】获取免费开源的人物模型,为obj fbx人物模型绑定人形骨骼和人形动画,并导入到unity中使用——mixamo的使用介绍

这里为了节省时间,我就不去mixamo网站一个个下载了,这里我直接使用之前unity免费的动画包:Basic Motions FREE
在这里插入图片描述

6、添加BlendTree混合动画

如果你还了解Blend Tree动画混合树的知识,可以先去查看:【unity游戏开发入门到精通——动画篇】动画混合(Blend Tree)

添加行为树,配置动画和参数。当然,在这里配置参数的做法有很多种。

1. 配置参数方式一

我们可以把移动阈值设置成1,奔跑设置成2。注意这里斜着走我们要设置成0.707,因为我们会发现斜着输入最大值就是-0.70到0.707,这涉及三角函数的计算,斜边长为1的等腰直角三角形的两边长度就是1/√2≈0.707
在这里插入图片描述

2. 配置参数方式二

点击xy按动画的xz方向上的速度自动分配阈值
在这里插入图片描述
然后我们可以参考Unity官方在标准资源包里的做法。

  • 我们把待机动画阈值设置成0。
    在这里插入图片描述
  • 把前后运动的动画x方向阈值设置成0,左右运动的动画y轴阈值设置0,另一个方向的阈值设置成和向前行走一样。
    在这里插入图片描述
  • 斜方向阈值都设置成向前行走的阈值*0.707,也就是+-1.066*0.707=0.753662,为什么是0.707前面已经解释了
    在这里插入图片描述
  • 奔跑动画同理
    在这里插入图片描述

注意:这里如果你对动画要求比较高,其实我们还可以进行更加精细的调整,比如修改缩放,让所有的行走奔跑动画移动速度都保持在一个值。这里我就不做调整了,具体的方式我在RootMotion动画混合树的知识的文章里有提到:【unity实战】OnAnimatorMove+Root Motion+BlendTree+CharacterController解决Animator动画和位移不同步问题,完美实现移动动画动作匹配

7、添加Animator控制代码

修改PlayerController

[Header("移动")]
public float currentSpeed = 0f;//当前速度
[SerializeField] private float walkSpeedValue = 1.066f;//行走速度
[SerializeField] private float runSpeedValue = 4.067f;//奔跑速度
Vector3 moveDirection;//角色的移动方向

//处理角色移动
void SetPlayerMove(){
    if(inputController.moveVector2.magnitude > 0.1f){
      if(inputController.isSprint){
          currentSpeed = runSpeedValue;
       }else{
          currentSpeed = walkSpeedValue;       
       }
     }else{
         currentSpeed = 0f;
     }

    // 根据输入创建移动方向向量
    direction = new Vector3(inputController.moveVector2.x, 0, inputController.moveVector2.y);

    //将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向
    moveDirection  = transform.TransformDirection(direction);
    characterController.Move(moveDirection.normalized * currentSpeed * Time.deltaTime);
}

新增AnimatorController 代码,控制动画切换

using UnityEngine;

public class AnimatorController : MonoBehaviour {
    InputController inputController;
    Animator animator;
    PlayerController playerController;

    private static int inputX = Animator.StringToHash("inputX");
    private static int inputY = Animator.StringToHash("inputY");

    void Awake()
    {
        inputController = GetComponent<InputController>();
        animator = GetComponent<Animator>();
        playerController = GetComponent<PlayerController>();
    }

    void Update()
    {
        UpdateAnimatorState();
    }

    private void UpdateAnimatorState(){
        animator.SetFloat(inputX, inputController.moveVector2.x * playerController.currentSpeed);
        animator.SetFloat(inputY, inputController.moveVector2.y * playerController.currentSpeed); 
    }
}

使用Animator.StringToHash哈希处理字符串
使用哈希的主要原因是提高效率。状态机中的标签和参数通常用字符串表示,但字符串比较在性能上可能较慢。通过使用哈希值,可以将字符串转换为唯一的整数值,进行更快速的比较操作。这避免了每帧都进行昂贵的字符串比较操作,从而提升了状态机查询的性能。

效果
在这里插入图片描述

8、添加动画过渡

虽然现在看到一切正常,但是其实混合树实际上并没有进行混合,它只是在不同的动画之间即时切换,这是因为你可以看到输入x和输入y参数立即被设置为新值,我们需要做的是缓慢地增加它们。

1 方法一 使用Vector3.Lerp进行平滑插值

我们可以在设置输入值前,使用Vector3.Lerp进行平滑插值,修改AnimatorController代码

[SerializeField] private float smoothTime = 4f;//平滑插值速度
Vector2 currentBlendInput = Vector3.zero;//当前移动输入

private void UpdateAnimatorState(){
	//平滑插值
    currentBlendInput = Vector2.Lerp(currentBlendInput, inputController.moveVector2, smoothTime * Time.deltaTime);
    animator.SetFloat(inputX, currentBlendInput.x);
    animator.SetFloat(inputY, currentBlendInput.y);
}

2 方法二 使用animator.SetFloat进行平滑插值

animator.SetFloat方法其实自带了平滑插值方法,我们只需要新增两个参数即可。

[SerializeField] private float smoothTime = 0.1f;//平滑插值速度

private void UpdateAnimatorState(){
    animator.SetFloat(inputX, inputController.moveVector2.x * playerController.currentSpeed, smoothTime, Time.deltaTime);
    animator.SetFloat(inputY, inputController.moveVector2.y * playerController.currentSpeed, smoothTime, Time.deltaTime); 
}

3 效果

在这里插入图片描述

9、使用Root Motion实现动画和位移同步

现在我们的动画切换虽然进行混合了,但是很明显我们人物移动出现了滑步的想象或者说很容易出现滑步现象。因为我们还没有对人物的速度进行限制。当然,你可以选择同样对速度进行平滑,然后解决这个滑步问题。

但是我这里打算使用Root Motion,要完全解决人物移动的滑步问题,使用Root Motion一定是最好的方式。

如果你还了解Root Motion动画混合树的知识,可以先去查看:【unity实战】OnAnimatorMove+Root Motion+Blend Tree+CharacterController解决Animator动画和位移不同步问题,完美实现移动动画动作匹配

修改,取消之前的SetPlayerMove方法,新增OnAnimatorMove方法代替

private void OnAnimatorMove()
{
    if(inputController.moveVector2.magnitude > 0.1f){
        if(inputController.isSprint){
           currentSpeed = runSpeedValue;
        }else{
           currentSpeed = walkSpeedValue;         
        }
    }else{
        currentSpeed = 0f;
    }

    Vector3 deltaPosition = animator.deltaPosition;//当前帧相对于上一帧的位移增量
    // 移动 CharacterController
    characterController.Move(deltaPosition);
}

开启animator动画物理
在这里插入图片描述
效果,现在使用动画根位移控制角色移动,实现了角色动画和位移绝对同步
在这里插入图片描述

10、添加下蹲移动

实现下蹲移动其实和前面的操作基本一样,新增一个下蹲bleedTree,再做切换即可,这里不浪费时间再介绍一次了
在这里插入图片描述
在这里插入图片描述
直接看看最终效果
在这里插入图片描述

11、添加重力和跳跃

修改PlayerController

[Header("跳跃")]
public float jumpHeight = 2f;//跳跃高度
[SerializeField] private bool isGround;//地面检测
[SerializeField] private float Gravity = -39.8f;//重力
private Vector3 verticalVelocity; // 垂直方向上的速度

void Update()
{
    SetPlayerRotation();
    SetPlayerGravity();
    SetPlayerJump();
}

// 处理角色重力
void SetPlayerGravity()
{
    // 应用重力
    characterController.Move(verticalVelocity * Time.deltaTime);

    //地面检测
    isGround = characterController.isGrounded;
    if (isGround)
    {
        // 当isGrounded为true时,将Y轴方向的速度设为了一个较小的负值,这是为了确保能稳定触碰地面,避免isGrounded误判为false
        verticalVelocity = new Vector3(0, -2f, 0);
    }
    else
    {
        // 更新重力影响的速度
        verticalVelocity.y += Gravity * Time.deltaTime;
    }
}

// 处理角色跳跃
void SetPlayerJump()
{
    // 跳跃处理
    if (isGround && inputController.isJump)
    {
        verticalVelocity.y = Mathf.Sqrt(jumpHeight * -2 * Gravity);
    }
}

效果
在这里插入图片描述

12、播放跳跃下落动画

我们添加一个跳跃下落的子状态机,并配置参数
在这里插入图片描述
跳跃下落的子状态机参数如下。(注意:可以看到这里默认有条黄线,不用管他,好像是unity6的显示bug)
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
修改AnimatorController,进行跳跃动画切换

private static int jumpSpeed = Animator.StringToHash("jumpSpeed");
private static int isJump = Animator.StringToHash("isJump");
private static int isGround = Animator.StringToHash("isGround");

animator.SetFloat(jumpSpeed, playerController.verticalVelocity.y);
animator.SetBool(isJump, inputController.isJump);
animator.SetBool(isGround, playerController.isGround);

效果
在这里插入图片描述

13、修复原地跳跃问题

应用跳跃动画之后,我们可以看到目前我们都只能在原地跳跃了,水平方向的速度一直都是0,其实是因为前面我们使用了OnAnimatorMove来控制人物移动,而我们的跳跃动画是没有水平方向位移的。

想解决这一问题,我们可以不在地面时手动设置一个水平方向的速度,修改PlayerController

private Vector3 horizontalVelocity; // 水平方向速度

//设置水平速度
void SetHorizontalVelocity()
{
    //不在地面时更新水平速度
    if (!isGround)
    {
            //将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向
            horizontalVelocity  = transform.TransformDirection(new Vector3(inputController.moveVector2.x, 0, inputController.moveVector2.y)) * currentSpeed;          
    }
    else
    {
        horizontalVelocity = Vector3.zero;
    }
    characterController.Move(horizontalVelocity * Time.deltaTime);
}

效果

在这里插入图片描述

14、原地转向

业余游戏开发者在做人物控制系统时,经常忽略的一个内容就是原地转身,如果你想用3D玩家控制器制作可发布的游戏,这确实是一个你必须拥有的机制。

老实说,这也是比较难做好的机制之一。这里我打算复刻一下类似PUBG吃鸡这样的原地转身机制。及人物面向方向与相机的水平正方向的夹角如果在45度范围内,角色不发生转身。如果超出这个范围,则等待3秒后角色平滑旋转,与相机的水平正方向对齐。
在这里插入图片描述

我们新增一个子状态机叫转身动作
在这里插入图片描述
配置子状态机内部连线,因为我这里只有左右转的动画,所以只配置了旋转角度大于小于0播放不同动画。如果你有更多动画问题,比如旋转45、90、180度动画,可以进行更加精细的配置,效果将会更好。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
修改PlayerController代码,我们要修改覆盖掉原来我们角色转身的代码。这里代码我都加了很详细的中文注释,方便大家理解,就不多做解释了。

[Header("相机跟随设置")]
[SerializeField] private float angleThreshold = 45f; // 角度阈值
[SerializeField] private float rotationSpeed = 8f; // 转向速度

[HideInInspector] public float _currentAngle;      // 当前角度差
public float turnDelay = 3f; // 转身动画延迟发生时间
private float lastTurnTime;//上次转身时间
public float turnTime = 0.6f;//转身需要的时间
[HideInInspector] public bool _isTurning = false; // 是否正在转身
private float _turnStartTime; // 转身开始时间
private Quaternion _startRotation; // 转身开始时的旋转
private Quaternion _targetRotation; // 转身目标旋转
private bool isIdle;//是否在原地
private Vector3 cameraForward;//获取相机水平方向

//...记得在update中调用下面这些函数

// 计算人物和相机的夹角
void CalculateCameraAngle()
{
    // 获取相机在水平面(XZ平面)的正前方向
    cameraForward = mainCamera.transform.forward;
    cameraForward.y = 0;
    cameraForward.Normalize();

    // 获取角色在水平面的正前方向
    Vector3 playerForward = transform.forward;
    playerForward.y = 0;
    playerForward.Normalize();

    // 通过点乘计算两个向量的夹角
    _currentAngle = Vector3.Angle(playerForward, cameraForward);

    // 计算两个向量的叉乘,确定角度方向(正负值)
    float crossY = Vector3.Cross(playerForward, cameraForward).y;

    //y小于0 证明相机在人物左侧
    if (crossY < 0) _currentAngle *= -1;
}

//处理旋转逻辑
private void HandleRotationLogic()
{
    isIdle = inputController.moveVector2.magnitude < 0.1f; // 如果移动向量长度小于0.1,则认为处于静止状态

    // 如果没有移动,且角度大于阈值,且当前没有正在转身,且上次转身时间超过延迟时间,且在地面上,且没有下蹲
    if (isIdle && Mathf.Abs(_currentAngle) > angleThreshold && !_isTurning && Time.time - lastTurnTime > turnDelay && isGround && !inputController.isCrouch)
    {
        _isTurning = true;
        _turnStartTime = Time.time;
        _startRotation = transform.rotation;
        _targetRotation = Quaternion.LookRotation(cameraForward);
    }

    // 如果转身动画结束或者移动,重置状态
    if ((_isTurning && Time.time - _turnStartTime >= turnTime) || !isIdle)
    {
        _isTurning = false;
        lastTurnTime = Time.time;
    }
}

// 处理角色旋转
private void SetPlayerRotation()
{
    if (_isTurning)
    {
        // 计算转身进度
        float turnProgress = (Time.time - _turnStartTime) / turnTime;
        // 确保进度在0到1之间
        turnProgress = Mathf.Clamp01(turnProgress);

        // 使用插值平滑旋转
        transform.rotation = Quaternion.Slerp(_startRotation, _targetRotation, turnProgress);
    }
    else if (!isIdle)//非待机状态角色才跟随相机旋转
    {
        lastTurnTime = Time.time;

        // 普通情况下的相机跟随旋转
        Vector3 cameraForward = mainCamera.transform.forward;
        cameraForward.y = 0;
        Quaternion targetRotation = Quaternion.LookRotation(cameraForward);
        transform.rotation = Quaternion.Slerp(
            transform.rotation,
            targetRotation,
            rotationSpeed * Time.deltaTime
        );
    }
}

效果
在这里插入图片描述

15、角色在下斜坡时,短暂浮空

角色在下斜坡时,会短暂浮空,特别是速度比较快的时候。
在这里插入图片描述
我们可以给角色一个向下的压力,我们可以将它配置化成参数,方便在面板调整。

[Header("斜坡处理")]
[SerializeField] private Vector3 downForceVector = new Vector3(0, -10, 0); //下压力

void Update()
{
    SetDownForce();//记得一定要在isGround检测之前调用
    //。。。
}

//加下压力
void SetDownForce(){
    if (!isGround) return;
    characterController.Move(downForceVector * Time.deltaTime);
}

效果
在这里插入图片描述

16、冲刺时跳跃,落地速度就会立即恢复到最高的跑步速度

我们可以先在PlayerController获取跳跃状态

public bool isJumping;//是否在跳跃

// 处理角色跳跃
void SetPlayerJump()
{
    // 跳跃处理
    if (isGround)
    {
        isJumping = false;
        if(inputController.isJump){
            isJumping = true;
            verticalVelocity.y = Mathf.Sqrt(jumpHeight * -2 * Gravity);
        }
    }
}

在AnimatorController里判断,如果正在跳跃,则inputX和inputY归零

if (playerController.isJumping)
{
    animator.SetFloat(inputX, 0f);
    animator.SetFloat(inputY, 0f);
}
else
{
    animator.SetFloat(inputX, inputController.moveVector2.x * playerController.currentSpeed, smoothTime, Time.deltaTime);
    animator.SetFloat(inputY, inputController.moveVector2.y * playerController.currentSpeed, smoothTime, Time.deltaTime);
}

效果
在这里插入图片描述

17、在墙壁边缘走上走下或者跳跃,会出现抖动卡顿

其实这是一个非常细微的问题,比如我把玩家的跳跃高度设置成1.7米,无论如何他都不能跳上2米的墙壁才对。但是事实并非如此。
在这里插入图片描述
这主要其实就是收到Character Controller的每步偏移量的影响。跳跃2米的墙,还差0.3米时他会把他当作是楼梯,然后顿一下走上前。
在这里插入图片描述
我的解决方案是我们在跳跃或下落(也就是空中)时,我们将步长偏移设置为零,在任何其他状态下,再将他设置回默认值。

[Header("楼梯处理")]
float stepOffset; //默认步长偏移

void Awake()
{
    //。。。

    stepOffset = characterController.stepOffset;
}

void SetPlayerGravity()
{
    //。。。
    
    if(isGround){
        //。。。
        characterController.stepOffset = stepOffset;
    }else{
       	//。。。
       characterController.stepOffset = 0f;
    } 
}

效果

在这里插入图片描述

18、我们跳上超斜的斜面,会被卡住

在这里插入图片描述
我们可以在玩家脚下使用射线检测,检测斜面的法线向量,然后计算出斜坡的角度。

RaycastHit hit; //射线检测结果
float slopeAngle; //斜率角度
Vector3 normal;//获取法线向量
public LayerMask layerMask; //射线检测的层
public float distance = 0.5f; //射线检测距离
    
// 处理角色重力
void SetPlayerGravity()
{
    if (isGround)
    {
        verticalVelocity = new Vector3(0, -2f, 0);
        characterController.stepOffset = stepOffset;
    }
    else
    {
        // 累加重力
        verticalVelocity.y += Gravity * Time.deltaTime;
        characterController.stepOffset = 0f;
    }
}

// 处理角色跳跃
void SetPlayerJump()
{
    // 跳跃处理
    if (isGround)
    {
        isJumping = false;
        if(inputController.isJump){
            isJumping = true;
            verticalVelocity.y = Mathf.Sqrt(jumpHeight * -2 * Gravity);
        }
    }

    verticalVelocity = SetSlopeVelocity(verticalVelocity);

    // 应用y轴速度
    characterController.Move(verticalVelocity * Time.deltaTime);

    //地面检测,注意地面检测一定要在Move之后,否则可能会检测不到
    isGround = characterController.isGrounded;
}

//斜坡检测
void CheckIsSlope()
{
    // 射线检测
    if (Physics.Raycast(transform.position, Vector3.down,out hit, distance, layerMask))
    {
        normal = hit.normal;

        // 计算斜面角度
        slopeAngle = Vector3.Angle(normal, Vector3.up);
    }
}
//斜坡速度处理
private Vector3 SetSlopeVelocity(Vector3 velocity)
{
    //角色在大斜坡上下落
    if (slopeAngle > characterController.slopeLimit && verticalVelocity.y < 0f)
    {
        // 则将速度投射到斜坡上
        velocity = Vector3.ProjectOnPlane(velocity, normal);
    }
    //返回处理后的速度向量
    return velocity;
}

我们可以使用OnDrawGizmos辅助函数,在场景显示射线,方便调试查看

private void OnDrawGizmos()
{
    Gizmos.color = Color.red;
    Gizmos.DrawLine(transform.position, transform.position + Vector3.down * distance;);
}

可以在场景视图看到射线检测的距离
在这里插入图片描述

记得修改玩家图层和检测图层
在这里插入图片描述
效果
在这里插入图片描述
不过如果你人物在边角的时候,检测还是会有问题。
在这里插入图片描述

如果你有更加精确的要求的话,可以使用多条射线检测,或者使用Physics.SphereCast球形检测、Physics.BoxCast方形检测、Physics.CapsuleCast胶囊体检测,效果会更好,这里就不演示了

if(Physics.SphereCast(transform.position, characterController.radius, Vector3.down, out hit, distance, layerMask)){
    Debug.Log(2222);
};

19、限制人物下落速度

如果我们开始下落,我们不想他无限加速,我希望限制这个值。我不希望玩家从很高的地方跌落,速度太快。

public float maxVelocityY = 50f;//最大垂直速度

//将y轴速度限制在+-maxVelocityY之间
verticalVelocity.y = Mathf.Clamp(verticalVelocity.y, -maxVelocityY, maxVelocityY);

在这里插入图片描述

20、物体会挡住相机视野和相机穿模

在这里插入图片描述
我们可以添加虚拟相机Cinemachine Deoccluder反遮挡器,具体可以参考:https://docs.unity3d.com/Packages/com.unity.cinemachine@3.1/manual/CinemachineDeoccluder.html

在这里插入图片描述
效果
在这里插入图片描述

21、相机上下楼梯,跳跃有点抖动

在这里插入图片描述
我们可以适当增大相机的阻尼
在这里插入图片描述
效果
在这里插入图片描述

待续

暂时先实现这么多,如果后续想到加其他功能再来添加。


专栏推荐

地址
【unity游戏开发入门到精通——C#篇】
【unity游戏开发入门到精通——unity通用篇】
【unity游戏开发入门到精通——unity3D篇】
【unity游戏开发入门到精通——unity2D篇】
【unity实战】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架/工具集开发】
【unity游戏开发——模型篇】
【unity游戏开发——InputSystem】
【unity游戏开发——Animator动画】
【unity游戏开发——UGUI】
【unity游戏开发——联网篇】
【unity游戏开发——优化篇】
【unity游戏开发——shader篇】

完结

好了,我是向宇,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

向宇it

创作不易,感谢你的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值