如何解决Unity Horizontal Gravity with Character Controller
我目前正在尝试基于 Unity 发布的新标准资产包中的新第三人称控制器创建角色控制器。我的目标是能够根据需要将重力方向更改为所有四个方向(上、下、左、右)。我已经按预期工作了上下方向,但我已经达到了我的极限,试图找出我在水平重力上做错了什么。
令我沮丧的是,我的代码有点未经优化,而且有点乱。如果您能帮助我确定我做错了什么或我在这里遗漏了什么,我将不胜感激。我已经让重力按预期工作,但我缺少一些东西来产生正确的步行能力和动画。这是有关其外观和代码的视频。
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 举报,一经查实,本站将立刻删除。