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

Unity如何实现预制体实例独立交互?解决碰撞批量触发问题

问题:Unity中事件触发导致所有物品同时响应的问题

我是C#和Unity3D新手,做超休闲游戏时遇到事件相关的问题。需求是玩家碰撞不同物品触发不同行为:

  • 碰撞炸弹:销毁该炸弹并播放爆炸动画
  • 碰撞金币:销毁金币、播放动画并加分

当前问题:玩家碰撞单个炸弹时,场景里所有炸弹和金币都会被销毁并播放动画,我需要每个预制体实例独立响应,而且没使用任何静态字段。

代码结构

1. 抽象类Item(继承自MonoBehaviour)

public abstract class Item : MonoBehaviour
{
    protected OnCollisionEvent _onCollisionEventScript;

    private void Awake()
    {
        _onCollisionEventScript = FindObjectOfType<OnCollisionEvent>();
    }

    protected virtual void HandleAnimation() { }

    protected virtual void DestroyOnHitting() { }
}

2. Bomb类(继承自Item)

public class Bomb : Item
{
    private void OnEnable()
    {
        _onCollisionEventScript.InteractOnCollision += HandleAnimation;
        _onCollisionEventScript.InteractOnCollision += DestroyOnHitting;
    }

    private void OnDisable()
    {
        _onCollisionEventScript.InteractOnCollision -= HandleAnimation;
        _onCollisionEventScript.InteractOnCollision -= DestroyOnHitting;
    }

    protected override void HandleAnimation()
    {
        Debug.Log("EXPLOSION");
    }

    protected override void DestroyOnHitting()
    {
        Destroy(gameObject);
    }
}

3. 挂载在Player上的OnCollisionEvent脚本

public class OnCollisionEvent : MonoBehaviour
{
    public event UnityAction InteractOnCollision;

    private void OnTriggerEnter(Collider other)
    {
        InteractOnCollision?.Invoke();
    }
}

4. SpawnManager生成脚本

public class SpawnManager : MonoBehaviour
{
    [SerializeField] private List<Item> ItemPrefabs;
    [SerializeField] private float SpawnRepeatRate;

    private readonly List<Item> _items = new();
    private float _time = 0f;

    private void Start()
    {
        foreach (var itemPrefab in ItemPrefabs)
            _items.Add(itemPrefab);
    }

    private void Update()
    {
        _time += Time.deltaTime;

        Item randomItem = _items[Random.Range(0, _items.Count)];

        if (_time >= SpawnRepeatRate)
        {
            Instantiate(randomItem, GetRandomSpawnPosition(), Quaternion.identity);

            _time = 0f;
        }
    }

    private Vector3 GetRandomSpawnPosition()
    {
        int[] CoordinatesOnY = new int[11];
        CoordinatesOnY[0] = 2;

        for (int i = 1; i < CoordinatesOnY.Length; i++)
            CoordinatesOnY[i] = CoordinatesOnY[i - 1] + 2;

        int randomSpawnPositionAlongY = Random.Range(CoordinatesOnY[0], CoordinatesOnY.Length);

        Vector3 spawnPosition = new Vector3(24f, randomSpawnPositionAlongY, 0f);

        return spawnPosition;
    }
}

疑问

错误出在事件与引用传递,还是SpawnManager的实现与引用传递?我怀疑事件使用有误,考虑在SpawnManager中创建预制体引用列表,但不知如何实现。


解决方案

问题根源

核心问题是事件设计逻辑错误:所有物品都监听了Player身上的同一个InteractOnCollision事件,当玩家触发任何碰撞时,这个事件会通知所有订阅它的物品,导致所有物品同时执行响应逻辑,完全没区分“到底碰撞了哪个物品”。

修正方案

不需要用全局事件处理碰撞,直接让每个Item子类自己处理自身的碰撞事件即可,这是Unity处理碰撞的常规方式:

1. 重构Item抽象类

移除全局事件引用,让每个Item自己处理碰撞:

public abstract class Item : MonoBehaviour
{
    private void OnTriggerEnter(Collider other)
    {
        // 只响应玩家的碰撞(记得给Player添加"Player"标签)
        if (other.CompareTag("Player"))
        {
            HandleCollision();
        }
    }

    // 让子类实现各自的碰撞处理逻辑
    protected abstract void HandleCollision();
}

2. 重构Bomb类

实现自身的碰撞响应逻辑:

public class Bomb : Item
{
    protected override void HandleCollision()
    {
        HandleAnimation();
        DestroyOnHitting();
    }

    private void HandleAnimation()
    {
        Debug.Log("EXPLOSION");
        // 在这里添加爆炸动画播放逻辑
    }

    private void DestroyOnHitting()
    {
        Destroy(gameObject);
    }
}

3. 删除Player上的OnCollisionEvent脚本

这个全局事件脚本完全多余,每个物品自己处理碰撞即可。

4. SpawnManager小优化

原脚本里的_items列表是多余的,直接用ItemPrefabs即可,同时简化Y坐标生成逻辑:

public class SpawnManager : MonoBehaviour
{
    [SerializeField] private List<Item> ItemPrefabs;
    [SerializeField] private float SpawnRepeatRate;

    private float _time = 0f;

    private void Update()
    {
        _time += Time.deltaTime;

        if (_time >= SpawnRepeatRate)
        {
            Item randomItem = ItemPrefabs[Random.Range(0, ItemPrefabs.Count)];
            Instantiate(randomItem, GetRandomSpawnPosition(), Quaternion.identity);
            _time = 0f;
        }
    }

    private Vector3 GetRandomSpawnPosition()
    {
        // 简化Y坐标生成,和原逻辑效果一致
        int randomY = 2 + Random.Range(0, 11) * 2;
        return new Vector3(24f, randomY, 0f);
    }
}

优化说明

  • 每个物品实例只关心自己是否被玩家碰撞,避免了全局事件广播导致的“全体响应”问题。
  • 逻辑更清晰,每个Item子类负责自身的碰撞行为,符合单一职责原则。
  • 移除不必要的全局事件依赖,降低了代码耦合度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 03:25:14