UDN
Search public documentation:

GameStateReplicationCH
English Translation
日本語訳
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 主页 > 复制 > 游戏状态复制

游戏状态函数复制


概述


对于每一个与特定客户端相关的 actor,服务器都会为该客户端在该 actor 上执行一系列函数复制检查。这些检查会确定哪些变量应该被复制到客户端,并确定是否应该将一个给定的函数发送给该客户端执行,而不是在本地机器上执行。同样,客户端可以将函数(而不是变量)复制返回到服务器。关于 actor 的数据传送过程被称为复制。传送某个 actor 的所有数据过程会溢出连接,就好像传送过程会改变数据一样。相反,Unreal 采用的方法是只复制在某个特定客户端上需要的数据。

函数复制检查


当您想到服务器中的客户端数量、每个客户端的相关 actor 的数量,以及每个 actor 的变量上的函数复制检查数量时,需要执行大部分这些检查来确定哪些数据要通过网络发送。在执行函数复制声明时应该特别注意,以便不会浪费太多 CPU 计算要发送什么数据。不要从函数复制声明内部调用函数,因为对于函数复制声明来说函数调用太慢。如果必要,在 tick(更新)中计算函数的结果,并使用您的函数复制声明中的变量值。按照老规矩,您应该尝试使您的检查尽量简单,如果可以只评估一个条件。由于为虚幻脚本内 Actor、Inventory 以及其他常见的类的变量计算函数复制条件需要时间;所以将这些检查移至 native 代码。这是您为什么在一些 Egine 类上看到 nativereplication 的原因。为函数复制而评估的任何变量将会被 native 函数复制代码覆盖。但是 UnrealScript 函数复制声明仍然很重要,而且可以用于诸如秘技保护和供没有源代码的改编作者使用的文档中。任何没有在 nativereplication 类中定义的变量(在一个非 nativereplication 子类或超类中)都将会通过 unrealscript 定义进行复制。

可靠与不可靠之间对比


在网络中使用的另一种优化是 reliable(可靠)与 unreliable(不可靠)数据之间的差。对于变量,所有数据都是可靠的。如果一个变量没有将其通过传送到客户端,那么会保证将其重新发送到客户端,最后I使其通过。为了允许有出现故障的包,使用编号最大的包中的变量数据,并确保新颖。但是函数同时存在于可靠和不可靠形式中。可靠函数,可以保证以发送时的顺序到达客户端,相对于其他可靠函数。但是发送到客户端的不可靠函数,不能保证是否可以被接收到(如果没有将它成功发动给客户端,那么服务器不会尝试再次发送这个函数)。通常情况下,在将函数导入到游戏中时应该使用可靠函数,这样客户端才会与服务器保持相对更高的仿真。使用不可靠函数也是可以的,但是不是必需的;例如特效。

函数复制数据近似


另一个优化是 Unreal 如何通过互联网发送向量、旋转量和数组。这些规则可以同时应用于函数参数和变量中。

  • Vectors - 四舍五入的每个分量。
  • Planes - 四舍五入的每个分量。
  • Rotators - 每个分量可以有 255 个不同的值。然后在连接的另一端将这些值按比例从 0-255 增加到 0-65536。
  • Structs - 所有或没有。如果一个元素改变,那么整个结构体会被复制。
  • Static Arrays - 只会发送数组中已经改变的元素。动态数组不可以进行复制。
  • Strings, Structs - 必须小于 448 字节,否则该变量会缺少该特定 actor 的其他变量
  • CompressedPosition - 使用向量和旋转器的以上变换控件(Rotator.Roll 除外,它通常是 0)。而速度分量会获得与向量相同的变换控件。

请参阅变量复制注意事项了解更多信息。如果您需要精确的向量、平面和旋转量,那么最好新建一个结构体进行结构体指定优化。通常最好按照要求尽量少发送数据,强烈推荐尽可能在客户端上多进行仿真。

函数复制声明规则


在编写您自己的函数复制声明时,需要了解一些有用的事情。首先,您只可以在其中已经定义变量的类中定义函数复制声明。这意味着您可以始终不覆盖函数复制声明,而您必须在可以定义数据的正确类中定义函数复制声明。如果您发现您自己需要覆盖变量的函数复制条件,很可能是您为您的 actor 使用的是错误角色,您需要重新考虑它应该使用什么类型的角色,它会允许您复制该数据。这样,如果 Actor 类包含一个变量 DrawType,那么您知道在以下地方可以寻找它的函数复制条件: 它可能存在于 Actor 类中。如果您仍然需要复制数据,您通常最好定义一个新的可以作为信息载体的变量,然后将其复制到另一端上的真正变量上。

现在只能使用函数修饰符 serverclientreliableunrealiable 进行复制的函数定义。这些修改量的意思都很直接。一个声明了 server 的函数将会在服务器上执行,而且通常被客户端调用。一个声明了 client 的函数将会在 actor 拥有的客户端。*Client* 和 server 互相对立,就像 reliableunreliable 一样;也就是说一个函数不可能同时是 serverclient 。一个声明了 reliable 的函数通常会按顺序执行,而且可以保证到达。一个声明了 unreliable 的函数可能不会将其发送到到另一端;也就是说,如果网络饱和,那么将会声明这个函数调用。

函数复制声明准则


使您的函数复制声明简单。如果您有很多有关这个数据的实例,那么很可能常常运行这些函数复制条件,任何可以执行的优化对您都会大有帮助。我们已经讨论了不做任何过度复杂的操作的重要性以及应该要遵守的规则。其次,如果可以,使用 bNet... 变量。这些变量,例如, bNetInitialbNetOwnerbNetDirty 都非常有用,而且使用这些变量不需要其他 CPU 需求,因为系统会自动为您设置它们。还可以执行检查进行各种操作,这取决于 角色/远程角色 是否是一个 Role_SimulatedProxy 或者是一个 Role_AutonomousProxy 。找到这些更错综复杂的检查实例最好的地方是在 Engine 类中,特别是 ActorPawnPlayerController 。毫无疑问,您不得改变函数复制声明中的任何内容。在函数复制声明中进行 i++ 会使 i 在被复制的过程中发生不可预知的改变,这些不是您真正想要进行的操作。而且最后,作为最后一个建议,服务器会检查所有输入的数据以确定它是否合情合理。如果客户端将某些内容复制到服务器,该服务器会临时交换 RoleRemoteRole 变量,然后判断这个函数复制条件以查看另一端是否具备发送该数据的合理理由。如果检查失败,那么会删除这个数据。这意味着当您编写函数复制声明的时候,要确保服务器也有执行函数复制检查需要的所有数据。否则,它将始终不会被服务器处理或接受。

编写函数复制声明


在 UnrealScript 中,每个类都可以有一个函数复制块,其中包含很多函数复制声明。每个函数复制说明由一个函数复制条件(判断 true 或 false 的声明)和一个或多个使用这个条件的变量组成。

一个类最好不包含函数复制声明,也就意味着这个类将不会将任何它定义的变量复制到另一端。事实上,大多数类不需要函数复制声明,因为大多数“有趣”的变量都会影响在 Actor 类中定义的展示,而且只可以由子类中的代码进行修改。

如果您在一个类中定义一个新变量,但是您没有将它列在函数复制定义中,这意味着您的变量绝对从来没有被复制过。这是正常现象;大多数变量不需要被复制。

以下是函数复制声明的 UnrealScript 语法的一个示例。它以 Pawn 类为例:

Pawn.uc
replication
{
  // 服务器应该发送给所有客户端的变量。
  if (bNetDirty && Role == ROLE_Authority)
  FlashLocation, bSimulateGravity, bIsWalking, PlayerReplicationInfo, HitDamageType, TakeHitLocation, DrivenVehicle, Health;

  // 发送给拥有的客户端的变量
  if (bNetDirty && bNetOwner && Role == ROLE_Authority)
    InvManager, Controller, GroundSpeed, WaterSpeed, AirSpeed, AccelRate, JumpZ, AirControl;

  // 发送给非拥有的客户端的变量
  if (bNetDirty && !bNetOwner && Role==Role_Authority)
    bIsCrouched, FlashCount, FiringMode;

  // 当 Pawn 被关闭时发送给所有客户端的变量。(bTearOff)
  if (bTearOff && bNetDirty && Role == ROLE_Authority)
    TearOffMomentum;

  // 发送给除拥有的客户端之外的所有客户端的变量
  if (!bNetOwner && Role==ROLE_Authority)
    RemoteViewPitch;
}

以下是一些复制的函数的示例,以 Controller 类为例:

Controller.uc
reliable server function ServerRestartPlayer()
{
  if (WorldInfo.NetMode != NM_Client && Pawn != None)
  {
    ServerGivePawn();
  }
}

reliable client function ClientSetWeapon( class<Weapon> WeaponClass )
{
  local Inventory Inv;

  if (Pawn == None)
  {
    return;
  }

  Inv = Pawn.FindInventoryType( WeaponClass );
  if (Weapon(Inv) != None)
  {
    Pawn.SetActiveWeapon(Weapon(Inv));
  }
}