Replicated Subobject

UObject 派生クラスおよびそれらに含まれるレプリケートされたプロパティをレプリケートする方法について説明します。

Unreal Engine (UE)Replicated Subobject では、UObject 派生クラスおよびそれらに含まれるレプリケートされたプロパティをレプリケートする方法が提供されます。コンポーネントとサブオブジェクトをレプリケートする旧システムでは、仮想関数 AActor::ReplicateSubobjects が使用されています。新しいシステムでは、アクタが、所有している アクタアクタ コンポーネント のリストにサブオブジェクトを登録するメソッドを持つようになり、それらの登録済みサブオブジェクトのレプリケーションはアクタ チャンネルによって自動的に処理されるようになっています。

両方のシステムについて以下に説明します。最初に、ReplicateSubobjects 関数を使用している 2 つの例を見ていきます。その後に、新しい 登録済みサブオブジェクト リスト を紹介し、その使用方法の概要を説明するコード サンプルを見ていきます。最後に、複雑なレプリケーション条件クライアントでの登録済みサブオブジェクト リスト といった他のトピックについても説明します。

Replicated Subobject の概要

コンポーネントとサブオブジェクトをレプリケートする旧システムは、仮想関数 AActor::ReplicateSubobjects に頼っています。レプリケートされたサブオブジェクトがあるアクタでは、レプリケートされたコンポーネントやサブオブジェクトのそれぞれに対して ReplicateSubobjectReplicateSubobjects を手動で呼び出す必要があるアクタを使用して、この関数をオーバーライドする必要があります。次の例について検討します。

コード サンプル

class AMyActor : public AActor
{
    UPROPERTY(Replicated)
    UMySubObjectClass* MySubObject;
}

class UMySubObjectClass : public UObject
{
    UPROPERTY(Replicated)
    int32 Counter = 0;
}

void AMyActor::CreateMyClass()
{
    MySubObject = NewObject<UMySubObjectClass>();
    MySubObject->Counter = 10;
}

void AMyActor::ReplicateSubobjects(...)
{
    Super::ReplicateSubobjects(...);
    Channel->ReplicateSubobject(MySubObject); // Becomes a subobject here
}

ウォークスルー

上記のコード サンプルでは、アクタが ReplicateSubobjects 関数内で MySubObject のコンテンツをサブオブジェクトにしています。この段階では、そのポインタはネット参照可能です。その後に、そのアクタがレプリケートされるごとに、Counter 変数がクライアントにレプリケートされています。Channel->ReplicateSubobject(MySubObject) を使用して MySubObject をサブオブジェクトにしていなかったら、null 変数はクライアントでは常に MySubObject になっています。

コード サンプル

class UMyDerivedSubObjectClass : public UMySubObjectClass 
{
    UProperty(Replicated)
    float Timer;
}

void AMyActor::CreateMyDerivedClass()
{
    MySubObject = NewObject<UMyDerivedSubObjectClass>();
    MySubObject->Counter = 100;
    Cast<UMyDerivedSubObjectClass>(MySubObject)->Timer = 60;
}

ウォークスルー

CreateMyClass() の後に CreateMyDerivedClass() が呼び出されたとします。ReplicateSubObjects が次回呼び出されると、新しいポインタはレプリケートされたサブオブジェクトになります。クライアント側では、MySubObject 変数が変化して UMyDerivedSubObjectClass タイプになり、その TimerCounter の両方の変数がクライアントにレプリケートされています。

登録済みサブオブジェクト リストの概要

アクタが、所有しているアクタやアクタ コンポーネントのリストにサブオブジェクトを登録する新しいメソッドを持つようになり、それらの登録済みサブオブジェクトのレプリケーションはアクタ チャンネルによって自動的に処理されるようになっています。登録済みサブオブジェクト リストを使用することで、サブオブジェクトの登録時にそのサブオブジェクトに対して ELifetimeCondition を指定できるようになっています。このプロセスでは、サブオブジェクトをレプリケートするタイミングと場所をさらに細かくコントロールできるため、ReplicateSubobjects でのロジックをユーザーが実装する必要はありません。アクタがバーチャル関数 AActor::ReplicateSubobjects を実装し、個々のサブオブジェクトを手動でレプリケートする必要もなくなっています。

登録済みサブオブジェクト リストを使用する

次のコード サンプルは、登録済みサブオブジェクト リストを有効にする方法の概要を示しています。

コード サンプル

AMyActor::AMyActor()
{
    bReplicateUsingRegisteredSubObjectList = true;
}

void AMyActor::CleanupSubobject()
{
    if (MySubobject)
    {
        RemoveReplicatedSubobject(MySubObject);
    }
}

void AMyActor::CreateMyClass()
{
    CleanupSubobject(); 

    MySubObject= NewObject<UMySubObjectClass>();
    MySubObject->Counter = 10;
    AddReplicatedSubObject(MySubObject);
}

void AMyActor::CreateMyDerivedClass()
{
    CleanupSubobject();

    MySubObject = NewObject<UMyDerivedSubObjectClass>();
    AddReplicatedSubObject(MySubObject);
}

void AMyActor::ReplicateSubobjects(...)
{
    //deprecated and not called anymore
}
ウォークスルー
  1. アクタ クラスに対して bReplicateUsingRegisteredSubObjectList = true プロパティを設定します。

    AMyActor::AMyActor()
    {
        bReplicateUsingRegisteredSubObjectList = true;
    }
  2. AddReplicatedSubObject を、ReadyForReplication または BeginPlay で呼び出すか、新しいサブオブジェクトの作成時に呼び出します。アクタ コンポーネント クラスで Replicated Subobject を使用する際には、いくつかの注意事項があります。アクタ コンポーネント クラス内で、ReadyForReplicationInitComponentBeginPlay の間で呼び出されています。コンポーネントをここで登録することによって、コンポーネントの BeginPlay 内の早い段階でリモート プロシージャ コール (RPC) を呼び出すことができます。

  3. サブオブジェクトを変更または削除するときは必ず RemoveReplicatedSubObject を呼び出します。

    void AMyActor::CleanupSubObject()
    {
        if (MySubObject)
        {
            RemoveReplicatedSubObject(MySubObject)
        }
    }

    この最後の手順は非常に重要です。その参照が削除されていなければ、変更されるか破壊用にマークされているサブオブジェクトへの生のポインタがリストに含まれたままです。その結果、そのオブジェクトに対してガベージ コレクションが行われると、クラッシュすることになります。

既存のコードを変換する場合は、net.SubObjects.CompareWithLegacy コンソール変数 (CVar) を設定して、ランタイム時に新しいリストが旧メソッドと比較されるようにできます。そうすることで、違いが検出されると処理文がトリガーされるようになります。

レプリケートされたアクタ コンポーネント

このシステムを使用する レプリケートされたアクタ コンポーネント は、レプリケートされたサブオブジェクトであるため、上記と同様の方法で処理されます。アクタ コンポーネントに対するレプリケーション条件を設定するには、所有しているアクタ クラスで AllowActorComponentToReplicate を実装し、特定のコンポーネントに対して望ましい ELifetimeCondition を返す必要があります。BeginPlay の後に SetReplicatedComponentNetCondition を呼び出して、コンポーネントの条件を直接変更できます。

AllowActorComponentToReplicate が新しい条件を返すようにする必要があります。そうしないと、UpdateAllReplicatedComponents がアクタに対して呼び出されたときに条件がリセットされます。

アクタ コンポーネントで Replicated Subobject を使用する

コード サンプル

ELifetimeCondition AMyWeaponClass::AllowActorComponentToReplicate(const UActorComponent* ComponentToReplicate) const
{
    // Do not replicate some components while the object is on the ground.
    if (!bIsInInventory)
    { 
        if (IsA<UDamageComponent>(ComponentToReplicate))
        {
            return COND_Never;
        }
    }
    Super::AllowActorComponentToReplicate(ComponentToReplicate);
}

void AMyWeaponClass::OnPickup()
{
    // Now replicate the component to all
    SetReplicatedComponentNetCondition(UDamageComponent, COND_None);
    bIsInInventory = true;
}
ウォークスルー

上記のコード サンプルでは、所有しているアクタ クラスは AMyWeaponClass です。アクタのインベントリに現在武器があるかどうかに基づいて、UActorComponent ComponentToReplicate に対するレプリケーション条件を設定します。そうするには、所有しているアクタ クラス AMyWeaponClassAllowComponentToReplicate を実装します。

武器が地面に置かれている間は、その武器はアクタのインベントリにはありません。したがって、ダメージ与えるコンポーネントをレプリケーションすることは望みません。この場合に返される ELifetimeCondition は、これらのコンポーネントがレプリケートされないことを指定する COND_Never です。その武器が拾い上げられたときなどにダメージ コンポーネントの条件を変更する場合は、SetReplicatedComponentCondition を直接呼び出して、レプリケーション条件を、コンポーネントが常にレプリケートされることを意味する COND_None に設定します。

ELifetimeCondition でサポートされている条件の一覧の詳細については、「条件付きプロパティのレプリケーション」を参照してください。

アクタ コンポーネントのサブオブジェクト リスト

アクタ コンポーネントは、レプリケートされたサブオブジェクトの独自のリストを持つこともでき、アクタがサブオブジェクトを登録および登録解除するのと同じ API を使用します。アクタ コンポーネント内のサブオブジェクトでレプリケーション条件を使用することもできます。

レプリケートされるサブオブジェクトの条件がチェックされる前に、所有しているコンポーネントを接続に対してレプリケートしておく必要があります。たとえば、サブオブジェクトに COND_OwnerOnly 条件がある場合、COND_SkipOwner 条件を使用しているコンポーネントにそのサブオブジェクトが登録されていれば、そのサブオブジェクトがレプリケートされることはありません。

複雑なレプリケーション条件

Replicated Subobject システムでは、サブオブジェクトに対するカスタム レプリケーション条件の作成がサポートされています。これは、NetConditionGroupManagerCOND_NetGroup を使用して実現されています。サブオブジェクトとプレイヤー コントローラーは、同時に複数のグループに属することができます。その場合、サブオブジェクトがクライアントの 1 つ以上のグループに属している場合は、そのクライアントに対してレプリケートされています。

レプリケーション グループを実装して使用する

  1. 条件が COND_NetGroup であるサブオブジェクトを登録します。

  2. レプリケーション条件を表す FName を作成します。

    FName NetGroupAlpha(TEXT("NetGroup_Alpha"))
  3. 対象とするサブオブジェクトをレプリケーション グループに追加します。

    FNetConditionGroupManager::RegisterSubObjectInGroup(MyAlphaSubObject, NetGroupAlpha)
  4. そのサブオブジェクトをレプリケートするクライアントを同じグループに追加します。そうするには、そのクライアントの PlayerController を使用します。

    PlayerControllerAlphaOwner->IncludeInNetConditionGroup(NetGroupAlpha)

これで、PlayerControllerAlphaOwner のクライアントは、所有するアクタがその接続に対してレプリケートされると必ず、この特別なサブオブジェクトを受け取るようになりました。

クライアント サブオブジェクト リスト

サーバーでは Replicated Subobject List を保持する必要がありますが、クライアント上のアクタおよびコンポーネントでも自分のサブオブジェクトのリストをローカルに保持する必要があります。このことは、プロジェクトでクライアント上のリプレイが録画されている場合に特に重要です。その場合、アクタのリプレイの録画時に、クライアント上のアクタは一時的にローカル Authority ロールに切り替えられています。そのため、リプレイが録画されたすべてのアクタは、自分のローカル NetRole に関係なく、自分のサブオブジェクト リストをクライアント上で保持する必要があります。

問題になるサブオブジェクトがレプリケートされたプロパティである場合は、クライアント上でのサブオブジェクト リストの管理は、そのプロパティに対して RepNotify 関数を使用することでより簡単にすることができます。クライアントは RepNotify 関数を使用して、サブオブジェクトがいつ変化したかを特定できるため、サブオブジェクトの以前のポインタを削除して新しいポインタを追加することができます。

サーバー上でサブオブジェクトをリストから削除すると、そのオブジェクトのレプリケートされたプロパティはどれもクライアントに送信されなくなり、UObject 自体がガベージとマークされるまでそのサブオブジェクトのポインタはネット参照可能のままになります。サーバーがその UObject が無効であることを検出すると、次回のリフレクション更新時にサーバーはそのサブオブジェクトをローカルで削除するようにクライアントに通知します。

Replicated Subobject List システムでは UActorChannel::KeyNeedsToReplicate() はサポートされていません。代わりに、サブオブジェクトのレプリケートされたプロパティに対してはプッシュ型のレプリケーションを使用することをお勧めします。新しいシステムをプッシュ型のレプリケーションと併用することで、少なくとも RepKey を使用するのと同程度の効率が得られるはずです。

Unreal Engine のドキュメントを改善するために協力をお願いします!どのような改善を望んでいるかご意見をお聞かせください。
調査に参加する
キャンセル