Unity第一人称控制器冲刺状态帧切换异常,stamina系统失效求助
第一人称控制器冲刺功能状态频繁切换问题修复
问题概述
开发第一人称控制器冲刺功能,架构包含PlayerMovementController、PlayerStatusController和存储 stamina 数据的 Scriptable Object PlayerStatusData。游戏视图中冲刺看似正常,但 Inspector 里isSprinting布尔值和movementSpeed会在true/false、3f/1.8f之间频繁切换,导致 stamina 系统的 Drain 和 Regen 事件反复触发,无法正常工作。
相关代码
PlayerMovementController 代码片段
private void Update() { HandleMovementInput(); ApplyMovement(); } private void HandleMovementInput() { if (Input.GetKey(sprintKey) && !isSprinting) // SPRINTING { if (OnCheckStamina != null && OnCheckStamina()) { StartSprint(); OnStaminaDrain?.Invoke(); } } else // NOT SPRINTING { StopSprint(); OnStaminaRegen?.Invoke(); } /*** SPRINTING ENDE ***/ currentInput = new Vector2(movementSpeed * Input.GetAxis("Vertical"), movementSpeed * Input.GetAxis("Horizontal")); float moveDirectionY = moveDirection.y; moveDirection = (transform.TransformDirection(Vector3.forward) * currentInput.x) + (transform.TransformDirection(Vector3.right) * currentInput.y); moveDirection.y = moveDirectionY; } private void ApplyMovement() { if (!characterController.isGrounded) moveDirection.y -= gravity * Time.deltaTime; characterController.Move(moveDirection * Time.deltaTime); } private void StartSprint() { isSprinting = true; movementSpeed = sprintSpeed; } private void StopSprint() { isSprinting = false; movementSpeed = walkSpeed; }
PlayerStatusController 完整代码
public class PlayerStatusController : MonoBehaviour { [Header("Scriptable Object")] public PlayerStatusData playerStatusData, pSD; private void OnEnable() { PlayerMovementController.OnCheckStamina += HasStamina; PlayerMovementController.OnStaminaDrain += DrainStamina; PlayerMovementController.OnStaminaRegen += RegenerateStamina; } private void OnDisable() { PlayerMovementController.OnCheckStamina -= HasStamina; PlayerMovementController.OnStaminaDrain -= DrainStamina; PlayerMovementController.OnStaminaRegen -= RegenerateStamina; } private bool HasStamina() { return playerStatusData.currentStamina > 0; } private void DrainStamina() { if (playerStatusData.currentStamina != playerStatusData.minStamina) { playerStatusData.currentStamina -= playerStatusData.staminaDrain * Time.deltaTime; playerStatusData.currentStamina = Mathf.Max(0, playerStatusData.currentStamina); // Ensure stamina doesn't go below 0 Debug.Log("DRAINING STAMINA"); } } private void RegenerateStamina() { if (playerStatusData.currentStamina < playerStatusData.maxStamina) { playerStatusData.currentStamina += playerStatusData.staminaRegen * Time.deltaTime; playerStatusData.currentStamina = Mathf.Min(100, playerStatusData.currentStamina); // Ensure stamina doesn't go above 100 Debug.Log("REGENERATING STAMINA"); } } }
问题根源
- 状态切换逻辑错误:原代码中,当按住冲刺键触发
StartSprint将isSprinting设为true后,下一帧因isSprinting为true不满足Input.GetKey(sprintKey) && !isSprinting条件,直接进入else分支调用StopSprint,导致状态在true/false之间反复横跳。 - 事件触发时机错误:每帧都会无条件触发
OnStaminaDrain或OnStaminaRegen,而非仅在状态切换时触发初始事件、状态持续期间持续执行逻辑。
修复方案
1. 修正冲刺状态判断逻辑
调整HandleMovementInput的条件判断,仅当实际需要切换状态时才执行对应操作:
private void HandleMovementInput() { // 先判断是否满足冲刺条件 bool shouldSprint = Input.GetKey(sprintKey) && (OnCheckStamina?.Invoke() ?? false); // 仅在状态需要切换时执行操作 if (shouldSprint && !isSprinting) { StartSprint(); } else if (!shouldSprint && isSprinting) { StopSprint(); } // 原有移动输入逻辑保持不变 currentInput = new Vector2(movementSpeed * Input.GetAxis("Vertical"), movementSpeed * Input.GetAxis("Horizontal")); float moveDirectionY = moveDirection.y; moveDirection = (transform.TransformDirection(Vector3.forward) * currentInput.x) + (transform.TransformDirection(Vector3.right) * currentInput.y); moveDirection.y = moveDirectionY; }
2. 调整 stamina 事件触发时机
将 stamina 消耗/恢复的事件触发移到Update中,根据当前状态持续执行:
private void Update() { HandleMovementInput(); // 根据当前冲刺状态持续触发对应事件 if (isSprinting) { OnStaminaDrain?.Invoke(); } else { OnStaminaRegen?.Invoke(); } ApplyMovement(); }
3. 优化 PlayerStatusController 浮点精度问题
移除!= playerStatusData.minStamina的判断,改用>避免浮点精度导致的逻辑异常:
private void DrainStamina() { if (playerStatusData.currentStamina > playerStatusData.minStamina) { playerStatusData.currentStamina -= playerStatusData.staminaDrain * Time.deltaTime; playerStatusData.currentStamina = Mathf.Max(playerStatusData.minStamina, playerStatusData.currentStamina); Debug.Log("DRAINING STAMINA"); } }
内容的提问来源于stack exchange,提问作者archivenine
相关产品推荐
相关产品推荐

