组件复制

设置组件用于在网络中进行复制

Windows
MacOS
Linux

虚幻引擎 4 支持组件复制。尽管使用起来简单直观,但它却有些不同寻常:大多数组件都不会复制。其多数游戏逻辑都是在 Actor 类和组件中完成,而它们 通常只代表了构成 Actor 的零散部分。实际复制的是 Actor 中的游戏逻辑,而这样做的结果,有时会调用/更改组件。 但在有些情况下,组件本身的属性或事件必须要直接复制。这份指南将介绍如何使用这一功能。

组件会作为其所属 Actor 的一部分进行复制。Actor 仍然掌管角色、优先级、相关性、剔除等方面的工作。一旦复制了 Actor,它就可以复制自身组件。这些组件 可以按 Actor 的方式复制属性和 RPC。组件必须以 Actor 的方式实施 ::GetLifetimeReplicatedProps (...) 函数。

组件复制中涉及两大类组件。一种是随 Actor 一起创建的静态组件。也就是说,在客户端或服务器上生成 所属 Actor 时,这些组件也会同时生成,与组件是否被复制无关。服务器不会告知客户端显式生成这些组件。 在此背景下,静态组件是作为默认子对象在 C++ 构造函数中创建,或是在蓝图编辑器的组件模式中创建。静态组件无需通过复制存在于 客户端;它们将默认存在。只有在属性或事件需要在服务器和客户端之间自动同步时,才需要进行复制。

动态组件是在运行时在服务器上生成的组件种,其创建和删除操作也将被复制到客户端。它们的运行方式与 Actor 极为一致。与静态组件不同, 动态组件需通过复制的方式存在于所有客户端。另外,客户端可以生成自己的本地非复制组件。这适合于很多种情形。只有当那些在服务器上触发的 属性或事件需要自动同步到客户端时,才会出现复制行为。

使用方法

在组件上设置属性和 RPC 的过程与 Actor 并无区别。将一个类设置为具有复本后,这些组件的实际实例也必须经过设置后才能复制。

C++

要进行组件复制,只需调用 AActorComponent::SetIsReplicated(true) 即可。如果您的组件是一个默认子对象,就应当在生成组件之后通过类构造函数来完成此调用。

例:

ACharacter::ACharacter()
{
    // Etc...

    CharacterMovement = CreateDefaultSubobject<UMovementComp_Character>(TEXT("CharMoveComp"));
    if (CharacterMovement)
    {
        CharacterMovement->UpdatedComponent = CapsuleComponent;

        CharacterMovement->GetNavAgentProperties()->bCanJump = true;
        CharacterMovement->GetNavAgentProperties()->bCanWalk = true;
        CharacterMovement->SetJumpAllowed(true);
        CharacterMovement->SetNetAddressable(); // Make DSO components net addressable
        CharacterMovement->SetIsReplicated(true); // Enable replication by default

    }
}

蓝图

要进行静态蓝图组件复制,只需在组件默认设置中切换 Replicates 布尔变量。同样,只有当组件中拥有需要复制的属性或事件时,才需要 进行此操作。静态组件需要在客户端和服务器上隐式创建。

components_checkbox.png

需要注意的是,并非所有组件都会如此显示,必须要支持某种复制形式才会显示。

要通过动态生成的组件来实现这一点,可以调用 SetIsReplicated 函数:

components_function.png

时间轴

时间轴必须通过其属性中的 Replicated 选项来启用复制。这会将服务器控制的运行位置、速率和方向复制到客户端。这是一种基本的 实施,可以根据需求的变化而进行演变。大多数时间轴都无需复制。和所有游戏对象复本一样,时间轴复本只应当在服务器上直接 操作 (start/stop etc)。客户端只应当查看运行位置的复本,而不应尝试改变时间轴本身。在进行复制更新的间歇,客户端将推测 运行位置。

带宽消耗

复制组件时的资源消耗量是比较低的。要复制的 Actor 中的每个组件都需要添加一个额外的 NetGUID(4 字节)"标头"和一个大约 1 字节的"标脚"(footer) 及其属性。在 CPU 层面上,基于 Actor 的属性复制与基于组件的复制之间应当有一个最小差异。

一般性的子对象复制

对这个问题,我们可以更进一步:所有 Actor 子对象都可以复制,而不只限于组件。

这个结论的应用范围很窄,但在某些情形下却非常实用。实现这一目的所用的接口需要在类的层面上定义:

/** FActory 方法,用于对模板化 TobjectReplicator 类进行实例化,以便实现子对象复制 */
virtual class FObjectReplicatorBase * InstantiateReplicatorForSubObject(UClass *SubobjClass);

/** 能让 Actor 在其 Actor 通道上复制子对象的方法 */
virtual bool ReplicateSubobjects(class UActorChannel *Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags);

/** 通过复制来动态创建新的子对象时,在 Actor 上进行调用 */
virtual void OnSubobjectCreatedFromReplication(UObject *NewSubobject);

对于希望复制非 ActorComponent 子对象的类,应当实施以上三种方法。

使用情形

这是一种很有用的手段,因为它能在 Actor 通道的层面上使用 UObject 和多态(polymorphism)。之前用于复杂数据结构的复制方法只适合那些 在 Actor 类中对类型进行静态定义的结构。利用子对象复制,您可以享受诸多优势,例如建立一个道具栏系统,使其中的每个物品作为一个从基本道具栏类扩展而来的类, 也可以进行完整复制,同时无需让这些项成为 Actor(资源负担太大)。

优化

如果有很多子对象需要复制,Actor 只需了解哪些子对象(如存在)最近发生过变化且需要复制,从而节省了大量时间。这需要通过访问器(accessor)函数来持续跟踪子对象的更改情况。 这一过程所用的接口位于 UActorChannel 中:

bool KeyNeedsToReplicate(int32 ObjID, int32 RepKey);

该函数应当由 Actor 在其 ::ReplicateSubobjects 实施中调用。Actor 类可以设置一个任意的对象 ID 和复制键,供复制系统跟踪每个客户端。当 ReplicateSubobjects 返回 true 时,调用方应当在使用该对象 ID 接受跟踪的任意子对象上调用 ReplicateSubobjects。 相关示例请查看 AQAInventory::ReplicateSubobjects。一定不要忘记,对象 ID 和复制键完全是任意指定的。对象 ID 仅用于引用"事情"。它可以是整个子对象列表、部分列表或单个对象。复制键同样可以任意指定 - 它可以是一个在对象 ID 跟踪变化时递增的计数器。这里讨论的优化问题完全是选读内容。

Select Skin
Light
Dark

Welcome to the new Unreal Engine 4 Documentation site!

We're working on lots of new features including a feedback system so you can tell us how we are doing. It's not quite ready for use in the wild yet, so head over to the Documentation Feedback forum to tell us about this page or call out any issues you are encountering in the meantime.

We'll be sure to let you know when the new system is up and running.

Post Feedback