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

Unity同脚本物体碰撞OnCollisionEnter2D重复生成对象问题

问题根因

两个挂载相同DropBase脚本的游戏对象发生碰撞时,双方的OnCollisionEnter2D回调会在同一帧分别触发一次。原有逻辑没有做重复执行拦截:A对象触发回调时检测到B带目标组件,执行销毁+生成;B对象触发回调时检测到A还没被真正销毁(Unity的Destroy方法是延迟到当前帧末尾才执行,回调触发时两个对象都处于激活状态),会再执行一次生成逻辑,最终就会生成2个新对象。
之前尝试的存活时长判定方案,在两个对象同帧创建时没有可参考的时长差,自然会失效。

解决方案

方案1:全局碰撞对标记(推荐,适配所有场景)

利用Unity全局唯一的实例ID标记已经处理过的碰撞对,从根源避免重复执行,哪怕多对象同帧碰撞也不会出问题。
完整实现代码如下:

using System.Collections.Generic;
using UnityEngine;

public class DropBase : MonoBehaviour
{
    // 静态全局集合,存储已经处理完成的碰撞对ID
    private static readonly HashSet<(int, int)> HandledCollisions = new HashSet<(int, int)>();
    [SerializeField] private GameObject _pile;

    private void OnCollisionEnter2D(Collision2D collision)
    {
        var targetDrop = collision.gameObject.GetComponent<DropBase>();
        if (targetDrop == null) return;

        // 对两个碰撞对象的实例ID排序,生成统一的碰撞键,避免(A,B)和(B,A)被识别为两次不同碰撞
        int selfId = gameObject.GetInstanceID();
        int targetId = collision.gameObject.GetInstanceID();
        var collisionKey = selfId < targetId ? (selfId, targetId) : (targetId, selfId);

        // 该碰撞对已经处理过,直接跳过
        if (HandledCollisions.Contains(collisionKey)) return;
        
        // 标记碰撞对为已处理
        HandledCollisions.Add(collisionKey);
        
        // 执行业务逻辑
        Destroy(collision.gameObject);
        Instantiate(_pile, transform.position, Quaternion.identity);
        Destroy(gameObject);

        // 帧末清理标记,规避极端情况下实例ID复用导致的异常
        StartCoroutine(ClearMarkNextFrame(collisionKey));
    }

    private System.Collections.IEnumerator ClearMarkNextFrame((int, int) key)
    {
        yield return null;
        HandledCollisions.Remove(key);
    }
}

方案2:本地销毁标记(轻量,适配常规双对象碰撞场景)

不需要静态集合,给每个对象加待销毁标记,只要任意一方已经被标记为待销毁,就不再执行生成逻辑,实现更简单:

using UnityEngine;

public class DropBase : MonoBehaviour
{
    [SerializeField] private GameObject _pile;
    private bool _waitToDestroy = false;

    private void OnCollisionEnter2D(Collision2D collision)
    {
        var targetDrop = collision.gameObject.GetComponent<DropBase>();
        // 无目标组件/自己待销毁/对方待销毁,直接返回
        if (targetDrop == null || _waitToDestroy || targetDrop._waitToDestroy) return;

        // 先标记双方为待销毁,拦截重复触发的回调
        _waitToDestroy = true;
        targetDrop._waitToDestroy = true;

        // 执行业务逻辑
        Destroy(collision.gameObject);
        Instantiate(_pile, transform.position, Quaternion.identity);
        Destroy(gameObject);
    }
}

注意:该方案在3个及以上同脚本对象同帧发生连锁碰撞的极端场景下可能出现判定偏差,普通双物体碰撞场景可以稳定使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 16:09:17