游戏逻辑模块组织及数据同步2
综合文库 410 1
实名

通过了实名认证的内容创造者

发布于 2017-8-30 10:09:07

您需要 登录 才可以下载或查看,没有账号?注册

x
本帖最后由 胖纸_DHW 于 2021-1-8 11:37 编辑

下面介绍数据同步的设计。


首先对于网络游戏,客户端所展示的数据是服务器传送过来的。当玩家操作导致数据发生变化时,最好也由服务器更新给客户端。曾经接手过一个项目,很多操作的结果都是客户端先算出来的,于是各种逻辑都是服务器和客户端各实现一遍,很容易两边的数据就不一致了,很让人头疼。


所以我们的同步思路是当客户端向服务器发起一个请求时,服务器将所有变化的数据同步给客户端,客户端收到服务器的返回后再更新数据,绝不私自改动数据。在这个指导思想下,我们消息包结构是这样的(以物品卖出举例):


  • message BagItemSellCG {
  • optional int32 id = 1;
  • opitnoal int32 count = 2;
  • }
  • message BagItemSellGC {
  • optional int32 result = 1;
  • optional Sync sync = 2;
  • opitonal BagItemSellCG postback = 3;
  • }

复制代码


服务器向客户端返回的消息几乎总是包含3个字段。result为操作结果可能是0或者错误码,sync中包含了所有的数据更新,postback将客户端的请求消息原封不动返回去,便于客户端进行界面更新或友好提示。


sync是一个比较复杂的message,包含了所有需要更新的Model的数据。感谢Protocol Buffer的optional选项,大多数情况下我们发送的数据只是其中很小的一部分。


先来看服务器端消息处理和同步的设计。


132801h6ncpp4qgcvplnu8.jpg

如图所示,我们在Model和Controller之上新加了一个Handler接口层。Handler负责解析消息包,调用Controller处理消息包,在必要的时候调用SyncController构建同步数据,最后打包成消息返回给客户端。


每个Model在管理数据的基础上会维护变化数据的集合,对于简单的Model比如MoneyModel就是一个bool脏标记,而BagModel则维护变化物品id的集合。变化数据列表在同步之后清除。


客户端的结构是类似的。


132801m8g8p7i7zycfpd1d.jpg

与服务器的区别就在于SyncController是负责调用Model更新数据,每个Model都实现数据更新接口。注意除SyncController之外,其他Controller只能读取Model而不能改变其数据,这样就保证了所有数据一定是从服务器同步的。


最后我想以出售物品为例子完整走一遍流程。从客户端进行操作开始,到请求发到服务器,最后再返回客户端更新数据和界面。完整的图比较复杂,混在一起基本上没法看了,只好删掉了客户端的任务模块……



BagView界面产生一个点击,因为BagController是BagView的观察者,所以BagController能得到点击事件的通知。


BagController识别出此点击是要出售物品,于是构建好消息包发往服务器。


服务器识别出消息类型是Sell,于是消息被派发给SellHandler。


SellHandler调用BagController执行逻辑。


BagController取出BagModel和MoneyModel的数据进行条件检查,如果无法执行操作则生成错误码返回给SellHandler,否则调用Model修改数据,此时BagModel会记录下变化物品的id,MoneyModel会做一个脏标记。


MoneyModel数据发生变化,通知自己的观察者(TaskController)。


TaskController判断任务完成,调用TaskModel更新数据。TaskModel会记录发生变化的任务。


SellHandler对BagController的调用返回后,如果出错则直接返回消息包给客户端。否则调用SyncController收集同步数据。


SyncController调用各个模块收集同步数据,各个模块提交同步数据后清除自己维护的标记。


SellHandler将操作结果和同步数据打包后发往客户端。


客户端识别出消息类型是Sell,消息被派发给SellHandler。


BagHandler将消息处理结果发给BagController。


BagController根据消息处理结果,通知BagView进行必要的提示。


SellHandler将消息包中的数据同步部分发给SyncController。


SyncController将同步数据同步给各个模块。


BagModel和MoneyModel的数据发生了变化,通知观察者,即对应的Controller。


Controller调用View进行界面更新。


Q&A


返回客户端提交的postback对于网络传输来说太过重量级, 可以尝试改为客户端保存一个rid-postback的键值对, id由客户端自增, 请求数据时把rid一起发送给服务器。


支持这个方案。


但我的想法不是出于数据量的考虑,因为一般网游客户端发往服务器的消息都是比较小的,服务器返回的消息会比较大。 原因是后来我们考虑到消息可能丢包的问题,当丢包发生时,客户端需要重发请求,这样一来rid检验及保存之前发送的请求就是必须的了。而保存下来的请求正好又可以用来替代上文的postback,所以你的方案非常合理。


我使用了背包里一个物品,在返回的sync中是返回使用掉的物品信息, 还是背包的全部物品信息?


因为我们背包里的物品会比较多,所以同步全部物品是不合适的。


我们的做法是删除物品后记录物品id,生成同步数据时如果发现对应id的物品不存在,则同步一个数量为0的物品信息,客户端收到数量为0的物品后做删除操作。 有的模块没有一个代表删除的特殊“零值”,比如任务。我们的做法是将新增/更新与删除分开同步:

评分

参与人数 1元素币 +30 展开 理由
元素界王神... + 30

查看全部评分

还没有设置签名!您可以在此展示你的链接,或者个人主页!
使用道具 <
Zh_Jason  发表于 2017-8-30 12:51:51  
2#
资源甚好,发帖艰辛,且阅且珍惜!
回复 收起回复
使用道具
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表