Okay so I finally successfully replicated a TArray of Objects, it is fairly simple once you get your head around Replication.
Begin with defining the UObject you want replicated. I actually create a UNetworkObject as a base class for all UObjects I want replicated. But the quick and dirty way is to simply override the IsSupportedForNetworking Then mark the properties you need replicated as UPROPERTY(Replicated)
class UNetworkObject : UObject
{
virtual bool IsSupportedForNetworking() override;
virtual bool ReplicateSubobjects(AActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags); // note no override because this is the FIRST declaration of this function.
}
With your Replicatable UObject setup you can now replicate the property from a parent Component or Actor… But What if your parent object IS another UObject? You will need to provide an implementation of ReplicateSubobjects
bool UNetworkObject::ReplicateSubobjects(AActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags)
{
return false;
}
Now this base object can replicate it’s own subobjects if you derive new objects from it. A bit of confusion arose for me because I did not have to declare GetLifetimeReplicatedProps, but a compile time error will happen if you don’t define that function when you have a uproperty marked replicated.
The following example demonstrates how to Replicate a TArray of subobjects from within another UObject using an ActorComponent (whch uses an AActor object) for replication.
MyTestObject.h
My Test object is a simple UObject that needs to replicate some information, but is complex enough that it does not warrent a simple Struct to do the job. For demonstration purposes I chose a very simple object.
class UMyTestObject : UNetworkObject
{
UPROPERTY(Replicated)
float MyTestProperty;
}
MyTestObject.cpp
void UMyTestObject::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
DOREPLIFETIME(MyTestObject, MyTestProperty);
}
Now my Second UObject that is the outer to "MyTestObject.
MyTestObjectManager.h
The TestObjectManager simply contains a bunch of MyTestObjects. Ideally this class would also provide some accessor functionality but for demonstration purposes I have left that out.
class UMyTestObjectManager : UNetworkObject
{
UPROPERTY(Replicated)
TArray<UMyTestObject*> MyTestObjects;
void BuildArrayOfTestObjects(int32 numObjects); // I'm not going to define this for demonstration sake, it would just create a bunch of UMyTestObjects and add them to the array.
virtual bool ReplicateSubobjects(AActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags) override; // override from the base class
}
MyTestObjectManager.cpp
void UMyTestObjectManager::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
DOREPLIFETIME(UMyTestObjectManager, MyTestObjects);
}
bool UMyTestObjectManager::ReplicateSubobjects(AActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags)
{
bool wroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
for(UMyTestObject * myObject : MyTestObjects)
{
if(myObject)
{
Channel->ReplicateObject(myObject, *Bunch, *RepFlags)
}
}
}
Okay now I don’t know exactly the details of what this is doing on the back end but think I’ve pretty much figured out why it works. First MyTestObject has a variable that freely replicates because it is a simple variable type (bool, int, float, struct). MyTestObjectManager contains an array of MyTestObjects. Now the TArray is marked to be replicated and TArrays support replication . The replication of THAT array occurs, but it’s contents do not because they are not simple variable types but instead pointers to UObjects. That is where ReplicateSubobjects comes into play. It iterates over each object in the array and replicates it to the client.
“But you said UObjects don’t replicate, your MyTestObjectManager is a UObject, How is it replicating?” - Well my friend we are not done.
The actual replication for this REQUIRES either an Actor OR a ActorComponent. Either way the procedure is the same.
MyActorComponent.h
MyActorComponent simply demonstrates how to do replication with a Component. You can just as easily use an AActor.
class UMyActorComponent : UActorComponent
{
UMyActorComponent();
virtual void InitializeComponent() override;
UMyTestObjectManager * myTestObjectManager;
virtual bool ReplicateSubobjects(AActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags) override;
}
MyActorComponent.cpp
UMyActorComponent::UMyActorComponent()
{
this->bWantsInitializeComponent = true;
this->SetIsReplicated(true);
}
void UMyActorComponent::InitializeComponent()
{
this->myTestObjectManager = ConstructObject<UMyTestObjectManager>(UMyTestObjectManager::StaticClass(), this);
if(this->myTestObjectManager)
{
this->myTestObjectManager->BuildArrayOfTestObjects(15);
}
}
void UMyActorComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
DOREPLIFETIME(UMyActorComponent, myTestObjectManager);
}
bool UMyActorComponent::ReplicateSubobjects(AActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags)
{
bool wroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
wroteSomething |= Channel->ReplicateSubobject(myTestObjectManager, *Bunch, *RepFlags); // replicate the subobject
wroteSomething |= myTestObjectManager->ReplicateSubobjects(Channel, Bunch, RepFlags); // replicate the subobject's subobjects.
return wroteSomething;
}
Now one thing to note. Remote Procedure Calls on a UObject (Client or Server functions) cannot be performed (at least to my knowledge and without some major coding upgrades made by you). I have heard it theoretically possible… but the technical process currently is beyond where I want (or need) to go. So if you want to do RPC functions you must do them on an Actor or a UActorComponent. For instance lets say you want to set the value:
UMyActorComponent->myTestObjectManager->myTestObjects[0]->MyTestProperty = 25.22f
The cool thing is this will actually replicate up. But what if all these properties are not public and you ONLY want the setting of this to be performed on the Server? You’d have to setup a daisy chain starting from the UMyActorComponent.
void UMyActorComponent::SetValueAtObjectIndex(int32 objectIndex, float value)
{
if(this->Role == RP_Authority)
{
this->myTestObjectManager->SetValueAtIndex(objectIndex, value);
}
else
{
this->ServerSetValueAtObjectIndex(objectIndex, value);
}
}
bool UMyActorComponent::ServerSetValueAtObjectIndex_Validate(int32 objectIndex, float value)
{
// do some logic to validate the user is not cheating
return true; // return false if you want to automatically kick the cheater from your game.
}
void UMyActorComponent::ServerSetValueAtObjectIndex_Implementation(int32 objectIndex, float value)
{
this->SetValueAtObjectIndex(objectIndex, value) // no point in duplicating code, this should be the authority now.
}
This will effectively change the value on the server, and then replicate the MyTestObject down to the client.