You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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");
        }
    }
}

问题根源

  1. 状态切换逻辑错误:原代码中,当按住冲刺键触发StartSprint将isSprinting设为true后,下一帧因isSprinting为true不满足Input.GetKey(sprintKey) && !isSprinting条件,直接进入else分支调用StopSprint,导致状态在true/false之间反复横跳。
  2. 事件触发时机错误:每帧都会无条件触发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 20:25:58