[Unity]尝试实现第三人称控制器(其五、添加重力)

[Unity]尝试实现第三人称控制器(其五、添加重力)

【声明】此第三人称控制器是复现unity明星资产中第三人称控制器,如果有需要可以直接下载该资产并学习。我个人是不喜欢重复造轮子的行为的,但是官方的控制器我使用时有些不尽如人意的BUG。尽管如此,该控制器的效果让我觉得很不错,虽然无法实现无缝动画,不过个人项目应该先想办法让其动起来才行。
【版本】此项目基于Unity【2021.3lts】版本制作

本章是为了做到能够达到原型的程度,所以添加这些功能。大家在制作开发阶段可以根据自己的节奏来添加这些功能。总之一句话,需要的时候再去做。

重力制作

在我的代码中,角色的位移下方是由有一个TODO的,这是我们有打算制作的功能。
在垂直方向上,我们需要添加重力。

添加地面检测

我们不在地面上,才会受到重力的影响。所以我们去做一个地面检测,当不在地面上时,我们采取考虑竖直方向上的分量。
地面检测有很多种,射线法,碰撞体。我们为了学习,与官方制作的一致,使用物理的CheckSphere(请在unity中文手册中查看)
首先确定脚和地面的交界线,这个位置就是CC的底部。当然,我们如果制作下落动画,这个交界线就不能够这么严格。原因在于我们下楼梯时高度不足以触发下落。我们在这里为以后的这个需求提前做功课:

[Header("重力及下落")]
[Tooltip("地面宽容度")]
public float GroundedOffset = -0.14f;

我们将获取应用了宽容度的检测点,然后根据这个检测点判断碰撞

private void GroundedCheack()
{
	Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z);
	Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers, QueryTriggerInteraction.Ignore);
}

重力

基本思想就是重力加速度

private void DoGravity()
    {
        if(Grounded)
        {
            //重置坠落计时器
            _fallTimeOutDelta = FallTimeout;
            //落地后我们停止垂直速度累加
            if(_verticalVelocity<0.0f)
            {
                _verticalVelocity = -2f;
            }
        }
        else
        {
            //此if用于决定是否是下落动画
            if(_fallTimeOutDelta>=0.0f)
            {
                _fallTimeOutDelta -= Time.deltaTime;
            }
            else
            {
                //下落动画
            }
        }

        if(_verticalVelocity<_terminalVelocity)
        {
            _verticalVelocity += Gravity * Time.deltaTime;
        }
    }

_characterController.Move(targetDir.normalized * (_currentSpeed * Time.deltaTime)
            + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);

结尾

这就是重力的部分。那么做到这里就已经是比较不错的第三人称控制器了。我不打算增加跳跃功能,毕竟第三人称射击游戏可以不用管这个吧,可有可无的功能。等到真的有需要我再加。

代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class ThirdPlayerMoveController : MonoBehaviour
{
    CharacterController _characterController;
    PlayerInput _playerInput;
    PlayerInputsMassage _inputsMassage;
    GameObject _mainCamera;
    Animator _anim;

    public PlayerActionState playerActionState = PlayerActionState.Idle;

    [Header("相机设置")]
    public GameObject _cinemachineFollowTarget;
    float _cinemachineTagertX;
    float _cinemachineTagertY;
    [Tooltip("相机仰角")]
    public float TopClamp = 70.0f;
    [Tooltip("相机俯角")]
    public float BottomClamp = -30.0f;
    [Tooltip("额外的度数覆盖摄像头。有用的微调相机位置时,锁定")]
    public float CameraAngleOverride = 0.0f;

    [Header("玩家设置")]
    [Tooltip("这将决定普通行走时的速度")]
    public float walkSpeed = 1.5f;
    [Tooltip("这将决定跑步时的速度")]
    public float _runSpeed = 5.0f;
    private bool _isCrouch = false;
    [Tooltip("这将决定蹲下行走的速度")]
    public float _crouchSpeed = 1.0f;
    [Tooltip("这将决定不同状态速度间切换的速度")]
    public float SpeedChangeRate = 10.0f;

    [Header("重力及下落")]
    [Tooltip("重力加速度")]
    public float Gravity = -15.0f;
    [Tooltip("是否着地")]
    public bool Grounded = true;
    [Tooltip("检测半径")]
    public float GroundedRadius = 0.28f;
    [Tooltip("检测的层级")]
    public LayerMask GroundLayers;
    [Tooltip("地面宽容度")]
    public float GroundedOffset = -0.14f;
    [Tooltip("进入坠落所需时间")]
    public float FallTimeout = 0.15f;
    private float _fallTimeOutDelta;
    private float _verticalVelocity;
    private float _terminalVelocity = 53.0f;


    private float _currentSpeed;
    private float _targetRotation = 0.0f;
    [Tooltip("角色光滑旋转时间")]
    private float RotationSmoothTime = 0.12f;
    [Tooltip("在角色光滑旋转过程中的速度")]
    private float _rotationVelocity;

    private float _threshold = 0.01f;

    private bool IsCurrentDeviceMouse
    {
        get { return _playerInput.currentControlScheme == "KeyboardMouse"; }
    }

    private void Awake()
    {
        // get a reference to our main camera
        if (_mainCamera == null)
        {
            _mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        _characterController = GetComponent<CharacterController>();
        _inputsMassage = GetComponent<PlayerInputsMassage>();
        _playerInput = GetComponent<PlayerInput>();
        _anim = GetComponentInChildren<Animator>();
    }

    private void Update()
    {
        DoGravity();
        GroundedCheack();
        Move();
    }

    private void LateUpdate()
    {
        CameraRotation();
    }

    /// <summary>
    /// 相机追踪点的控制
    /// </summary>
    private void CameraRotation()
    {
        if(_inputsMassage.look.sqrMagnitude>_threshold)//look值大于误差代表有输入
        {
            float deltaTimeMultiplier = IsCurrentDeviceMouse ? 1f : Time.deltaTime;

            _cinemachineTagertX += _inputsMassage.look.x * deltaTimeMultiplier;
            _cinemachineTagertY += _inputsMassage.look.y * deltaTimeMultiplier;
        }
        _cinemachineTagertX = ClampAngle(_cinemachineTagertX, float.MinValue, float.MaxValue);
        _cinemachineTagertY = ClampAngle(_cinemachineTagertY, BottomClamp, TopClamp);

        _cinemachineFollowTarget.transform.rotation = Quaternion.Euler((-_cinemachineTagertY - CameraAngleOverride) * Settings.mouseYmoveTimes,
                _cinemachineTagertX * Settings.mouseXmoveTimes, 0.0f);
    }

    private void Move()
    {
        _isCrouch = _inputsMassage.crouch;
        //在这里进行状态的判断
        PlayerStateJudge();

        //首先将移动速度赋予临时变量,考虑到有可能在其他地方使用,我们将其存储起来
        //_currentSpeed = walkSpeed;(转换为更加完善的速度控制)
        float targetSpeed = playerActionState switch
        {
            PlayerActionState.Idle => 0f,
            PlayerActionState.Walk => walkSpeed,
            PlayerActionState.Run => _runSpeed,
            _ => 0f
        };
        if (_isCrouch) targetSpeed = _crouchSpeed;

        //玩家当前水平速度的参考
        float currentHorizontalSpeed = new Vector3(_characterController.velocity.x, 0.0f, _characterController.velocity.z).magnitude;
        //偏离度,保证目标速度与目前速度相差大才可以插值,避免小幅度的抽搐
        float speedOffset = 0.1f;
        //判断偏离度
        if (currentHorizontalSpeed < targetSpeed - speedOffset ||
                currentHorizontalSpeed > targetSpeed + speedOffset)
        {
            _currentSpeed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed ,Time.deltaTime * SpeedChangeRate);
            //四舍五入到小数点后3位
            _currentSpeed = Mathf.Round(_currentSpeed * 1000f) / 1000f;
        }
        else
        {
            _currentSpeed = targetSpeed;
        }

        //判断是否进行移动输入
        if (_inputsMassage.move == Vector2.zero) _currentSpeed = 0;

        var currentInput = new Vector3(_inputsMassage.move.x, 0, _inputsMassage.move.y).normalized;

        //单位向量的方向,或者说位移方向
        if (_inputsMassage.move!=Vector2.zero)
        {
            _targetRotation = Mathf.Atan2(currentInput.x, currentInput.z) * Mathf.Rad2Deg + _mainCamera.transform.eulerAngles.y;


            #region 在位移过程中的转向
            float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,
                RotationSmoothTime);
            transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
            #endregion

        }

        Vector3 targetDir = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;
        _characterController.Move(targetDir.normalized * (_currentSpeed * Time.deltaTime)
            + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
        //TODO:这里的Move可以执行垂直方向的速度,直接加上垂直的Vector就可以

        _anim.SetFloat("Speed", _currentSpeed);
        _anim.SetBool("Crouch", _isCrouch);
    }

    private void DoGravity()
    {
        if(Grounded)
        {
            //重置坠落计时器
            _fallTimeOutDelta = FallTimeout;
            //落地后我们停止垂直速度累加
            if(_verticalVelocity<0.0f)
            {
                _verticalVelocity = -2f;
            }
        }
        else
        {
            //此if用于决定是否是下落动画
            if(_fallTimeOutDelta>=0.0f)
            {
                _fallTimeOutDelta -= Time.deltaTime;
            }
            else
            {
                //下落动画
            }
        }

        if(_verticalVelocity<_terminalVelocity)
        {
            _verticalVelocity += Gravity * Time.deltaTime;
        }
    }

    /// <summary>
    /// 限制角度
    /// </summary>
    /// <param name="lfAngle"></param>
    /// <param name="lfMin"></param>
    /// <param name="lfMax"></param>
    /// <returns></returns>
    private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
    {
        if (lfAngle < -360f) lfAngle += 360f;
        if (lfAngle > 360f) lfAngle -= 360f;
        return Mathf.Clamp(lfAngle, lfMin, lfMax);
    }

    /// <summary>
    /// 对玩家状态进行判断
    /// </summary>
    private void PlayerStateJudge()
    {
        playerActionState = PlayerActionState.Idle;
        if (_inputsMassage.move!=Vector2.zero)
        {
            playerActionState = PlayerActionState.Walk;
            if (_inputsMassage.run)
                playerActionState = PlayerActionState.Run;
        }

    }

    private void GroundedCheack()
    {
        Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z);
        Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers, QueryTriggerInteraction.Ignore);
    }

}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值