把组合(树)模式集成到游戏服务器引擎中去!

什么是组合模式?

组合模式是一种设计模式,和对象组合的概念是不同的。对象组合是相对于对象派生而言的一种对象间协作的关系。而组合模式是将对象组合成树形结构以表示整体-部分的层次结构,使得用户对单个对象和组合对象的使用更加一致的一种设计模式。( compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.)

组合模式对于具有树状结构的对象进行整体-部分的管理时,具有特别明显地作用。

可以看到当用户访问一个对象Component时,它会级联访问所有的Child(同时也是Component),直到每一层的所有对象都完成指定的Action。类似Unity的对象树,就是采用了这种组合树的模式。

常见的应用场景

文件夹结构的访问。Action包括

  • 打印文件列表
  • 设置访问权限,级联设置
  • 删除文件

人事管理系统的设计。Action包括

  • 打印组织架构图
  • 部门考勤/分组考勤

这里应用组合设计模式时,有两个主要的共同点

  1. 树状结构
  2. 级联调用

游戏引擎对组合模式的依赖

在我之前的博客(links:口水游戏主循环和组件)中,我讲到了游戏架构中,我们需要协同很多组件一起来完成复杂的游戏逻辑。而且组件层次很深,比如玩家有很多平行的组件,背包,任务,战斗,而组件又有自己的组件,比如背包有整理组件Sorter,任务有组件主线任务,日常任务。于是,我们面临了2个问题,找到我要的组件在哪一层的哪个位置,以及如果有一些共有的逻辑,该如何去更好的管理和维护。

于是,我们考虑,是不是可以引入组件模式,更好的为服务器架构来做一些服务。

class LogicComponent
{
    private List<LogicComponent> childList;
    public void Add(LogicComponent item);
    public void Remove(LogicComponent item);


    public int Id { get;set }
    public int Name { get;set }
    public bool Enable { get;set }

    private List<IService> serviceList;
    public T GetService<T>(string name);
    public void SendMsg(string func, string msg);
}

我们尝试把这种组件的关系,定义成一颗LogicComponent树,它本身不提供具化的功能,而仅仅扮演着架构枝干的功能。具体的工作则交给IService的对象来完成。比如IService可以是BagService,QuestService等等。

这种情况下明显的区别是,我们访问节点的方式变成了,Player.Root.GetService<BagService>(),而不是之前的Player.BagService。直接的好处是,我们解了对象之间的耦合,坏处是性能可能会变差(但关键的高频访问函数其实我们可以通过直接访问的方式来提高效果)。那再让我们看看,除了这些改变,我们还能获得些什么。

其他功能

模块开关

我们可以按需设定每一个组件的开启条件,如果出现玩家非法的访问(等级不到10级,却去pvp了),底层框架处就可以直接处理了。并且,因为每个组件知道自己的子组件是谁,完成级联的开关设定也会变得非常容易。

模块查找

通过GetService方法就可以找到自己要的服务,而他在哪一个级别,根本无所谓。这种设计方式,对组件的隐藏性支持非常好,组件再多层次再复杂,对外部访问人员来说也是毫无区别。

公共事件触发

公有行为的调用,比如Update,Init,NewDay,等事件的触发消息,就可以直接通过组合树的根节点发送下去,变得非常方便。

调试路径和性能

可以把一次请求访问的所有模块的路径打印出来,比如PlayerItem->PlayerPeople->PlayerItem->PlayerQuest。我们可以统计出一个模块的访问次数等性能参数。这个功能还是非常酷的,也是老的模式下无法想象的。

直接调用和消息传递模式

我们有了2种对象间调用的方式,一种通过GetService找服务,这可能是一种相对实时的操作。一种是SendMsg的操作,可以理解为传递了一个信息,但可以延迟处理(比如更新了排行榜,传递了一条消息个日志模块)。因为SendMsg是同时对所有子对象递归调用的,如果我们对组件PlayerQuest发送Msg("invoke", "123"),这时我们可以通过PlayerQuest的子组件LogResender来收集这个Msg,并完成其他的监控操作。

猜你喜欢

转载自blog.csdn.net/narlon/article/details/83241268