MMO即时战斗:地图角色同步管理和防作弊实现
一、前言
无论是端游、页游、手游如果是采用了MMO即时战斗游戏模式,基本都会遇到同屏多角色实时移动、释放技能、战斗等场景,于是自然也需要实现如何管理同屏内各种角色的信息同步:例如角色的位置、以及角色身上的装备、时装、buffer等状态的实时切换。本文主要介绍之前参与开发的一款在外网上线运营了几年时间的mmorpg游戏,重点介绍地图的管理和地图上角色同步方法,以及游戏运营期间和外挂做斗争的一点经验分享和小结。相信会有一定的实战参考意义,当然也有讨论和改进的空间,这也是写这篇文章的主要目的。
图1
如图1,怎么保证地图上每个玩家看到的地图内的信息是基本一致的?
解决同步问题的最简单的方法就是把每个玩家的动作都向其他玩家广播一遍,这里其实就存在一些问题:1向哪些玩家广播,广播哪些消息;2如果网络延迟怎么办
角色的定义一般包括人物、怪物、宠物、NPC等,由于各种角色在地图上基本处于随时不规则移动并且各种属性信息也处于不断变化中(例如:变身、穿脱装备,使用隐身药水),所以首先需要实现地图的区块上各种角色的列表管理、切换地图、进出区块管理,实时同步角色的位置信息,以及附近角色的属性信息变化到游戏内相应的玩家身上,而且这些信息的同步需要实时,否则基本就失去了即时战斗的意义。同时由于MMO网络游戏环境的复杂性,管理好角色信息的同时还需要保证游戏的公平性,防止作弊、外挂,例如:判定人物的移动速度异常或者瞬间移动,纠正人物释放技能的时间间隔等,相信只要在公网运营过的游戏都多少会遇到防作弊的问题。
二、 地图的管理
无论是3D还是2D游戏:既然是即时地图战斗,那就自然有空间的概念,于是就产生了地图,一般游戏内玩家最经常发生的交互也是在地图上面发生的。moorpg的地图一般会有固定的一些属性:例如:地图的宽度、高度、最大角色数、地图上面怪物的AI、以及常用的九宫格划分区块大小等等属性。地图宽高度用于控制地图的大小,最大角色数用于控制地图的最大承载容量,防止过载,影响玩家体验。例如:图2对地图的部分关键属性进行定义
图 2
2.1地图区块划分
九宫格 通常的游戏玩法:地图上面的玩家在地图里面只需要看到视野内周围发生的事情,并不需要关心不同地图,甚至相同地图离自己很远地方此刻正在发生的实时场景,即使要关心,一般也是通过聊天公告等信息同步,并不需要收看现场直播)。于是,对地图采用分而治之的方法,把每张地图进行区块切分,定义好区块的大小,例如图3采用正方形的划分方法,规定每个区块的边长为6,一般区块的大小不会经常进行随意变动(除非在一些特殊的副本地图里面,该值如果进行了变化则需要进行特殊处理)这就是地图区块的概念。于是每个在线玩家在地图上面都会被定位到属于自己的区块,而当玩家在地图上面移动,则会在不同的区块之间进行来回的切换。同时,玩家在地图里面必然需要实时看到周围地图发生的场景,一般采用九宫格的方式,也就是说会实时同步包括玩家所在区块在内的周边9个区块的角色信息给予相应的玩家,理论上玩家只能看到9宫格内发生的事情。
图表 3
2.2 地图管理
划分好了地图区块之后,地图的管理至少还要包括:
A)阻挡的信息:包括静态阻挡和动态阻挡:角色移动的同时需要考虑地图区块里面的阻挡信息(例如:来自角色阻挡、来自地图固定建筑的阻挡等) ,如图4
图表 4
B)角色管理:需要管理地图上面角色实时信息,并且维护各个区块的最新角色实时列表信息:用于九宫格内玩家信息的同步。对于进入地图固定区块的玩家需要实时同步自己的信息给予附近的玩家,告诉他们有角色进入视野了,相反也要同步区块周围的角色信息给该玩家,同时,对于离开地图区块的玩家,需要同步信息告诉附近的玩家离开视野的消息,保证下一帧该角色不会再出现在该区块上。而处在同个9宫格内的玩家,也需要互相同步属性信息,保证看到的是最新的角色属性变化位置信息等;并且地图上面的NPC、怪物等角色自动刷新也需要地图逻辑来处理,例如怪物死亡之后,需要处理怪物退出游戏世界,一般还要让怪物经过一段时间自动复活,重新加入地图,另外还有地图上面怪物的AI,会在另外一篇文章单独讨论。
C)实现方法:具体角色在地图上面管理代码的实现:针对所有角色我们首先采用定时刷新的机制,在所有的角色身上绑定定时器,例如:在GamePlayer,GameMonster,GameNpc定时触发刷新机制:根据玩家实时所在的地图比较前后所在的区块是否一致,如果不一致,自然就需要处理附近玩家有角色进出视野的信息。例如:角色A定时触发了刷新机制,发现已经从地图亚特兰蒂斯区块99进入到了亚特兰蒂斯98区块,这时,自然就要重新计算玩家的九宫格区块变化,通知相关有区块信息变化上面的地图角色位置信息:并且需要实时维护一份每个区块每张地图上面的角色列表,这样做的目的:作为地图管理者,有必要知道当前我的地图上到底都有谁,常用于玩家附近的聊天,玩家同地图的聊天,并且根据玩法一般还有地图刷怪通知该地图所有玩家的信息等需求。另外,单独针对玩家的位置信息管理,则还跟游戏的特定玩法有关系,例如可以飞地图的游戏,则当玩家实时切换地图之后,则会直接触发进出区块视野的信息,而并不需要等到定时器触发来更新角色位置信息,还有玩家重新登录或者退出游戏,自然而然也要实时处理相应的位置同步信息;还有玩家换装、使用技能、上下坐骑等都即时发消息通知九宫格内的玩家同步属性信息
三、 人物的移动
管理好了地图之后,下面重点就是管理地图上面各种角色。对于mmorpg,玩家的移动几乎无时不在,并且相对于怪物的移动,宠物的移动等,玩家的移动更加核心,更加复杂不可控。特别是在大规模团战中,玩家会经常移动,于是需要管理好地图上玩家的移动,如果管理不好,则会容易出现大规模的外挂,严重影响游戏的公平性,对于整个游戏也几乎是毁灭性的打击s
3.1人物移动实现方法
对于游戏内玩家的移动一般有几种处理方法:1客户端只通知服务器要移动的位置,但并不需要经过后台的验证就直接开始移动了,通常服务器需要对最终客户端移动的位置进行校验,如果没有该步检测,那外挂就可以为所欲为了2客户端每一次移动都需要通过服务器的验证,然后再进行移动,该方法在网络延迟的情况下,会变得比较不流畅,给玩家带来很不爽的感觉。方法1同样存在问题:同步的误差,特别是在网络延迟特别严重的时候:比如有一个玩家A向服务器发了条指令,说我现在在P1点,要去P2点。指令发出的时间是T0,服务器收到指令的时间是T1,然后向周围的玩家广播这条消息,消息的内容是“玩家A从P1到P2有一个在A附近的玩家B,收到服务器的这则广播的消息的时间是T2,然后开始在客户端上画图,A从P1到P2点。这个时候就存在一个不同步的问题,玩家A和玩家B的屏幕上显示的画面相差了T2-T1的时间,要解决该问题,参考了之前的一篇文章,大致的内容如下:“有个解决方案:预测拉扯,首先要定义一个值叫:预测误差。然后需要在服务器端每个玩家连接的类里面加一项属性,叫latency,然后在玩家登陆的时候,对客户端的时间和服务器的时间进行比较,得出来的差值保存在latency里面。还是上面的那个例子,服务器广播消息的时候,就根据要广播对象的latency,计算出一个客户端的CurrentTime,然后在消息头里面包含这个CurrentTime,然后再进行广播。并且同时在玩家A的客户端本地建立一个队列,保存该条消息,直到获得服务器验证就从未被验证的消息队列里面将该消息删除,如果验证失败,则会被拉扯回P1点。然后当玩家B收到了服务器发过来的消息“玩家A从P1到P2”这个时候就检查消息里面服务器发出的时间和本地时间做比较,如果大于定义的预测误差,就算出在T2这个时间,玩家A的屏幕上走到的地点P3,然后把玩家B屏幕上的玩家A直接拉扯到P3,再继续走下去,这样就能保证同步。更进一步,为了保证客户端运行起来更加smooth,我并不推荐直接把玩家拉扯过去,而是算出P3偏后的一点P4,然后用(P4-P1)/T(P4-P3)来算出一个很快的速度S,然后让玩家A用速度S快速移动到P4,这样的处理方法是比较合理的,这种解决方案的原形在国际上被称为(Full plesiochronous),当然,该原形被我篡改了很多来适应网络游戏的同步,所以而变成所谓的:预测拉扯”
方法1实现:进行人物移动管理,需要定义以下相应的移动消息:具体的消息定义如下
(a)MSG_PLAYERMOVINGPOSTOSERVER //客户端向服务器端发送移动中玩家位置改变
(b)MSG_PLAYERMOVINGPOSANDDIRTOSERVER, //移动中玩家位置和朝向改变
(c)MSG_PLAYERPOSTOSERVER, //原地不动玩家的位置消息
(d)MSG_PLAYERPOSANDDIRTOSERVER, //原地不动玩家的位置和朝向消息
消息a和b负责向服务器同步人物需要移动到的目标位置和朝向信息,服务器需要对该位置信息进行阻挡、状态判断等合法性检测通过后,则同步角色位置信息到9宫格内的其它角色,相反如果失败例如移动到阻挡里面,则需要通知客户端纠正位置。消息c和d则同时用于前后台校验玩家的位置信息,例如角色一定时间内移动后最终停下来的位置。
四、 防作弊
常用的前后端消息加密,以及客户端加壳的机制几乎已经是通用的做法,所以这里不再重复,而且再高明的加密或者加壳几乎都有被破解的可能,但这些机制依然要坚持使用,至少可以提高作弊的成本,可以延长游戏的寿命,下面再描述我们目前除了消息加密和加壳之外采用的方法
限制客户端发送移动消息的频率:一般游戏内玩家并不需要进行太过于频繁的移动,就算需要频繁的移动客户端也可以对移动进行合并处理再上报移动位置信息,所以对于频繁的移动消息完全可以当做非法请求不处理。目的用于防止外挂封包频繁的发送移动消息,进行非法的快速移动(例如运营中发现玩家使用变速齿轮等插件,用于抢掉落宝箱等场景,会有玩家进行瞬移到宝箱附近拿走物品,这时候守门的人就崩溃了,严重影响了游戏的公平性)
移动距离检测:如图5,如何计算玩家的移动合法性。记录客户端每次发送移动消息的服务器时间间隔,根据人物的正常移动速度,算出合法的移动范围(一般需要加上一定的误差,由于网络的延迟等原因,不可能做到100%精确),如果发现不正常的移动速度,一般先采用和平的方法,让该移动消息失效。目的用于防止外挂封包发送不符合人物移动速度的位移信息
图表 5
消息时间校验:使用外挂的玩家,例如变速齿轮等插件,而且变速齿轮可以调整倍数,所以一般可以尝试出游戏的检测频率,因此必须采取手段防止玩家使用该插件。分析出变速齿轮的原理,一般是通过修改API函数GETTICKCOUNT和TIMEGETTIME,骗过了游戏和程序的定时器导致游戏和程序速度被改变。服务端发送时间种子到客户端.客户端做个差值.举个例子:服务端发来的种子是timeGetTime()=2000,客户端本地取时间是timeGetTime()=1000那么差值就是1000客户端所有的协议中增加时间字clienttime=timeGetTime()+1000到服务端。服务端取当前时间对这个时间做个容错校验.容错范围需要你自己调节.一般最好设大点.不然容易误判.
五、 运营中遇到的问题和优化空间
5.1服务器性能瓶颈
即时战斗类游戏一般都会设计有跨服战、国战等这样的玩法,会遇到某时段同屏角色数非常多的特殊场景,这时候大量的角色战斗中移动和释放技能,上下坐骑、必然会造成消息量暴增,服务器压力骤增。以线上运营的游戏为例,解决办法:
首先,对峰值期间的消息进行统计分析,对频繁发送并且流量大的消息进行重点监测,例如:分析出来大量角色移动进入区块,同步角色信息包括人物身上的时装,坐骑,宠物、装备等,会有一个峰值。但游戏中一般大规模团战的地图中,玩家一般最先关心的是敌人的动向位置信息,反而对人物的坐骑,时装,装备等信息可以延后,于是可以对某些特殊的场景例如国战地图,跨服战地图进行特殊的刷新机制进行优化,当区块内角色数到达一定数量后,同步信息只同步人物位置,模型等信息,减少消息的流量
其次,为了防止高峰时期服务器处理消息量过大,待处理消息队列以及发送队列拥挤,造成雪崩。对消息进行分级别定义,定义消息的时候进行消息级别定义,目前分为低、中、高三种消息类型,并且限制每种类型在等待处理的消息队列中的最大个数,每种消息类型在队列中大于特定的值,则直接丢弃,不处理。例如:服务器ping消息,人物跳跃等则可以定义为优先级低的消息,同理对于服务器需要发送出去的消息包也进行分级
实现方法如下图:
5.2 刷钱刷经验
一般外网运营一段时间的游戏很多都会遇到刷钱刷经验的bug,也许一些没有交易系统或者休闲类的游戏不会遇到,不过反正我们遇到了,就算没遇到做好预防措施也是必要的。解决方案是:请数值策划定制好根据游戏玩法角色对应一天最多能获得多少经验和金币,由服务器进行合法性检测,如果超过了阀值则必须采取处理措施。我们模仿了现实社会,给游戏设计了一张监狱地图,监狱顾名思义就是给犯法的人准备的,游戏里面发现有作弊,或者刷钱刷经验等的行为都会自动被传送到该地图,该地图没有传送点,只能一直呆在里面不能打怪升级也不能交易等诸多限制,进入该地图的玩家只有等坐牢时间到期了或者通过客服申诉成功,才会被传送出该地图
5.3 协议优化
随着游戏上线运营时间的变化,很多协议都会变化,量变会引起质变,影响玩家体验。例如:登录时需要加载的角色基本数据,游戏刚运营可能只有基本的一些人物装备、背包、任务等数据,但随着游戏运营时间的加长,必然会增加各种类似附魔、圣杯、宠物等等附加项需要加载,这样会导致协议的加载时间不断变长,于是需要有完善的日志统计每个协议的处理时间,重点观察处理时间长或者变化迅速的协议进行优化。例如:我们对登录协议特别进行了压缩,然后再解压,之后玩家登录游戏的时间大大缩减,这样牺牲了服务器一点cpu的时间,能换来体验的改善。类似的优化还比较多,运营期需要不断分析总结