如何重构代码避免从类外部触发事件?游戏碰撞场景实践
解决碰撞检测系统触发GameObject内部碰撞事件的问题
这是游戏架构里很常见的设计痛点——既要让专门的碰撞系统负责检测逻辑,又要保持每个GameObject的事件封装性,不让内部的触发方法暴露成public。我给你几个优雅的解决方案:
方案1:通过接口暴露受控的触发入口
最推荐的方式是定义一个专门的接口,让所有需要响应碰撞的GameObject实现它。这个接口只暴露触发碰撞事件的必要方法,而GameObject内部依然保持OnCollided的protected访问权限,完美符合封装原则。
代码实现:
首先定义接口:
public interface ICollidable { void TriggerCollision(GameEventArgs e); }
然后修改你的GameObject类实现这个接口:
public class GameObject : ICollidable { protected event GameEventHandler Collided; protected void OnCollided(GameEventArgs e) { Collided?.Invoke(this, e); } // 实现接口方法,内部调用OnCollided,相当于给碰撞系统开了个受控入口 public void TriggerCollision(GameEventArgs e) { OnCollided(e); } }
最后在CollisionSystem里,只需要调用接口方法即可:
public class CollisionSystem { public void HandleCollision(GameObject gameObject1, GameObject gameObject2) { // 准备碰撞事件参数,比如把碰撞的另一方传递过去,方便后续逻辑处理 var args1 = new GameEventArgs { OtherObject = gameObject2 }; var args2 = new GameEventArgs { OtherObject = gameObject1 }; // 触发两个物体的碰撞事件,先判断是否实现了接口,避免空引用 (gameObject1 as ICollidable)?.TriggerCollision(args1); (gameObject2 as ICollidable)?.TriggerCollision(args2); } }
为什么这个方案好:
- 保持了
OnCollided的protected属性,遵循“事件应该由类内部触发”的设计理念,TriggerCollision只是一个受控的外部入口 - 接口的方式灵活性很高,你可以让不同类型的GameObject按需实现,不需要响应碰撞的物体直接跳过即可
- 可以通过
GameEventArgs传递更多碰撞细节(比如碰撞点、碰撞法线),让事件订阅者能做出更精准的响应
方案2:使用内部访问修饰符(仅限C#)
如果你不想引入接口,也可以把触发方法设为internal,然后通过特性让碰撞系统所在的程序集访问它。不过这个方案耦合度比接口高,更适合小型项目:
代码实现:
修改GameObject:
public class GameObject { protected event GameEventHandler Collided; protected void OnCollided(GameEventArgs e) { Collided?.Invoke(this, e); } internal void TriggerCollision(GameEventArgs e) { OnCollided(e); } }
然后在GameObject所在项目的AssemblyInfo.cs里添加:
[assembly: InternalsVisibleTo("YourCollisionSystemAssemblyName")]
这样CollisionSystem就能直接调用gameObject.TriggerCollision()了。
额外小建议
- 建议给
GameEventArgs补充更多实用属性,比如CollisionPoint、CollisionNormal,让事件处理逻辑能获取更详细的碰撞信息 - 如果游戏里有大量不需要响应碰撞的GameObject,碰撞系统可以先判断对象是否实现了
ICollidable再触发,避免不必要的调用
内容的提问来源于stack exchange,提问作者D646
相关产品推荐
相关产品推荐

