Advanced Game Tech All

Glenn – networked physics


Glenn Fiedler is the founder of Network Next where he’s hard at work as CEO/CTO. Network Next is creating a new internet for games and e-sports.



Networked Physics in Virtual Reality




About a year ago, Oculus approached me and offered to sponsor my research. They asked me, effectively: “Hey Glenn, there’s a lot of interest in networked physics in VR. You did a cool talk at GDC. Do you think could come up with a networked physics sample in VR that we could share with devs? Maybe you could use the touch controllers?”



I replied “F*** yes!” cough “Sure. This could be a lot of fun!”. But to keep it real, I insisted on two conditions. One: the source code I developed would be published under a permissive open source licence (for example, BSD) so it would create the most good. Two: when I was finished, I would be able to write an article describing the steps I took to develop the sample.



Oculus agreed. Welcome to that article! Also, the source for the networked physics sample is here, wherein the code that I wrote is released under a BSD licence. I hope the next generation of programmers can learn from my research into networked physics and create some really cool things. Good luck!



What are we building?


When I first started discussions with Oculus, we imagined creating something like a table where four players could sit around and interact with physically simulated cubes on the table. For example, throwing, catching and stacking cubes, maybe knocking each other’s stacks over with a swipe of their hand.【Oculus那边希望的场景是四个人围在桌子边,桌上有一堆的方块可以做物理交互。】


But after a few days spent learning Unity and C#, I found myself actually inside the Rift. In VR, scale is so important. When the cubes were small, everything felt much less interesting, but when the cubes were scaled up to around a meter cubed, everything had this really cool sense of scale. You could make these huge stacks of cubes, up to 20 or 30 meters high. This felt really cool!【但是我在接触VR一段时间后发现 Scale 在VR里面非常重要。如果桌上一堆小的方块你会觉得不那么有趣,但是如果放大到一米一个左右,你会发现整个世界非常的酷。】


It’s impossible to communicate visually what this feels like outside of VR, but it looks something like this…



… where you can select, grab and throw cubes using the touch controller, and any cubes you release from your hand interact with the other cubes in the simulation. You can throw a cube at a stack of cubes and knock them over. You can pick up a cube in each hand and juggle them. You can build a stack of cubes and see how high you can make it go.【这种感觉很难表达,看起来就像这个图所示的这些方块你可以物理的交互他们。】


Even though this was a lot of fun, it’s not all rainbows and unicorns. Working with Oculus as a client, I had to define tasks and deliverables before I could actually start the work.【在Oculus工作,我需要在开工前确定任务目标】


I suggested the following criteria we would use to define success:

  1. Players should be able to pick up, throw and catch cubes without latency.
  2. Players should be able to stack cubes, and these stacks should be stable (come to rest) and be without visible jitter.
  3. When cubes thrown by any player interact with the simulation, wherever possible, these interactions should be without latency.


At the same time I created a set of tasks to work in order of greatest risk to least, since this was R&D, there was no guarantee we would actually succeed at what we were trying to do.【与此同时我创造了一系列工作任务,将工作风险最小化。因为这是研发,我们不能保证我们实际上能够成功实现我们的目标。】



Network Models


First up, we had to pick a network model. A network model is basically a strategy, exactly how we are going to hide latency and keep the simulation in sync.【首先我们需要选择网络模型,网络模型就是一套我们怎么隐藏延迟保持同步的机制。】


There are three main network models to choose from:

  1. Deterministic lockstep
  2. Client/server with client-side prediction
  3. Distributed simulation with authority scheme



I was instantly confident of the correct network model: a distributed simulation model where players take over authority of cubes they interact with. But let me share with you my reasoning behind this.【我很有信心的选择了我认为正确的网络模型:分布式模拟,每个玩家是其交互方块的authority,我来慢慢解释为什么选这个。】


First, I could trivially rule out a deterministic lockstep network model, since the physics engine inside Unity (PhysX) is not deterministic. Furthermore, even if PhysX was deterministic I could still rule it out because of the requirement that player interactions with the simulation be without latency.【首先我很确性的排除帧同步的方案,因为unity的物理引擎结果的不确定性。此外,即使PhysX是确定性的,我仍然可以排除它,因为这里我们的要求是玩家模拟交互没有延迟。】


The reason for this is that to hide latency with deterministic lockstep I needed to maintain two copies of the simulation and predict the authoritative simulation ahead with local inputs prior to render (GGPO style). At 90HZ simulation rate and with up to 250ms of latency to hide, this meant 25 physics simulation steps for each visual render frame. 25X cost is simply not realistic for a CPU intensive physics simulation.【理由是帧同步隐藏延时,我需要保留两个仿真副本,并在渲染之前使用本地输入预测权威仿真。在90HZ的模拟速率下隐藏时间达到250ms,这意味着每个视觉渲染帧需要25个物理模拟步骤。 对于CPU密集型物理模拟来说,25X成本根本不现实。】【这边我的理解:90hz说的就是一秒刷新90帧,250ms延时意味着4帧/s,一帧25个物理模拟步骤意味着一秒一共需要100个物理模拟步骤,这是对不上的。其实这边理解很简单的应该就是每秒需要计算90次物理模拟步骤,对于CPU压力会很大。】


This leaves two options: a client/server network model with client-side prediction (perhaps with dedicated server) and a less secure distributed simulation network model.【这样就还剩下两种方案】


Since this was a non-competitive sample, there was little justification to incur the cost of running dedicated servers. Therefore, whether I implemented a client/server model with client-side prediction or distributed simulation model, the security would be effectively the same. The only difference would be if only one of the players in the game could theoretically cheat, or all of them could.【由于这是一个非竞争性的样本,没有理由承担运行专用服务器的成本来保证公平和安全。】


For this reason, a distributed simulation model made the most sense. It had effectively the same amount of security, and would not require any expensive rollback and resimulation, since players simply take authority over cubes they interact with and send the state for those cubes to other players.【因为这些理由,我们选择分布式模拟的方案。它实际上具有相同的安全性,并且不需要任何昂贵的回滚和重新模拟,因为玩家只需对与其交互的立方体授权并将这些立方体的状态发送给其他玩家。】



Authority Scheme


While it makes intuitive sense that taking authority (acting like the server) for objects you interact can hide latency – since, well if you’re the server, you don’t experience any lag, right? – what’s not immediately obvious is how to resolve conflicts.【尽管直观地表明,为你所交互的对象赋予权限(像服务器一样)可以隐藏延迟因为如果你是服务器,那么你不会遇到任何延迟,对吧?这里存在的明显问题就是如何解决冲突。】


What if two players interact with the same stack? What if two players, masked by latency, grab the same cube? In the case of conflict: who wins, who gets corrected, and how is this decided?【如果两个玩家与同一个堆栈交互会怎样?
如果两个玩家,在延迟的掩盖下抓住同一个立方体怎么办? 在冲突的情况下:谁获胜,谁得到纠正,这是如何决定的?】


My intuition at this point was that because I would be exchanging state for objects rapidly (up to 60 times per-second), that it would be best to implement this as an encoding in the state exchanged between players over my network protocol, rather than as events.【因为我会经常快速地交换对象的状态(每秒最多60次),所以直觉上最好在网络中交换状态时使用编码,而不是作为事件。】


I thought about this for a while and came up with two key concepts:

  1. Authority
  2. Ownership



Each cube would have authority, either set to default (white), or to whatever color of the player that last interacted with it. If another player interacted with an object, authority would switch and update to that player. I planned to use authority for interactions of thrown objects with the scene. I imagined that a cube thrown by player 2 could take authority over any objects it interacted with, and in turn any objects those objects interacted with, recursively.【每个方块都必须要有Authority,默认的或者就是最后一次与他交互的玩家。如果其他玩家于这个方块交互,authority需要跟新切换到这个新的玩家。我计划使用Authority来处理投掷对象与场景的交互。


Ownership was a bit different. Once a cube is owned by a player, no other player could take ownership until that player reliquished ownership. I planned to use ownership for players grabbing cubes, because I didn’t want to make it possible for players to grab cubes out of other player’s hands after they picked them up.Ownership 则不一样,一旦一个立方体属于一个玩家,其他玩家在这个玩家释放Ownership权限前都无法获得这个立方体的Ownership权限。我计划将Ownership用于抓取立方体的玩家,因为我不想让玩家抢夺其他玩家手中抓取的立方体。】


I had an intuition that I could represent and communicate authority and ownership as state by including two different sequence numbers per-cube as I sent them: an authority sequence, and an ownership sequence number. This intuition ultimately proved correct, but turned out to be much more complicated in implementation than I expected. More on this later.【我想到可以通过 an authority sequence, and an ownership sequence number 来做表示和同步,这个最后被证明是可行的,后面说实现细节。】



State Synchronization


Trusting I could implement the authority rules described above, my first task was to prove that synchronizing physics in one direction of flow could actually work with Unity and PhysX. In previous work I had networked simulations built with ODE, so really, I had no idea if it was really possible.【要证实我可以实现上述的权威规则,我的第一个任务是证明UnityPhysX情况下物理同步是可行的。】


To find out, I setup a loopback scene in Unity where cubes fall into a pile in front of the player. There are two sets of cubes. The cubes on the left represent the authority side. The cubes on the right represent the non-authority side, which we want to be in sync with the cubes on the left.【我设置了一个unity测试场景,两坨方块,左边的是Authority,右边的是同步得到的结果,就是Non-Authority。】



At the start, without anything in place to keep the cubes in sync, even though both sets of cubes start from the same initial state, they give slightly different end results. You can see this most easily from top-down:【两边一模一样的初始化开始,但是得到了不一样的最终结果,如下图。】



This happens because PhysX is non-deterministic. Rather than tilting at non-determinstic windmills, I fight non-determinism by grabbing state from the left side (authority) and applying it to the right side (non-authority) 10 times per-second:【这是因为Physx是不确定性的,我通过每秒10次同步状态来解决不确定性这个问题。】



The state I grab from each cube looks like this:【对于每个方块同步的状态组成如下】



And when I apply this state to the simulation on the right side, I simply snap the position, rotation, linear and angular velocity of each cube to the state captured from the left side.【当我将这个状态应用到右侧的模拟中时,我只需将左侧每个立方体捕获的位置,旋转,线性和角速度等状态赋给右侧。】


This simple change is enough to keep the left and right simulations in sync. PhysX doesn’t even diverge enough in the 1/10th of a second between updates to show any noticeable pops.【这个简单的改变就可以保证从左边到右边的模拟同步,Physx不会产生足够的不一致。】



This proves that a state synchronization based approach for networking can work with PhysX. (Sigh of relief). The only problem of course, is that sending uncompressed physics state uses way too much bandwidth…【这证实physx支持基于网络状态的同步,唯一的问题当然是发送未压缩的物理状态使用了太多的带宽



Bandwidth Optimization


To make sure the networked physics sample is playable over the internet, I needed to get bandwidth under control.【接下来我们要做的就是优化带宽】


The easiest gain I found was to simply encode the state for at rest cubes more efficiently. For example, instead of repeatedly sending (0,0,0) for linear velocity and (0,0,0) for angular velocity for at rest cubes, I send just one bit:【一个简单的高收益方法就是更有效地编码静态方块。例如下方所示,velocity选项变的可有可无。】



This is lossless technique because it doesn’t change the state sent over the network in any way. It’s also extremely effective, since statistically speaking, most of the time the majority of cubes are at rest.【这是无损的一种方法,没有改变任何的网络状态,但同时非常的有效,因为场景中存在的大量方块都是静态的。】


To optimize bandwidth further we need to use lossy techniques. For example, we can reduce the precision of the physics state sent over the network by bounding position in some min/max range and quantizing it to a resolution of 1/1000th of a centimeter and sending that quantized position as an integer value in some known range. The same basic approach can be used for linear and angular velocity. For rotation I used the smallest three representation of a quaternion.【为了进一步优化带宽,我们需要使用有损技术。
例如我们通过在某些最小/最大范围内限定并量化为1/1000厘米的分辨率,其可以用整数来替代,有效的减少数据量。 相同的基本方法可以用于速度和角速度。 旋转我使用了通过三个值来表示的四元数来表示。】


But while this saves bandwidth, it also adds risk. My concern was that if we are networking a stack of cubes (for example, 10 or 20 cubes placed on top of each other), maybe the quantization would create errors that add jitter to that stack. Perhaps it would even cause the stack to become unstable, but in a particularly annoying and hard to debug way, where the stack looks fine for you, and is only unstable in the remote view (eg. the non-authority simulation), where another player is watching what you do.【这虽然节省了带宽,但也增加了风险。
我担心的是如果我们联网了一堆立方体(例如,1020个立方体放在另一个立方体上),也许量化会产生jitter这类的错误。 也许甚至会导致堆栈变得不稳定(自己是好的,别人看到的你都是有问题的)。】


The best solution to this problem that I found was to quantize the state on both sides. This means that before each physics simulation step, I capture and quantize the physics state exactly the same way as when it’s sent over the network, then I apply this quantized state back to the local simulation.【我发现这个问题的最佳解决方案是量化双方的状态。


Now the extrapolation from quantized state on the non-authority side exactly matches the authority simulation, minimizing jitter in large stacks. At least, in theory.【现在,Non-Authority方的状态和Authority的模拟完全匹配,最大限度的减少了Jitter。



Coming To Rest


But quantizing the physics state created some very interesting side-effects!【但量化物理状态创造了一些非常有趣的副作用!】


  1. PhysX doesn’t really like you forcing the state of each rigid body at the start of every frame and makes sure you know by taking up a bunch of CPU.PhysX并不真的喜欢你在每一帧开始时强制每个刚体的状态,且这事CPU占用严重。】
  2. Quantization adds error to position which PhysX tries very hard to correct, snapping cubes immediately out of penetration with huge pops!【量化会增加PhysX尝试纠正的位置错误,这个有时候会导致方块失控。】
  3. Rotations can’t be represented exactly either, again causing penetration. Interestingly in this case, cubes can get stuck in a feedback loop where they slide across the floor!【旋转不能这么搞,会导致立方体可能会卡在反馈回路中,并在地面上滑动!】
  4. Although cubes in large stacks seem to be at rest, close inspection in the editor reveals that they are actually jittering by tiny amounts, as cubes are quantized just above surface and falling towards it.【尽管大堆中的立方体似乎处于静止状态,但编辑人员仔细检查发现,实际上它们实际上在微小的抖动。】


There’s not much I could do about the PhysX CPU usage, but the solution I found for the depenetration was to set maxDepenetrationVelocity on each rigid body, limiting the velocity that cubes are pushed apart with. I found that one meter per-second works very well.【关于PhysX CPU使用情况我没有太多可以做的,但是我发现的解决方案的解决方案是在每个刚体上设置maxDepenetrationVelocity,限制立方体被推开的速度。 我发现每秒一米的效果非常好。】


Getting cubes to come to rest reliably was much harder. The solution I found was to disable the PhysX at rest calculation entirely and replace it with a ring-buffer of positions and rotations per-cube. If a cube has not moved or rotated significantly in the last 16 frames, I force it to rest. Boom. Perfectly stable stacks with quantization.【让立方体完全静止是非常困难的。
我找到的解决方案是完全停用PhysX,并将其替换为每个立方体的位置和旋转的ring-buffer。 如果一个立方体在最后的16帧中没有明显的移动旋转,我强迫它休息(静止)。 】


Now this might seem like a hack, but short of actually getting in the PhysX source code and rewriting the PhysX solver and at rest calculations, which I’m certainly not qualified to do, I didn’t see any other option. I’m happy to be proven wrong though, so if you find a better way to do this, please let me know 🙂【这可能看起来不是完美的解决方案,最好的就是进入PhysX源代码并重新编写PhysX解算器,加入休眠计算,我没有办法这么做,但我没有看到任何其他好的替代方案,你能解决的话求告诉我。】



Priority Accumulator


The next big bandwidth optimization I did was to send only a subset of cubes in each packet. This gave me fine control over the amount of bandwidth sent, by setting a maximum packet size and sending only the set of updates that fit in each packet.【我做的下一个带来巨大带宽优化的做法是每个网络包发送一组方块的数据。


Here’s how it works in practice:【具体做法】


  1. Each cube has a priority factor which is calculated each frame. Higher values are more likely to be sent. Negative values mean “don’t send this cube”.【每个立方体都具有优先系数,更高的值更可能被发送,负值意味着”不要发送这个立方体”。】
  2. If the priority factor is positive, it’s added to the priority accumulator value for that cube. This value persists between simulation updates such that the priority accumulator increases each frame, so cubes with higher priority rise faster than cubes with low priority.【如果方块的优先级因子为正,则将其添加到其优先级累加器值。
  3. Negative priority factors clear the priority accumulator to -1.0.【否定优先因子将优先累加器清除为-1.0。】
  4. When a packet is sent, cubes are sorted in order of highest priority accumulator to lowest. The first n cubes become the set of cubes to potentially include in the packet. Objects with negative priority accumulator values are excluded.【按照优先级累加器数值从高到低排列方块,前n个立方体成为一个数据包中的立方体集合,排除具有负优先级累加器值的对象。】
  5. The packet is written and cubes are serialized to the packet in order of importance. Not all state updates will necessarily fit in the packet, since cube updates have a variable encoding depending on their current state (at rest vs. not at rest and so on). Therefore, packet serialization returns a flag per-cube indicating whether it was included in the packet.【立方体按照重要性排序串行化到数据包,不是所有的方块状态都需要更新。】
  6. Priority accumulator values for cubes sent in the packet are cleared to 0.0, giving other cubes a fair chance to be included in the next packet.【数据包中发送的方块的优先累加器值被清除为0.0,使其他多维数据集有机会被包含在下一个数据包中。】


For this demo I found some value in boosting priority for cubes recently involved in high energy collisions, since high energy collision was the largest source of divergence due to non-deterministic results. I also boosted priority for cubes recently thrown by players.【因为高能碰撞是非确定性结果引起的最大分歧源,因此我提高了最近抛出的立方体的优先级。】


Somewhat counter-intuitively, reducing priority for at rest cubes gave bad results. My theory is that since the simulation runs on both sides, at rest cubes would get slightly out of sync and not be corrected quickly enough, causing divergence when other cubes collided with them.【有点反直觉,降低休息立方体的优先级给出了不好的结果。



Delta Compression


Even with all the techniques so far, it still wasn’t optimized enough. With four players I really wanted to get the cost per-player down under 256kbps, so the entire simulation could fit into 1mbps for the host.【即使采用了上面所讲的所有技术,仍然不够。


I had one last trick remaining: delta compression.【我还剩下最后一招:增量压缩】


First person shooters often implement delta compression by compressing the entire state of the world relative to a previous state. In this technique, a previous complete world state or ‘snapshot’ acts as the baseline, and a set of differences, or delta, between the baseline and the current snapshot is generated and sent down to the client.【第一人称射击者通常对相对于先前状态的整个世界的所有状态来实施增量压缩。


This technique is (relatively) easy to implement because the state for all objects are included in each snapshot, thus all the server needs to do is track the most recent snapshot received by each client, and generate deltas from that snapshot to the current.【由于所有对象的状态都包含在每个快照中,所以该技术相对容易实现,因此所有服务器需要执行的操作是跟踪每个客户端接收到的最新快照,并生成从该快照到当前的增量。】


However, when a priority accumulator is used, packets don’t contain updates for all objects and delta encoding becomes more complicated. Now the server (or authority-side) can’t simply encode cubes relative to a previous snapshot number. Instead, the baseline must be specified per-cube, so the receiver knows which state each cube is encoded relative to.【但是使用了优先级累加器后,数据包不包含所有对象的更新,这使得增量编码变得更加复杂。
现在服务器不能简单地将立方体相对于先前的快照进行编码。 而是必须指定每个多维数据集的基线,以便接收器知道每个多维数据集相对于哪个进行编码。】


The supporting systems and data structures are also much more complicated:【支持系统和数据结构也复杂得多】

  1. A reliability system is required that can report back to the sender which packets were received, not just the most recently received snapshot.【系统需要能够向发送方报告接收到哪些数据包,而不仅仅是最近接收到的快照】
  2. The sender needs to track the states included in each packet sent, so it can map packet level acks to sent states and update the most recently acked state per-cube. The next time a cube is sent, its delta is encoded relative to this state as a baseline.【发送者需要跟踪发送的每个数据包中包含的状态,因此它可以将数据包级别映射到发送状态,并更新最近每个多维数据集的最近查询状态。
  3. The receiver needs to store a ring-buffer of received states per-cube, so it can reconstruct the current cube state from a delta by looking up the baseline in this ring-buffer.【接收器的环形缓冲区需要存储每个立方体接收到的状态,因此它可以在该环形缓冲区中查找任意一个当前的立方体状态的基线来重建当前状态。】


But ultimately, it’s worth the extra complexity, because this system combines the flexibility of being able to dynamically adjust bandwidth usage, with the orders of magnitude bandwidth improvement you get from delta encoding.【但这一切的复杂性是值得的,因为这方案能够动态调整带宽的使用,同时获得数量级级别的带宽改进。】



Delta Encoding


Now that I have the supporting structures in place, I actually have to encode the difference of a cube relative to a previous baseline state. How is this done?【上面讲到了整个增量压缩的架构,实际上怎么进行差异编码这里来讲。】


The simplest way is to encode cubes that haven’t changed from the baseline value as just one bit: not changed. This is also the easiest gain you’ll ever see, because at any time most cubes are at rest, and therefore aren’t changing state.【最简单的方法是用一位来标记有没有变化。


A more advanced strategy is to encode the difference between the current and baseline values, aiming to encode small differences with fewer bits. For example, delta position could be (-1,+2,+5) from baseline. I found this works well for linear values, but breaks down for deltas of the smallest three quaternion representation, as the largest component of a quaternion is often different between the baseline and current rotation.【更高级的策略是对当前值和基准值之间的差异进行编码,目的就是用较少的位对差异进行编码。
例如标记起点从基线(-1+ 2+ 5)开始。 我发现这适用于线性值,但是对于用三个值表示的四元数作用不大。】


Furthermore, while encoding the difference gives some gains, it didn’t provide the order of magnitude improvement I was hoping for. In a desperate, last hope, I came up with a delta encoding strategy that included prediction. In this approach, I predict the current state from the baseline assuming the cube is moving ballistically under acceleration due to gravity.【此外,虽然编码差异带来了一些收益,但它达不到数量级的提升。
最后我想出了一种包含预测的增量编码策略。 在这种方法中,假设立方体由于重力而在加速度下运动,我可以基于此来预测基线的当前状态】


Prediction was complicated by the fact that the predictor must be written in fixed point, because floating point calculations are not necessarily guaranteed to be deterministic. But after a few days of tweaking and experimentation, I was able to write a ballistic predictor for position, linear and angular velocity that matched the PhysX integrator within quantize resolution about 90% of the time.【预测因子写入固定值这做法很难,因为浮点计算不一定保证确定性。


These lucky cubes get encoded with another bit: perfect prediction, leading to another order of magnitude improvement. For cases where the prediction doesn’t match exactly, I encoded small error offset relative to the prediction.【这些幸运的cubes用一个bit来表示,使得压缩效果达到了数量级的提升。


In the time I had to spend, I not able to get a good predictor for rotation. I blame this on the smallest three representation, which is highly numerically unstable, especially in fixed point. In the future, I would not use the smallest three representation for quantized rotations.【但是对于旋转量是没有办法这么做的,因为四元数的三个值数值上是非常不稳定的。 将来,我不会使用三个值表示的四元数来表示旋转。】


It was also painfully obvious while encoding differences and error offsets that using a bitpacker was not the best way to read and write these quantities. I’m certain that something like a range coder or arithmetic compressor that can represent fractional bits, and dynamically adjust its model to the differences would give much better results, but I was already within my bandwidth budget at this point and couldn’t justify any further noodling 🙂【编码差异和错误补偿也是非常明显的,因为这种封装方式并不是读取和写入这些数值的最佳方式。



Synchronizing Avatars


After several months of work, I had made the following progress:【经过几个月的工作,我取得了以下进展:】


  • Proof that state synchronization works with Unity and PhysX【证明状态同步适用于UnityPhysX
  • Stable stacks in the remote view while quantizing state on both sides【远程和本地状态同步且稳定】
  • Bandwidth reduced to the point where all four players can fit in 1mbps【四个玩家的情况下带宽控制在了1mbps


The next thing I needed to implement was interaction with the simulation via the touch controllers. This part was a lot of fun, and was my favorite part of the project 🙂【我需要实现的下一件事是通过Touch控制器交互,这部分我非常喜欢且非常有趣】


I hope you enjoy these interactions. There was a lot of experimentation and tuning to make simple things like picking up, throwing, passing from hand to hand feel good, even crazy adjustments to ensure throwing worked great, while placing objects on top of high stacks could still be done with high accuracy.【我希望你喜欢这些交互,这里我们为了保证拾取,投掷,传球等动作效果好,做了疯狂的实验和调整】


But when it comes to networking, in this case the game code doesn’t count. All the networking cares about is that avatars are represented by a head and two hands driven by the tracked headset and touch controller positions and orientations.【一般情况下头和双手都是由头盔和手柄的位置和方向驱动的。但是对于网络传输过去的avatar,本地设备驱动是不奏效的。】


To synchronize this I captured the position and orientation of the avatar components in FixedUpdate along the rest of the physics state, and applied this state to the avatar components in the remote view.【我获取了avatar组件的位置和旋转量来同步传输和应用给远端的avatar。】


But when I first tried this it looked absolutely awful. Why?【但是当我第一次尝试这个时,它看起来非常糟糕。


After a bunch of debugging I worked out that the avatar state was sampled from the touch hardware at render framerate in Update, and was applied on the other machine at FixedUpdate, causing jitter because the avatar sample time didn’t line up with the current time in the remote view.【debug的时候发现,硬件设备的位置跟新和基于渲染的frame更新,时间是不一致的,这个会导致远程跟新jitter。】


To fix this I stored the difference between physics and render time when sampling avatar state, and included this in the avatar state in each packet. Then I added a jitter buffer with 100ms delay to received packets, solving network jitter from time variance in packet delivery and enabling interpolation between avatar states to reconstruct a sample at the correct time.【为了解决这个问题,我存储物理和渲染的时候的avatar的状态差异,并将其包含在每个avatar状态的数据包中。


To synchronize cubes held by avatars, while a cube is parented to an avatar’s hand, I set the cube’s priority factor to -1, stopping it from being sent with regular physics state updates. While a cube is attached to a hand, I include its id and relative position and rotation as part of the avatar state. In the remote view, cubes are attached to the avatar hand when the first avatar state arrives with that cube parented to it, and detached when regular physics state updates resume, corresponding to the cube being thrown or released.【为了同步由Avatar持有的cubes,当一个cube处于化身状态时,我将其优先因子设置为-1,从而阻止它以常规物理状态更新发送。当一个cube附着在手上时,我会将其ID和相对位置以及旋转作为avatar的状态的一部分。 在远端当第一个avatar状态到达时,立方体与该虚拟形体相连,并在常规物理状态更新恢复时分离,对应于正在抛出或释放的立方体。】



Bidirectional Flow


Now that I had player interaction with the scene working with the touch controllers, it was time to start thinking about how the second player can interact with the scene as well.【现在我已经可以使用控制器与场景进行交互了,现在开始考虑第二个玩家如何与场景互动。】


To do this without going insane switching between two headsets all the time (!!!), I extended my Unity test scene to be able to switch between the context of player one (left) and player two (right).【要做到这一点,为了测试的时候无需一直在两个头盔之间进行疯狂的切换,我扩展了Unity测试场景,以便能够在玩家1(左)和玩家2(右)之间切换。】


I called the first player the “host” and the second player the “guest”. In this model, the host is the “real” simulation, and by default synchronizes all cubes to the guest player, but as the guest interacts with the world, it takes authority over these objects and sends state for them back to the host player.【我把第一个玩家称为”host”,第二个玩家称为”guest”。


To make this work without inducing obvious conflicts the host and guest both check the local state of cubes before taking authority and ownership. For example, the host won’t take ownership over a cube already under ownership of the guest, and vice versa, while authority is allowed to be taken, to let players throw cubes at somebody else’s stack and knock it over while it’s being built.【为了解决冲突,host和guest在获得权限和所有权之前都会检查立方体的本地状态。例如,host不会对guest拥有所有权的cubes拥有所有权,反之亦然。】


Generalizing further to four players, in the networked physics sample, all packets flow through the host player, making the host the arbiter. In effect, rather than being truly peer-to-peer, a topology is chosen that all guests in the game communicate only with the host player. This lets the host decide which updates to accept, and which updates to ignore and subsequently correct.【进一步推广到四个玩家,在网络物理示例中,所有数据包都会流经host玩家,让host仲裁。
这实际上不是真正的点对点,而是选择游戏中的所有guest仅与host玩家通信的拓扑方式。 这让host可以决定接受哪些更新以及忽略哪些更新并随后进行更正。】


To apply these corrections I needed some way for the host to override guests and say, no, you don’t have authority/ownership over this cube, and you should accept this update. I also needed some way for the host to determine ordering for guest interactions with the world, so if one client experiences a burst of lag and delivers a bunch of packets late, these packets won’t take precedence over more recent actions from other guests.【要应用这些更正,我需要某种方式让host覆盖guest的结果。我还需要一些方法让host确定这些guests与整个世界互动的顺序。因此如果一个客户端经历了一段时间的延迟并延迟发送大量数据包,这些数据包的优先级将低于来自其他客户的数据包。】


As per my hunch earlier, this was achieved with two sequence numbers per-cube:【按照我之前的做法,这一切通过每个立方体的两个序列号实现的:】

  1. Authority sequence
  2. Ownership sequence


These sequence numbers are sent along with each state update and included in avatar state when cubes are held by players. They are used by the host to determine if it should accept an update from guests, and by guests to determine if the state update from the server is more recent and should be accepted, even when that guest thinks it has authority or ownership over a cube.【这些序号随着每个状态更新一起发送,并包含玩家持有的cubes的状态。


Authority sequence increments each time a player takes authority over a cube and when a cube under authority of a player comes to rest. When a cube has authority on a guest machine, it holds authority on that machine until it receives confirmation from the host before returning to default authority. This ensures that the final at rest state for cubes under guest authority are committed back to the host, even under significant packet loss.【每次玩家取得和释放cube的权限,权限序列都会增加。
当guest具有一个cube的权限时,它将保持这个权限直到它从host接收到返回默认状态的信息。 这确保了即使在大量丢包的情况下,guest权限下的cubes处于静止状态也会被提交给主机(因为主机不确认就一直随着avatar发送信息)。】


Ownership sequence increments each time a player grabs a cube. Ownership is stronger than authority, such that an increase in ownership sequence wins over an increase in authority sequence number. For example, if a player interacts with a cube just before another player grabs it, the player who grabbed it wins.【每次玩家抓取cube时,ownership序列都会增加。
ownership比authority更强大,因此ownership序列的增加会赢得authority 序列号的增加。 例如,如果玩家在另一个玩家抓住它之前与立方体进行交互,那么抓住它的玩家将获胜。】


In my experience working on this demo I found these rules to be sufficient to resolve conflicts, while letting host and guest players interact with the world lag free. Conflicts requiring corrections are rare in practice even under significant latency, and when they do occur, the simulation quickly converges to a consistent state.【根据我在这个demo中的经验,我发现这些规则足以解决冲突,同时让host和guest与世界进行互动。





High quality networked physics with stable stacks of cubes is possible with Unity and PhysX using a distributed simulation network model.UnityPhysX使用分布式仿真网络模型,可以实现高品质的网络物理和稳定的立方体堆栈。】


This approach is best used for cooperative experiences only, as it does not provide the security of a server-authoritative network model with dedicated servers and client-side prediction.【这种方法最适合仅用于协作体验,因为它不能提供具有专用服务器和客户端预测的服务器权威网络模型的安全性。(也就是安全性是不够的)】


Thanks to Oculus for sponsoring my work and making this research possible!【感谢Oculus赞助我的工作并使这项研究成为可能!】


The source code for the networked physics sample can be downloaded here.