素质课网站设计与建设,建设网站需要什么基础知识,电子商务网站建设实训总结,ssc网站建设交流群首先模板链接在这里#xff0c;你可以直接下载并导入unity即可查看官方为开发者写好一套控制器 本文的ai工具用到了豆包#xff0c;其灵活程度很高#xff0c;总结能力也强过我太多 因此大量使用#xff0c;不喜勿喷 Starter Assets - ThirdPerson | Updates in new Charac… 首先模板链接在这里你可以直接下载并导入unity即可查看官方为开发者写好一套控制器 本文的ai工具用到了豆包其灵活程度很高总结能力也强过我太多 因此大量使用不喜勿喷 Starter Assets - ThirdPerson | Updates in new CharacterController package | 必备工具 | Unity Asset Store 目录
一.前提准备 虚拟相机 角色控制器 新输入系统 动画状态机
二.玩家输入处理类 先看代码 变量/方法图解释 类图
三 .第三人称控制类 整体代码 类图编辑
分步解析
1.初始化 2.交互处理
3.移动方法 4.跳跃和重力处理 5.着地检测 6.相机旋转处理 7..动画处理
四.角色推动刚体类 一.前提准备 虚拟相机 位置 角色控制器 新输入系统 动画状态机 Idel walk run blend 二.玩家输入处理类 先看代码 其实这个脚本没什么好说的仅仅是用新输入系统处理了输入的逻辑 还没有将其应用于角色实际的运动相当于地基 因此我将其放在了本文章的最开始的部分 注意InputValue 是新输入系统的一个重要的结构体其内部使用一种灵活的数据存储方式可以根据不同的输入类型存储相应的数据当调用 GetT() 方法时它会尝试将存储的数据转换为指定的类型如果转换成功则返回转换后的值如果转换失败可能会抛出异常或者返回默认值具体取决于输入系统的实现 using UnityEngine;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endifnamespace StarterAssets
{// 该类用于处理角色的输入逻辑public class StarterAssetsInputs : MonoBehaviour{[Header(角色输入值)]// 角色的移动输入向量包含水平和垂直方向public Vector2 move;// 相机的视角输入向量包含水平和垂直方向public Vector2 look;public bool jump;public bool sprint;[Header(移动设置)]// 是否使用模拟输入进行移动public bool analogMovement;[Header(鼠标光标设置)]// 是否锁定鼠标光标public bool cursorLocked true;// 是否使用鼠标光标输入来控制视角public bool cursorInputForLook true;#if ENABLE_INPUT_SYSTEMpublic void OnMove(InputValue value){MoveInput(value.GetVector2());}// 处理视角输入事件public void OnLook(InputValue value){// 仅当允许使用鼠标光标输入控制视角时才处理if (cursorInputForLook){// 将输入的视角向量传递给 LookInput 方法LookInput(value.GetVector2());}}// 处理跳跃输入事件public void OnJump(InputValue value){// 将跳跃键的按下状态传递给 JumpInput 方法JumpInput(value.isPressed);}// 处理冲刺输入事件public void OnSprint(InputValue value){// 将冲刺键的按下状态传递给 SprintInput 方法SprintInput(value.isPressed);}
#endifpublic void MoveInput(Vector2 newMoveDirection){move newMoveDirection;}// 设置相机的视角输入向量public void LookInput(Vector2 newLookDirection){look newLookDirection;}public void JumpInput(bool newJumpState){jump newJumpState;}public void SprintInput(bool newSprintState){sprint newSprintState;}// 当应用程序获得或失去焦点时调用private void OnApplicationFocus(bool hasFocus){SetCursorState(cursorLocked);}// 设置鼠标光标的锁定状态private void SetCursorState(bool newState){// 如果 newState 为 true则锁定鼠标光标否则解锁Cursor.lockState newState ? CursorLockMode.Locked : CursorLockMode.None;}}
} 变量/方法图解释
变量名类型说明moveVector2角色的移动输入向量包含水平和垂直方向lookVector2相机的视角输入向量包含水平和垂直方向jumpbool跳跃输入状态true 表示按下跳跃键sprintbool冲刺输入状态true 表示按下冲刺键analogMovementbool是否使用模拟输入进行移动cursorLockedbool是否锁定鼠标光标默认为 truecursorInputForLookbool是否使用鼠标光标输入来控制视角默认为 true
方法名访问修饰符返回类型说明OnMove(InputValue value)publicvoid处理移动输入事件调用 MoveInput 方法OnLook(InputValue value)publicvoid处理视角输入事件仅当 cursorInputForLook 为 true 时调用 LookInput 方法OnJump(InputValue value)publicvoid处理跳跃输入事件调用 JumpInput 方法OnSprint(InputValue value)publicvoid处理冲刺输入事件调用 SprintInput 方法MoveInput(Vector2 newMoveDirection)publicvoid设置 move 变量的值LookInput(Vector2 newLookDirection)publicvoid设置 look 变量的值JumpInput(bool newJumpState)publicvoid设置 jump 变量的值SprintInput(bool newSprintState)publicvoid设置 sprint 变量的值OnApplicationFocus(bool hasFocus)privatevoid当应用程序获得或失去焦点时调用调用 SetCursorState 方法SetCursorState(bool newState)privatevoid设置鼠标光标的锁定状态 类图 三 .第三人称控制类 整体代码
using UnityEngine;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif/* 注意角色和胶囊体的动画通过控制器调用并使用动画器空值检查*/namespace StarterAssets
{[RequireComponent(typeof(CharacterController))]
#if ENABLE_INPUT_SYSTEM [RequireComponent(typeof(PlayerInput))]
#endifpublic class ThirdPersonController : MonoBehaviour{[Header(玩家)][Tooltip(角色的移动速度单位米/秒)]public float MoveSpeed 2.0f;[Tooltip(角色的冲刺速度单位米/秒)]public float SprintSpeed 5.335f;[Tooltip(角色转向移动方向的速度)][Range(0.0f, 0.3f)]public float RotationSmoothTime 0.12f;[Tooltip(加速和减速的速率)]public float SpeedChangeRate 10.0f;public AudioClip LandingAudioClip;public AudioClip[] FootstepAudioClips;[Range(0, 1)] public float FootstepAudioVolume 0.5f;[Space(10)][Tooltip(玩家能够跳跃的高度)]public float JumpHeight 1.2f;[Tooltip(角色使用自定义的重力值引擎默认值为 -9.81f)]public float Gravity -15.0f;[Space(10)][Tooltip(再次跳跃所需的间隔时间设置为 0f 可立即再次跳跃)]public float JumpTimeout 0.50f;[Tooltip(进入下落状态前所需的时间适用于下楼梯等情况)]public float FallTimeout 0.15f;[Header(玩家是否着地)][Tooltip(角色是否着地此判断并非基于 CharacterController 内置的着地检查)]public bool Grounded true;[Tooltip(适用于不平整地面的偏移量)]public float GroundedOffset -0.14f;[Tooltip(着地检查的半径应与 CharacterController 的半径一致)]public float GroundedRadius 0.28f;[Tooltip(角色判定为地面的图层)]public LayerMask GroundLayers;[Header(Cinemachine 相机)][Tooltip(Cinemachine 虚拟相机所跟随的目标对象)]public GameObject CinemachineCameraTarget;[Tooltip(相机向上移动的最大角度单位度)]public float TopClamp 70.0f;[Tooltip(相机向下移动的最大角度单位度)]public float BottomClamp -30.0f;[Tooltip(用于覆盖相机角度的额外度数在锁定相机位置时可用于微调相机位置)]public float CameraAngleOverride 0.0f;[Tooltip(是否锁定相机在所有轴上的位置)]public bool LockCameraPosition false;// Cinemachine 相机相关private float _cinemachineTargetYaw;private float _cinemachineTargetPitch;// 玩家相关private float _speed;private float _animationBlend;private float _targetRotation 0.0f;private float _rotationVelocity;private float _verticalVelocity;private float _terminalVelocity 53.0f;// 超时计时器private float _jumpTimeoutDelta;private float _fallTimeoutDelta;// 动画 IDprivate int _animIDSpeed;private int _animIDGrounded;private int _animIDJump;private int _animIDFreeFall;private int _animIDMotionSpeed;#if ENABLE_INPUT_SYSTEM private PlayerInput _playerInput;
#endifprivate Animator _animator;private CharacterController _controller;private StarterAssetsInputs _input;private GameObject _mainCamera;private const float _threshold 0.01f;private bool _hasAnimator;// 判断当前输入设备是否为鼠标private bool IsCurrentDeviceMouse{get{
#if ENABLE_INPUT_SYSTEMreturn _playerInput.currentControlScheme KeyboardMouse;
#elsereturn false;
#endif}}private void Awake(){// 获取主相机的引用if (_mainCamera null){_mainCamera GameObject.FindGameObjectWithTag(MainCamera);}}private void Start(){// 初始化 Cinemachine 相机目标的偏航角_cinemachineTargetYaw CinemachineCameraTarget.transform.rotation.eulerAngles.y;// 尝试获取动画器组件_hasAnimator TryGetComponent(out _animator);// 获取角色控制器组件_controller GetComponentCharacterController();// 获取输入组件_input GetComponentStarterAssetsInputs();
#if ENABLE_INPUT_SYSTEM // 获取玩家输入组件_playerInput GetComponentPlayerInput();
#elseDebug.LogError( Starter Assets 包缺少依赖项请使用 Tools/Starter Assets/Reinstall Dependencies 进行修复);
#endif// 分配动画 IDAssignAnimationIDs();// 初始化跳跃和下落超时计时器_jumpTimeoutDelta JumpTimeout;_fallTimeoutDelta FallTimeout;}private void Update(){// 尝试获取动画器组件_hasAnimator TryGetComponent(out _animator);// 处理跳跃和重力逻辑JumpAndGravity();// 检查角色是否着地GroundedCheck();// 处理角色移动逻辑Move();}private void LateUpdate(){// 处理相机旋转逻辑CameraRotation();}// 分配动画参数的哈希 IDprivate 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(){// 设置球体位置并添加偏移量Vector3 spherePosition new Vector3(transform.position.x, transform.position.y - GroundedOffset,transform.position.z);// 检测球体范围内是否与地面图层发生碰撞Grounded Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers,QueryTriggerInteraction.Ignore);// 如果有动画器组件更新动画参数if (_hasAnimator){_animator.SetBool(_animIDGrounded, Grounded);}}// 处理相机旋转逻辑private void CameraRotation(){// 如果有鼠标或其他输入并且相机位置未锁定if (_input.look.sqrMagnitude _threshold !LockCameraPosition){// 根据当前输入设备确定时间乘数float deltaTimeMultiplier IsCurrentDeviceMouse ? 1.0f : Time.deltaTime;// 更新相机的偏航角和俯仰角_cinemachineTargetYaw _input.look.x * deltaTimeMultiplier;_cinemachineTargetPitch _input.look.y * deltaTimeMultiplier;}// 限制相机的旋转角度在 360 度范围内_cinemachineTargetYaw ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);_cinemachineTargetPitch ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);// 设置 Cinemachine 相机目标的旋转角度CinemachineCameraTarget.transform.rotation Quaternion.Euler(_cinemachineTargetPitch CameraAngleOverride,_cinemachineTargetYaw, 0.0f);}// 处理角色移动逻辑private void Move(){// 根据是否按下冲刺键设置目标速度float targetSpeed _input.sprint ? SprintSpeed : MoveSpeed;// 简单的加速和减速逻辑便于修改或扩展// 注意Vector2 的 运算符使用近似值不会出现浮点误差且比计算向量长度更高效// 如果没有输入将目标速度设为 0if (_input.move Vector2.zero) targetSpeed 0.0f;// 获取玩家当前的水平速度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;// 加速或减速到目标速度if (currentHorizontalSpeed targetSpeed - speedOffset ||currentHorizontalSpeed targetSpeed speedOffset){// 使用插值计算速度使速度变化更自然_speed Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude,Time.deltaTime * SpeedChangeRate);// 将速度值保留三位小数_speed Mathf.Round(_speed * 1000f) / 1000f;}else{_speed targetSpeed;}// 插值计算动画混合值_animationBlend Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);if (_animationBlend 0.01f) _animationBlend 0f;// 归一化输入方向Vector3 inputDirection new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;// 注意Vector2 的 ! 运算符使用近似值不会出现浮点误差且比计算向量长度更高效// 如果有移动输入并且角色正在移动则旋转角色if (_input.move ! Vector2.zero){// 计算目标旋转角度_targetRotation Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg _mainCamera.transform.eulerAngles.y;// 平滑旋转角色float rotation Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,RotationSmoothTime);// 旋转角色以面向输入方向相对于相机位置transform.rotation Quaternion.Euler(0.0f, rotation, 0.0f);}// 计算目标移动方向Vector3 targetDirection Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;// 移动角色_controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);// 如果有动画器组件更新动画参数if (_hasAnimator){_animator.SetFloat(_animIDSpeed, _animationBlend);_animator.SetFloat(_animIDMotionSpeed, inputMagnitude);}}// 处理跳跃和重力逻辑private void JumpAndGravity(){if (Grounded){// 重置下落超时计时器_fallTimeoutDelta FallTimeout;// 如果有动画器组件更新动画参数if (_hasAnimator){_animator.SetBool(_animIDJump, false);_animator.SetBool(_animIDFreeFall, false);}// 当角色着地时避免垂直速度无限下降if (_verticalVelocity 0.0f){_verticalVelocity -2f;}// 处理跳跃逻辑if (_input.jump _jumpTimeoutDelta 0.0f){// 根据跳跃高度和重力计算所需的垂直速度_verticalVelocity Mathf.Sqrt(JumpHeight * -2f * Gravity);// 如果有动画器组件更新动画参数if (_hasAnimator){_animator.SetBool(_animIDJump, true);}}// 处理跳跃超时逻辑if (_jumpTimeoutDelta 0.0f){_jumpTimeoutDelta - Time.deltaTime;}}else{// 重置跳跃超时计时器_jumpTimeoutDelta JumpTimeout;// 处理下落超时逻辑if (_fallTimeoutDelta 0.0f){_fallTimeoutDelta - Time.deltaTime;}else{// 如果有动画器组件更新动画参数if (_hasAnimator){_animator.SetBool(_animIDFreeFall, true);}}// 角色未着地时禁止跳跃_input.jump false;}// 应用重力当垂直速度未达到终端速度时逐渐增加垂直速度if (_verticalVelocity _terminalVelocity){_verticalVelocity Gravity * Time.deltaTime;}}// 限制角度范围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);}// 当对象在场景视图中被选中时绘制调试辅助线private void OnDrawGizmosSelected(){Color transparentGreen new Color(0.0f, 1.0f, 0.0f, 0.35f);Color transparentRed new Color(1.0f, 0.0f, 0.0f, 0.35f);// 根据角色是否着地设置调试线颜色if (Grounded) Gizmos.color transparentGreen;else Gizmos.color transparentRed;// 绘制着地检测球体的调试线Gizmos.DrawSphere(new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z),GroundedRadius);}// 脚步声事件处理private void OnFootstep(AnimationEvent animationEvent){if (animationEvent.animatorClipInfo.weight 0.5f){if (FootstepAudioClips.Length 0){// 随机选择一个脚步声音频剪辑var index Random.Range(0, FootstepAudioClips.Length);// 在角色中心位置播放脚步声音频AudioSource.PlayClipAtPoint(FootstepAudioClips[index], transform.TransformPoint(_controller.center), FootstepAudioVolume);}}}// 着陆事件处理private void OnLand(AnimationEvent animationEvent){if (animationEvent.animatorClipInfo.weight 0.5f){// 在角色中心位置播放着陆音频AudioSource.PlayClipAtPoint(LandingAudioClip, transform.TransformPoint(_controller.center), FootstepAudioVolume);}}}
} 类图
分步解析
1.初始化
private void Awake()
{// 获取主相机的引用if (_mainCamera null){_mainCamera GameObject.FindGameObjectWithTag(MainCamera);}
}private void Start()
{// 初始化 Cinemachine 相机目标的偏航角_cinemachineTargetYaw CinemachineCameraTarget.transform.rotation.eulerAngles.y;// 尝试获取动画器组件_hasAnimator TryGetComponent(out _animator);// 获取角色控制器组件_controller GetComponentCharacterController();// 获取输入组件_input GetComponentStarterAssetsInputs();
#if ENABLE_INPUT_SYSTEM // 获取玩家输入组件_playerInput GetComponentPlayerInput();
#elseDebug.LogError( Starter Assets 包缺少依赖项请使用 Tools/Starter Assets/Reinstall Dependencies 进行修复);
#endif// 分配动画 IDAssignAnimationIDs();// 初始化跳跃和下落超时计时器_jumpTimeoutDelta JumpTimeout;_fallTimeoutDelta FallTimeout;
}private void AssignAnimationIDs()
{_animIDSpeed Animator.StringToHash(Speed);_animIDGrounded Animator.StringToHash(Grounded);_animIDJump Animator.StringToHash(Jump);_animIDFreeFall Animator.StringToHash(FreeFall);_animIDMotionSpeed Animator.StringToHash(MotionSpeed);
}
Awake 方法在对象实例化时调用用于获取主相机的引用。Start 方法在对象启用后调用进行一系列的初始化操作 初始化 Cinemachine 相机的偏航角获取所需的组件如 Animator、CharacterController、StarterAssetsInputs 和 PlayerInput调用 AssignAnimationIDs 方法分配动画参数的哈希 ID初始化跳跃和下落超时计时器 2.交互处理
private StarterAssetsInputs _input;// 在 Move 方法中使用输入
private void Move()
{float targetSpeed _input.sprint ? SprintSpeed : MoveSpeed;if (_input.move Vector2.zero) targetSpeed 0.0f;Vector3 inputDirection new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;// ...
}// 在 JumpAndGravity 方法中使用输入
private void JumpAndGravity()
{if (Grounded _input.jump _jumpTimeoutDelta 0.0f){_verticalVelocity Mathf.Sqrt(JumpHeight * -2f * Gravity);if (_hasAnimator){_animator.SetBool(_animIDJump, true);}}// ...
}// 在 CameraRotation 方法中使用输入
private void CameraRotation()
{if (_input.look.sqrMagnitude _threshold !LockCameraPosition){float deltaTimeMultiplier IsCurrentDeviceMouse ? 1.0f : Time.deltaTime;_cinemachineTargetYaw _input.look.x * deltaTimeMultiplier;_cinemachineTargetPitch _input.look.y * deltaTimeMultiplier;}// ...
}
_input 是 StarterAssetsInputs 类的实例用于获取玩家的移动、冲刺、跳跃和视角输入。在 Move 方法中根据 _input.sprint 判断是否冲刺根据 _input.move 确定移动方向和目标速度。在 JumpAndGravity 方法中根据 _input.jump 判断是否触发跳跃。在 CameraRotation 方法中根据 _input.look 控制相机的旋转
3.移动方法
private void Move()
{// 根据是否按下冲刺键设置目标速度float targetSpeed _input.sprint ? SprintSpeed : MoveSpeed;// 如果没有输入将目标速度设为 0if (_input.move Vector2.zero) targetSpeed 0.0f;// 获取玩家当前的水平速度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;// 加速或减速到目标速度if (currentHorizontalSpeed targetSpeed - speedOffset ||currentHorizontalSpeed targetSpeed speedOffset){_speed Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude,Time.deltaTime * SpeedChangeRate);_speed Mathf.Round(_speed * 1000f) / 1000f;}else{_speed targetSpeed;}// 插值计算动画混合值_animationBlend Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);if (_animationBlend 0.01f) _animationBlend 0f;// 归一化输入方向Vector3 inputDirection new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;// 如果有移动输入并且角色正在移动则旋转角色if (_input.move ! Vector2.zero){_targetRotation Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg _mainCamera.transform.eulerAngles.y;float rotation Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,RotationSmoothTime);transform.rotation Quaternion.Euler(0.0f, rotation, 0.0f);}// 计算目标移动方向Vector3 targetDirection Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;// 移动角色_controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);// 如果有动画器组件更新动画参数if (_hasAnimator){_animator.SetFloat(_animIDSpeed, _animationBlend);_animator.SetFloat(_animIDMotionSpeed, inputMagnitude);}
}
根据玩家的冲刺输入设置目标速度如果没有移动输入则将目标速度设为 0计算当前水平速度并根据当前速度和目标速度的差异使用 Mathf.Lerp 进行平滑加速或减速。计算动画混合值用于控制动画的过渡根据玩家的移动输入计算目标旋转角度并使用 Mathf.SmoothDampAngle 进行平滑旋转计算目标移动方向并使用 CharacterController.Move 方法移动角色如果有动画器组件更新动画参数 _animIDSpeed 和 _animIDMotionSpeed 4.跳跃和重力处理
private void JumpAndGravity()
{if (Grounded){// 重置下落超时计时器_fallTimeoutDelta FallTimeout;// 如果有动画器组件更新动画参数if (_hasAnimator){_animator.SetBool(_animIDJump, false);_animator.SetBool(_animIDFreeFall, false);}// 当角色着地时避免垂直速度无限下降if (_verticalVelocity 0.0f){_verticalVelocity -2f;}// 处理跳跃逻辑if (_input.jump _jumpTimeoutDelta 0.0f){_verticalVelocity Mathf.Sqrt(JumpHeight * -2f * Gravity);if (_hasAnimator){_animator.SetBool(_animIDJump, true);}}// 处理跳跃超时逻辑if (_jumpTimeoutDelta 0.0f){_jumpTimeoutDelta - Time.deltaTime;}}else{// 重置跳跃超时计时器_jumpTimeoutDelta JumpTimeout;// 处理下落超时逻辑if (_fallTimeoutDelta 0.0f){_fallTimeoutDelta - Time.deltaTime;}else{if (_hasAnimator){_animator.SetBool(_animIDFreeFall, true);}}// 角色未着地时禁止跳跃_input.jump false;}// 应用重力当垂直速度未达到终端速度时逐渐增加垂直速度if (_verticalVelocity _terminalVelocity){_verticalVelocity Gravity * Time.deltaTime;}
}
如果角色着地 重置下落超时计时器。更新动画参数将跳跃和自由落体状态设为 false。确保垂直速度不会无限下降。如果玩家按下跳跃键且跳跃超时计时器已过则根据跳跃高度和重力计算垂直速度并更新动画参数。递减跳跃超时计时器。 如果角色未着地 重置跳跃超时计时器。递减下落超时计时器如果超时则更新动画参数为自由落体状态。禁止跳跃输入。 应用重力使垂直速度逐渐增加直到达到终端速度 5.着地检测
private void GroundedCheck()
{// 设置球体位置并添加偏移量Vector3 spherePosition new Vector3(transform.position.x, transform.position.y - GroundedOffset,transform.position.z);// 检测球体范围内是否与地面图层发生碰撞Grounded Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers,QueryTriggerInteraction.Ignore);// 如果有动画器组件更新动画参数if (_hasAnimator){_animator.SetBool(_animIDGrounded, Grounded);}
}
在角色位置下方设置一个球体使用 Physics.CheckSphere 方法检测球体是否与指定的地面图层发生碰撞。根据检测结果更新 Grounded 变量。如果有动画器组件更新动画参数 _animIDGrounded。 6.相机旋转处理
private void CameraRotation()
{// 如果有鼠标或其他输入并且相机位置未锁定if (_input.look.sqrMagnitude _threshold !LockCameraPosition){// 根据当前输入设备确定时间乘数float deltaTimeMultiplier IsCurrentDeviceMouse ? 1.0f : Time.deltaTime;// 更新相机的偏航角和俯仰角_cinemachineTargetYaw _input.look.x * deltaTimeMultiplier;_cinemachineTargetPitch _input.look.y * deltaTimeMultiplier;}// 限制相机的旋转角度在 360 度范围内_cinemachineTargetYaw ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);_cinemachineTargetPitch ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);// 设置 Cinemachine 相机目标的旋转角度CinemachineCameraTarget.transform.rotation Quaternion.Euler(_cinemachineTargetPitch CameraAngleOverride,_cinemachineTargetYaw, 0.0f);
}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);
}
如果有视角输入且相机位置未锁定根据输入更新相机的偏航角和俯仰角同时根据输入设备确定时间乘数。使用 ClampAngle 方法限制相机的旋转角度在指定范围内。设置 Cinemachine 相机目标的旋转角度。 7..动画处理
// 在 Move 方法中更新动画参数
if (_hasAnimator)
{_animator.SetFloat(_animIDSpeed, _animationBlend);_animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
}// 在 GroundedCheck 方法中更新动画参数
if (_hasAnimator)
{_animator.SetBool(_animIDGrounded, Grounded);
}// 在 JumpAndGravity 方法中更新动画参数
if (_hasAnimator)
{_animator.Set
四.角色推动刚体类 这个类是一个单独挂载于player的类 已经详细标明了注释 还请自行查看
using UnityEngine;// 该类用于实现角色推动刚体的功能
public class BasicRigidBodyPush : MonoBehaviour
{// 可推动刚体所在的图层遮罩只有这些图层的刚体才能被推动public LayerMask pushLayers;// 是否允许推动刚体的开关若为 false 则不会触发推动逻辑public bool canPush;// 推动刚体的力量强度取值范围在 0.5f 到 5f 之间默认值为 1.1f[Range(0.5f, 5f)] public float strength 1.1f;// 当角色控制器与其他碰撞体发生碰撞时调用此方法private void OnControllerColliderHit(ControllerColliderHit hit){// 只有当 canPush 为 true 时才调用 PushRigidBodies 方法来处理推动逻辑if (canPush) PushRigidBodies(hit);}// 处理推动刚体的具体逻辑private void PushRigidBodies(ControllerColliderHit hit){// 参考文档https://docs.unity3d.com/ScriptReference/CharacterController.OnControllerColliderHit.html// 获取碰撞体所附着的刚体组件Rigidbody body hit.collider.attachedRigidbody;// 如果没有刚体或者刚体是运动学刚体即不受物理模拟影响则不进行推动操作直接返回if (body null || body.isKinematic) return;// 获取刚体所在游戏对象的图层对应的图层遮罩var bodyLayerMask 1 body.gameObject.layer;// 检查刚体所在的图层是否在可推动的图层范围内如果不在则不进行推动操作直接返回if ((bodyLayerMask pushLayers.value) 0) return;// 如果角色的移动方向主要是向下y 轴分量小于 -0.3f则不进行推动操作直接返回// 这是为了避免角色在向下移动时推动下方的物体if (hit.moveDirection.y -0.3f) return;// 计算推动方向只考虑水平方向的移动忽略垂直方向Vector3 pushDir new Vector3(hit.moveDirection.x, 0.0f, hit.moveDirection.z);// 对刚体施加力力的大小为推动方向乘以推动强度力的模式为冲量模式// 冲量模式会瞬间改变刚体的动量body.AddForce(pushDir * strength, ForceMode.Impulse);}
}