在 Unity 项目的架构设计中,我们经常会遇到如下需求:


  • GameWorld 管理整个游戏运行环境。
  • Player 和多个 NPC 在世界中交互。
  • 子对象(如 NPC)需要访问 GameWorld 的某些方法或数据。

如果直接将 GameWorld 引用传递给 NPC,虽然可以快速实现功能,但很容易导致模块之间强耦合,使得后期维护、测试和扩展变得困难。

本文将分享一种更为清晰、可维护、易于扩展的解决方案 —— 通过接口进行上下文解耦设计


一、问题背景

典型的设计初始结构如下:

public class GameWorld
{
    public Player player;
    public List<NPC> npcs;

    public void Initialize()
    {
        player = new Player();
        npcs = new List<NPC> { new NPC(), new NPC() };
    }

    public void SomeWorldMethod() { }
}

NPC 需要访问 GameWorld.SomeWorldMethod(),那么常见方式有:

  • ✅ 将 GameWorld 引用传入 NPC
  • ❌ 使用单例 GameWorld.Instance

这些方法虽然可行,但没有很好地实现模块解耦


二、解耦设计方案:引入 IGameContext 接口

我们引入一个接口,来代表 GameWorld 提供给子对象的访问能力。

接口定义

public interface IGameContext
{
    Player GetPlayer();
    Vector3 GetSpawnPoint();
    IEventDispatcher GetEventDispatcher();
}

这样,NPC 只依赖这个接口,而不是整个 GameWorld 类。


三、GameWorld 的接口实现

我们将实际的 GameWorld 封装成一个上下文对象 GameWorldContext,实现接口:

public class GameWorldContext : IGameContext
{
    private GameWorld world;
    private IEventDispatcher dispatcher;

    public GameWorldContext(GameWorld world)
    {
        this.world = world;
        this.dispatcher = new DefaultEventDispatcher();
    }

    public Player GetPlayer() => world.GetPlayer();
    public Vector3 GetSpawnPoint() => world.GetSpawnPoint();
    public IEventDispatcher GetEventDispatcher() => dispatcher;
}

四、子系统的使用方式(以 NPC 为例)

public class NPC
{
    private readonly IGameContext context;

    public NPC(IGameContext context)
    {
        this.context = context;
    }

    public void Act()
    {
        var player = context.GetPlayer();
        var spawn = context.GetSpawnPoint();
        context.GetEventDispatcher().Dispatch("NPC_Acted", this);
    }
}

此时 NPCGameWorld 已完全解耦,仅通过 IGameContext 接口交互。


五、事件系统(可选扩展)

为了进一步解耦系统间通信,我们引入 IEventDispatcher

public interface IEventDispatcher
{
    void Dispatch(string eventName, object data = null);
    void Subscribe(string eventName, Action<object> callback);
    void Unsubscribe(string eventName, Action<object> callback);
}

用于 NPC 向外广播行为、GameWorld 接收并处理事件。


六、测试支持:MockGameContext

有了解耦接口后,单元测试也变得轻松简单:

public class MockGameContext : IGameContext
{
    public Player GetPlayer() => new GameObject("MockPlayer").AddComponent<Player>();
    public Vector3 GetSpawnPoint() => Vector3.zero;
    public IEventDispatcher GetEventDispatcher() => new DefaultEventDispatcher();
}

七、目录结构建议

为了更好地组织代码,可以采用如下项目结构:

Game/
├── Interfaces/
│   ├── IGameContext.cs
│   └── IEventDispatcher.cs
├── GameWorld/
│   ├── GameWorld.cs
│   └── GameWorldContext.cs
├── Characters/
│   ├── Player.cs
│   ├── NPC.cs
├── Events/
│   └── DefaultEventDispatcher.cs
└── Tests/
    └── MockGameContext.cs

八、优点总结

优点 描述
✅ 高内聚低耦合 子系统只依赖接口,不依赖具体实现
✅ 易于扩展 更换 GameWorld 实现或添加 PVPWorld 不影响子系统
✅ 易于测试 可以用 MockContext 进行单元测试
✅ 更强可维护性 系统职责边界明确,便于多人协作

九、结语

这套通过接口实现解耦的设计思想,适用于多数 Unity 中的子系统开发,如:

  • 任务系统访问世界状态
  • AI 系统感知环境
  • 技能系统调用全局事件

随着项目复杂度提升,良好的架构设计会显著提升开发效率与维护性。如果你有类似的解耦需求,不妨试试这套接口架构设计。