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

Unity平台游戏多跳机制问题:走下平台后跳跃次数未正确递减

平台游戏多跳机制修复:走出平台后跳跃次数正确递减的方案

问题核心

多跳功能(双跳/三跳)正常,但玩家未触发跳跃、水平走出平台时,可用跳跃次数未按预期递减:比如maxJumps=2时,走出平台后仍能跳2次,期望仅保留1次跳跃机会。要求保留碰撞法线y分量>0.5的判断逻辑,用于区分地面与墙面。

问题根源分析

  1. 原代码中OnCollisionExit2D里的col.contacts在碰撞退出时已无有效数据,无法通过法线判断是否离开地面
  2. 拆分脚部碰撞类后的逻辑存在漏洞:仅通过keyX和keyJump判断,未验证是否离开的是地面(法线y>0.5),且可能因碰撞边缘多次触发导致jumpCount错误递减

修复方案一:基于原代码的最小修改

核心思路:在OnCollisionStay2D中记录是否处于地面状态,在FixedUpdate中检测离开地面的瞬间(从地面变为非地面),且此时未触发跳跃,才递减jumpCount

using UnityEngine;
using UnityEngine.InputSystem;
using System.Collections;

public class PlayerBehavior : MonoBehaviour
{
    [Header("References")]
    public Rigidbody2D rb;
    public Transform respawn;
    public SpriteRenderer spriteRend;
    public Animator animator;

    [Header("Movement")]
    public float moveSpeed = 10f;
    public float jumpHeight = 15f;
    public int jumpCount, maxJumps = 2;

    [Header("Input")]
    private float keyX = 0;
    private bool keyJump = false;
    public InputAction playerMovement, playerJump;

    [Header("Effects")]
    public float fadeDuration = 0.5f;

    // 记录上一帧是否在地面
    private bool wasGrounded = false;
    // 当前是否在地面
    private bool isGrounded = false;

    private void OnEnable()
    {
        playerMovement.Enable();
        playerJump.Enable();
    }

    private void OnDisable()
    {
        playerMovement.Disable();
        playerJump.Disable();
    }

    private void Start()
    {
        spriteRend = GetComponent<SpriteRenderer>();
        spriteRend.flipX = false;

        rb = GetComponent<Rigidbody2D>();
        rb.gravityScale = 6f;

        jumpCount = maxJumps;
        animator = GetComponent<Animator>();
        wasGrounded = false;
        isGrounded = false;
    }

    private void Update()
    {
        keyX = playerMovement.ReadValue<float>();
        rb.linearVelocity = new Vector2(keyX * moveSpeed, rb.linearVelocity.y);

        animator.SetFloat("X Velocity", Mathf.Abs(rb.linearVelocity.x));
        animator.SetFloat("Y Velocity", rb.linearVelocity.y);

        if (keyX > 0) spriteRend.flipX = false;
        else if (keyX < 0) spriteRend.flipX = true;

        keyJump = playerJump.triggered;
        if (keyJump && jumpCount > 0)
        {
            rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpHeight);
            jumpCount--;
            animator.SetBool("isJumping", true);
        }
        else
        {
            animator.SetBool("isJumping", false);
        }
    }

    private void FixedUpdate()
    {
        // 检测从地面变为非地面的瞬间
        if (wasGrounded && !isGrounded)
        {
            // 未触发跳跃且有水平移动(确认是走出平台而非主动跳跃)
            if (!keyJump && Mathf.Abs(keyX) > 0.1f)
            {
                // 确保jumpCount不会减到负数
                if (jumpCount > 0)
                {
                    jumpCount--;
                }
            }
        }
        // 更新上一帧地面状态
        wasGrounded = isGrounded;
        // 重置当前地面状态,等待OnCollisionStay更新
        isGrounded = false;
    }

    private void OnCollisionStay2D(Collision2D col)
    {
        if (col.gameObject.CompareTag("Surface"))
        {
            foreach (ContactPoint2D contact in col.contacts)
            {
                // 仅当接触地面时标记为在地面
                if (contact.normal.y > 0.5f)
                {
                    isGrounded = true;
                    break;
                }
            }
        }
    }

    private void OnCollisionEnter2D(Collision2D col)
    {
        if (col.gameObject.CompareTag("Surface"))
        {
            foreach (ContactPoint2D contact in col.contacts)
            {
                if (contact.normal.y > 0.5f)
                {
                    jumpCount = maxJumps;
                    break;
                }
            }
        }

        if (col.gameObject.CompareTag("Hazard"))
        {
            StartCoroutine(DeathEffect());
        }
    }

    private IEnumerator DeathEffect()
    {
        SpriteRenderer sr = GetComponent<SpriteRenderer>();
        Color originalColor = sr.color;

        float t = 0;
        while (t < fadeDuration)
        {
            t += Time.deltaTime;
            sr.color = new Color(
                originalColor.r,
                originalColor.g,
                originalColor.b,
                1 - (t / fadeDuration)
            );
            yield return null;
        }

        sr.color = new Color(originalColor.r, originalColor.g, originalColor.b, 1);
        transform.position = respawn.transform.position;
    }
}

修复方案二:优化脚部碰撞类实现

如果坚持使用脚部碰撞检测,需在脚部类中保留地面接触的法线判断,并在玩家类中正确处理状态变化:

脚部碰撞类(PlayerFeetBehavior)

using UnityEngine;

public class PlayerFeetBehavior : MonoBehaviour
{
    private PlayerBehavior playerBehavior;
    // 记录脚部是否接触地面
    public bool IsGrounded { get; private set; }

    private void Start()
    {
        playerBehavior = GetComponentInParent<PlayerBehavior>();
        IsGrounded = false;
    }

    private void OnCollisionStay2D(Collision2D col)
    {
        if (col.gameObject.CompareTag("Surface"))
        {
            foreach (ContactPoint2D contact in col.contacts)
            {
                if (contact.normal.y > 0.5f)
                {
                    IsGrounded = true;
                    return;
                }
            }
        }
        IsGrounded = false;
    }

    private void OnCollisionExit2D(Collision2D col)
    {
        if (col.gameObject.CompareTag("Surface"))
        {
            IsGrounded = false;
        }
    }
}

玩家行为类(PlayerBehavior)修改关键部分

using UnityEngine;
using UnityEngine.InputSystem;
using System.Collections;

public class PlayerBehavior : MonoBehaviour
{
    // 新增脚部引用
    public PlayerFeetBehavior feet;

    private bool wasGrounded = false;

    private void Start()
    {
        // ... 原有代码 ...
        wasGrounded = false;
        // 确保脚部引用已赋值
        if (feet == null)
        {
            feet = GetComponentInChildren<PlayerFeetBehavior>();
        }
    }

    private void FixedUpdate()
    {
        if (wasGrounded && !feet.IsGrounded)
        {
            if (!keyJump && Mathf.Abs(keyX) > 0.1f)
            {
                if (jumpCount > 0)
                {
                    jumpCount--;
                }
            }
        }
        wasGrounded = feet.IsGrounded;
    }

    private void OnCollisionEnter2D(Collision2D col)
    {
        if (col.gameObject.CompareTag("Surface"))
        {
            foreach (ContactPoint2D contact in col.contacts)
            {
                if (contact.normal.y > 0.5f)
                {
                    jumpCount = maxJumps;
                    break;
                }
            }
        }

        if (col.gameObject.CompareTag("Hazard"))
        {
            StartCoroutine(DeathEffect());
        }
    }

    // ... 原有其他代码保持不变 ...
}

关键修改说明

  1. 使用OnCollisionStay2D而非OnCollisionExit2D获取有效碰撞法线,确保正确判断地面接触
  2. 通过wasGrounded和isGrounded的状态变化,精准捕捉离开地面的瞬间
  3. 增加Mathf.Abs(keyX) > 0.1f的判断,确保仅在水平移动走出平台时才递减跳跃次数,排除主动跳跃的情况
  4. 对jumpCount做下限保护,避免减到负数

内容的提问来源于stack exchange,提问作者ScienceCode Creations

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 09:34:57