Unreal Engine (UE) の Replicated Subobject では、UObject 派生クラスおよびそれらに含まれるレプリケートされたプロパティをレプリケートする方法が提供されます。コンポーネントとサブオブジェクトをレプリケートする旧システムでは、仮想関数 AActor::ReplicateSubobjects
が使用されています。新しいシステムでは、アクタが、所有している アクタ や アクタ コンポーネント のリストにサブオブジェクトを登録するメソッドを持つようになり、それらの登録済みサブオブジェクトのレプリケーションはアクタ チャンネルによって自動的に処理されるようになっています。
両方のシステムについて以下に説明します。最初に、ReplicateSubobjects
関数を使用している 2 つの例を見ていきます。その後に、新しい 登録済みサブオブジェクト リスト を紹介し、その使用方法の概要を説明するコード サンプルを見ていきます。最後に、複雑なレプリケーション条件、クライアントでの登録済みサブオブジェクト リスト といった他のトピックについても説明します。
Replicated Subobject の概要
コンポーネントとサブオブジェクトをレプリケートする旧システムは、仮想関数 AActor::ReplicateSubobjects
に頼っています。レプリケートされたサブオブジェクトがあるアクタでは、レプリケートされたコンポーネントやサブオブジェクトのそれぞれに対して ReplicateSubobject
と ReplicateSubobjects
を手動で呼び出す必要があるアクタを使用して、この関数をオーバーライドする必要があります。次の例について検討します。
コード サンプル
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
タイプになり、その Timer
と Counter
の両方の変数がクライアントにレプリケートされています。
登録済みサブオブジェクト リストの概要
アクタが、所有しているアクタやアクタ コンポーネントのリストにサブオブジェクトを登録する新しいメソッドを持つようになり、それらの登録済みサブオブジェクトのレプリケーションはアクタ チャンネルによって自動的に処理されるようになっています。登録済みサブオブジェクト リストを使用することで、サブオブジェクトの登録時にそのサブオブジェクトに対して 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(...)
{
//非推奨となり、呼び出されなくなりました。
}
ウォークスルー
-
アクタ クラスに対して
bReplicateUsingRegisteredSubObjectList = true
プロパティを設定します。AMyActor::AMyActor() { bReplicateUsingRegisteredSubObjectList = true; }
-
AddReplicatedSubObject
を、ReadyForReplication
またはBeginPlay
で呼び出すか、新しいサブオブジェクトの作成時に呼び出します。アクタ コンポーネント クラスで Replicated Subobject を使用する際には、いくつかの注意事項があります。アクタ コンポーネント クラス内で、ReadyForReplication
はInitComponent
とBeginPlay
の間で呼び出されています。コンポーネントをここで登録することによって、コンポーネントのBeginPlay
内の早い段階でリモート プロシージャ コール (RPC) を呼び出すことができます。 -
サブオブジェクトを変更または削除するときは必ず
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
{
// オブジェクトが地面にある間は、一部のコンポーネントを複製しないでください。
if (!bIsInInventory)
{
if (IsA<UDamageComponent>(ComponentToReplicate))
{
return COND_Never;
}
}
Super::AllowActorComponentToReplicate(ComponentToReplicate);
}
void AMyWeaponClass::OnPickup()
{
// ここでコンポーネントをすべてに複製します。
SetReplicatedComponentNetCondition(UDamageComponent, COND_None);
bIsInInventory = true;
}
ウォークスルー
上記のコード サンプルでは、所有しているアクタ クラスは AMyWeaponClass
です。アクタのインベントリに現在武器があるかどうかに基づいて、UActorComponent ComponentToReplicate
に対するレプリケーション条件を設定します。そうするには、所有しているアクタ クラス AMyWeaponClass
で AllowComponentToReplicate
を実装します。
武器が地面に置かれている間は、その武器はアクタのインベントリにはありません。したがって、ダメージ与えるコンポーネントをレプリケーションすることは望みません。この場合に返される ELifetimeCondition
は、これらのコンポーネントがレプリケートされないことを指定する COND_Never
です。その武器が拾い上げられたときなどにダメージ コンポーネントの条件を変更する場合は、SetReplicatedComponentCondition
を直接呼び出して、レプリケーション条件を、コンポーネントが常にレプリケートされることを意味する COND_None
に設定します。
ELifetimeCondition
でサポートされている条件の一覧の詳細については、「条件付きプロパティのレプリケーション」を参照してください。
アクタ コンポーネントのサブオブジェクト リスト
アクタ コンポーネントは、レプリケートされたサブオブジェクトの独自のリストを持つこともでき、アクタがサブオブジェクトを登録および登録解除するのと同じ API を使用します。アクタ コンポーネント内のサブオブジェクトでレプリケーション条件を使用することもできます。
レプリケートされるサブオブジェクトの条件がチェックされる前に、所有しているコンポーネントを接続に対してレプリケートしておく必要があります。たとえば、サブオブジェクトに COND_OwnerOnly
条件がある場合、COND_SkipOwner
条件を使用しているコンポーネントにそのサブオブジェクトが登録されていれば、そのサブオブジェクトがレプリケートされることはありません。
複雑なレプリケーション条件
Replicated Subobject システムでは、サブオブジェクトに対するカスタム レプリケーション条件の作成がサポートされています。これは、NetConditionGroupManager
と COND_NetGroup
を使用して実現されています。サブオブジェクトとプレイヤー コントローラーは、同時に複数のグループに属することができます。その場合、サブオブジェクトがクライアントの 1 つ以上のグループに属している場合は、そのクライアントに対してレプリケートされています。
レプリケーション グループを実装して使用する
- 条件が
COND_NetGroup
であるサブオブジェクトを登録します。 -
レプリケーション条件を表す FName を作成します。
FName NetGroupAlpha(TEXT("NetGroup_Alpha"))
-
対象とするサブオブジェクトをレプリケーション グループに追加します。
FNetConditionGroupManager::RegisterSubObjectInGroup(MyAlphaSubObject, NetGroupAlpha)
-
そのサブオブジェクトをレプリケートするクライアントを同じグループに追加します。そうするには、そのクライアントの
PlayerController
を使用します。PlayerControllerAlphaOwner->IncludeInNetConditionGroup(NetGroupAlpha)
これで、
PlayerControllerAlphaOwner
のクライアントは、所有するアクタがその接続に対してレプリケートされると必ず、この特別なサブオブジェクトを受け取るようになりました。
クライアント サブオブジェクト リスト
サーバーでは Replicated Subobject List を保持する必要がありますが、クライアント上のアクタおよびコンポーネントでも自分のサブオブジェクトのリストをローカルに保持する必要があります。このことは、プロジェクトでクライアント上のリプレイが録画されている場合に特に重要です。その場合、アクタのリプレイの録画時に、クライアント上のアクタは一時的にローカル Authority ロールに切り替えられています。そのため、リプレイが録画されたすべてのアクタは、自分のローカル NetRole に関係なく、自分のサブオブジェクト リストをクライアント上で保持する必要があります。
問題になるサブオブジェクトがレプリケートされたプロパティである場合は、クライアント上でのサブオブジェクト リストの管理は、そのプロパティに対して RepNotify 関数を使用することでより簡単にすることができます。クライアントは RepNotify 関数を使用して、サブオブジェクトがいつ変化したかを特定できるため、サブオブジェクトの以前のポインタを削除して新しいポインタを追加することができます。
サーバー上でサブオブジェクトをリストから削除すると、そのオブジェクトのレプリケートされたプロパティはどれもクライアントに送信されなくなり、UObject 自体がガベージとマークされるまでそのサブオブジェクトのポインタはネット参照可能のままになります。サーバーがその UObject が無効であることを検出すると、次回のリフレクション更新時にサーバーはそのサブオブジェクトをローカルで削除するようにクライアントに通知します。
Replicated Subobject List システムでは UActorChannel::KeyNeedsToReplicate()
はサポートされていません。代わりに、サブオブジェクトのレプリケートされたプロパティに対してはプッシュ型のレプリケーションを使用することをお勧めします。新しいシステムをプッシュ型のレプリケーションと併用することで、少なくとも RepKey を使用するのと同程度の効率が得られるはずです。