Unity Horizo​​ntal Gravity with Character Controller

问题描述

我目前正在尝试基于 Unity 发布的新标准资产包中的新第三人称控制器创建角色控制器。我的目标是能够根据需要将重力方向更改为所有四个方向(上、下、左、右)。我已经按预期工作了上下方向,但我已经达到了我的极限,试图找出我在水平重力上做错了什么。

令我沮丧的是,我的代码有点未经优化,而且有点乱。如果您能帮助我确定我做错了什么或我在这里遗漏了什么,我将不胜感激。我已经让重力按预期工作,但我缺少一些东西来产生正确的步行能力和动画。这是有关其外观和代码的视频。

https://streamable.com/pzhn4f

using UnityEngine;
using UnityEngine.InputSystem;
 
[RequireComponent(typeof(PlayerInput))]
public class PlayerController : MonoBehavIoUr
{
    [Header("Player")]
    [Tooltip("Move speed of the character in m/s")]
    public float MoveSpeed = 2.0f;
    [Tooltip("Sprint speed of the character in m/s")]
    public float SprintSpeed = 5.335f;
    [Tooltip("How fast the character turns to face movement direction")]
    [Range(0.0f,0.3f)]
    public float RotationSmoothTime = 0.12f;
    [Tooltip("acceleration and deceleration")]
    public float SpeedChangeRate = 10.0f;
 
    [Space(10)]
    [Tooltip("The height the player can jump")]
    public float JumpHeight = 1.2f;
 
    [Space(10)]
    [Tooltip("Time required to pass before being able to jump again. Set to 0f to instantly jump again")]
    public float JumpTimeout = 0.50f;
    [Tooltip("Time required to pass before entering the fall state. Useful for walking down stairs")]
    public float FallTimeout = 0.15f;
 
    [Header("Player Grounded")]
    [Tooltip("If the character is grounded or not. Not part of the CharacterController built in grounded check")]
    public bool Grounded = true;
    [Tooltip("Useful for rough ground")]
    public float GroundedOffset = -0.14f;
    [Tooltip("The radius of the grounded check. Should match the radius of the CharacterController")]
    public float GroundedRadius = 0.28f;
    [Tooltip("What layers the character uses as ground")]
    public LayerMask GroundLayers;
 
    [Header("Cinemachine")]
    [Tooltip("The follow target set in the Cinemachine Virtual Camera that the camera will follow")]
    public GameObject CinemachineCameraTarget;
    [Tooltip("How far in degrees can you move the camera up")]
    public float TopClamp = 70.0f;
    [Tooltip("How far in degrees can you move the camera down")]
    public float BottomClamp = -30.0f;
    [Tooltip("Additional degress to override the camera. Useful for fine tuning camera position when locked")]
    public float CameraAngleOverride = 0.0f;
    [Tooltip("For locking the camera position on all axis")]
    public bool LockCameraPosition = false;
 
    // player
    private float _speed;
    private float _animationBlend;
    private float _targetRotation = 0.0f;
    private float _rotationVeLocityX;
    private float _rotationVeLocityY;
    private float _rotationVeLocityZ;
    private float _verticalVeLocity;
    private float _horizontalVeLocity;
 
    // timeout deltatime
    private float _jumpTimeoutDelta;
    private float _fallTimeoutDelta;
 
    // animation IDs
    private int _animIDSpeed;
    private int _animIDGrounded;
    private int _animIDJump;
    private int _animIDFreeFall;
    private int _animIDMotionSpeed;
 
    private Animator _animator;
    private CharacterController _controller;
    private PlayerControllerInput _input;
    private GameObject _mainCamera;
 
    private const float _threshold = 0.01f;
 
    private bool _hasAnimator;
 
    private Vector3 _desiredRotationAngles;
    private byte _rotationAxis = 1;
 
    private void Awake()
    {
        // get a reference to our main camera
        if (_mainCamera == null)
        {
            _mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
        }
    }
 
    private void Start()
    {
        _hasAnimator = TryGetComponent(out _animator);
        _controller = GetComponent<CharacterController>();
        _input = GetComponent<PlayerControllerInput>();
 
        AssignAnimationIDs();
 
        // reset our timeouts on start
        _jumpTimeoutDelta = JumpTimeout;
        _fallTimeoutDelta = FallTimeout;
 
        _desiredRotationAngles = new Vector3(0.0f,90.0f,0.0f);
    }
 
    private void Update()
    {
        _hasAnimator = TryGetComponent(out _animator);
 
        JumpAndGravity();
        GroundedCheck();
        Move();
    }
 
    private void AssignAnimationIDs()
    {
        _animIDSpeed = Animator.StringToHash("Speed");
        _animIDGrounded = Animator.StringToHash("Grounded");
        _animIDJump = Animator.StringToHash("Jump");
        _animIDFreeFall = Animator.StringToHash("FreeFall");
        _animIDMotionSpeed = Animator.StringToHash("MotionSpeed");
    }
 
    private void GroundedCheck()
    {
        if (_input.Rotate)
        {
            Grounded = false;
            _controller.Move(_input.Gravity.normalized * Time.deltaTime * 1.5f);
            return;
        }
 
        // set sphere position,with offset
        Vector3 spherePosition = new Vector3(transform.position.x,transform.position.y - GroundedOffset,transform.position.z);
        Grounded = Physics.CheckSphere(spherePosition,GroundedRadius,GroundLayers,QueryTriggerInteraction.Ignore);
 
        // update animator if using character
        if (_hasAnimator)
        {
            _animator.SetBool(_animIDGrounded,Grounded);
        }
    }
 
    private void Move()
    {
        // set target speed based on move speed,sprint speed and if sprint is pressed
        float targetSpeed = SprintSpeed;
 
        // a simplistic acceleration and deceleration designed to be easy to remove,replace,or iterate upon
 
        // note: Vector2's == operator uses approximation so is not floating point error prone,and is cheaper than magnitude
        // if there is no input,set the target speed to 0
        if (_input.Move == Vector2.zero) targetSpeed = 0.0f;
 
        // a reference to the players current horizontal veLocity
        float currentHorizontalSpeed = new Vector3(_controller.veLocity.x,0.0f,_controller.veLocity.z).magnitude;
 
        float speedOffset = 0.1f;
        float inputMagnitude = _input.AnalogMovement ? _input.Move.magnitude : 1f;
 
        // accelerate or decelerate to target speed
        if (currentHorizontalSpeed < targetSpeed - speedOffset || currentHorizontalSpeed > targetSpeed + speedOffset)
        {
            // creates curved result rather than a linear one giving a more organic speed change
            // note T in Lerp is clamped,so we don't need to clamp our speed
            _speed = Mathf.Lerp(currentHorizontalSpeed,targetSpeed * inputMagnitude,Time.deltaTime * SpeedChangeRate);
 
            // round speed to 3 decimal places
            _speed = Mathf.Round(_speed * 1000f) / 1000f;
        }
        else
        {
            _speed = targetSpeed;
        }
        _animationBlend = Mathf.Lerp(_animationBlend,targetSpeed,Time.deltaTime * SpeedChangeRate);
 
        // normalise input direction
        Vector3 inputDirection = new Vector3(_input.Move.x,_input.Move.y).normalized;
 
        // note: Vector2's != operator uses approximation so is not floating point error prone,and is cheaper than magnitude
        // if there is a move input rotate player when the player is moving
        if (_input.Move != Vector2.zero)
        {
            if (_rotationAxis == 0)
            {
                // Movement rotates on X axis
                _targetRotation = inputDirection.x > 0 ? 90.0f : -90.0f;
                float rotationX = Mathf.SmoothdampAngle(transform.eulerAngles.x,_targetRotation,ref _rotationVeLocityX,RotationSmoothTime);
                float rotationZ = Mathf.SmoothdampAngle(transform.eulerAngles.z,_desiredRotationAngles.z,ref _rotationVeLocityZ,RotationSmoothTime * 2.0f);
 
                // Rotate to face input direction relative to camera position
                transform.rotation = Quaternion.Euler(rotationX,rotationZ);
            }
            else if (_rotationAxis == 1)
            {
                // Movement rotates on Y axis
                _targetRotation = inputDirection.x > 0 ? 90.0f : -90.0f;
 
                float rotationY = Mathf.SmoothdampAngle(transform.eulerAngles.y,ref _rotationVeLocityY,RotationSmoothTime * 2.0f);
 
                // Rotate to face input direction relative to camera position
                transform.rotation = Quaternion.Euler(0.0f,rotationY,rotationZ);
            }
        }
        else
        {
            if (_rotationAxis == 0)
            {
                // Movement rotates on X axis
                float rotationZ = Mathf.SmoothdampAngle(transform.eulerAngles.z,RotationSmoothTime * 2.0f);
                Quaternion expectedRotation = Quaternion.Euler(transform.rotation.eulerAngles.x,rotationZ);
 
                if (!transform.rotation.Equals(expectedRotation))
                    transform.rotation = expectedRotation;
            }
            else if (_rotationAxis == 1)
            {
                float rotationZ = Mathf.SmoothdampAngle(transform.eulerAngles.z,RotationSmoothTime * 2.0f);
                Quaternion expectedRotation = Quaternion.Euler(0.0f,transform.rotation.eulerAngles.y,rotationZ);
 
                if (!transform.rotation.Equals(expectedRotation))
                    transform.rotation = expectedRotation;
            }
        }
 
        if (_rotationAxis == 0)
        {
            Vector3 targetDirection = Quaternion.Euler(_targetRotation,_desiredRotationAngles.z) * Vector3.forward;
 
            // move the player
            _controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) + new Vector3(_horizontalVeLocity,_verticalVeLocity,0.0f) * Time.deltaTime);
        }
        else if (_rotationAxis == 1)
        {
            Vector3 targetDirection = Quaternion.Euler(0.0f,0.0f) * Time.deltaTime);
        }
 
 
        // update animator if using character
        if (_hasAnimator)
        {
            _animator.SetFloat(_animIDSpeed,_animationBlend);
            _animator.SetFloat(_animIDMotionSpeed,inputMagnitude);
        }
    }
 
    private void JumpAndGravity()
    {
        Vector3 gravitynormal = _input.Gravity.normalized;
 
        if (gravitynormal.Equals(Vector3.up))
        {
            // Movement rotation axis is Y
            _desiredRotationAngles = new Vector3(0.0f,180.0f);
            _rotationAxis = 1;
        }
 
        if (gravitynormal.Equals(Vector3.down))
        {
            // Movement rotation axis is Y
            _desiredRotationAngles = new Vector3(0.0f,0.0f);
            _rotationAxis = 1;
        }
 
        if (gravitynormal.Equals(Vector3.right))
        {
            // Movement rotation axis is X
            _desiredRotationAngles = new Vector3(90.0f,90.0f);
            _rotationAxis = 0;
        }
 
        if (gravitynormal.Equals(Vector3.left))
        {
            // Movement rotation axis is X
            _desiredRotationAngles = new Vector3(90.0f,-90.0f);
            _rotationAxis = 0;
        }
 
        if (Grounded)
        {
            // reset the fall timeout timer
            _fallTimeoutDelta = FallTimeout;
 
            // update animator if using character
            if (_hasAnimator)
            {
                _animator.SetBool(_animIDJump,false);
                _animator.SetBool(_animIDFreeFall,false);
            }
 
            if (gravitynormal.Equals(Vector3.up))
            {
                if (_verticalVeLocity > 0.0f)
                {
                    _verticalVeLocity = 2.0f;
                    _horizontalVeLocity = 0.0f;
                }
            }
 
            if (gravitynormal.Equals(Vector3.down))
            {
                if (_verticalVeLocity < 0.0f)
                {
                    _verticalVeLocity = -2.0f;
                    _horizontalVeLocity = 0.0f;
                }
            }
 
            if (gravitynormal.Equals(Vector3.right))
            {
                if (_horizontalVeLocity > 0.0f)
                {
                    _horizontalVeLocity = 2.0f;
                    _verticalVeLocity = 0.0f;
                }
            }
 
            if (gravitynormal.Equals(Vector3.left))
            {
                if (_horizontalVeLocity < 0.0f)
                {
                    _horizontalVeLocity = -2.0f;
                    _verticalVeLocity = 0.0f;
                }
            }
 
            // Jump
            if (_input.Jump && _jumpTimeoutDelta <= 0.0f)
            {
                // the square root of H * -2 * G = how much veLocity needed to reach desired height
                float jumpVeLocity = -_input.Gravity.normalized.y * Mathf.Sqrt(JumpHeight * -2.0f * -10.0f);
 
                if (_rotationAxis == 0)
                    _horizontalVeLocity = jumpVeLocity;
                else
                    _verticalVeLocity = jumpVeLocity;
 
                // update animator if using character
                if (_hasAnimator)
                {
                    _animator.SetBool(_animIDJump,true);
                }
            }
 
            // jump timeout
            if (_jumpTimeoutDelta >= 0.0f)
            {
                _jumpTimeoutDelta -= Time.deltaTime;
            }
        }
        else
        {
            // reset the jump timeout timer
            _jumpTimeoutDelta = JumpTimeout;
 
            // fall timeout
            if (_fallTimeoutDelta >= 0.0f)
            {
                _fallTimeoutDelta -= Time.deltaTime;
            }
            else
            {
                // update animator if using character
                if (_hasAnimator)
                {
                    _animator.SetBool(_animIDFreeFall,true);
                }
            }
 
            // if we are not grounded,do not jump
            _input.Jump = false;
        }
 
        // apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)
        if (_rotationAxis == 0)
        {
            _horizontalVeLocity += _input.Gravity.x * Time.deltaTime;
            _verticalVeLocity = 0.0f;
        }
        else
        {
            _verticalVeLocity += _input.Gravity.y * Time.deltaTime;
            _horizontalVeLocity = 0.0f;
        }
    }
 
    private void OnDrawGizmosSelected()
    {
        Color transparentGreen = new Color(0.0f,1.0f,0.35f);
        Color transparentRed = new Color(1.0f,0.35f);
 
        if (Grounded) Gizmos.color = transparentGreen;
        else Gizmos.color = transparentRed;
 
        // when selected,draw a gizmo in the position of,and matching radius of,the grounded collider
        Gizmos.DrawSphere(new Vector3(transform.position.x,transform.position.z),GroundedRadius);
    }
}

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)