Unity多GameObject与管理器通信优化及批量通知方案咨询
场景背景
- 场景内存在名为
ResourceManager的GameObject,其脚本包含多个整数变量(如resourceAmount) - NPC对象由预制体动态生成,带有
NPC标签,仅在场景运行时创建 - 当前实现:NPC触发碰撞器时,在
Start()方法中通过FindObjectOfType<ResourceManager>()获取实例,执行resourceManager.resourceAmount +=2
疑问1
若有100个NPC使用该脚本,是否会出现问题?是否应替换为Action、Delegate或Event系统?若使用事件系统,是否由ResourceManager监听事件?100个NPC调用同一脚本触发事件是否有问题?
疑问2
当ResourceManager中资源量达到100时,需通知所有NPC执行指定脚本,当前考虑按标签查找对象但担心CPU开销,求高效实现方案。
疑问1解答
当前实现的问题
FindObjectOfType是遍历场景所有活跃对象来匹配类型的API,100个NPC同时在Start()中调用它,会在初始化帧产生大量重复遍历操作,很可能导致帧时间波动。另外这种硬编码查找的方式耦合性极强,后续ResourceManager的类型或挂载对象变动时,所有NPC脚本都需要修改,维护成本很高。必须替换为事件/委托系统
肯定要换,事件系统能彻底解耦NPC与ResourceManager,同时性能更优。事件系统的正确实现方式
让ResourceManager暴露一个静态事件,NPC无需主动查找ResourceManager,直接触发事件传递资源增量即可;ResourceManager自身监听该事件,在回调中处理资源增加逻辑。
示例代码:
ResourceManager脚本
public class ResourceManager : MonoBehaviour { // 定义静态事件,传递资源增量 public static event Action<int> OnResourceGained; public int resourceAmount; void OnEnable() => OnResourceGained += AddResource; void OnDisable() => OnResourceGained -= AddResource; void AddResource(int amount) { resourceAmount += amount; } }
NPC脚本
public class NPC : MonoBehaviour { void OnTriggerEnter(Collider other) { // 触发条件满足时,直接调用事件传值 ResourceManager.OnResourceGained?.Invoke(2); } }
- 100个NPC触发事件无问题
完全不用担心,事件的底层是委托链,这里只有ResourceManager一个订阅者,触发事件的开销和直接调用resourceAmount +=2几乎一致。就算后续增加更多订阅者,事件系统的处理效率也远高于批量查找对象。
疑问2解答
使用GameObject.FindGameObjectsWithTag确实存在CPU开销问题,每次调用都需要遍历全场景对象,资源满的瞬间若场景对象较多,容易导致卡顿。高效的解决方案是主动维护NPC集合:
核心思路
在ResourceManager中维护一个NPC的集合,NPC在激活时自动加入集合,销毁/失活时自动移除。当资源量达到100时,直接遍历该集合通知所有NPC,无需每次去场景中查找。
代码实现
修改ResourceManager脚本
public class ResourceManager : MonoBehaviour { public static event Action<int> OnResourceGained; // 用HashSet替代List,提升频繁增删操作的效率 public static HashSet<NPC> activeNPCs = new HashSet<NPC>(); public int resourceAmount; void OnEnable() => OnResourceGained += AddResource; void OnDisable() => OnResourceGained -= AddResource; void AddResource(int amount) { resourceAmount += amount; if (resourceAmount >= 100) { NotifyAllNPCs(); } } void NotifyAllNPCs() { foreach (var npc in activeNPCs) { npc.DoResourceFullLogic(); } } }
修改NPC脚本
public class NPC : MonoBehaviour { void OnEnable() => ResourceManager.activeNPCs.Add(this); void OnDisable() => ResourceManager.activeNPCs.Remove(this); void OnTriggerEnter(Collider other) { ResourceManager.OnResourceGained?.Invoke(2); } // 资源满时执行的指定逻辑 public void DoResourceFullLogic() { // 此处编写具体逻辑,如切换状态、播放动画等 Debug.Log("资源已满,执行NPC指定逻辑"); } }
额外优化
如果NPC数量极多(如数千个),可将通知逻辑改为协程分批执行,避免单帧处理过多逻辑导致卡顿:
IEnumerator NotifyAllNPCsCoroutine() { int count = 0; foreach (var npc in activeNPCs) { npc.DoResourceFullLogic(); // 每处理10个NPC就暂停一帧,平衡性能 if (++count % 10 == 0) yield return null; } }
然后将NotifyAllNPCs中的调用替换为StartCoroutine(NotifyAllNPCsCoroutine());
内容的提问来源于stack exchange,提问作者Paul Sotena

