所有Unreal网游开发者都应该看的文章:使用虚幻引擎4年,再谈UE的网络架构( 二 )


) , 也许中间会丢包会延迟(actor首次同步时是reliable的) , 但是其内置的机制会保证属性的值最终送达到客户端 。 借用一句经典的话来说就是 , 同步数据也许会迟到 , 但是永远不会缺席


无论是RPC , 还是属性同步 , 你会发现他都是基于UObject的 , 或者更确切的讲都是基于Actor的(以及其附属组件) 。 因为这两种功能一个是利用类中的函数 , 另一个是利用类对象的属性 , 他们都需要与某一个具体的对象作为媒介 , 而在UE的架构中 , 设计都是面向对象的 , 每个Actor都可以理解为现实世界的对象 。 既然是基于Actor的 , 那么整个同步就与GamePlay框架紧密相连 。 由于我们在发送同步数据的时候需要知道这个数据应该发向哪个客户端 , 而客户端与服务器的链接信息(IP等)又在Playercontroller里面 , 所以同步的逻辑与playercontroller密切相关 。 很多刚接触unreal的朋友经常会遇到RPC数据发不出去或者收不到的问题 , 就是没有认识到playercontroller其实是包含客户端与服务器的连接信息的 。 最典型的 , 假如你有服务器上连着10个玩家客户端 , 服务器上有一辆车 , 让他执行Client RPC , 他怎么知道发给哪个客户端?当然是通过这个车找到控制他的playercontroller , 然后找到对应客户端的IP , 如果这个车不被任何客户端控制 , 那他就不知道要发给谁 。

当然 , RPC与属性同步的实现原理不同也决定了他们有很多差异 。 由于属性同步是跟着每一个实例对象走的 , 所以不存在“随用随发” 。 也就是说 , 属性同步需要在每帧特定的时机通过统一的引擎接口写到发送缓存(sendbuffer)里面 。 这样带来的问题就是 , 你在同一帧里面修改的属性只有最后的那个值会传到客户端那里 , 进而导致你的回调函数也只会执行一次 。 而RPC不同 , 每次执行时都会立刻将数据塞到发送缓存里面 , 从而保证不会丢失任何一次RPC的调用(假如RPC是可靠的) 。

所有Unreal网游开发者都应该看的文章:使用虚幻引擎4年,再谈UE的网络架构
文章图片


另外 , 这里面还有一个深坑 , 就是关于Actor以及Component的同步顺序问题 。 一个对象的同步首先要给客户端上的对象与服务器上的对象建立关联 , 这样服务器的A变化了才会告诉客户端上的A也去变化 。 但是A是一个对象 , 对象也是需要同步的 , 一个场景里面有那么多的对象 , 同步肯定是按顺序的来的 。 这样就会经常出现A的对象里面有很多指向B对象的同步指针属性 , 但是A对象出现的时候B还没同步过来 , 所以在A的Beginplay里面访问B是不行的 。 那么如何解决这个问题?答案是用属性回调 , 一旦执行了属性回调 , 就可以确保A的B指针是存在的 。 不过 , 属性回调并不能解决所有问题 。 假如B对象还有C对象的指针 , 回调的时候C还没同步过来 , 你想用B去访问C发现又是空指针 。 这问题目前在现在的虚幻引擎里面还没有完美的解决方案 , 所以我们要尽可能的避免这种情况(我本人正在尝试实现一些可行的方法) 。 类似引发的更细节的问题还有很多 , 后面我会列举一些 。

所有Unreal网游开发者都应该看的文章:使用虚幻引擎4年,再谈UE的网络架构
文章图片


二、移动同步


两种同步手段已经介绍完毕 , 我们现在把视角锁定在网络同步的解决方案上 。 游戏中同步本质上是同步客户端之间的表现 , 而RPC与属性同步都只是数据上的同步 , 我们需要将其与画面表现结合起来 。 画面表现说白了就是物体的显示与隐藏、动画、位置等 , 其中位置同步就是最复杂的一项 , 因为游戏中的角色可能是每帧都在移动的 , 移动组件(movementcomponent)就是为了解决这个问题而诞生的 。


推荐阅读