文章目录
- 前言
- NetworkTransform是什么
- 玩家移动脚本
- NetworkTransform字段讲解
- Synchronizing ("Syncing")
- Thresholds
- Local space
- Interpolation
- Slerp Position
- Use Quaternion Synchronization
- Use Quaternion Compression
- Use Half Float Precision
- Authority modes
- Server Authoritative Mode
- Owner Authoritative Mode
- Additional Virtual Methods of Interest
- 后话
- 官方链接
前言
这次教程主要是讲同步两个玩家的位置信息的一个非常重要的组件NetworkTransform
,以及这个组件的作用与说明。
NetworkTransform是什么
同步物体的Transform
是Netcode当今多人游戏中最常见的任务之一。这个概念似乎很简单:
- 确定您要同步的变换轴。
- 序列化这些值。
- 将序列化的值作为消息发送给所有其他连接的客户端。
- 处理消息并反序列化值。
- 将这些值应用到适当的轴上。
乍一看,上面列出的任务似乎相对简单,但当您开始实现每个任务时,几乎任何经验丰富的Netcode软件工程师都会同意:这可能迅速变得复杂。
例如,上面列出的任务并没有考虑以下几点:
- 谁控制同步(即每个客户端、服务器或者根据要同步的对象而定可能两者都控制)?
- 以何种频率同步这些值,以及确定何时需要同步这些值的逻辑应该是什么?
- 如果您有复杂的父子关系层次结构(父变换带有一个或多个子变换),是应该同步世界空间轴值还是本地空间轴值?
- 如何优化每次变换更新的带宽成本?
幸运的是,Netcode for GameObjects (NGO) 提供了NetworkTransform
组件的实现,它处理了一些变换同步的棘手方面,并可以通过在编辑器中的检视面板中访问的属性轻松配置。
玩家移动脚本
Package Manager
--> 左上角+
号旁边的包 选择Unity Registry
, 搜索Cinemachine
下载并导入
把PlayerMove
移动到Scripts
,在预制体Player
上添加该脚本
NetworkTransform字段讲解
Synchronizing (“Syncing”)
这个是用来指定同步位置,旋转,缩放的,需要同步哪些值就勾选哪些
一般情况下,不需要同步GameObject的所有变换值。例如,如果GameObject的缩放从不改变,可以在面板中的Syncing Scale
禁用。禁用同步可以节省CPU成本和网络带宽。
Thresholds
您可以使用阈值值来设置最小阈值。这可以用来通过只同步大于或等于阈值值的变化(低于阈值的变化不会同步)来降低同步更新的频率。例如:
如果NetworkTransform
启用了插值(Interpolate
),您可能会发现可以降低位置阈值的分辨率(增加位置阈值值),而不影响对象运动的"平滑度",同时还减少位置更新的频率(减少每个实例的带宽成本)。增加阈值分辨率(降低位置阈值值)会增加对象的位置同步的潜在频率(可能会增加每个实例的带宽成本)。
阈值值不会被同步,但可以在
authoritative
实例上进行更新。在使用所有者authoritative
模式的实例时,应该牢记这一点,因为更改所有权将使用新所有者实例上当前设置的任何值。如果您计划在运行时更改阈值值并计划更改所有权,那么您可能需要同步阈值值。
Local space
默认情况下,NetworkTransform
以世界空间同步
对象的变换。In Local Space
配置选项允许您改为在本地空间中同步变换。子对象的本地空间轴值(主要是位置和旋转)始终是相对于父变换的偏移。而子对象的世界空间轴值包括父对象的轴值。
在具有父对象的NetworkTransform
上使用本地空间可以改善在对象重新父对象化时的变换同步,因为重新父对象化不会改变对象的本地空间变换,但会改变世界空间位置。
authoritative
实例确实会同步对LocalSpace属性的更改。因此,您可以在运行时在authoritative
实例上调整此属性,而非authoritative
实例将自动更新。
Interpolation
默认情况下启用了插值(·Interpolation·),如果您希望在非authoritative
实例上的变换更新之间实现平滑的过渡,这是推荐的设置。插值会缓冲传入的状态更新,这可能会在authoritative
实例和非authoritative
实例之间引入轻微的延迟。当禁用插值属性时,变换的更改会立即应用到非authoritative
实例上,这可能会导致视觉上的"抖动",或者在延迟较高时似乎会"跳跃"到新应用的状态更新。
在权威实例上在运行时更改插值属性将与所有非权威实例同步。
NetworkTransform
组件仅在客户端上进行插值。为了在主机或服务器上实现更平滑的移动,用户可能还希望在服务器端实现插值。尽管服务器不会受到网络引起的抖动影响,但仍然可能会在本地出现一些卡顿(例如,在FixedUpdate
中进行的低物理更新率的移动)。
Slerp Position
当设置了这个属性并且同时启用了插值(Interpolation
),非authoritative
实例将通过Slerp而不是Lerp朝着它们的目标位置插值。通常,这可以在对象遵循圆形和/或基于样条的运动路径时使用,以保留该路径的曲线性。由于在两点之间进行"lerp"插值会在两点之间的线上产生线性进展,因此在某些情况下,位置状态更新的频率可能会导致对象运动曲线的损失。
Use Quaternion Synchronization
默认情况下,使用欧拉角值来同步旋转增量。对于许多情况,使用欧拉角值可能就足够了。然而,有些情况下,同步欧拉角增量会产生不希望的结果。一个情况是当您有复杂的嵌套NetworkTransforms,其中父子变换之间的旋转不同。当您将插值结合在一起(记住插值是有缓冲的,在非权威的当前旋转和目标旋转之间存在固有的延迟时),立即发生在Quaternion中的调整会处理更复杂的变换相关问题(例如吉姆巴锁等)。
启用Quaternion同步时,权威实例仍然会根据欧拉轴值与阈值值进行比较,以确定是否需要更新变换的旋转,但是会更新整个Quaternion,而不仅仅是检测到变化的欧拉轴。这意味着可以确保正确的旋转将应用于非权威实例,并且已经考虑到了使用欧拉角时可能出现的更复杂问题。
Quaternion同步是有代价的。它会增加带宽成本,每个实例增加16字节,以处理更复杂的旋转问题,这种问题在使用嵌套的NetworkTransform(一个或多个父变换和一个或多个子变换)时更常见。但是,当您启用Use Quaternion Synchronization
属性时,您会注意到在同步轴选择复选框和一个新的Use Quaternion Compression
属性会出现:
当启用
Use Quaternion Synchronization
时,不再提供旋转同步轴复选框(因为同步变换的四元数将始终更新所有旋转轴),而Use Quaternion Compression
成为可见选项。
Use Quaternion Compression
由于同步四元数可能会增加NetworkTransform
旋转状态更新的带宽成本,因此有两种方法可以减小四元数同步的总体带宽成本:
-
四元数压缩(
Quaternion Compression
):这提供了最高的压缩率(每次更新减少到4字节),但精度损失略高于半浮点精度。 -
半浮点精度(
Half Float Precision
):当启用并且禁用四元数压缩时,这提供了中等级别的替代压缩(每次更新减少到8字节),比完全浮点值的精度低,但比四元数压缩的精度高。
四元数压缩是基于最小三算法的,当旋转精度不如带宽成本重要时,可以使用它。您可能有需要某种形式的旋转同步的附属对象/抛射物,但在项目的整体方案中不需要完美对齐。
如果带宽成本和精度都是问题,那么备选的推荐压缩方法是半浮点精度。此外,建议尝试不同的压缩选项,您可能会发现精度的部分损失对于项目的需求是完全可以接受的(并且可以减小所有实例的总体带宽成本,最多减少50%的带宽成本,而不使用完全精度时)。
此属性值可以在权威实例在运行时进行更新,并将同步到所有非权威实例。提醒:在authoritative
实例上在运行时更新此值将导致NetworkTransform
的完全同步,所有非authoritative
实例的插值器将被重置。
Use Half Float Precision
启用此属性将会将任何变换轴值从4字节浮点数转换为2字节半浮点数,但会以精度损失为代价。启用此选项时,所有标记为同步的变换轴都将使用半浮点精度。然而,关于位置和旋转,半浮点精度有一些独特的方面。
由于存在精度损失,位置状态更新仅提供相对于上一个已知完整位置的位置增量。NetworkDeltaPosition
可序列化结构会跟踪上一个已知完整位置和当前与上一个已知完整位置的当前增量偏移之间的当前增量。此外,NetworkDeltaPosition
会在发送更新时自动纠正精度损失。从先前更新的精度损失将包含在下一个位置更新中。换句话说,非authoritative
实例可以在1个tick周期的持续时间内或直到接收到下一个变换状态更新之前,潜在地与authoritative
实例具有来自每次应用的更新的分数增量。此外,NetworkDeltaPosition
填补了半浮点值的最大值与Unity世界空间的最大边界(全局/项目缩放相关)之间的差距。
推荐的Unity世界空间单位每秒:
每个更新的最大增量不应超过64个Unity世界空间单位。如果您使用默认的tick(30),那么对象不应以等于或超过每秒1920个Unity世界空间单位的速度移动(即30 x 64)。作为参考,默认摄像机的远裁剪平面是1000个Unity世界空间单位,这意味着以1920个Unity世界空间单位的速度移动的物体可能不会在渲染视锥体中被视觉检测到,或者会以短暂的"闪烁"出现。
当启用Use Quaternion Synchronization
和Use Half Float Precision
并且禁用Use Quaternion Compression
时,四元数值通过HalfVector4可序列化结构进行同步,其中每个轴值(x、y、z和w)都存储为半浮点值。这意味着每个旋转更新从完全精度的每次16字节减少到每次8字节。对于旋转,使用半浮点精度提供了比四元数压缩更好的精度,带宽成本是其两倍,但是只有完全精度的一半成本。
当启用Use Quaternion Synchronization
,Use Half Float Precision
和Use Quaternion Compression
时,四元数压缩将用于代替半浮点精度的旋转。
当在authoritative
实例上进行更新时,所有这些属性都将同步到非authoritative
实例。
Authority modes
Server Authoritative Mode
默认情况下,NetworkTransform在服务器authoritative
模式下运行。这意味着服务器端检测到要同步的变换轴(标记为同步),并将其推送给连接的客户端。这也意味着对变换轴值的任何更改都将被authoritative
状态(在这种情况下是服务器端的变换状态)覆盖。
还有一个关于轴同步与初始同步的变换值的概念需要牢记。未标记为同步的任何轴在NetworkObject
被生成或客户端第一次同步时仍会被更新为authoritative
的初始状态。
举个例子:
假设您只标记了位置和旋转轴来同步,但在NetworkTransform
组件的网络预制上排除了所有缩放轴。当您生成网络预制的实例时,初始的权威方缩放值将在生成时同步。从那时起,非authoritative
实例(在这种情况下是客户端实例)将保持相同的缩放轴值,即使它们不再更新。
Owner Authoritative Mode
(又名:ClientNetworkTransform)
服务器端权威的NetworkTransform
在同步转换和在所有连接的客户端上应用更新之间提供了平衡。然而,有时您希望特定NetworkObject
(通常是玩家)在客户端上立即更新位置。NetworkTransform
的所有者权威由NetworkTransform.OnIsServerAuthoritative
方法决定,该方法在首次初始化NetworkTransform
组件时调用。如果该方法返回true(默认值),则它将初始化为服务器权威的NetworkTransform
。如果返回false,则它将初始化为所有者authoritative
的NetworkTransform
(也称为ClientNetworkTransform
)。这可以通过从NetworkTransform
派生,覆写OnIsServerAuthoritative
虚拟方法,并像下面的代码示例中一样返回false来实现:
using Unity.Netcode.Components;
using UnityEngine;
namespace Unity.Multiplayer.Samples.Utilities.ClientAuthority
{
/// <summary>
/// 用于同步客户端端的变换更改。这包括主机。不支持纯服务器作为所有者,请使用NetworkTransform。
/// 用于那些始终由服务器拥有的Transform。
/// </summary>
[DisallowMultipleComponent]
public class ClientNetworkTransform : NetworkTransform
{
/// <summary>
/// Used to determine who can write to this transform. Owner client only.
/// This imposes state to the server. This is putting trust on your clients. Make sure no security-sensitive features use this transform.
/// </summary>
protected override bool OnIsServerAuthoritative()
{
return false;
}
}
}
ClientNetworkTransform
示例:
选择Window
--> Package Manager
来打开包管理器。
Add(+)
--> 从git URL
添加…
复制并粘贴以下Git URL:https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git?path=/Packages/com.unity.multiplayer.samples.coop#main
或者修改项目的manifest.json
,添加
"com.unity.multiplayer.samples.coop": "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git?path=/Packages/com.unity.multiplayer.samples.coop#main"
该Transform将所有者客户端的位置与服务器和所有其他客户端同步,从而实现客户端authoritative
的游戏玩法。
Additional Virtual Methods of Interest
NetworkTransform.OnAuthorityPushTransformState
: 此虚拟方法在权威实例正在将新的NetworkTransformState
推送给非authoritative
实例时调用。这可以用于更精确地确定用于预测相关任务的非authoritative
实例的更新值。
NetworkTransform.OnNetworkTransformStateUpdated
: 此虚拟方法在非权威实例接收来自authoritative
实例的推送NetworkTransformState
更新时调用。这可以用于更精确地确定用于预测相关任务的非authoritative
实例的更新值。
NetworkTransform.Awake
: 为了提供自定义初始化的能力,此方法已被设置为虚拟方法。如果您覆盖此方法,建议首先调用base.Awake()
。
NetworkTransform.OnInitialize
: 此虚拟方法在相关的NetworkObject首次生成以及所有权更改时调用。
NetworkTransform.Update
: 为了提供您对派生NetworkTransform
类进行任何自定义的能力,此方法已被设置为虚拟方法。如果您覆盖此方法,要求所有非authoritative
实例调用base.Update()
,但对于authoritative
实例则不是必须的。
后话
有了NetworkTransform
这个组件可以为位置同步省了很多功夫,后面讲NetworkTransform
的实战应用
官方链接
https://docs-multiplayer.unity3d.com/netcode/current/components/networktransform/