读《大型多人在线游戏开发》笔记

本文探讨了结构建模中的辅助类、核心类及管理器类,详细介绍了行为建模,包括用户与行动者关联、动作请求与调度、事件处理机制等。文章还讨论了C++权限机制、ID资源回收利用、多进程并发处理、日志管理等方面,并提出了AI算法优化策略,如算法封装、组件配置模式等。

结构建模

(一)  辅助类:

1.         词典类

a)         添加条目

b)        查询条目

c)        删除条目

例如仿真状态{状态集词典,ID}

2.         仿真事件: 系统的事务对象

a)         (事件类型,源行动者,目标行动者,参数,频道属性)

3.         仿真状态: 系统状态的描述

a)         能否进行转换

b)        进行转换

4.         动作状态: 表示典型的游戏仿真操作

a)         开始时间

b)        持续时间

5.         控制状态: 定义命今的来源

6.         用户控制状态: 管理客户端发向服务器的动作请求队列并执行请动

7.         AI控制状态:确定用执行的合适动作


 

(二)  核心类:

1.         仿真对象: 处理对象的基类,仿真对象之间用仿真事件的订阅和取消来通信,并带有一个内容词典和引用其者的ID.

2.         执行者: 抽象类.是行动者和行动代理者的共同接口。

a)         调度的优先级。

b)        ID

c)        当前状态(表示当前对象所执行的动作)。

3.         行动者:被定义能够与仿真环境进行交交的服务器端仿真对象。

a)         控制状态

b)        执行动作

c)        请求动作

d)        接收事件队列

4.         行动代理:行动者在客户端的副本。

a)         接收事件队列

b)        请求动作

c)        非请求动作

5.         非执行者:不需要和仿真环境直接交互的核心类。

a)         物品。

b)        障碍物。

c)        代理对象。

d)        区域。


 

(三)  管理器类

1.         仿真对象工厂:负责创建仿真对象并确保它们具有独一无二的标识。

2.         仿真对象管理器:负责维护一个仿真对象的词。

a)         提供把仿真对象的标识转为对象的引用

b)        保存

c)        载入

3.         调度管理器:负责为仿真中每一个活动的执行者进行调度。

4.         查询管理器:为执行期访问静态数据提供一个快速高效的机制。


(四)  仿真类

仿真类:管理者的总管理者,对所有管理单件的引用,对仿真对象的引用。


 

行为建模

把用户和行动者关联起来

连接用户

断开用户

动作请求:动作标示,参数

动作调度:

事件广播和处理

服务端事件处理

客户端事件处理

客户端代理

仿真与表示分离:通过钩子传送给表示管理器。

仿真与数据库分离:通过钩子传送数据库管理器。

 

 

//有些算法如果把参数抽象成基类信息处理会十分降低效率,只有在子类信息里处理才最有效,这时就需要把参数进行匹配转换成子类。

解决方法一: 不断定义新类去匹参数类的ID, 从而确定参数类是属于那类。

解决方法二: 建立一张Map,把所有匹配函数按参数类的ID并填进Map里, 使用时按参数类的ID找出匹配函数然后直接用。

 

C++的权限机制还有严重的不完善之处:只能定义“权限实例(public, private, protected)”, 不能定义“权限规则”。这样无法在执行期调整权限。

 

有时会面临new了一个东西,但是有多个地方引用,而且彼此不晓得对方删了没有。 解决方法1是用STL的智能指针;解决方法2是这些引用的地方全部禁掉new和delete的功能,只拥有对象的ID。同时另起一个第三者管理机构用于new和delete。

 

//----------

traits 技术可用于算法优化。 例如接口总是整个队列和相关数据, 但是通过traits决定是所有都遍历,还是取特定ID。

 

解决多进程并发问题。 不想用锁时, 可采用提供原子操作的功能,然后用不同的模块编写,但把原子操作用一定的“注册”方式注册进主模块(以便主模块了解子模块的功能),然后由主模块来实现串行的原子操作。 读操作公开, 写操作保护。

 

提供一个专用于错误或提示的类,然后继承给子类。

 

       有时会面临一些模拟两可的组合,例如ActorSendMsgActorIsOnline的绑定关系:1,可以API_ActorSendMsg内部绑定API_ActorIsOnline再真正的发送 2,可以API_ActorSendMsgAPI_ActorIsOnline互相独立,再加一个“套子”把它们组合在一起。前者好像更方便,但其实后者更好用点。主要在责任单一,逻辑分工明确,是API_ActorSendMsg的问题,还是API_ActorIsOnline的问题,还是他们之间的组合问题,都可以有专门的异常。调试和逻辑流程复杂度简化。于是采用后者。

 

ID资源的回收利用的解决方案: 回收利用会有一个问题,即需及时通知旧有的ID持有者更新数据。 这效率极大,有时甚至不可能。 一个解决方案是让ID带有生命周期,在生命期里充许使用但不充许回收; 一旦过了生命周期,则会进行回收同时不充许这个生命周期限的ID可以使用了。

 

 

世界上有两种最赚钱的人,一种是真正的生产者,他们用自己独一无二的聪慧、灵感和劳动创造了价值;一种是极其老道的投机者,专门从别人口袋里把钱掏出来放到自己口袋里。他们都受人尊敬、受人瞩目,后一种让自己的生活变得更美好,但是前一种,让整个世界更美好。

总有些人想将个人价值理念和原则强加给他人,并在其难以融入时给予定点清除的暴力行为。

 

设置日志类

logManager:  通过标志性接口, 定制想要输出的内容 例如  ERROR|DEBUG,  同时为以后的输出留下扩展余地,例如某个副本的标志符。 在输出信息时,要填写标志符等级。

通过某类机制设定输出方向。

 

优化想法: AI由客户端实现,通过“建议”的方式发送给服务器, 服务器依据每个客户端的可信权值和实际环境进行选择。

 

继承与组合的选择关键区分点:数量。  如果基类和子类总是1V1的关系, 那则是继承。如果某类和另类可能出现1VN的关系, 则采用组合。

 

//------------------------------------------------------------------------------------------

 

机会主义设计。

系统化设计:这是一种有意图的,预先确定的努力。其目的是贯穿整个产品线,创建并应用多用途的软件架构。

 

类库是第一代面向对象开发技术,是通用的可复用的,独立于特定领域的“积木”。但也因此,其不具某应用的控制流,协作和可变性,所以复用规模有限。类库不具有对应用环境的资源权限,它需要应用开发者可能要编写一堆实现控制逻辑的“胶水代码”才可把各种复用类绑定在一起。

 

框架是第二代面向对象开发技术,提供一组集成的,针对特定领域的结构和功能。其极大的依赖于应用领域,并通过各种回调实现“控制时的反转”,并封装了部份控制逻辑和类之间的协作,是半完成的应用。

 

白箱框架:可扩展性通过各种面向对象语言特性来获得,例如通过继承,模板方式等。可以对已有功能进行复用和定制,但要求开发者必须对内部结构有所了解。

 

黑箱框架:可扩展性通过对象的组合和委托而被插入框架中。应用开发者对框架内部结构无需太多了解,更易于使用,但是也更难以设计。因为该框架开发者必须定义“易碎的”,预见到一系列使用情况的接口。

 

组件是实现特定功能的模块对象,拥有自身逻辑和对应用环境的主动资源控制权,其甚至可以脱离应用开发者逻辑绑定的胶水代码,直接主动对应用环境进行应用服务。

 

//-----------------------------------

我的寻路优化方案: 建立一张静态地图导航图:

导航网:  令纵轴表示出发地, 令横轴表示目的地,  项值为出发地到目的地的下一个路径点坐标。寻路时,遍历导航网即可以最短的路径到达目的地(因为A -> B -> C最短, 则包含B->C最短)。

通过机器预处理每张地图, 预先建立这张导航网, 则执行期静态地图寻路为常数,时间复杂度O(1)。  建立导航图的成本为O(N!), 但仅一次性成本。

//-------------------------------

解决恶意用占位问题捣蛋的方法:

可以给NPC一个走路的冲刺力属性, 普通玩家冲刺力为0, 必到某点的NPC冲刺力为9999, 这样想让不能互撞的玩家, 可以不让其互撞; 想必须让NPC到达某地的玩家可以让其必能到达。

 

 

//------------------------------------

框架宜采用组件配置模式, 如果不采用,而试图采用策略模式或桥模式,则必须在编译期计算出所有可能需求, 这不现实。 这种不现实的成本是会带来不断的重启,更新, 测试, 发布; 同时服务过于僵化, 并可能占用没必要的组件资源。

 

//----------------------------------

AI状态模式的实现, 实际上, 可能会有以下两种方案:
a, 每个事件的执行,会开一条线程, 这样总有一条线程去接收新指令,并实现中断另一线程的可能。
b, 永远只有一条线程。 这样则必须上个事件触发完毕,才能执行下一个。

 

多线程,会面临资源的解放问题; 多线程的切换开销。

所以AI可能是单线程在处理, 为了及时响应和反馈,可能会采用“帧状态”的划分。 这等于把程序的成本转移到代码的成本, 例如会让一些算法的实现成本复杂化。 例如让NPC陷入思考状态, 搜索下几步的局面最优解, 你必须把这个整体的搜索算法, 分割成几个“帧状态”以实现了, 而这几个帧状态之间,可能还存在数据关联,需要保存一些临时数据。   但是怎么分割以适应帧这个就不清楚了, 难道是每尝试一种分割的颗粒度大小, 就捏表算一下,合适就上,不合适就再重新划分?

 

//------------------------------------------

问题总结。
问题: 假定有一种状态是需要很长时间操作的, 例如需要NPC从A点走到很远的B点的寻路状态。这会经历一个很长的的寻路过程。但是途遇一个玩家, 合理的话,NPC是应该马上结束他的寻路状态以进入攻击状态。  可是这是一个单线程, 理论上,NPC在寻路状态中是没法处理途遇玩家的信息,需等到寻路状态结束后才可处理。  于是现象和理论产生矛盾, 那么项目到底是如何处理才解决这个问题

的呢?

项目答案: 状态中不充许长时间操作的功能。 也就是说, 上文寻路状态用了某种编码技巧,实现不一次性执行完的完整的寻路, 但可以分批执行以达完整(例如分成N个阶段点, 在这N个阶段点集中处理是否要切换状态等问题)。 或者其状态就不是寻路状态, 而是划分为“片断寻路状态”。(目前项目是采取N个阶段点集中处理, 也就是说充许大状态,然后内部再划分N个阶段点, 而不是一开始就直接不存在大状态,而是一个个小状态划分)。


我的设计是: 解决方法采用“帧状态”, 即上文的后者“片断状态”, 从设计之初,就实现一个个小状态。


答案不完善的地方在于: 对于什么属于长时间操作的功能,没有一个标准。 而我所谓的“帧状态”,虽然有一种时间标准的概念, 但是却没有好的检验方法, 提炼不出一种怎么把长时间状态划分为

片断状态的方法。

 //----------------------------------------------------------------------------------------------------------

对白的解决方案:
A:一句对白一个NPC
B:或者说, NPC可以有个属性设置对白片断的ID。
不过这个可能没有一句对白一个NPC好用, 因为有时侯会连NPC的头像也换掉, 这时程序就需要划分,什么时侯是NPC不换,对白换; 什么时侯是NPC和对白都要换。 这样程序复杂。

类似的, 对话框的选项内容也可以这样做。 依据ID来展示内容, 而不是依据特殊场景特殊处理的方法展示。

 

//------------------------------------------------------------------------------------------------------------

多核技术编程技术猜想:  建立以物品处理为标签的消息项, 标签有交集的为同一消息队列, 无交集的为不同的消息队列。

能否有张过滤网, 当串化消息通过此网时, 就自动转化N个并行消息队列? 某个变量地址并行是没有效的,必须是某个消息块,或某段操作并行才有效。这个过滤网或许可以是神经网络,然后通过后向传播和指导式反馈使非法的并化走向串化。

呃,不行。 设想有<P,q> 和<M, n> 操作对,因为没有共享资源,他们会变成两个消息队列,但此时如果新添一个消息<P,n>,则并入那个消息队列呢?挂倒。

队列不行, 那么消息网怎么样? 取消息时串行, 处理消息时并行?

 

用消息网还是太复杂了。 或许可以用“挂诊式”,只有当消息的每个标签“都”不在使用状态时,该消息才可以取出来处理? 一条并行消息队列只能取那些“都”没有资源相关的标签的消息,或标签里仅有当前队列所占的标签才有使用状态的消息。 取完需把所占有的资源置为“使用状态”。  标签一般是全局对象,或其属性才有关联吧,局部对象并行应没什么问题的。

 

//------------------------------------------------

必须有个全局对象管理器,全局对象通过注册和反注册进“全。。。管理器”,从而提供对外接口。

或许需要一个命令式的服务配置器, 以实现不重启的更新。

 

//------------------------------------------------------

一个消息的处理函数的流程:
派发否决消息。
等待否决消息结果。
派发Action消息(将要执行消息)。
等待Action消息执行完毕。
处理内容。
派发Onresponse消息(执行完成消息)。
等待Onresponse消息执行完毕。

 

 //------------------------------------------------------

或许我们一直以来的解决并行的思路是错误的, 总想着大一统。 例如我们总是把“需求逻辑”和“机器逻辑”,混搭在一起实现,一个函数里即实现应用逻辑,又要考虑线程,互斥等机器问题。 或许这个问题应该分两层实现,第一层实现需求逻辑(这一层仅仅只是考虑需求功能,不考虑任务并行串行,谁执行这些代码的问题), 第二层再实现需求逻辑的如何执行问题(即通过例如一些挂载机制,或者底层的功能,对这些需求逻辑以回调的方式,通过一些算法分配到可以并行的资源上去。解决谁执行这些代码的问题)。 这种分层机制,冯诺依曼模型似乎并没有解决。

 

//-------------------------------------------------------

可以完全无等级经验等硬性限制, 通过像类似法力值一样的可消耗回复的“体力值”来实现管理玩家的发展速度。  像一些到大地图,或副本的, 也可以通过体力值管理。 另外, 体力可以限制玩家无限刷副本, 令其可以多种方式尝试游戏的其它方面(例如可以通过刷副本消耗大体力, 玩任务消耗小体力。 这样当体力值不够时, 为免浪费,玩家就会跑去做任务了)。

 

对象池: 回收时如果发现该对象还未存于对象池,则不直接delete掉,而是存放于池里以备new用.

 

对于先删先有效的问题解决很简单, 加一个标示生死的符号即可。 这样就可以把索引随意分配不用管理其删除的问题了。

 

对于有些是同步处理, 有些是异步处理的, 可以在主线程中全都按同步方式编码, 由同步处理时new出来的对象的内部还再去搞出相应的异步处理,而不是在主线程中去做各种区分处理(例如一个new里面又有一个new的结构), 相当于不直接做异步处理, 而是搞一个异步处理的代理对象。 例如:
void onClickNPC
{
 ...
 p = new NPC(this); 
 p->OpenDlg();
}
class NPC
{
 void OpenDlg()
 {
  sendOpenDlgMsg();
 }
 void onMsg()
 {
  pClientRes->OpenDlg();
 }
}

 

 

用消息码派分,再找到相应的处理好像开发太麻烦了, 因为还要一个映射过程。 如果能直接传函数名

,那边再直接通过全局函数进行处理, 就更好了。 再进一步, 可以是先用传函数名的方式, 然后能

哈夫曼编码让机器自动按点击频率进行最优化的映射编码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值