现代游戏引擎 - 网络游戏的进阶架构(十九)
位移同步(Character Movement Replication)
角色位移同步(character movement replication)是现代大型网络游戏必须要实现的功能。由于网络环境的不稳定,玩家操作角色在自己视角和其他玩家视角下的行为往往会有一定的延迟,即角色在其他玩家视角下的动作会滞后于操作玩家的第一视角。
内插和外插(Interpolation & Extrapolation)
在这种情况下我们可以使用内插(interpolation)和外插(extrapolation)两种插值方法来缓解延迟。
内插(Interpolation)
内插是指利用已知的控制点来获得中间的状态。当网络存在波动时利用内插的方法可以保证角色的动作仍然是足够平滑的。
在具体进行插值时还可以人工设置一个offset来保证buffer中有足够多的控制点,这样虽然会提高一些延迟但可以获得更加光滑稳定的插值结果。
当然内插也存在一些问题。由于内插加剧了网络延迟的问题,同样的游戏世界在操作者和其他玩家的视角下会有非常大的差别。这样的问题在一些延迟要求很高的游戏中可能是不可接受的。
内插(Extrapolation)
相比于内插,外插的本质是利用已有的信息来预测未来的状态。当我们对网络延迟有一定的估计时就可以通过外插的方法来推断角色的状态。
实际上外插的思想在很多领域都有非常多的应用,dead reckoning算法那就是外插在导航领域的实践。
投影速度混合(Projective Velocity Blending - PVB)
回到游戏领域的应用中来,projective velocity blending (PVB)是一种使用外插来更新角色位置的算法。假设
我们的目的是通过调整速度使得角色可以在
上式意味着对速度进行线性插值使得
这里需要注意的是PVB算法并不符合动力学原理,它仅仅是一种插值方法。
碰撞问题(Collision Issues)
外插在处理碰撞时会容易产生严重的物体穿插问题。
要处理这样的情况一般需要切换到本地物理引擎来处理碰撞问题,比如说在看门狗2中就使用了这样的方法。
使用场景(Usage Scenarios)
简单总结一下,对于玩家操作角色经常出现瞬移或是具有很大加速度的情况比较适合内插,对于操作角色比较符合物理规律的情况比较适合外插,而在一些大型在线游戏中还会同时结合这两种插值方法来提升玩家的游戏体验。
命中判定(Hit Registration)
命中判定(hit registration)同样是现代网络游戏必须要提供的基本服务。以FPS游戏为例,从玩家开枪到击中敌人这一过程实际上有着非常大的一段延迟,在各种不确定因素的影响下如何判断玩家确实击中目标就需要一些专门的设计。
挑战(Challenges)
命中判定的难点之一在于确定敌人在什么位置。由于网络延迟和插值算法的存在,玩家视角下的目标是落后于服务器上目标的真实位置的。
命中判定的另一个难点在于如何判定是否击中了目标。在游戏场景中目标往往是运动的,而且往往会位于一些掩体附近。当弹道不是瞬间命中时就需要一些额外的算法来进行判断。
客户端命中检测(Client-Side Hit Detection)
因此命中判定的目标是保证游戏中的玩家就是否命中的问题能够达成一个共识(consensus)。目前主流的处理方法包括在客户端上进行检测,称为client-side hit detection,以及在服务器端进行判断的server-side hit registration。
在客户端上进行检测时的基本思想是一切以玩家客户端视角下的结果为准。玩家开枪后的弹道轨迹以及击中判断都先在本地进行,然后发送到服务器上再进行验证。
在服务器上会对玩家的行为进行一些验证从而保证确实击中了目标。当然在实际的游戏中这个验证过程是相当复杂的,涉及到大量的验证和反作弊检测。
在客户端上进行命中检测的优势在于它非常高效而且可以减轻服务器的负担,但它的核心问题在于它不够安全,一旦客户端被破解或是网络消息被劫持就需要非常复杂的反作弊系统来维持游戏平衡。
服务器端命中检测(Server-Side Hit Registration)
在服务器端进行检测的一个难点在于服务器上角色的位置和状态是领先于玩家视角的,当玩家开枪时目标很可能已经移动到其它的位置上了。从这样的角度来看,玩家很难命中移动中的目标。
因此我们需要对网络延迟进行一定的补偿。当服务器收到射击的消息时不会直接使用当前的游戏状态,而是根据延迟使用之前保存的游戏状态。
掩体问题(Cover Problems)
对于掩体的问题,由于网络延迟的存在可能会出现射击者优势或窥视者优势的情况。
为了缓解延迟的问题在游戏设计时还可以利用动作前摇(startup frames)来掩盖掉网络同步。
类似地,也可以使用各种特效来为服务器同步争取时间。
基础MMO网络架构(MMOG Network Architecture)
对于大型多人在线游戏(massively multiplayer online game, MMOG)我们还需要一些额外的网络架构设计。
MMO体系结构(MMO Architecture)
MMOG中一般会有各种子系统来组成整体的玩法系统。
简单的MMO分层架构如下:
连接层(Link Layer)
连接层(link layer)是玩家和游戏建立连接的一层。在MMO中为了保护服务器不受攻击,我们需要单独的连接层来分离玩家和服务器数据。
大厅(Lobby)
连接到link layer后玩家会进入大厅(lobby)。大厅可以理解为一个特殊的游戏模式,从而方便管理等待进行游戏的玩家。
角色服务器(Character Server)
由于MMO中玩家的数据往往非常巨大,玩家的数据一般会保存在一个专门的服务器上称为character server。
交易系统(Trading System)
交易系统(trading system)是MMO的重要组成部分,在设计交易系统时需要保证系统有足够高的安全性。
社交系统(Social System)
玩家之间的聊天和交互功能一般会通过专门的社交系统(social system)来进行实现。
匹配(Matchmaking)
很多MMO还需要实现玩家的匹配
数据存储(Data Storage)
数据存储(data storage)是MMO中非常重要的问题。不同于单机游戏,在MMO中玩家可能会随时下线但服务器则必须要保证一直运行。因此如何存储和调用数据是一个非常重要也非常困难的问题。
关系型数据库(Relational Data Storage)
关系数据库是最基本的数据存储方式,像游戏中玩家数据和游戏数据都会使用关系数据库进行存储。现代网络游戏中往往还会结合分布式的技术进行存储。
非关系型数据库(Non-Relational Data Storage)
除了关系数据库外,非关系数据库也是目前非常流行的数据存储方法。像游戏中的日志还有各种game state就比较适合使用非关系数据库。
内存数据库(In-Memory Data Storage)
随着游戏系统变得越来越复杂,在服务器运行过程中会产生大量的中间数据存储在内存中。因此我们需要一些内存数据管理工具。
分布式系统(Distributed System)
随着玩家数量的增长,在单个服务器上支撑所有玩家进行游玩是几乎不可能的。在这种情况下就需要使用分布式系统(distributed system)来提供游戏服务。
分布式系统有很多自身的难点:
负载均衡(Load Balancing)
负载均衡(load balancing)是分布式系统中非常重要的概念,我们希望在不同服务器上都有相近的负载从而避免某些服务器负载过大的问题。
consistent hashing是实现负载均衡的经典技术。它的核心在于为服务器以及玩家设计对应的hashing算法,然后把服务器和玩家映射到一个环上,当玩家需要连接服务器时只需要利用hashing值寻找最近的服务器即可。当某个服务器从圆环上删除时只需要把玩家连接到下一个服务器上即可,类似地添加新的服务器只需要在圆环上添加新的节点。
显然consistent hashing算法的效果很大程度上依赖于hashing函数,当服务器或是玩家在环上的分布不够均匀时是很难保证负载均衡的。为了缓解这样的问题我们可以在环上设置一些虚拟的服务器,然后再把虚拟服务器链接到真实服务器上。
服务器管理(Servers Management)
分布式系统的另一大难点在于如何管理大量同时运行的服务。
我们可以使用Apache或是etcd这样的工具来监视和管理各种服务。
带宽优化(Bandwidth Optimization)
带宽优化(bandwidth optimization)是现代网络游戏必须要解决的问题。网络带宽不仅仅是游戏运营成本的重要组成部分,对玩家的游戏体验也有着重要的影响。
带宽的计算实际上非常简单,根据计算视角的不同我们有不同的衡量指标:
数据压缩(Data Compression)
数据压缩(data compression)是经典的带宽优化方法。在网络游戏中我们可以把浮点数转换为低精度的定点数来减少数据量。
我们甚至可以对游戏地图进行分区然后在小区域中使用定点数来缓解低精度数值带来的影响。
对象相关性(Object Relevance)
另一种缓解带宽需求的方式是只同步和玩家相关的对象,这样可以极大地减少每次和玩家同步的数据量。
我们可以把整个游戏世界划分为若干个区域,这样每个玩家都会位于某个区域中。不同区域的数据是相互隔绝的,因此在同步时只需要同步区域中的数据即可。
在一些开放世界游戏中我们不希望出现区域的划分,此时则可以利用area of inerest (AOI)的概念。AOI的意义在于我们只需要关注玩家附近的情况而无需考虑更远区域的信息。
AOI最简单的实现方法是利用一个半径来查询附近信息。
当玩家数量很多时还可以利用网格划分的方法来加速查询。
除此之外我们也可以使用十字链表这样的数据结构来进行加速。
当然也可以使用PVS这样的技术来剔除不相关的对象。
变化的更新频率(Varying Update Frequency)
除了上面介绍的方法我们也可以通过调整更新频率的方式来降低带宽。一种经典的策略是根据物体和玩家之间的距离来设置更新频率,使得距离玩家远的物体更新得慢一些,距离近的更新得快一些。
反作弊(Anti-Cheat)
反作弊(anti-cheat)同样是现代网络游戏必须要实现的功能,作弊行为对游戏运营和其他玩家都会造成非常巨大的伤害。
网络游戏中的作弊手段是相当多样的,比如说可以修改本地的代码或是内存中的数据,骇入底层三方库的接口,甚至可以通过劫持网络通信来实现作弊行为。
混淆内存(Obfuscating Memory)
作弊最基本的方法是修改本地的内存。对于很多在客户端进行校验的游戏只需要修改游戏内存中的数据就可以进行作弊。
为了应对这种作弊方式,我们可以对客户端套一个壳来防止侵入。类似地,也可以对内存数据进行加密来防止侵入。
通过哈希验证本地文件(Verifying Local Files by Hashing)
另一种常见的作弊方法是修改本地的资源文件。我们可以通过对比本地和服务器上资源的hashing来进行处理。
数据包拦截和操作(Packet Interception and Manipulation)
消息的劫持和篡改是网络游戏中经常遇到的作弊方式,因此对于客户端和服务器发出的包就必须进行加密。
目前主流的加密算法包括对称加密和非对称加密两种。对称加密是指客户端和服务器共享一副秘钥,它的缺陷在于当客户端被破解后秘钥就是公开的了,此时加密也就失效了。因此在实践中更多地使用的是非对称加密算法,此时客户端只有公钥而在服务器端存有私钥。这样即使客户端被破解了也无法完全获知所有数据。
系统软件调用(System Software Invoke)
除此之外,作弊者还可以通过修改底层游戏引擎代码来进行作弊。
针对这种情况可以使用各种安全软件来检测游戏引擎是否存在注入的情况。
AI作弊(AI Cheat)
目前随着AI技术的发展还出现了使用AI进行作弊的现象,这对下一代反作弊系统的开发又提出了新的挑战。
构建可拓展的游戏世界(Build a Scalable World)
构建可拓展的游戏世界可以说是每个游戏从业者的梦想。
引用
- 本文作者:樱白 - Cherry White
- 本文链接:https://cherry-white.github.io/posts/d8819d45.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!