一种适合网游的界面系统设计方案说到界面系统,在windows平台上用c++编程的人多少都会知道MFC,我也曾仿照MFC的设计实现过一款单机游戏的界面系统。然而现在多数人认为MFC是一个设计不佳,复杂且难于使用的系统。使用MFC编程需要很长的学习过程,界面逻辑被分散到各个窗口类中,为了实现一个复杂界面,你不得不为界面中几乎每一个窗口创建派生类,并将界面逻辑嵌入到这些窗口类中。整个界面形成一个父窗口、子窗口、控件等对象组成的树状结构,许多消息在这个树状结构中通过广播的方式找到它的处理函数。要操纵子窗口或者控件完成一些事情不得不先找到包含它们的父窗口,再通过父窗口函数或者成员变量来访问子窗口或控件。这些特点迫使界面逻辑要了解界面的组织结构,甚至依赖于这个组织结构,难以实现逻辑和界面结构、布局的分离。游戏界面有其自身的特点,比如游戏的界面往往比较简单,不需要太复杂的结构和布局,能够满足特定游戏的需求就行,不会象MFC那样被用来实现各种各样的应用程序,面对大量的不同需求,甚至要实现不可预期的需求,这就要求MFC这样的系统具有极大的灵活性,和可定制性。另外游戏界面系统往往用c++语言实现,而其使用者可能是各种脚本语言,这种跨语言的使用接口更加要求简洁、清晰,而同一种语言实现的各模块之间的接口则可以很复杂,并充分利用特殊的语言的特性。因此我们的目标是创建一种不同于MFC结构,避免MFC缺点的适合游戏特点的界面系统。我们以WOW的界面系统为参考,希望实现一个类似的系统。要说明的是我并没有玩过WOW,当然也就没有使用过它的界面,但是我听过一个介绍WOW界面系统的讲座,并且看过操作演示,所以我并不保证我对WOW界面系统的理解是全面或正确的,有什么错误之处大家可以回帖讨论。另外这个设计也参考了QT,同样我对QT的理解也很初步,提一下只是方便大家参考。另外这个设计目前还只是个思路,并没有被实现出来,很多细节可能还有待商榷,同样欢迎大家回帖讨论。下面就具体介绍一下设计方案。界面系统分为两个部分,一个是C++部分,一个是脚本语言部分。C++部分实现各种窗口类、控件类、和各种管理器及辅助类。在运行时C++模块内部保存着各种窗口和控件类的实例,它们按布局关系被组织成一个树状结构,父窗口包含其子窗口,子窗口包含其上的控件。这个树状结构被隐藏在C++模块内部,对脚本模块不可见,用来维护着界面系统的创建、销毁、移动,以及所有与操纵、显示相关的界面行为,举例来说父窗口创建其子窗口及控件,父窗口销毁时负责销毁其子窗口和控件,父窗口移动子窗口及控件跟随移动,父窗口隐藏、显示、互相叠盖等行为也控制着子窗口和控件的相应行为,标签页、滚动条、各种按钮的本质行为也都由C++模块完成。C++模块完成后为系统创建新的界面时全部在脚本模块完成,C++模块不需要改写。从脚本模块看界面系统看不到C++模块内部的树装结构,取而代之的是一个扁平的列表结构,界面系统中所有的窗口和控件不依赖其布局关系被同等地看待。一个界面脚本包可以被看作一个界面插件,其中实现一个或者多个窗口。当脚本包被加载,系统会调用它的初始化函数,在初始化函数中可以通过加载一个界面配置文件,或者通过一组脚本语句创建界面对象(系统会为每个界面元素分配一个全局唯一的ID,这个ID可以用系统分配给脚本包的基础ID加上控件在包内的ID得到),并注册一组界面元素的事件响应函数,可以象这样://controlID:控件ID//eventID:事件ID,比如按钮的Clicked事件,滚动条的Scroll事件等等//function:脚本函数,对ID为controlID的控件的eventID事件做出响应RegistEventFunction(controlID,eventID,function);当ID为controlID的控件产生eventID事件时,系统会调用脚本包注册的响应函数。在响应控件事件时可能需要操纵控件行为,比如设置一个按钮上的文字内容,可以象这样://controlID:控件ID//text:要设置的字符串SetText(controlID,text);可以看到无论是响应控件事件还是操纵控件行为都是通过控件ID来进行,同等的对待所有控件,不需要知道它们在树状结构中的位置,不需要知道他的父窗口是谁,当然如果逻辑有需要也可以通过函数查询控件的父窗口。脚本语言模块中不必为每个界面元素生成相应的对象,更不必复制C++模块中的树状结构,要做的一切只是注册一组事件响应函数。游戏系统还会为界面提供许多游戏内部事件的通知消息,比如定时器消息、玩家生命值发生变化、玩家受到攻击等等。在界面脚本包的初始化函数中同样会调用一组函数向系统注册自己感兴趣的所有系统消息,可以象这样:RegistInterestedSystemEvent(sysEventID);当系统内部发生某个向界面系统开放的事件时,系统会检查所有注册过对此事件感兴趣的脚本包,并调用脚本包对应的系统事件响应函数,比如OnSystemEvent。脚本包在收到定时器消息时可以查询一些系统状态,更新自己的显示,在收到玩家被攻击的消息时可以让界面闪烁以提示玩家。多个界面包可能对同一系统事件感兴趣,并以不同的方式做出响应。下面以一张图结束本文的探讨。