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,可能导致异常逻辑干扰物理检测。
修复方案
移除重复协程,重构速度归零逻辑
将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())调用。检查碰撞体设置
确保玩家和交互对象(如NPC、House)只有一个用于触发/碰撞的Collider2D组件,若需多个碰撞体,可设置其中一个为IsTrigger,其余用于物理碰撞,避免重复触发事件。修正Start方法的敌人查找逻辑
替换错误的敌人查找代码,改为正确的按tag查找方式:void Start() { enemy = GameObject.FindGameObjectsWithTag("Enemy"); }
内容的提问来源于stack exchange,提问作者PolyRose
相关产品推荐
相关产品推荐

