[Unity角色控制专题](借助ai)详细解析官方第三人称控制器
- 创业
- 2025-09-06 15:42:02
详细解析官方第三人称控制器](/0pic/pp_40.jpg)
首先模板链接在这里,你可以直接下载并导入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 是新输入系统的一个重要的结构体,其内部使用一种灵活的数据存储方式,可以根据不同的输入类型存储相应的数据,当调用 Get<T>() 方法时,它会尝试将存储的数据转换为指定的类型,如果转换成功,则返回转换后的值;如果转换失败,可能会抛出异常或者返回默认值,具体取决于输入系统的实现
using UnityEngine; #if ENABLE_INPUT_SYSTEM using UnityEngine.InputSystem; #endif namespace 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_SYSTEM public void OnMove(InputValue value) { MoveInput(value.Get<Vector2>()); } // 处理视角输入事件 public void OnLook(InputValue value) { // 仅当允许使用鼠标光标输入控制视角时才处理 if (cursorInputForLook) { // 将输入的视角向量传递给 LookInput 方法 LookInput(value.Get<Vector2>()); } } // 处理跳跃输入事件 public void OnJump(InputValue value) { // 将跳跃键的按下状态传递给 JumpInput 方法 JumpInput(value.isPressed); } // 处理冲刺输入事件 public void OnSprint(InputValue value) { // 将冲刺键的按下状态传递给 SprintInput 方法 SprintInput(value.isPressed); } #endif public 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))] #endif public 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; // 动画 ID private int _animIDSpeed; private int _animIDGrounded; private int _animIDJump; private int _animIDFreeFall; private int _animIDMotionSpeed; #if ENABLE_INPUT_SYSTEM private PlayerInput _playerInput; #endif private 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_SYSTEM return _playerInput.currentControlScheme == "KeyboardMouse"; #else return 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 = GetComponent<CharacterController>(); // 获取输入组件 _input = GetComponent<StarterAssetsInputs>(); #if ENABLE_INPUT_SYSTEM // 获取玩家输入组件 _playerInput = GetComponent<PlayerInput>(); #else Debug.LogError( "Starter Assets 包缺少依赖项,请使用 Tools/Starter Assets/Reinstall Dependencies 进行修复"); #endif // 分配动画 ID AssignAnimationIDs(); // 初始化跳跃和下落超时计时器 _jumpTimeoutDelta = JumpTimeout; _fallTimeoutDelta = FallTimeout; } private void Update() { // 尝试获取动画器组件 _hasAnimator = TryGetComponent(out _animator); // 处理跳跃和重力逻辑 JumpAndGravity(); // 检查角色是否着地 GroundedCheck(); // 处理角色移动逻辑 Move(); } private void LateUpdate() { // 处理相机旋转逻辑 CameraRotation(); } // 分配动画参数的哈希 ID 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() { // 设置球体位置并添加偏移量 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 的 == 运算符使用近似值,不会出现浮点误差,且比计算向量长度更高效 // 如果没有输入,将目标速度设为 0 if (_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 = GetComponent<CharacterController>(); // 获取输入组件 _input = GetComponent<StarterAssetsInputs>(); #if ENABLE_INPUT_SYSTEM // 获取玩家输入组件 _playerInput = GetComponent<PlayerInput>(); #else Debug.LogError( "Starter Assets 包缺少依赖项,请使用 Tools/Starter Assets/Reinstall Dependencies 进行修复"); #endif // 分配动画 ID AssignAnimationIDs(); // 初始化跳跃和下落超时计时器 _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; // 如果没有输入,将目标速度设为 0 if (_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) { // 参考文档: docs.unity3d /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); } }[Unity角色控制专题](借助ai)详细解析官方第三人称控制器由讯客互联创业栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“[Unity角色控制专题](借助ai)详细解析官方第三人称控制器”
上一篇
Python学习之网络编程