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

Unity2D中OnTrigger/OnCollision方法被重复调用三次的问题

Unity2D中OnTrigger/OnCollision重复触发三次的问题排查

我在开发一款类Mario的Unity2D游戏,添加了交互时玩家停止移动的功能(用于NPC交互和过场动画)后,发现OnTrigger/OnCollision方法被触发三次,调试日志显示触发次数远超预期(仅需一次)。

调试截图:
触发三次的调试日志

相关C#代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class playerMovement : MonoBehaviour
{
    public float moveSpeed;
    private float moveDirection;
    public float jumpForce;
    [SerializeField] private bool isJumping = false;

    private Rigidbody2D rb;
    private bool facingRight = true;

    public Transform ceilingCheck;
    public Transform groundCheck;
    public LayerMask groundObjects;
    public bool isGrounded;
    public float checkRadius;
    public GameObject camera;

    public AudioSource audioSource;
    public AudioClip otherClip;

    public float score;
    public TextMeshProUGUI scoreCount;

    public float health;
    public TextMeshProUGUI lifeCount;

    public GameObject[] enemy;
    public GameObject deathBarrier;
    public GameObject nPC;
    public GameObject youHaveSoMuchToLiveFor;
    public GameObject youCannotEscape;

    public bool talking;
    public bool bossTimerStart;


    //nabs stuff before start so there's no lag and so you don't have to nab it yourself
    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    // Start is called before the first frame update
    void Start()
    {
        enemy = GetComponentsInParent<GameObject>(tag == "Enemy");
    }

    // Update is called once per frame
    void Update()
    {
        //get inputs
        ProcessInputs();

        //flip
        Animate();

        StartCoroutine(DoThings());

        scoreCount.text = "Score: " + score;
        lifeCount.text = "Health: " + health;

        if (Input.GetKeyDown(KeyCode.RightShift))
        {
            moveSpeed = 3;

        }
        if (Input.GetKeyUp(KeyCode.RightShift))
        {
            moveSpeed = 8;
        }

        if (Input.GetKeyDown(KeyCode.LeftShift))
        {
            moveSpeed = 3;
       

        }
        if (Input.GetKeyUp(KeyCode.LeftShift))
        {
            moveSpeed = 8;
            
        }

    

}

    private void FixedUpdate()
    {
        //move
        if (!talking)
        {
            Move();
        }

        isGrounded = Physics2D.OverlapCircle(groundCheck.position, checkRadius, groundObjects);
    }

    private void ProcessInputs()
    {
        moveDirection = Input.GetAxis("Horizontal");
        if (Input.GetButtonDown("Jump") && isGrounded)
        {
            isJumping = true;
        }
    }

    public IEnumerator DoThings()
    {
        while (talking)
        {
            rb.velocity = Vector2.zero;
            yield return null;
        }
    }


    private void Move()
    {
        rb.velocity = new Vector2(moveDirection * moveSpeed, rb.velocity.y);
        if (isJumping)
        {
            rb.AddForce(new Vector2(0f, jumpForce));
        }
        isJumping = false;

    }


    private void FlipCharacter()
    {
        facingRight = !facingRight;
        transform.Rotate(0f, 180f, 0f);

    }

    private void Animate()
    {
        //flip
        if (moveDirection > 0 && !facingRight)
        {
            FlipCharacter();
        }
        if (moveDirection < 0 && facingRight)
        {
            FlipCharacter();
        }
    }
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.tag == "Coin")
        {
            score += 10;
            Destroy(other.gameObject);
        }

        if (other.tag == "Npc")
        {
            nPC.gameObject.SetActive(true);
        }
        if (other.tag == "House")
        {
            youHaveSoMuchToLiveFor.gameObject.SetActive(true);
            talking = true;
        }
    }
    private void OnTriggerExit2D(Collider2D other)
    {
        if (other.tag == "Npc")
        {
            nPC.gameObject.SetActive(false);
        }
        if (other.tag == "House")
        {
            youHaveSoMuchToLiveFor.gameObject.SetActive(false);
        }
        if (other.gameObject.tag == "BossArea")
        {
            Debug.Log("uHOH CUTSCENE TIME WOOOOOO");
            Destroy(other.gameObject);
            youCannotEscape.gameObject.SetActive(true);
        }
    }

    private void OnCollisionEnter2D(Collision2D other)
    {
        if (other.gameObject.tag == "EnemyLeft")
        {
            Debug.Log("scream but right");
            rb.AddForce(new Vector2(5000f, 200f));
            if (health > 0)
            {
                Debug.Log("apparently let's check this");
                health -= 1;
            }
            if (health <= 0)
            {
                StartCoroutine(Woooo());
                StartCoroutine(YouGonDie());
            }
        }
        if (other.gameObject.tag == "EnemyRight")
        {
            Debug.Log("scream but left");
            rb.AddForce(new Vector2(-5000f, 200f));
            if (health > 0)
            {
                health -= 1;
            }
            if (health <= 0)
            {
                StartCoroutine(Woooo());
                StartCoroutine(YouGonDie());
            }
        }
        if (other.gameObject.tag == "Bullet")
        {
            Debug.Log("oopsie you got shot UwU");
            if (health > 0)
            {
                health -= 1;
            }
            if (health <= 0)
            {
                StartCoroutine(Woooo());
                StartCoroutine(YouGonDie());
            }
        }
            if (other.gameObject.tag == "EnemyTop")
        {
            Debug.Log("bonk");
            Destroy(other.transform.parent.gameObject);
            score += 5;
        }
        if (other.gameObject == deathBarrier)
        {
            StartCoroutine(YouFellLol());
        }

        IEnumerator Woooo()
    {
            Debug.Log("istg i think this game is an elaborate moistcritikal reference, truly what i've been waiting for, yeah baby");
            yield return new WaitForSecondsRealtime(1.2f);
        Destroy(gameObject);
    }

        IEnumerator YouGonDie()
        {
            AudioSource.PlayClipAtPoint(otherClip, new Vector3(0, 0, 0));
            yield return new WaitForSecondsRealtime(0.001f);
        }

        IEnumerator YouFellLol()
        {
            Debug.Log("Hey stinky! You fell uwu");
            yield return new WaitForSecondsRealtime(0.001f);
            Destroy(gameObject);
        }


        
    }
    public void TalkingWith()
    {
        talking = true;
    }
    public void StopTalkingWith()
    {
        talking = false;
    }


}

问题原因分析

  • 协程重复启动导致物理异常:Update方法中每帧都调用StartCoroutine(DoThings()),会生成大量重复的协程实例。这些协程不断将刚体速度设为0,导致玩家在碰撞/触发时出现反复的位置抖动,进而多次触发碰撞/触发事件。
  • 多碰撞体重复触发:检查玩家或交互对象是否挂载了多个Collider2D组件,多个碰撞体同时与目标接触会导致事件多次调用。
  • 协程逻辑冗余:DoThings协程的设计不合理,无需每帧启动,应该只在talking状态切换时处理速度归零逻辑,或者直接在FixedUpdate中处理,避免协程带来的冗余。
  • Start方法逻辑错误:enemy = GetComponentsInParent<GameObject>(tag == "Enemy");写法有误,该方法无法通过tag筛选,会错误获取父物体下的所有GameObject,可能导致异常逻辑干扰物理检测。

修复方案

  1. 移除重复协程,重构速度归零逻辑
    将DoThings协程删除,把速度归零逻辑移到FixedUpdate中:

    private void FixedUpdate()
    {
        if (talking)
        {
            rb.velocity = Vector2.zero;
            isGrounded = Physics2D.OverlapCircle(groundCheck.position, checkRadius, groundObjects);
            return;
        }
        Move();
        isGrounded = Physics2D.OverlapCircle(groundCheck.position, checkRadius, groundObjects);
    }
    

    同时删除Update中的StartCoroutine(DoThings())调用。

  2. 检查碰撞体设置
    确保玩家和交互对象(如NPC、House)只有一个用于触发/碰撞的Collider2D组件,若需多个碰撞体,可设置其中一个为IsTrigger,其余用于物理碰撞,避免重复触发事件。

  3. 修正Start方法的敌人查找逻辑
    替换错误的敌人查找代码,改为正确的按tag查找方式:

    void Start()
    {
        enemy = GameObject.FindGameObjectsWithTag("Enemy");
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 19:57:02