Believable Dead Reckoning for Networked Games (为联网游戏而设计的可信的导航预测)
Curtiss Murphy
Alion Scence and Technology
18.1 Introduction
Your team's producer decides that it's time to release a networked game, saying "We can publish across a network, right?" Bob kicks off a few internet searches and replies, "Doesn't look that hard." He dives into the code, and before long, Bob is ready to begin testing. Then, he stares in bewilderment as the characters jerk and warp across the screen and the vehicles hop, bounce, and sink into the ground. Thus begins the nightmare that will be the next few months of Bob's life, as he attempts to implement dead reckoning "just one more tweak" at a time.
你团队的制作人感觉是时候发个带联机功能的游戏版本了,他说:“我们可以搞一个可以联机的,对吧?” Bob去网上查了下,回复说道:“貌似不是很难”。他去研究了下代码,没过多久Bob就准备开始测试新功能了。接下来,他困惑地盯着屏幕上的人物,他们在屏幕上颠簸、摇晃,车辆跳跃、弹跳、沉入地面。因此,Bob未来几个月的时间里都要在噩梦里渡过,因为他正在不断的“再做一次调整”,期望能调校出一个可用的航位推算算法。
This gem describes everything needed to add believable, stable, and efficient dead reckoning to a networked game. It covers the fundamental theory, compares algorithms, and makes a case for a new technique. It explains what's tricky about dead reckoning, addresses common myths, and provides a clear implementation path. The topics are demonstrated with a working networked game that includes source code. This gem will help you avoid countless struggles and dead ends so that you don't end up like Bob.
本精粹描述了用于联网游戏的、可信、稳定和高效的航位推测算法所需要的全部知识。涵盖了基本理论,比较多种算法,并提出了一种新的技术。它解释了航位推算的棘手之处,解惑各种黑科技,并提供了一套清晰的实现方法。这些主题通过一个包含源代码的可运行的联网游戏来演示。本精粹将帮助你免去无数的挣扎和死胡同,这样就不会最后变成像Bob那样。
18.2 Fundamentals
Bob isn't a bad developer; he just made some reasonable, but misguided, assumptions. After all, the basic concept is pretty straight forward. Dead reckoning is the process of predicting where an actor is right now by using its last known position, velocity, and acceleration. It applies to almost any type of moving actor, including cars, missiles, monsters, helicopters, and characters on foot. For each remote actor being controlled somewhere else on the network, we receive updates about its kinematic state that include its position, velocity, acceleration, orientation, and angular velocity. In the simplest implementation, we take the last position we received on the network and project it forward in time. Then, on the next update, we do some sort of blending and start the process all over again. Bob is right that the fundamentals aren't that complex, but making it believable is a different story.
Bob不是一个弱鸡开发者;他只是做了一些合理但错误的假设。毕竟,基本概念非常直接。航位推算是利用一个行动者最后已知的位置、速度和加速度来预测他现在在哪里的过程。它适用于几乎所有类型的运动对象,包括汽车、导弹、怪物、直升机,以及行走的角色。对于每一个远端对象,它被网络上的某个地方所操控,我们会(通过网络)更新他(在本地)的运动状态参数,比如position, velocity, acceleration, orientation, 和 angular velocity(之类的)。最简单的实现方法,是我们先使用网络发过来的最后一次准确位置和运动状态,并用这些信息计算后续可能的位置。然后在后续收到的更新里,和前面的结果做一定程度上的融合,再投射计算过程。整个过程一直不断重复。Bob对于基本理论的理解是对的,它并不复杂,但是要让结果看起来可信就是另外一回事了。
Myth Busting-Ground Truth
Let's start with the following fact: there is no such thing as ground truth in a networked environment. "Ground truth" implies that you have perfect knowledge of the state of all actors at all times. Surely, you can't know the exact state of all remote actors without sending updates every frame in a zero packet loss, zero latency environment. What you have instead is your own perceived truth. Thus, the goal becomes believable estimation, as opposed to perfect re-creation.
澄清-真实值
让我们从以下事实开始:在网络环境中没有"真实值"这样的东西。“真实值”意味着你一直对所有对象的状态有着完美的了解。当然,即使在一个零数据包丢失、零延迟的环境中,如果不每帧发送更新,就无法知道所有远程参与者的确切状态。相反,你拥有的是你自己感知到的真相。因此,解决问题的目标是成为可信的估计(模拟),而不是完美的重现。
Basic Math
To derive the math, we start with the simplest case: a new actor comes across the network. In this case, one of our opponents is driving a tank, and we received our first kinematic state update as it came into view. From here, dead reckoning is a straightforward linear physics problem, as described by Aronson [1997]. Using the values from the message, we put the vehicle at position ??0 ′, and begin moving it at velocity ??0 ′, with acceleration ??0 ′,, as shown in Figure 18.1. The deadreckoned position ????, at a specific time Tis calculated with the equation
Figure 18.1. The first update is simple.
为了掌握数学,我们先从最简单的情况开始:一个对象通过网络出现了。在这个情况里,我们的对手是一辆行进中的坦克,当他进入我们视野时,我们获得了第一个关于它的运动状态。目前来讲,航位推算只是一个简单的线性直线运动的物理学问题[Aronson 1997]。使用网络消息中的值,我们将载具的位置记为[P0'], 速度记为[V0'], 加速度记为[A0'],如图18.1。 航位推算出来的位置记为[Qt], 任意时间里,Qt的值可以由下列公式计算出:
[Qt] = [P0'] + [V0'] * [Tt] + 0.5 * [A0'] * [Tt]^2;
Continuing our scenario, the opponent saw us, slowed his tank, and took a hard right. Soon, we receive a message updating his kinematic state. At this point, we have conflicting realities. The first reality is the position ???? where we guessed he would be using the previous formula. The second reality is where he actually went, our new ??0 ′, which we refer to as the last known state ??0 ′ ??0 ′ because it's the last thing we know to be correct. This dual state is the beginning of Bob's nightmares. Since there are two versions of each value, we use the prime notation (e.g., ??0 ′) to indicate the last known, as shown in Figure 18.2.
Figure 18.2. The next update creates two realities. The red line is the estimated path, and the green curve is the actual path.
继续我们的探索,对手看到了我们,给他的坦克减速,并立即右转。很快,我们收到了一条消息来更新他的运动状态。在这个时刻,我们有了两个相互发生冲突的坐标。第一个坐标是我们之前用公式推算出来的位置[Qt]。第二个坐标是他实际上走到的位置,也就是[P0']的新值,[P0']之是我们用来表述最近一次知道的确切位置。这种双重状态就是Bob噩梦的开始。由于每个值有两个版本,所以我们使用素数表示法(例如[P0′])来表示最后一个已知值,如图 18.2.
如图 18.2. 接下来的更新创造了两个坐标。红线是推测的路径,绿色曲线是实际路径。
To resolve the two realities, we need to create a believable curve between where we thought the tank would be, and where we estimate it will be in the future. Don't bother to path the remote tank through its last known position, ??0 ′ Instead, just move it from where it is now, ??0 ′, to where we think it is supposed to be in the future, ??1 ′.
为了解决两个坐标的冲突,我们需要创建一个可信的曲线轨迹,基于我们对坦克猜测的位置和我们模拟出来的它未来的位置。不要费心的让坦克穿过它的上个已知点[P0'],而是直接从他现在的位置去移动到基于新的[P0']推算出来的新的未来的位置[P1']即可.
Myth Busting-Discontinuities Are Not Minor
人类的大脑在识别模式方面是惊人的[Koster 2005],更重要的是,模式的变化,比如当最微小的一片模糊物经过我们的周边视觉时。这意味着玩家在意识到车辆位置错误之前,会先注意到车辆路径中的细微不连续。因此,不连续性,如跳变、翘曲、摇摆和摇晃才是关键。
18.3 Pick an Algorithm, Any Algorithm
If you crack open any good 3D math textbook, you'll find a variety of algorithms for defining a curve. Fortunately, we can discard most of them right away because they are too CPU intensive or are not appropriate (e.g., B-splines do not pass through the control points). For dead reckoning, we have the additional requirement that the algorithm must work well for a single segment of a curve passing through two points: our current location Po and the estimated future location P. Given all these requirements, we can narrow the selection down to a few types of curves: cubic Bézier splines, Catmull-Rom splines, and Hermite curves [Lengyel 2004, Van Verth and Bishop 2008].
只要翻开任何一本好的3D数学的书,你都能找到一堆生成曲线的方法。幸运的是大部分算法都可以扔了,它们要么是CPU负担太重,要么就是特征不满足游戏(比如B样条曲线不会经过给定的控制点坐标)。对于行为推算来说,我们需要找到能在给定的两个点之间生成一段曲线,并且曲线经过给定的两个点的方法,两个点分别是我们的当前位置[P0]和它的未来位置[P].根据这些需求,我们能将可选的曲线的范围缩小到如下几种:cubic Bezier splines,Catmull-Rom splines, 和 Hermite curves[Lengyel 2004, Van Verth and Bishop 2008].
These curves perform pretty well and follow smooth, continuous paths. However, they also tend to create minor repetitive oscillations. The oscillations are relatively small, but noticeable, especially when the actor is making a lot of changes (e.g., moving in a circle). In addition, the oscillations tend to become worse when running at inconsistent frame rates or when network updates don't come at regular intervals. In short, they are too wiggly.
这些曲线算法的性能非常好,生成的路径也连续和平滑。不过,这些算法生成的路线会有微小的重复振荡(类似波浪)。这些振荡相对来说很微笑,但是足够被注意到,特别是当对象的状态快速发生变化时(比如沿着圆圈前进)。而且,如果帧率不稳定或者网络消息的间隔不规律,这些振荡会让事情变得更糟糕。简单来说,他们太抖了。
Projective Velocity Blending
Let's try a different approach. Our basic problem is that we need to resolve two realities (the current P0 and the last known ??0 ′). Instead of creating a spline segment, let's try a straightforward blend. We create two projections, one with the current and one with the last known kinematic state. Then, we simply blend the two together using a standard linear interpolation (lerp). The first attempt looks like this: ????=??0+??0????+1 2??0 ′????2 (projecting from where we were), ???? ′=??0 ′+??0 ′????+1 2??0 ′????2 (projecting from last known), ????=????+(???? ′−????)?? �+1 2??0 ′????2 (combined). This gives Qt the dead-reckoned location at a specified time. (Time values such as Tt, and ?? � are explained in Section 18.4.) Note that both projection equations above use the last known value of acceleration ??0 ′. In theory, the current projection Pt should use the previous acceleration A0 to maintain C2 continuity. However, in practice, ??0 ′ converges to the true path much quicker and reduces oscillation.
投影速度混合算法
让我们来试一下另外一种方法。我们主要问题是需要解决两个位置的冲突(当前预测位置[P0]和最新更新的最后可信位置[P0'])。我们不创建样条曲线,而是直接混合。创建两个投影,一个是当前位置一个是最后可信位置。我们对他们做简单的线性插值。第一个意图看起来是这样:
[Pt] = [P0] + [V0] * [Tt] + 0.5 * [A0'] * [Tt]^2 (projecting from where we were),
[Pt'] = [P0'] + [V0'] * [Tt] + 0.5 * [A0'] * [Tt]^2 (projecting from last known),
[Qt] = [Pt] + ([Pt'] - [Pt])* [Tdn] + 0.5 * [A0'] * [Tt]^2 (combine),
This technique actually works pretty well. It is simple and gives a nice curve between our points. Unfortunately, it has oscillations that are as bad as or worse than the spline techniques. Upon inspection, it turns out that with all of these techniques, the oscillations are caused by the changes in velocity (V0 and ??0 ′). Maybe if we do something with the velocity, we can reduce the oscillations. So, let's try it again, with a tweak. This time, we compute a linear interpolation between the old velocity V0 and the last known velocity ??0 ′ to create a new blended velocity Vb. Then, we use this to project forward from where we were. The technique, projective velocity blending, works like this:
And the red lines in Figure 18.3 show what it looks like in action.
Figure 18.3. Dead reckoning with projective velocity blending shown in red.
这项技术的效果真心不错。它超级简单,在我们的已知点之间给出了一条非常合适的曲线。不幸的是,它也有像样条曲线一样甚至更糟的振荡现象。经检验,包括所有这些插值技术,振荡是由于[V0]和[V0']的变化引起的。如果我们对速度做一些其他处理,也许可以减少振荡现象。那么,让我们稍微调整下再试。这次我们对旧速度[V0]和最后可信速度[V0']做个线性插值,其结果为[Vb]. 然后我们用这个速度来投射位置。现在投影速度混合算法看起来是这个样子:
[Vb] = [V0] + [V0'-V0] * [Tdn] (velocity blending)
[Pt] = [P0] + [Vb] * [Tt] + 0.5 * [A0'] * [Tt]^2 (projecting from where we were),
[Pt'] = [P0'] + [V0'] * [Tt] + 0.5 * [A0'] * [Tt]^2 (projecting from last known),
[Qt] = [Pt] + ([Pt'] - [Pt])* [Tdn] (combine),
In practice, this works out magnificently! The blended velocity and change of acceleration significantly reduce the oscillations. In addition, this technique is the most forgiving of both inconsistent network update rates and changes in frame rates.
在测试中表现的很好!添加对速度的差值之后,显著的减少了振荡现象,而且这个方法是目前最兼容网络变化和帧率变化的方案。
Prove It!
So it sounds good in theory, but let's get some proof. We can perform a basic test by driving a vehicle in a repeatable pattern (e.g., a circle). By subtracting the real location from the deadreckoned location, we can determine the error. The images in Figure 18.4 and statistics in Table 18.1 show the clear result. The projective velocity blending is roughly five to seven percent more accurate than cubic Bézier splines. That ratio improves a bit more when you can't publish acceleration. If you want to test it yourself, the demo application on the website has implementations of both projective velocity blending and cubic Bézier splines.
Figure 18.4. Cubic Bézier splines (left) versus projective velocity blending (right), with acceleration (top) and without acceleration (bottom). Table 18.1. Improvement using projective velocity blending. Deck-reckoning (DR) error is measured in meters. Update Rate Cubic Bézier Projective Velocity Improvement 1 update/sec 1.5723 m 1.4584 m 7,24% closer 3 update/sec 0.1041 m 0.1112 m 6.38% closer 5 update/sec 0,0574 m 0.0542 m 5.57% closer As a final note, if you decide to implement a spline behavior instead of projective velocity blending, you might consider the cubic Bézier splines [Van Verth and Bishop 2008]. They are slightly easier to implement because the control points can simply be derived from the velocities V0 and V0 ′. The source code on the website includes a full implementation.
证明给你看!
balabala...
这就是我的测试结果(反正就是很好)。如果你非要用样条曲线,我建议你用 cubic Bézier splines [Van Verth and Bishop 2008]. 它实现起来最方便.(译者曾在2010年的网游里尝试过上面三种样条曲线方案,最终由于在拐点--比如突然的90度以上的转弯--会产生极不自然、极突出的翘曲,最后放弃了样条插值,谁头铁请接着试)。
18.4 Time for T
So far, we've glossed over time. That's okay for an introduction, but, once you begin coding, the concept of time gets twisted up in knots. So, let's talk about T.
目前为止,我们忽略了时间。这在介绍原理的时候问题不大,但是如果你开始编码了,时间的概念就会的胶着。因此,让我们来聊聊[T].
What Time Is It?
The goal is to construct a smooth path that an actor can follow between two moments in time T0 and T1. These two times mark the exact beginning and end of the curve and are defined by locations P0. and ??1 ′, respectively. The third time Tt is how much time has elapsed since T0. The final time ?? � represents how far the actor has traveled along the path as a normalized value, with 0.0 ≤ ?? �≤ 1.0. T0 is easy. It's the time stamp when the last known values were updated. Basically, it's "now" at the time of the update. If you've seen the movie Spaceballs, then T0 is "now, now." When we process a new network update, we mark T0 as now and set T1, back to zero. The slate is wiped clean, and we start a whole new curve, regardless of where we were. If T0 is now, then T1, must be in the future. But how far into the future, TΔ, should the projection go? Well, if we knew that the actor updates were coming at regular intervals, then we could just use the inverse update rate. So, for three updates per second, TΔ = 0.333 s. Even though network updates won't always be perfectly spaced out, it still gives a stable and consistent behavior. Naturally, the update rate varies significantly depending on the type of game, the network conditions, and the expected actor behavior. As a general rule of thumb, an update rate of three per second looks decent and five or more per second looks great.
时间是什么?
我们的目标是,在对象[T0]时刻和[T1]时刻的两个位置之间创造一条平滑的行进路径。这两个时刻分别指明了曲线确切的开始点[P0]和结束[P1']。第三个时间[Tt]是指从[T0]开始算的流逝的时间(译者:Tt = Tnow - T0 or T0+frameTime)。最后一个时间[Tdn]代表对象沿着路径走了多远,他是一个归一化到[0,1]的标量。[T0]很容易理解。它是最后可信值被更新的时刻。基本上,他就是收到更新消息时的那个时刻。如果你看过电影《Spaceballs》,那么T0就是 “now, now”(译者:是真的冷...)。当我们处理一个新的update时,我们只需要将[T0]设置为当前时刻,[T1]则重置为0。石板擦干净了(准备工作做好了),现在不管我们当前在哪里我们都生成一段全新的曲线。如果说[T0]是"现在",那么[T1]一定是未来某个时间。但是我们需要投射多远的"未来",也就是[Tdn]的值是多少?好吧,如果我们已经预先知道更新消息会以规则间隔发过来,那么我们只需要根据更新频率算出来即可。因此对于每秒三次更新,[Tdt] = 0.333s。就算网络不稳定,这个算法依然会给出稳定和确切的行为表现。当然,不同的游戏类型、网络条件、对象行为特征等等都是更新频率多种多样的原因。作为惯例,更新频率如果有3fps看起来就比较正常,5fps就更棒了。
Time to Put It Together
From an implementation perspective, normalized time values from zero to one aren't terribly useful. In many engines, you typically get a time Tf since the last frame. We can easily add this up each frame to give the total time since the last update Tt. Once we know Tt, we can compute our normalized time ?? � as follows:
???? ← ???? + ????
?? �= ???? ??△
Now we have all the times we need to compute the projective velocity blending equations. That leaves just one final wrinkle in time. It happens when we go past TΔ (i.e., Tt > TΔ). This is a very common case that can happen if we miss an update, have any bit of latency, or even have minor changes in frame rate. From earlier,
???? =???? +(???? ′ −???? )?? � .
Because ?? � is clamped at one, the Pt, drops out, leaving the original equation
???? =??0 ′+??0 ′???? + 1 2??0 ′????2
The math simplifies quite nicely and continues to work for any value of ?? � ≥ 1.0.
是时候全部串起来了
从实现的角度来看,从0到1的标准化时间值并没有太大的用处。在许多引擎里,你得到的是当前帧和上一帧的时间间隔[Tf]。 我们可以简单的将其累加到[Tt]的值上。一旦我们有了[Tt],我们就可以计算出当前的[Tdn]值,计算方法如下:
[Tt] = [Tt] + [Tf]
[Tdn] = [Tt] / [Tdt]
现在我们有了公式里全部的时间,现在关于时间只剩最后一个特殊情况。它会发生于[Tt]>[Tdt]时。这在我们丢失了某次update、网络延迟、甚至微小的帧率变化时都会经常发生。如果用早先的公式
[Qt] = [Pt] + ([Pt'] - [Pt]) * [Tdn]
因为 [Tdn] 已经被截断为1了,等于[Pt]就被丢弃了。这时可以采用原来的公式:
[Qt] = [P0'] + [V0'] * [Tt] + 0.5 * [A0'] * [Tt]^2;
这个公式够简单,也能很好的处理Tt>1时的情况。
Just in Time Notes
Here are a few tips to consider:
*)Due to the nature of networking, you can receive updates at any time, early or late. In order to maintain C1 continuity, you need to calculate the instantaneous velocity between this frame's and the last frame's dead-reckoned position, (????−????−1 )/????.When you get the next update and start the new curve, use this instantaneous velocity for V0. Without this, you will see noticeable changes in velocity at each update.
*)Actors send updates at different times based on many factors, including creation time, behavior, server throttling, latency, and whether they are moving. Therefore, track the various times separately for each actor (local and remote).
*)If deciding your publish rate in advance is problematic, you could calculate a run-time average of how often you have been receiving network updates and use that for TΔ. This works okay but is less stable than a predetermined rate.
*)In general, the location and orientation get updated at the same time. However, if they are published separately, you'll need separate time variables for each.
*)It is possible to receive multiple updates in a single frame. In practice, let the last update win. For performance reasons, perform the dead reckoning calculations later in the game loop, after the network messages are processed. Ideally, you will run all the dead reckoning in a single component that can split the work across multiple worker threads.
*)For most games, it is not necessary to use time stamps to sync the clocks between clients/servers in order to achieve believable dead reckoning.
关于 Time 的注意事项
*)对于正常的网络来说,你可能在任何时刻受到更新,早一点或晚一点都有可能。为了保证C1的连续性,你需要计算当前帧位置和上一帧的航位推测出的位置上的瞬时速度,([P<t>]-[P<t-1>])/T[f]。当你获得下一个更新,用这个瞬时速度作为V0的值。如果不这么做,你会在每次获得更新时看到可察觉的速度变化。
*)对象发送自身状态的更新的触发时机各种各样。因此需要每对象单独跟踪时间。(最起码local actor 和 remote actor要分开)
*)如果你没法预先确定更新频率,你可以通过实时计算收到更新包的历史平均间隔来作为[Tdt]的值。这个方法还算管用,但是比预先设定好一个更新频率的效果要差。
*)一般来说位置和朝向是同时更新的,但如果你是分开更新,那你需要分别处理两者。
*)有可能在一帧之内收到多个更新。实践中,直接使用最后一个更新即可。因为性能原因,最好是让DR能在游戏循环中比较靠后的时机来执行,要在网络消息处理结束之后。理想情况下,你可以将所有的DR计算任务都集中到一个组件中来执行,这样可以利用多线程来分担。
*)对于大多数游戏尔雅,并不需要客户端和服务器对时就能做到可信的行为推算。
18.5 Publish or Perish
So far, the focus has been on handling network updates for remote actors. However, as with most things, garbage in means garbage out. Therefore, we need to take a look at the publishing side of things. In this section, forget about the actors coming in over the network and instead focus on the locally controlled actors.
When to Publish?
Let's go back and consider the original tank scenario from the opponent's perspective. The tank is now a local actor and is responsible for publishing updates on the network. Since network bandwidth is a precious resource, we should reduce traffic if possible. So the first optimization is to decide when we need to publish. Naturally, there are times when players are making frequent changes in direction and speed and five or more updates per second are necessary. However, there are many more times when the player's path is stable and easy to predict. For instance, the tank might be lazily patrolling, might be heading back from a respawn, or even sitting still (e.g., the player is chatting). The first optimization is to only publish when necessary. Earlier, we learned that it is better to have a constant publish rate (e.g., three per second) because it keeps the remote dead reckoning smooth. However, before blindly publishing every time it's allowed (e.g., every 0.333 s), we first check to see if it's necessary. To figure that out, we perform the dead reckoning as if the vehicle was remote. Then, we compare the real and the dead-reckoned states. If they differ by a set threshold, then we go ahead and publish. If the real position is still really close to the dead-reckoned position, then we hold off. Since the dead reckoning algorithm on the remote side already handles Tt > TΔ, it'll be fine if we don't update right away. This simple check, shown in Listing 18.1, can significantly reduce network traffic.
赶紧广播或等会儿
目前,我们的焦点是如何处理远端对象发到本地的更新消息。然而和大多数事情一样,给了无意义的输入就会有无意义的输出。因此我们需要看一下发送端。这这个章节,我们先不要管通过网络发过来的对象,而是关注本地操控的对象。
什么时候广播?
让我们用对手的视角再回去看看坦克同步的那个方案。现在坦克是本地对象,并负责向网络发送更新信息。网络带宽是非常珍贵的资源,我们应该尽量减少网络负载。因此第一个优化就是决定何时广播。一般来讲,有时候玩家会频繁的改变方向和速度,这会需要5fps或高的更新频率。但是很多时候对象的运动轨迹其实不咋变化,因此很容易预测正确。举例来说,坦克在巡逻(按固定线路运动),从复活点径直赶往战场,或只是待在原地(可能在聊天)。第一个优化方案就是必要时再广播。之前我们说过,最好是一个常量更新频率(比如3fps),这样远端的推算才会比较平滑。不过,在无脑的每次到点(比如每0.333s)就发送更新之前,我们先看一下是不是真的有必要发。 为了弄清楚这个,我们像远端载具一样对本地对象执行航位推算。然后我们比较真正的位置和航位推算位置。如果它们的差异超过了某个阈值,我们才执行广播流程。如果真实位置其实和推算位置非常接近,那就没必要了。就算远端的航位推算算法已经发生 [Tt] > [Tdt]的情况,我们现在不立即广播的话也还好。这个简单的检查可以显著的减少网络负载,参见 Listing 18.1。
Listing 18.1.
//Publish-is an update necessary?
bool ShouldForceUpdate(const Vec3& pos, const Vec3& rot)
{
bool forceUpdateResult = false;
if (enoughTimeHasPassed)
{
Vec3 posMoved = pos - mCurDeadReckoned_Pos;
Vec3 rotTurned = rot - mCurDeadReckoned_Rot;
if ((posMoved.lenght2() > mPosThreshold2)
|| (rotTurned.length2() > mRotThreshold2))
{
// Rot.length2 is a fast approx(i.e., not a quaternion).
forceUpdateResult = true;
}
// ... Can use other checks such as velocity and accel.
}
return (forceUpdateResult);
}
What to Publish
Clearly, we need to publish each actor's kinematic state, which includes the position, velocity, acceleration, orientation, and angular velocity. But there are a few things to consider. The first, and least obvious, is the need to separate the actor's real location and orientation from its last known location and orientation. Hopefully, your engine has an actor property system [Campbell 2006] that enables you to control which properties get published. If so, you need to be absolutely sure you never publish (or receive) the actual properties used to render location and orientation. If you do, the remote actors will get an update and render the last known values instead of the results of dead reckoning. It's an easy thing to overlook and results in a massive one-frame discontinuity (a.k.a. blip). Instead, create publishable properties for the last known values (i.e., location, velocity, acceleration, orientation, and angular velocity) that are distinct from the real values. The second consideration is partial actor updates, messages that only contain a few actor properties. To obtain believable dead reckoning, the values in the kinematic state need to be published frequently. However, the rest of the actor's properties usually don't change that much, so the publishing code needs a way to swap between a partial and full update. Most of the time, we just send the kinematic properties. Then, as needed, we send other properties that have changed and periodically (e.g., every ten seconds) send out a heartbeat that contains everything. The heartbeat can help keep servers and clients in sync.
很清晰的,我们需要广播每个对象的运动状态,包括位置、速度、加速度、朝向、角速度等。但是有一些事情需要先考虑下。首先,也是最不明显的,就是需要将对象的真正位置和朝向与最新的可信分位置和朝向分开。你不能直接应用最新的可信数据,不然会产生单帧不连续现象。(收到信息的那一帧使用的是更新的值而不是DR计算的值)。第二点需要注意的是,你可能只需要部分更新对象和运动状态相关的属性,而更新对象属性的所有字段用更低的频率去做(比如0.1fps)。
Myth Busting-Acceleration Is Not Always Your Friend
In the quest to create believable dead reckoning, acceleration can be a huge advantage, but be warned that some physics engines give inconsistent (a.k.a. spiky) readings for linear acceleration, especially when looked at in a single frame as an instantaneous value. Because acceleration is difficult to predict and is based on the square of time, it can sometimes make things worse by introducing noticeable under- and overcompensations. For example, this can be a problem with highly jointed vehicles for which the forces are competing on a frame-byframe basis or with actors that intentionally bounce or vibrate. With this in mind, the third consideration is determining what the last known values should be. The last known location and orientation come directly from the actor's current render values. However, if the velocity and acceleration values from the physics engine are giving bad results, try calculating an instantaneous velocity and acceleration instead. In extreme cases, try blending the velocity over two or three frames to average out some of the sharp instantaneous changes.
澄清-加速度并不是啥时候都好用
为了完成可信的DR,加速度会很有用。但是要当心物理引擎可能会给出的线性加速度不一致(也就是尖峰),特别是在看单帧的瞬时值时。这是因为加速度非常难预测,并且他跟时间的平方有关,因此他可能会引入更多可感知波动。例如,对于高度连接的车辆来说,这可能是一个问题,因为这些车辆的力是逐帧竞争的,或者与有意弹跳或振动的参与者竞争。考虑到这一点,第三个考虑因素是确定最后一个已知值应该是什么。最后一个已知的位置和方向直接来自参与者的当前渲染值。但是,如果物理引擎的速度和加速度值给出了错误的结果,请尝试计算瞬时速度和加速度。在极端情况下,尝试混合两到三帧以上的速度,以平均出一些急剧的瞬间变化。(也就是说如果是物理引擎计算的加速度,它可能给出的值是不靠谱的,要么自己算,要么把给出来的值做2-3帧的平均)
Publishing Tips
Below are some final tips for publishing:
*) Published values can be quantized or compressed to reduce bandwidth [Sayood 2006]. If an actor isn't stable at speeds near zero due to physics, consider publishing a zero velocity and/or acceleration instead. The projective velocity blend will resolve the small translation change anyway.
*) If publishing regular heartbeats, be sure to sync them with the partial updates to keep the updates regular. Also, try staggering the heartbeat time by a random amount to prevent clumps of full updates caused by map loading.
*) Some types of actors don't really move (e.g., a building or static light). Improve performance by using a static mode that simply teleports actors.
*)In some games, the orientation might matter more than the location, or vice versa. Consider publishing them separately and at different rates.
*) To reduce the bandwidth using ShouldForceUpdate(), you need to dead reckon the local actors in order to check against the threshold values.
*) Evaluate the order of operations in the game loop to ensure published values are computed correctly. An example order might include: handle user input, tick local (process incoming messages and actor behaviors), tick remote (perform dead reckoning), publish dead reckoning, start physics (background for next frame), update cameras, render, finish physics. A bad order will cause all sorts of hard-to-debug dead reckoning anomalies.
*) There is an optional damping technique that can help reduce oscillations when the acceleration is changing rapidly (e.g., zigzagging). Take the current and previous acceleration vectors and normalize them. Then, compute the dot product between them and treat it as a scalar to reduce the acceleration before publishing (shown in the ComputeCurrentvelocity() function in Listing 18.2). Acceleration in the up/down direction can sometimes cause floating or sinking. Consider publishing a zero instead.
广播的技巧
*) 广播的内容可以被压缩从而减少网络带宽的需求[Sayood 2006]. 如果对象速度几乎为0,你可以尝试将同步速度设置为0,如果有加速度也是一样设置为0。投影速度混合算法能适应这些微小差异。(丢失一些精度是可接受的)
*) 如果你在发心跳包,确保心跳包的频率和部分状态同步包一致(而不是全状态包)。另外尝试将心跳包随机的错开(后面的原因没看懂,加载地图和更新有啥关系)。
*) 有些东西根本就不移动(如建筑或静态光源)。用针对性的模式来处理这些对象能提升性能。
*)在一些游戏里,朝向比位置更重要,反之亦然。可以让两者以不同的频率进行更新。
*) 为了使用ShouldForceUpdate检查来减少你的网络负载,你需要对本地用户也进行航位推算来确定是否超过阈值。
*) 计算顺序很重要。举例来说,一个正确的游戏循环流程可能是:(我的理解是尽可能:本地算完了再做广播,本地收完了协议再做remote对象的dr模拟)
响应用户输入 / 更新本地事务(处理网络协议和对象行为逻辑) / 更新远端事务(执行DR) / 广播DR参数 / 开启本轮物理模拟(后台或下一帧)/ 更新相机位置 / 渲染 / 完成物理模拟。
错误的顺序可能会导致非常难调试的推算错误出现。
*) 有一种可选的阻尼技术,可以帮助减少加速度快速变化时的振荡(例如,锯齿形)。取当前和以前的加速度向量,并将其归一化。然后,计算它们之间的点积,并将其作为一个标量来减少发布前的加速度(如清单18.2中的computecurrentvelocity()函数所示方法求加速度)。上/下方向的加速度有时会导致浮动或下沉。考虑直接用0来广播。(简单来说,就是利用点积比较两次加速度之间的变化,包括方向和大小,再丢弃一些没超过你自己设定的加速度阈值的变化,就能减少网络广播的次数了)。
The Whole Story
When all the pieces are put together, the code looks roughly like Listing 18.2.
Listing 18.2. Publish-the whole story.
void OnTickRemote(const TickMessage& tickMessage)
{
// This is for local actors, but happens during Tick Remote.
double elapsedTime = tickMessage.GetDeltaSimTime();
bool forceUpdate = false, fullUpdate = false;
Vec3 rot = GetRotation();
Vec3 pos = GetTranslation();
mSecsSinceLastUpdateSent += elaspedTime;
mTimeUntilHeartBeat -= elaspedTime;
// Have to update instant velocity even if we don't publish.
ComputeCurrentVelocity(elapsedTime, pos, rot);
if ((mTimeUntilHeartBeat <= 0.0f) || (IsFullUpdateNeeded()))
{
fullUpdate = true;
forceUpdate = true;
}
else
{
forceUpdate = ShouldForceUpdate(pos, rot);
fullUpdate = (mTimeUntilHeartBeat < HEARTBEAT_TIME * 0.1f);
}
if (forceUpdate)
{
SetLastKnownValuesBeforePublish(pos, rot);
if (fullUpdate)
{
mTimeUntilHeartBeat = HEARTBEAT_TIME; // +/- random offset
NotifyFullActorUpdate();
}
else
{
NotifyPartialActorUpdate();
}
mSecsSinceLastUpdateSent = 0.0f;
}
}
void SetLastKnownValuesBeforePublish(const Vec3& pos, const Vec3& rot)
{
SetLastKnownTranslation(pos);
SetLastKnownRotation(rot);
SetLastKnownVelocity(ClampTinyValues(GetCurrentVel()));
SetLastKnownAngularVel(ClampTinyValues(GetCurrentAngularVel()));
// (OPTIONAL!) ACCELERATION dampen to prevent wild swings.
// Normalize current accel. Dot with accel from last update. Use
// the product to scale our current Acceleration.
Vec3 curAccel = GetCurrentAccel();
curAccel.normalize();
float accelScale = curAccel * mAccelOfLastPublish;
mAccelOfLastPublish = curAccel; //(pre-normalized)
SetLastKnownAccel(GetCurrentAccel() * Max(0.0f, accelScale));
}
void ComputeCurrentVelocity(float deltaTime, const Vec3& pos, const Vec3& rot)
{
if ((mPrevFrameTime > 0.0f) && (mLastPos.length2() > 0.0f))
{
Vec3 prevComputedLinearVel = mComputedLinearVel;
Vec3 distanceMoved = pos -mLastPos;
mComputedLinearVel = distanceMoved / mPrevFrameTime;
ClampTinyValues(mComputedLinearVel);
// accel = the instantaneous differential of the velocity.
Vec3 deltaVel = mComputedLinearVel - prevComputedLinearVel;
Vec3 computedAccel = deltaVel // mPrevDeltaFrameTime;
computedAccel.z() = 0.0f; // up/down accel isn't always helpful.
SetCurrentAcceleration(computedAccel);
SetCurrentVelocity(mComputedLinearVel);
}
mLastPos = pos;
mPrevFrameTime = deltaTime;
}
18.6 Ground Clamping
No matter how awesome your dead reckoning algorithm becomes, at some point, the problem of ground clamping is going to come up. The easiest way to visualize the problem is to drop a vehicle off of a ledge. When it impacts the ground, the velocity is going to project the dead-reckoned position under the ground. Few things are as disconcerting as watching a tank disappear halfway into the dirt. As an example, the demo on the website allows mines to fall under ground. Can We Fix It? As with many dead reckoning problems, there isn't one perfect solution. However, some simple ground clamping can make a big difference, especially for far away actors. Ground clamping is adjusting an actor's vertical position and orientation to make it follow the ground. The most important thing to remember about ground clamping is that it happens after the rest of the dead reckoning. Do everything else first. The following is one example of a ground clamping technique. Using the final dead reckoned position and orientation, pick three points on the bounding surface of the actor. Perform a ray cast starting above those points and directed downward. Then, for each point, check for hits and clamp the final point if appropriate. Compute the average height H of the final points Q0, Q1, and Q2, and compute the normal N of the triangle through those points as follows: ??= (Q0)?? + (Q1)?? + (Q2)?? 3 ?? =(Q1−Q0 )× (Q2−Q0 ) Use H as the final clamped ground height for the actor and use the normal to determine the final orientation. While not appropriate for all cases, this technique is fast and easy to implement, making it ideal for distant objects.
Other Considerations
Another possible solution for this problem is to use the physics engine to prevent interpenetration. This has the benefit of avoiding surface penetration in all directions, but it can impact performance. It can also create new problems, such as warping the position, the need for additional blends, and sharp discontinuities. Another way to minimize ground penetration is to have local actors project their velocities and accelerations into the future before publishing. Then, damp the values as needed so that penetration will not occur on remote actors (a method known as predictive prevention). This simple trick can improve behavior in all directions and may eliminate the need to check for interpenetration. When working with lots of actors, consider adjusting the ground clamping based on distance to improve performance. You can replace the three-point ray multicast with a single point and adjust the height directly using the intersection normal for orientation. Further, you can clamp intermittently and use the offset from prior ground clamps. For character models, it is probably sufficient to use single-point ground clamping. Singlepoint clamping is faster, and you don't need to adjust the orientation. Consider supporting several ground clamp modes. For flying or underwater actors, there should be a "no clamping" mode. For vehicles that can jump, consider an "only clamp up" mode. The last mode, "always clamp to ground," would force the clamp both up and down.
18.7 Orientation
Orientation is a critical part of dead reckoning. Fortunately, the basics of orientation are similar to what was discussed for position. We still have two realities to resolve: the current drawn orientation and the last known orientation we just received. And, instead of velocity, there is angular velocity. But that's where the similarities end. Hypothetically, orientation should have the same problems that location had. In reality, actors generally turn in simpler patterns than they move. Some actors turn slowly (e.g., cars) and others turn extremely quickly (e.g., characters). Either way, the turns are fairly simplistic, or oscillations are rarely a problem. This means C1 and C2 continuity is less important and explains why many engines don't bother with angular acceleration.
18.7 朝向
朝向是DR中的关键部分之一。幸运的是,对于朝向的处理和前面讨论的位置的处理很相似。我们同样需要解决两个值之间的冲突:当前绘制的朝向和网络发来的最近可信朝向。并且,这里不是速度,而是角速度。好了,所有的相似性到此为止了。假设,朝向应该有着和位置一样的问题。实际中,对象的转向比移动要单纯一些。有些对象转向很慢(比如汽车)但是另外一些就特别快(比如角色)。不管哪种,转向都是最容易处理的,或者说振荡一般不会成为问题。这就是说[C1]和[C2]的连续性不是很重要,这也解释了为什么大多数引擎不太担心角加速度。
Myth Busting-Quaternions
Your engine might use HPR (heading, pitch, and roll), XYZ vectors, or full rotation matrices to define an orientation. However, when it comes to dead reckoning, you'll be rotating and blending angles in three dimensions, and there is simply no getting around quaternions [Hanson 2006]. Fortunately, quaternions are easier to implement than they are to understand [Van Verth and Bishop 2008]. So, if your engine doesn't support them, do yourself a favor and code up a quaternion class. Make sure it has the ability to create a quaternion from an axis/angle pair and can perform spherical linear interpolations (slerp). A basic implementation of quaternions is provided with the demo code on the website. With this in mind, dead reckoning the orientation becomes pretty simple: project both realities and then blend between them. To project the orientation, we need to calculate the rotational change from the angular velocity. Angular velocity is just like linear velocity; it is the amount of change per unit time and is usually represented as an axis of rotation whose magnitude corresponds to the rate of rotation about that axis. It typically comes from the physics engine, but it can be calculated by dividing the change in orientation by time. In either case, once you have the angular velocity vector, the rotational change ??∆?? ′ is computed as shown in Listing 18.3. If you also have angular acceleration, just add it to rotationAngle. Next, compute the two projections and blend using a spherical linear interpolation. Use the last known angular velocity in both projections, just as the last known acceleration was used for both equations in the projective velocity blending technique:
Listing 18.3. Computing rotational change.
Vec3 angVelAxis(mLastKnownAngularVelocityVector);
// normalize() returns length.
float angVelMagnitude = angVelAxis.normalize();
// Rotation around the axis is magnitude of ang vel * time.
float rotationAngle = angVelMagnitude * actualRotationTime;
Quat ratationFromAngVel(rotationAngle, angVelAxis);
??∆?? ′=quat�??mag ′ ???? ,??dir ′� (impact of angular velocity),
?? ??=??∆?? ′ ??0 (rotated from where we were),
?? ?? ′=??∆?? ′ ??0 (rotated from last known),
?? ??=slerp�?? �, ????, ???? ′� (combined).
This holds true for ?? � < 1.0. Once again, ?? � is clamped at one, so the math simplifies when ?? � ≥1.0:
????=??∆?? ′ ??0 (rotated from last known ).
澄清-四元数
你的引擎可能在用HPR表示法(heading, pitch, and roll), XYZ 向量, 或者完整的旋转矩阵来定义朝向。不管哪种,进入DR的范畴,你需要旋转和混合三个轴上的角度,没有比使用四元数更简单的方法了[Hanson 2006]. 幸运的是,理解四元数比实现它的计算要简单。[Van Verth and Bishop 2008]. 因此,如果你的引擎不支持,你可以自己写一个四元数类。确保能通过轴&角来构造四元数,这会用于球面线性插值(slerp)。你可以去本书的网站去扒代码。用这种运算方法,推算朝向变得非常简单:对两个两个朝向值都做投射(预测),然后再对结果做混合。为了投射朝向,我们需要根据角速度计算旋转变化。角速度和线速度很像,它是单位时间内的角度变化(真的要解释这个吗?)。在两种情况下,如果你有角速度向量,那么角度变化量的计算方法如 Listing 18.3所示。如果你同时还加入了角加速度,直接将其累加到旋转值中。接下来计算两个投射结果,对它们进行球面线性插值(slerp)。直接使用最后可信角度来做两个投射,就像直接使用最后可信加速度来处理位移速度公式那样。
[Rdt'] = quat([Rmag'][Tt], [Rdir']) ()
[Rt] = [Rdt'][R0] (这里是不是错了???)
[Rt'] = [Rdt'][R0] ()
[St] = slerp([Tdn], [Rt],) ()
这个公式在 [Tdn] < 1.0 时有效。大于1会被截断成1,因此对于 >= 1.0时,公式可以简化为:
[St] = [Rdt'][R0]
Two Wrong Turns Don't Make a Right
This technique may not be sufficient for some types of actors. For example, the orientation of a car and its direction of movement are directly linked. Unfortunately, the dead-reckoned version is just an approximation with two sources of error. The first is that the orientation is obviously a blended approximation that will be behind and slightly off. But, even if you had a perfect orientation, the remote vehicle is following a dead-reckoned path that is already an approximation. Hopefully, you can publish fast enough that neither of these becomes a problem. If not, you may need some custom actor logic that can reverse engineer the orientation from the dead-reckoned values; that is, estimate an orientation that would make sense given the dead reckoned velocity. Another possible trick is to publish multiple points along your vehicle (e.g., one at front and one in back). Then, dead reckon the points and use them to orient the vehicle (e.g., bind the points to a joint).
两个错误的值不会让事情变正确
这项技术可能对一些类型的对象不太合适。举例来说,一辆车的朝向和他的运动方向是有直接联系的。不幸的是,航位推算值只是用两个错误的值计算出来的近似值。首先混合得到的朝向近似值显然是落后于实际情况的,并且还有一定的偏差。但是就算你有一个完全正确的朝向,远端车辆的行进路线也是推算出来的近似值路径。你广播的够频繁的话也许能让它不是问题,不然你可能需要一些自定义逻辑来通过位置推算值反向计算朝向;这就是说根据推算出的速度,估计一个有意义的朝向。另一个可能的技巧是广播一些额外的属于你车辆的点(比如一个车头的点和一个车尾的点),对这些点做推算,并用他们来确定车辆的朝向。(比如将这些点绑在关节处)
18.8 Advanced Topics
This last section introduces a variety of advanced topics that impact dead reckoning. The details of these topics are generally outside the scope of this gem, but, in each case, there are specific considerations that are relevant to dead reckoning.
Integrating Physics with Dead Reckoning (在物理计算中集成DR算法)
Some engines use physics for both the local and the remote objects. The idea is to improve believability by re-creating the physics for remote actors, either as a replacement for or in addition to the dead reckoning. There are even a few techniques that take this a step further by allowing clients to take ownership of actors so that the remote actors become local actors, and vice versa [Feidler 2009]. In either of these cases, combining physics with dead reckoning gets pretty complex. However, the take away is that even with great physics, you'll end up with cases where the two kinematic states don't perfectly match. At that point, use the techniques in this gem to resolve the two realities.
Server Validation(服务器校验)
Dead reckoning can be very useful for server validation of client behavior. The server should always maintain a dead-reckoned state for each player or actor. With each update from the clients, the server can use the previous last known state, the current last known state, and the ongoing results of dead reckoning as input for its validation check. Compare those values against the actor's expected behavior to help identify cheaters.
DR对于服务器校验非常有用。服务器应该总是维持着每一个玩家或对象的DR状态。每当有客户端的更新过来时,服务器可以使用前一个最后可信状态,当前可信状态,和用这些输入值参与DR计算得出的结果来做合法性验证。比较出不符合预期的值,这对发现挂B有好处。
Who Hit Who?(谁打着谁了?)
Imagine player A (local) shoots a pistol at player B (remote, slow update). If implemented poorly, player A has to "lead" the shot ahead or behind player B based on the ping time to the server. A good dead reckoning algorithm can really help here. As an example, client A can use the current dead-reckoned location to determine that player B was hit and then send a hit request over to the server. In turn, the server can use the dead-reckoned information for both players, along with ping times, to validate that client A's hit request is valid from client A's perspective. This technique can be combined with server validation to prevent abuse. For player A, the game feels responsive, seems dependent on skill, and plays well regardless of server lag.
想象玩家A(本地)开枪射击玩家B(远端对象,低频更新)。如果实现的不够好,玩家A就总是需要根据自己到服务器的ping值“算着来”射击玩家B的前方或着后方。一个好的DR算法能有效改善这种情况。举个例子,客户端A可以根据自己的DR位置来预测是否击中了玩家B,然后将击中结果发往服务器。服务器则可以根据双方的DR信息,ping的耗时算在时间参数里,来验证客户端A的视角下,是否击中结果有效。此项技术可以和服务器验证相结合,以防止有人滥用此机制。
Articulations(关节)
Complex actors often have articulations, which are attached objects that have their own independent range of motion and rotation. Articulations can generally be lumped into one of two groups: real or fake. Real articulations are objects whose state has significant meaning, such as the turret that's pointing directly at you! For real articulations, use the same techniques as if it were a full actor. Fortunately, many articulations, such as turrets, can only rotate, which removes the overhead of positional blending and ground clamping. Fake articulations are things like tires and steering wheels, where the state is either less precise or changes to match the dead-reckoned state. For those, you may need to implement custom behaviors, such as for turning the front tires to approximate the velocity calculated by the dead reckoning.
Path-Based Dead Reckoning(预设路径的DR算法)
Some actors just need to follow a specified path, such as a road, a predefined route, or the results of an artificial intelligence plan. In essence, this is not much different from the techniques described above. Except, instead of curving between two points, the actor is moving between the beginning and end of a specified path. If the client knows how to recreate the path, then the actor just needs to publish how far along the path it is, ?? �, as well as how fast time is changing, Tv When applicable, this technique can significantly reduce bandwidth. Moyer and Speicher [2005] have a detailed exploration of this topic.
Delayed Dead Reckoning(带延迟的DR算法)
The first myth this gem addresses is that there is no ground truth. However, one technique, delayed dead reckoning, can nearly re-create it, albeit by working in the past. With delayed dead reckoning, the client buffers network updates until it has enough future data to re-create a path. This eliminates the need to project into the future because the future has already arrived. It simplifies to a basic curve problem. The upside is that actors can almost perfectly re-create the original path. The obvious downside is that everything is late, making it a poor choice for most real-time actors. This technique can be useful when interactive response time is not the critical factor, such as with distant objects (e.g., missiles), slow-moving system actors (e.g., merchant NPCs), or when playing back a recording. Note that delayed dead reckoning can also be useful for articulations.
Subscription Zones(划分区域)
Online games that support thousands of actors sometimes use a subscriptionzoning technique to reduce rendering time, network traffic, and CPU load [Cado 2007]. Zoning is quite complex but has several impacts on dead reckoning. One significant difference is the addition of dead reckoning modes that swap between simpler or more complex dead reckoning algorithms. Actors that are far away or unimportant can use a low-priority mode with infrequent updates, minimized ground clamping, quantized data, or simpler math and may take advantage of delayed dead reckoning. The high-priority actors are the only ones doing frequent updates, articulations, and projective velocity blending. Clients are still responsible for publishing normally, but the server needs to be aware of which clients are receiving what information for which modes and publish data accordingly.
18.9 Conclusion
Dead reckoning becomes a major consideration the moment your game becomes networked. Unfortunately, there is no one-size-fits-all technique. The games industry is incredibly diverse and the needs of a first-person MMO, a top-down RPG, and a high-speed racing game are all different. Even within a single game, different types of actors might require different techniques.
The underlying concepts described in this gem should provide a solid foundation for adding dead reckoning to your own game regardless of the genre. Even so, dead reckoning is full of traps and can be difficult to debug. Errors can occur anywhere, including the basic math, the publishing process, the data sent over the network, or plain old latency, lag, and packet issues. Many times, there are multiple problems going on at once and they can come from unexpected places, such as bad values coming from the physics engine or uninitialized variables. When you get stuck, refer back to the tips in each section and avoid making assumptions about what is and is not working. Believable dead reckoning is tricky to achieve, but the techniques in this gem will help make the process as easy as it can be.
18.9 总结
DR很NB,但是它不是万金油,实现起来坑挺多,又难调试,遇到问题回去翻翻提示,但愿本文能帮你轻松点。
Acknowledgements
Special thanks to David Guthrie for all of his contributions.
References
[Aronson 1997] Jesse Aronson. "Dead Reckoning: Latency Hiding for Networked Games." Gamasutra, September 19, 1997. Available at http://www.gamasutra.com/view/feature/3230/de ad_reckoning_latency_hiding_for_.php.
[Cado 2007] Olivier Cado. "Propagation of Visual Entity Properties Under Bandwidth Constraints." Gamasutra, May 24, 2007. Available at http://www.gamasutra.com/
[Campbell 2006] Matt Campbell and Curtiss Murphy. "Exposing Actor Properties Using Nonintrusive Proxies." Game Programming Gems 6, edited by Michael Dickheiser. Boston: Charles River Media, 2006.
[Feidler 2009] Glenn Fiedler. "Drop in COOP for Open World Games." Game Developer's Conference, 2009.
[Hanson 2006] Andrew Hanson. Visualizing Quaternions. San Francisco: Morgan Kaufmann, 2006.
[Koster 2005] Raph Koster. A Theory of Fun for Game Design. Paraglyph Press, 2005.
[Lengyel2004] Eric Lengyel. Mathematics for 3D Game Programming & Computer Graphics, Second Edition. Hingham, MA: Charles River Media, 2004.
[Moyer and Speicher 2005] Dale Moyer and Dan Speicher. "A Road-Based Algorithm for Dead Reckoning." Interservice/Industry Training, Simulation, and Education Conference, 2005.
[Sayood 2006] Khalid Sayood. Introduction to Data Compression, Third Edition. San Francisco: Morgan Kaufmann, 2006.
[Van Verth and Bishop 2008] James Van Verth and Lars Bishop. Essential Mathematics in Games and Interactive Applications: A Programmer's Guide, Second Edition. San Francisco: Morgan Kaufmann, 2008.