Online Game Development in C++ 第五部分总结
I. 教程案例框架描述
该套教程做了一个简单的汽车控制系统,没有用到物理模拟。用油门和方向控制汽车的加速度和转向,同时还有一些空气阻力和滚动摩擦力的设置增加了真实感。汽车的位置是通过加速度和时间等计算出来的。
关键的参数包括:加速度,速度,质量,最大驱动力,最小转弯半径等。
详细的计算方式就不细说了,重点是没有用到物理模拟。
II. 联网游戏要面对的两大问题,以及模拟这些困难的方法
1. 网络更新频率
在每一个Actor中都能设置其网络更新(Replicate)的频率(前提是该Actor需要Replicate)。
具体方法是在BeginPlay中加入以下语句:
if (HasAuthority())
{
NetUpdateFrequency = ;//默认值是10
}
(注意NetUpdateFrequency是针对属性的Replicate的,和RPC无关)
这个频率和游戏运行帧率相比较来说要低很多。尽管这个值可以手动设置的更高,但是更新的越频繁,给网络带来的负担越大,在多人联网游戏中必须合理分配带宽,而不能无限制地提高某一个actor的数据传输量。
在一个对操作比较敏感的游戏中(例如极品飞车)如果不做任何处理,仅依靠0.1秒一次的更新是无法保证把本地的游戏对象的运动完美复制到另外一端的。玩家的操作是会随时发生很细腻的变化的,仅0.1秒更新一次输入会产生很不连续的跳动,其对操控对象造成的影响会产生更大的偏差,多种因素(例如油门和转向)造成的误差累积起来会相差很远。

正因为要克服这个问题,所以在开发时需要刻意将问题“夸大”,模拟这个困难,把更新频率调低,比如设置成1次/秒,更容易看出采取一些方法前后的效果差别。
2. 网络延迟
一方发出的操作指令通过路由到达另外一方需要一定的时间,这个时间就是网络延迟,通常以毫秒记。如果延迟过大,而不做任何处理,会看到操作对象有明显的“跳跃”。

延迟是网络环境决定的,在代码上无法避免延迟,但是我们可以做到让操作对象平滑地移动,虽然整体迟一些,但仍是连贯的。
为了夸大这个问题,我们可以输入以下console命令模拟网络延迟:Net PktLag=xxxx。 该值的单位是毫秒,例如我们可以设置为1000来模拟一秒钟的网络延迟。
III. Actor Role
教程中的一幅图对Actor Role总结的很好

图中上方绿色的框表示服务端,下方蓝色和红色的框分别表示两个客户端。
绿色方块代表一些在服务端控制其移动的对象,例如Listen Server自己控制的Pawn,或者移动的平台、机关等。
蓝色小人表示蓝色客户端的pawn,红色小人表示红色客户端的pawn。
IV. 三种Actor Role的同步方案
为了解决后面的一系列问题,将“输入”和“状态”分别进行封装。
“输入”封装为Move,包括油门数值、转向数值、DeltaTime、时间标签。
“状态”封装为State,包括Transform,速度,最后的输入。
这里只进行方法上的描述,具体代码就不列出了。
1. Authority
服务端所有的Actor都是Authority。自己控制移动的Actor当然是Authority,而其他玩家的Pawn的控制也是提交到服务端进行计算后再次同步给所有客户端的,而且服务器计算出来的就是标准,所以也称为Authority。
服务端自己控制的Actor自然可以在服务端本地让其平滑移动。但这个Actor在客户端怎么移动呢?实际上它在客户端的Role(即Remote Role)就变成了Simulated Proxy。
具体情况和处理方法见后面的Simulated Proxy。
2.Autonomous Proxy
客户端自己控制的Actor是Autonomous proxy(自治代理)。
Auonomouse Proxy它可以首先获取输入,所以在本地就可以平滑地模拟移动,然后在Tick中,创建一个Move,通过一个Reliable 的RPC函数SendMove将Move提交到服务端运行。由于是在Tick中,而且是Reliable函数,所以在服务端执行的频率和Tick是一致的,这是唯一比较耗费带宽的操作,带来的好处就是服务端也会平滑精确地响应其输入。
在该RPC函数中,会改变一个ServerState属性,是前面封装的State类型结构体。
而ServerState属性是一个OnRep函数,每次更新它,就会在客户端触发另外一个函数OnRep_ServerState(),在这个函数中覆盖客户端自己计算的汽车状态。
需要注意的是ServerState是属性,它的Replicate的频率并不是每个Tick一次,而是使用网络更新频率。虽然频率不高,但是在网络延迟不严重的情况下,服务端和客户端计算出来的位置应该不会有太大差别,所以这个覆盖行为也不会让玩家有明显的感觉。
但是如果考虑到网络延迟,上述方法仍不能完美应对。
可以手动将网络延迟模拟为1秒,就会明显感觉到车辆在移动和转弯时频繁地跳回之前的某个状态,非常难以控制,而且根本谈不上平滑。
原因也很好理解,因为根据上述方法的描述,服务端会定期覆盖本地的车辆状态,而因为网络延迟的原因,服务端的“反应”总是慢半拍。比如客户端车辆已经启动,并且走了一段距离了,服务端的车辆才刚刚启动,那么在同步时,服务端的车辆位置和客户端的车辆位置有一定的差距,生硬的去覆盖当然会产生一个跳跃,并且这个问题会持续下去,以至于根本无法进行正常的操作。
我们虽然无法避免网络延迟,但是我们有办法将操作变得平滑,不再跳跃。教程称之为:“Keeping ahead of the server”,但我宁愿称之为“缓存操作”。方法概述如下:

在客户端本地的Tick中创建Move(和之前是一样的),然后将Move缓存入一个数组(不同的地方),然后依照前面的方法,本地模拟,再通过RPC在服务端模拟。在同步ServerState时多做一些事情:在Autonomous Proxy中对比ServerState的LastMove中的时间标签和前面缓存的Move数组,时间早于LastMove的都清理掉(因为这些在服务端已经得到了执行)。然后通过一个For循环在本地瞬间模拟剩下的未执行的Move数组。
对比这个操作和直接生硬的从服务端往客户端覆盖,就会发现这个操作实际上把服务端还没接收到的一系列操作在本地瞬间重演了,并在服务端的结果上进行偏移给与客户端,这样虽然增加了计算量,但是保证了本地运动是平滑的,同时也最大程度的保证了服务器的权威性——因为仍是在服务器计算的结果之上进行的偏移。
3. Simulated Proxy
Simulated Proxy只存在于客户端(因为服务端的都是Authority),它无法受到自己控制,是从服务端同步来的(不管是谁控制的,可能是其他客户端,也可能是直接由服务器控制),所以叫做“模拟代理”。
方法1:
简单粗暴的做法是让其同步移动。
C++的做法是在构造函数中加入 bReplicateMovement = true;
蓝图中是在属性中搜索ReplicateMovement,将其打钩。
这样虽然可以同步其移动,但是因为同步频率的问题,在客户端会看到类似定格动画的移动,很不平滑。
方法2:
注意该方法需要设置bReplicateMovement=false。不让Movement自动同步,而是手动处理。
正常的处理方法是滞后一次更新,对物体位置进行Linear Interpolation,对旋转进行Slerp。这样Simulated Proxy的移动虽然会慢一点,但是好处是移动会平滑很多。
方法3:
注意该方法需要设置bReplicateMovement=false。不让Movement自动同步,而是手动处理。
更好的办法是利用Hermite Cubic Spline Interpolation。

重点公式:
Derivative(曲线斜率)= DeltaLocation/DeltaAlpha
Velocity (速度)= DeltaLocation/DeltaTime
DeltaAlpha = DeltaTime/TimeBetweenLastUpdates(两个点之间的时间差)
推倒得出:
Derivative = Velocity * TimeBetweenLastUpdates
注意虚幻里的速度默认单位是m/s,而位置的单位是cm,所以在速度转换为位置的时候一定要记得*。
虚幻里提供了两个函数可以帮助我们模拟Hermite Cubic Spline,分别是:
FMath::CubicInterp()
和
FMath::CubicInterpDerivative()。
前者用来求插值的位置,后者用来求插值的速度,具体用法如下:
FVector NewLocation = FMath::CubicInterp(初始位置,初始Derivative,目标位置,目标Derivative,比例);
FVector NewDerivative = FMath::CubicInterpDerivative(初始位置,初始Derivative,目标位置,目标Derivative,比例);
求出的NewDerivative转换成速度也很简单,直接除以(TimeBetweenLastUpdates*100)即可。
但是如果讲Velocity的方向设置为旋转方向,倒车时候就会瞬间调转车头,这是我们不希望看到的。所以旋转上最好还是结合第二种方法来做。总之旋转上没有非常好的平滑方法。
需要知道,上述这写方法都是针对非常低的同步频率(测试使用的是1次/秒)来处理的,在正常同步频率下(10次/秒)的表现还都是非常好的。
Online Game Development in C++ 第五部分总结的更多相关文章
- 浅谈Excel开发:一 Excel 开发概述
做Office相关的开发工作快一年多了,在这一年多里,在插件的开发中遇到了各种各样的问题和困难,还好同事们都很厉害,在和他们的交流讨论中学到了很多的知识.目前Office相关的开发资料是比较少 ...
- rails4.2.6配置发送邮件
调试了很久,最后终于可以发送了 1 在config/environments/development.rb文件里配置邮件信息 config.action_mailer.raise_delivery_e ...
- LAMP的安装
一,LAMP的安装流程:mysql.apache.php或者apache.mysql.php.php放到最后的原因是,php在编译安装的时候是依赖于前2者的. 二,Mysql的安装: 1.下载mysq ...
- Excel 开发概述
浅谈Excel开发:一 Excel 开发概述 做Office相关的开发工作快一年多了,在这一年多里,在插件的开发中遇到了各种各样的问题和困难,还好同事们都很厉害,在和他们的交流讨论中学到了很多的知识. ...
- 【转载】浅谈Excel开发:一 Excel 开发概述
博客园就是好,想要什么都给总结了,多谢 原文转载:http://www.cnblogs.com/yangecnu/p/Excel-Develpment-Introduction.html 做Offic ...
- Mybatis配置一对多的关联关系(五)
问题:是查询一个部门中的员工? 一.web项目构架 二.lib文件的jar 三.配置大小配置和该工具类 1大配置mybatis-config.xml <?xml version="1. ...
- 【五】将博客从jekyll迁移到了hexo
本系列有五篇:分别是 [一]Ubuntu14.04+Jekyll+Github Pages搭建静态博客:主要是安装方面 [二]jekyll 的使用 :主要是jekyll的配置 [三]Markdo ...
- 1.3 PROGRAM DEVELOPMENT ENVIRONMENT
1.3 PROGRAM DEVELOPMENT ENVIRONMENT 1.4 WIN32 EXECUTEABLE FILE FORMAT We should also know that compl ...
- 五步搞定Android开发环境部署
引言 在windows安装Android的开发环境不简单也说不上算复杂,本文写给第一次想在自己Windows上建立Android开发环境投入 Android浪潮的朋友们,为了确保大家能顺利完成开发 ...
随机推荐
- GCC 环境变量 & eclipse CDT 头文件配置
转:http://blog.csdn.net/statdm/article/details/7751000 GCC 环境变量 & eclipse CDT 头文件配置 在unix 下使用e ...
- 20191112 Spring Boot官方文档学习(4.5-4.6)
4.5.国际化 Spring Boot支持本地化消息,因此您的应用程序可以迎合不同语言首选项的用户.默认情况下,Spring Boot messages在类路径的根目录下查找message resou ...
- webView实现网页缩放
项目中遇到要实现webview上面的网页缩放功能,在网上查了资料加自己实践后得出结论: //缩放开关,设置此属性,仅支持双击缩放,不支持触摸缩放 mWebView.getSettings().setS ...
- Bubble Sort(冒泡排序)
冒泡排序(英语:Bubble Sort,台湾另外一种译名为:泡沫排序)是一种简单的排序算法.它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行 ...
- 【监控笔记】【1.4】Pssdiag和Sqldiag管理器
--没有实操过,有点复杂,先写上以后有用到再深入研究 统计与诊断数据是任何 SQL故障修复工作的关键所在. 如果没有掌握这些数据,就无法确定数据性能问题的根源.数据表的瓶颈可能并不是由索引问题造成的: ...
- C++中的深拷贝和浅拷贝构造函数
1,对象的构造在实际工程开发当中是相当重要的,C++ 中使用类就要创建对象,这 就涉及了对象的构造,本节课讲解对象的构造和内存操作方面的问题: 2,实际工程开发中,bug 产生的根源,必然的会有内存操 ...
- C#设计模式:备忘录模式(Memento Pattern)
一,C#设计模式:备忘录模式(Memento Pattern) 1.发起人角色(Originator):记录当前时刻的内部状态,负责创建和恢复备忘录数据.负责创建一个备忘录Memento,用以记录当前 ...
- 关于ES6的新特性
1 let声明变量 01 let声明的变量,不可重复声明,比如 let a=1 : let a=2 :这样申明会报错 02 let声明的变量,有块级作用域,比如 if( ){ ...
- svn版本服务器的搭建和简单使用
⼀ 服务器搭建篇 1 在”应⽤用程序”⽂文件夹下,找到”实⽤用⼯工具”,打开”终端”APP 2 运⾏行svnadmin create repository,运⾏行完毕之后,可以在当前⺫⽬目录下找 到⼀ ...
- python基础知识的入门介绍
一.什么是编程语言 任何词语都是一种高度的概括和总结,所以找关键字.如下: (1)1.什么是"语言":一个人与另一个人沟通的介质 2人将自己的思维逻辑和想法通过计算机能过识别的语言 ...