Unreal 5.1 replicated subobject list is not syncing references in owner

I’m creating a new subobject in an UActorComponent derived calss with “bReplicateUsingRegisteredSubObjectList = true” in BeginPlay function:

void MyActorComp::BeginPlay() {
	if (!SubObject && SubObjectType)
	{
		SubObject = NewObject<SubObjectType>(this, SubObjectType);
		AddReplicatedSubObject(SubObject);
	}
}

If I print the GetUniqueID in the SubObjectType’s “PostInitProperties” (Derived from UObect) in the client I get 2 unique ids, so 2 instances of the subobject are being created in client side, one from the replication and the other from the client’s BeginPlay being executed. The problem is that in the client calls to the SubObject are to the instances created from the BeginPlay, and remote calls from the server, are referencing the object created by the AddReplicatedSubObject. Do you know how to have only one instance and being able to have them in sync?

They follow the same rules as any other replicated object, so you need to create them server-side only in this particular case. You can guard against this using either HasAuthority() or !IsNetMode(NM_Client) etc, depending on the actor/components use-case, and skip creating the object on the client.

The objects will be automatically created client-side when recieved through the actor channel, but you would need to populate the “SubObject” UPROPERTY yourself somehow. The simplest way is to also replicate that pointer property - but personally I prefer using a registration pattern of some kind.

The documentation fails to mention that object created client-side from Replication will always use the actor as the outer - NOT the component. There is no way around this outside of using independent deterministic generation on Server and Client. This requires a deterministic naming system and overriding some UObject function. It’s more complex and requires a deeper integration.


The tedious part of this new system, is that you must maintain the replicated subobject list independently on the client and server. The engine could easily do this automatically with no network overhead so why we have to add all this plumbing ourselves I’ll never know, but here we are.

There are a variety of ways to do this - again, the documentation suggests the easiest but also the worst approach; which is a replicated TArray of those subobjects. However, you can probably rebuild the list deterministically client-side with a simple registration pattern between the subobject and it’s parent in many cases.

Note: The client-side list only seems to matter if you’re using the demo replay system, which most people aren’t - so you don’t always need to bother with it. I’ve personally not had any issues so far with not updating the subobject list client-side, but I don’t use demo replay and my use-cases are tightly integrated so milage may vary.


All I will say is, the documentation seems to suggest replicating custom UObjects is somehow “easier” with this new approach - but this is not really true IMO. I’ve been able to port my existing uses without too many issues, but they still have a lot of supporting code to make it function properly.

Thanks for answering, I was able to make it work, even with an RPC in the custom UObject owned by the ActorComponent, I got it called on the server and it gets called on all machines . I didn’t have to change the outer of the object to the actor, I didn’t even needed to create it only on server. All I needed was to add the “Replicated” specifier to the reference and override the “GetLifetimeReplicatedProps” in the ActorComponent

Below is a sample code with the solution, also if object is created on blueprints the property “Repliaction->Advanced->Replicate Using Registered Sub Object List” needs to be turned on in both the “Actor” and the “ActorComponent”. For some reason it doesn’t pick it from the “ActorComponent Constructor”

UCLASS(Blueprintable)
class MY_API UMySubObject : public UObject
{
	GENERATED_BODY()
	
public:
	virtual bool IsSupportedForNetworking() const override
	{
		return true;
	}

	virtual int32 GetFunctionCallspace(UFunction* Function, FFrame* Stack)
	{
		int32 Callspace = FunctionCallspace::Local;
		if (UObject* Outer = GetOuter()) Callspace = Outer->GetFunctionCallspace(Function, Stack);
		return Callspace;
	}

	virtual bool CallRemoteFunction(UFunction* Function, void* Parms, FOutParmRec* OutParms, FFrame* Stack) override
	{
		bool Called = false;
		UActorComponent* Outer = Cast<UActorComponent>(GetOuter());
		AActor* Actor = Outer ? Outer->GetOwner() : nullptr;
		UNetDriver* NetDriver = Actor ? Actor->GetNetDriver() : nullptr;
		if (NetDriver)
		{
			NetDriver->ProcessRemoteFunction(Actor, Function, Parms, OutParms, Stack, this);
			Called = true;
		}
		return Called;
	}

private:
	UFUNCTION(NetMulticast, Reliable)
	void MyCustomRPC(AActor* OtherActor, bool IsBegin);

	void UMySubObject::MyCustomRPC_Implementation(AActor* OtherActor, bool IsBegin)
	{
		// This is called on server and clients
	}
};

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MY_API UMyActorComp : public UActorComponent
{
	GENERATED_BODY()

private:
	UPROPERTY(EditAnywhere)
	TSubclassOf<class UMySubObject> MySubObjectType;

	UPROPERTY(Replicated)
	TObjectPtr<class UMySubObject> MySubObject;

public:
	UMyActorComp()
	{
		bReplicateUsingRegisteredSubObjectList = true;	
	}

	virtual void BeginPlay() override
	{
		if (MySubObjectType && !MySubObject)
		{
			MySubObject = NewObject<UMySubObject>(this, MySubObjectType);
			AddReplicatedSubObject(MySubObject);
		}
	}

	void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
	{
		Super::GetLifetimeReplicatedProps(OutLifetimeProps);
		DOREPLIFETIME(ThisClass, MySubObject);
	}
}

I’m sorry,how do you make this system work?I try to follow the document,but its not replicate to the client.and I have to use the new system,because ReplicateSubobject will be deprecate at next engine update.really thanks.