2.2 为MMP游戏制作仿真框架,第二部分:行为建模
Thor Alexander, Hard Coded Games
thor@hardcodedgames.com
|
本 |
文将以第2.1节中描述的类框架为基础,介绍怎样把这些类扩展为一个可以运行的在线仿真环境。
2.2.1 把用户和行动者关联起来
服务端仿真(ServerSimulation)和客户端仿真(ClientSimulation)都提供了对AttachUser(连接用户)和DetachUser(断开用户)方法的实现。这使调用这些方法的用户(User)可以被映射到服务端某个特定的行动者实例上以及与之对应的客户端行动代理对象上。设计者把用户与行动者连接起来后,就可以发送动作请求并且接收仿真事件了。目标行动者对象(target Actor)是接受还是拒绝这个连接请求完全取决于它当前所处的控制状态。这样的连接机制还有另一个好处:它可以支持一些高级的功能,譬如说可以让多个用户与同一个行动者对象连接。通过这个功能,客户支持人员不仅可以在某个玩家惹麻烦时接管他的角色,还可以在遇到麻烦的新玩家寻求帮助时,站在和玩家相同的视角来观察这个游戏。
2.2.2 动作请求
一旦与服务端的某个用户相连接,动作请求将成为客户端与外界通信的主要形式。客户端仿真类(ClientSimulation)的SendActionRequest(发送动作请求)方法接受一个动作状态标识(ActionStateId)以及一个代表用户想要执行的动作的参数列表,并把它们传递给服务端的仿真层。服务端把这些请求依次委派给由所连接的行动者对象及其控制状态组成的一个职责链(chain of responsibility),由它来决定到底是处理还是拒绝这个请求。图2-15是用来阐述动作请求处理过程的一个UML顺序图。
![]()

2.2.3 动作调度
服务端仿真类提供了一个Tick方法,游戏开发人员可以从仿真层的外部调用这个方法来对所有还未执行的动作进行处理。这个方法负责计算仿真处理可用的时间片,并且把这个时间片通过ProcessTasks方法传送给调度管理器。因为通常服务端物理层的处理频率必须比仿真层更高,所以Tick方法为维护游戏主循环的物理层提供了一个很好的回调方法,图2-16是动作调度的顺序图。

图2-16 动作调度的顺序图
2.2.4 事件广播和处理
当行动者试图进行转换时,它会调用控制状态对象(ControlState)的PerformAction方法,这个方法会返回一个目标动作状态(desiredActionState)。随后行动者调用这个目标动作状态的CanTransition方法来进行预检,如果通过了,就可以调用目标动作状态的Transition方法来进行真正的转换操作。每个动作状态都需要提供其各自的特定实现。通常这个实现需要把状态转换通知给其他仿真对象,这可以通过使用仿真事件来完成。活动的仿真对象还维护了一个订阅者(subscribers)词典,对其动作感兴趣的其他对象可以进行登记。仿真对象会调用这些订阅者的ReceiveEvent方法来通知他们,并由消息的接收者决定是立即还是消极处理这个消息。在前一种情况下,消息会在接收后立刻得到处理;后一种情况下,消息会被放入队列中并且在接受者的PerformAction方法下一次被Tick调用时进行处理。图2-17显示了相应的顺序图。

图2-17 服务端行动者把动作改变的结果作为事件进行传播
2.2.5 服务端事件处理
当事件订阅者接收到一个事件时,它会把这个事件传递给这个类型的事件所对应的事件处理程序。这个事件处理程序会负责进行所有的服务端处理。另外,它还需要检查这个行动者对象是否有一个相连接的用户。如果有的话,它必须从服务端仿真中把事件发送给客户端仿真。这要求把这个事件对象分解并且放入一个可以通过网络传递给客户端的事件消息。图2-18展示了这个过程。

图2-18 服务端行动者向客户端的用户发送消息
2.2.6 客户端事件处理
一旦客户端接收到事件消息,就会把它解开并且恢复为一个事件对象;接着会把事件的源行动者标识(sourceId)从事件对象中取出,并把它传递给客户端仿真的行动者管理器(actorManager),以查找相应的行动代理(ActorProxy)对象。这个代理对象是服务端对应的行动者对象在客户端的代表。如果行动者管理器没有找到这个代理对象,就意味着这是客户端第一次和这个行动者打交道,或是这个代理对象已经从代理对象缓存(这里面保存了客户端所关心的有限数量的代理对象)中清除了。在这种情况下,客户端需要使用它的仿真对象工厂(SobFactory)来创建一个新的行动代理,并且把它加入到行动者管理器所维护的缓存中。现在已经有了一个代理对象,可以把服务端传来的消息传递给它,处理过程如图2-19所示。
2.2.7 客户端代理
当客户端代理对象接收到一个事件时,“它”处理该事件的方式与之前在服务端处理该事件的方式非常相似。行动代理对象为事件类型确定合适的处理程序,并且把事件传给它。这和服务端找到的那个事件处理程序不同,它是一个负责进行客户端处理的代理事件处理(ProxyEventHandler)对象。为了复制服务端相应对象的状态,通常这些处理需要把行动代理对象转换到相应的代理行动状态(ProxyActionState),处理过程如图2-20所示。

图2-19 客户端仿真把事件发送给行动代理

图2-20 事件被客户端行动代理所处理
2.2.8 仿真与表示分离
通常,这样的状态转换需要在客户端有一个可视化的表示,为此仿真框架提供了一个钩子,以通知任何对状态改变或其他相关事件感兴趣的表示管理器(presentation manager)。这些管理器可以是用户界面要素、音频播放器或是一个二维/三维渲染引擎。如图2-21所示,这个通知系统是基于观察者(Observer)设计模式的。游戏的代理动作状态(ProxyActionState)类有一个Attach(连接)方法,任何对这个状态感兴趣的观察者都能通过调用这个方法进行注册;它还包含了一个Notify(通知)方法,在状态发生改变时,这个方法会调用所有已注册观察者的Update(更新)方法来通知它们。本书第2.8节会对观察者设计模式进行更详细的讨论。

图2-21 观察者模式使得仿真和表示相分离
这样的系统使游戏可以在仿真层和任意的表示层之间维护一个强有力的分隔。通过将表示和仿真分离开来,游戏可以方便地移植到不同的平台,而不需要重新编写客户/服务器代码。随着图像技术不断地向前发展,还可以更换不同的渲染引擎来使MMP游戏外观在市场上保持领先。这种分离的体系结构还可以实现在新兴的平台上开展游戏服务,譬如在游戏机和无线网络上运行MMP游戏。
2.2.9 总结
MMP游戏开发所要面临的挑战常常被低估了。它通常由很多大型的部分组成。从一个类似于这篇文章中所介绍的基于可靠软件工程原则的框架开始开发将是一个良好的开端,它可以使游戏开发人员把时间和精力放在创建具有新意的、引人入胜的游戏模式上。






