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

Unity 2D玩家射击次数超限Bug排查求助

Unity 2D玩家控制器射击次数超限Bug排查与修复

问题描述

玩家设定最多可射击2次,需落地 reload 后才能再次射击,但实际场景中,玩家射击1次后等待片刻,可连续射击3次,超出次数上限。

根源分析

  1. 输入处理位置错误:射击逻辑依赖Input.GetMouseButtonDown(0),该API仅在Update帧中准确响应单次按下,放在FixedUpdate中可能导致单次鼠标按下被多次检测,额外增加shotsFired计数。
  2. 冷却时间时间源不匹配:FixedUpdate中使用Time.time(对应Update时间戳)判断冷却,而非Time.fixedTime(FixedUpdate专属时间戳),可能导致冷却提前失效,允许超出次数射击。
  3. 变量初始化不明确:shotsFired未在Start中明确初始化,可能因默认值异常导致计数逻辑混乱。

修复方案

1. 将输入相关逻辑移至Update方法

输入事件必须在Update中处理,确保单次操作仅被响应一次:

private void Update()
{
    // 鼠标朝向与射击方向计算
    Vector2 mousePosition = mainCamera.ScreenToWorldPoint(Input.mousePosition);
    Vector2 shotDirection = (mousePosition - (Vector2)transform.position).normalized;

    // 射击逻辑
    if (Input.GetMouseButtonDown(0) && shotsFired < maxShotsBeforeReload && !isReloading && !animator.GetBool("IsLayingDown") && Time.time - lastShotTime >= shotCooldown)
    {
        shotsFired++;
        lastShotTime = Time.time;

        rb.AddForce(-shotDirection * recoilForce, ForceMode2D.Impulse);
        animator.SetBool("IsRecoiling", true);
        StartCoroutine(StopRecoilAnimation());

        audioSource.PlayOneShot(shootSound);
    }

    // 玩家朝向控制
    float facingDirection = spriteRenderer.flipX ? -1.0f : 1.0f;
    animator.SetFloat("LookHorizontal", shotDirection.x * facingDirection);
    animator.SetFloat("LookVertical", shotDirection.y);
    
    if (!animator.GetBool("IsLayingDown"))
    {
        spriteRenderer.flipX = shotDirection.x < 0;
    }

    // 鼠标移动与Idle状态判断
    if (Input.GetAxis("Mouse X") != 0 || Input.GetAxis("Mouse Y") != 0)
    {
        lastMouseMovementTime = Time.time;
        animator.SetBool("MouseMoving", true);
    }
    else
    {
        animator.SetBool("MouseMoving", false);
    }

    animator.SetBool("IsIdle", Time.time - lastMouseMovementTime > idleTimeThreshold);
}

2. 修正FixedUpdate中的时间依赖与逻辑分离

保留物理相关逻辑在FixedUpdate中,确保时间判断使用Time.fixedTime:

private void FixedUpdate()
{
    isGrounded = Physics2D.OverlapBox(groundCheck.position, groundCheckSize, 0, groundLayer);

    // 下落与落地逻辑
    if (rb.velocity.y < 0)
    {
        timeFalling += Time.fixedDeltaTime;

        if (timeFalling >= windSoundDelay && !isPlayingWindSound && !isFalling)
        {
            isFalling = true;
            animator.SetBool("IsFalling", isFalling);

            if (!windAudioSource.isPlaying)
            {
                windAudioSource.clip = windSound;
                windAudioSource.Play();
                isPlayingWindSound = true;
            }
        }

        if (isPlayingWindSound)
        {
            float volumeFactor = Mathf.Clamp(timeFalling / 10.0f, 0.0f, 1.0f);
            windAudioSource.volume = volumeFactor;
        }
    }
    else if (isGrounded)
    {
        timeFalling = 0.0f;

        if (isFalling)
        {
            isFalling = false;
            animator.SetBool("IsFalling", isFalling);
            animator.SetBool("IsLayingDown", true);
        }

        if (isPlayingWindSound)
        {
            StartCoroutine(FadeOutWindSound(0.2f));
        }
    }

    // 自动Reload逻辑
    if (shotsFired >= maxShotsBeforeReload && !isReloading && isGrounded)
    {
        isReloading = true;
        // 防止协程重复启动
        if (!IsInvoking(nameof(Reload)))
        {
            StartCoroutine(Reload());
        }
    }

    // 地面停留时间计算
    groundedTime = isGrounded ? groundedTime + Time.fixedDeltaTime : 0.0f;

    // 移动逻辑
    float horizontalInput = Input.GetAxis("Horizontal");
    animator.SetFloat("Speed", Mathf.Abs(horizontalInput));
    
    if (Mathf.Abs(horizontalInput) > 0 && isGrounded)
    {
        animator.SetBool("IsLayingDown", false);
    }

    if (isGrounded || canMoveFreely)
    {
        Vector2 horizontalForce = new Vector2(horizontalInput * moveSpeed, 0);
        rb.AddForce(horizontalForce, ForceMode2D.Force);
    }

    // 速度限制
    if (rb.velocity.magnitude > maxSpeed)
    {
        rb.velocity = rb.velocity.normalized * maxSpeed;
    }

    animator.SetBool("IsGrounded", isGrounded);
}

3. 明确初始化变量

在Start方法中初始化关键变量,避免默认值异常:

private void Start()
{
    rb = GetComponent<Rigidbody2D>();
    spriteRenderer = GetComponent<SpriteRenderer>();
    animator = GetComponent<Animator>();
    
    shotsFired = 0;
    lastShotTime = Time.time;
    lastMouseMovementTime = Time.time;
}

额外排查建议

  • 在shotsFired变量的修改处添加日志,追踪数值变化:
    // 射击时添加日志
    shotsFired++;
    Debug.Log($"当前射击次数: {shotsFired}");
    
    // Reload时添加日志
    shotsFired = 0;
    Debug.Log($"Reload完成,射击次数重置为: {shotsFired}");
    
  • 检查Initialize方法是否被意外调用,若无需外部初始化可直接删除。

内容的提问来源于stack exchange,提问作者Parker Vincent

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 23:37:06