Download

Replicating UObject Containing UActorComponent

I have a replication issue that I can not figure out. Worse, I may be trying to do something that can’t be done.

The good news is that I have a basic inventory system that replicates UObject just fine. I have subclassed my base item (UItemBase) to extend functionality for each type of item I have. These items get replicated to and from my UInventoryComponent (UActorComponent) which keeps a TArray of these basic items. Using GetLifetimeReplicatedProps() and ReplicateSubobjects() this works perfectly.

The problem starts when I decided that I wanted one of my items to also contain an inventory. I subclassed UItemBase to create a UBackpackItem. The backpack item needs to keep an inventory, so I created an instance of UInventoryComponent to keep an inventory of my UItemBase items. While I can get the UBackpackItem to replicate, I am unable to get the inventory within it to replicate. Is it possible to replicate nested items like I am trying to do?

My UInventoryComponent knows how to replicate its subobjects since I use this same component in other places. Within my UBackpackItem I have my UInventoryComponent property set to Replicate. I also have a DOREPLIFETIME entry for the UInvnetoryComponent I am trying to replicate. I am not using ReplicateSubobjects in the backpack since UInventoryCompnent should be responsible for its subobjects.

It seems that creating the UInventoryComponent inside of a UItemBase (UObject) won’t allow subobjects to replicate. Any ideas?

I guess I can fall back and use an Actor base for my Backpack, but I was really trying to keep all my inventory items as UObject. However, my game does need to be able to nest inventories within inventories.

Hair.Pulling.Out.

I can’t really answer the question itself, but if I understand you correctly, every inventory item is an instanced UObject attached to the inventory? If so, this seems to be a terrible solution especially if you have to replicate these to clients. You can define an inventory item as a struct. For example, if every player has access to the “database”, then all the struct has to contain is an item id and a stack count. Other stuff like what object to spawn when the item is in hand, what the item does, what’s the name/description, etc. can be gathered locally.

You can definitively have nested replicated UObject’s. Pretty sure that ReplicateSubobjects won’t be called for the inner UInventoryComponent in your UBackpackItem. If I remember correctly the engine calls ReplicateSubobjects for the Actor which replicates each Component by first calling ReplicateSubobjects on the component itself to allow the component to also replicate any subobjects and then ReplicateSubobject. Which means if the inner UInventoryComponent isn’t registered as a component of the outer Actor nothing will call ReplicateSubobjects on it unless you call it yourself.
https://github.com/EpicGames/UnrealEngine/blob/c3caf7b6bf12ae4c8e09b606f10a09776b4d1f38/Engine/Source/Runtime/Engine/Private/DataChannel.cpp#L3177
https://github.com/EpicGames/UnrealEngine/blob/c3caf7b6bf12ae4c8e09b606f10a09776b4d1f38/Engine/Source/Runtime/Engine/Private/ActorReplication.cpp#L450

Have you tried placing a breakpoint in ReplicateSubobjects to verify it is called for the inner UInventoryComponent? Also the inner UInventoryComponent itself may not actually be replicated because of the above.

If you want to stick with your solution you most likely need to add a virtual ReplicateSubobjects(...) function to your UItemBase class that you then need to call on each item in the inventory from UInventoryComponent::ReplicateSubobjects. This way you can override it in your UBackpackItem and replicate its subobjects.

I think you got me on the right track but I am still having problems replicating the inner inventory. Your last paragraph is what I am trying to implement.

In my UItem base class, I created a new function:

bool UItem::ReplicateSubobjects(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)
{
	// base class has no subobjects to replicate
	return false;
}

In my backpack (UWearableInventoryItem) I override this new function with:

bool UWearableInventoryItem::ReplicateSubobjects(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)
{
	bool bWroteSomething = false;
	
	//if (Channel->KeyNeedsToReplicate(Inventory->GetUniqueID(), Inventory->GetReplicatedItemsKey()))
	{
		bWroteSomething = Channel->ReplicateSubobject(Inventory, *Bunch, *RepFlags);
	}

	return bWroteSomething;
}

In my UInventoryComponent I called the new function so that subobjects of each UItem could be written to the channel

bool UInventoryComponent::ReplicateSubobjects(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)
{
	bool bWroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);

	if(Channel->KeyNeedsToReplicate(GetUniqueID(), ReplicatedItemsKey))
	{
		for(auto& Item : Items)
		{
			// provides an opportunity for Item to replicate subobjects
			bWroteSomething |= Item->ReplicateSubobjects(Channel, Bunch, RepFlags);
			
			if(Channel->KeyNeedsToReplicate(Item->GetUniqueID(), Item->RepKey))
			{
				bWroteSomething |= Channel->ReplicateSubobject(Item, *Bunch, *RepFlags);				
			}
		}
	}

	return bWroteSomething;
}

So far my UInventoryComponent inside the UItem contains no items after replication. I wanted to respond where I am so far to make sure I understood what you were saying, but I am also trying to simplify my project to make debugging easier. I’ll report back more information once I get a bit further in my testing. Thanks!

I feel like I am getting closer, but have not reached the goal yet. I modified my backpack (UWearableInventoryItem) ReplicateSubobjects() function to iterate each Item and replicate it. I have verified that it is being called, but the return value after the Channel->ReplicateSubobject(Item, *Bunch, *RepFlags); is always false.

bool UWearableInventoryItem::ReplicateSubobjects(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)
{
	bool bWroteSomething = false;
		
	for(auto& Item : Inventory->GetItems())
	{		
		//if (Channel->KeyNeedsToReplicate(Item->GetUniqueID(), Item->RepKey))
		{
			bWroteSomething |= Channel->ReplicateSubobject(Item, *Bunch, *RepFlags);
		}
	}	

	return bWroteSomething;
}

I have removed all the RepKey checks before writing the channel assuming this will ALWAYS cause data to get written to the channel. I am not sure why it’s returning false. I am starting to think this might have something to do with the Inventory being a Component.

No matter what I do I am unable to get the Inventory (UActorComponent) to replicated IF it is attached to a UObject. If the Inventory is attached to an AActor, all is good. I think this makes sense because the hierarchy is maintained automatically when using AActors and UActorComponents. I thought overriding ReplicateSubobjects was the answer, but even when called correctly, it will not replicated the subobjects to the client.

All types of actors in my game use the same InventoryComponent and work well. Now that I need to be able to nest inventories inside of one another, I guess I’ll need to come up with another plan.

Yeah, the ReplicateSubobject for a single object or some other replication-related function may perform some checks that prevents this with a UActorComponent. You could try moving the functionality from your UInventoryComponent into a regular UObject derived class and then just have nothing more than an instance of this class in the UInventoryComponent instead. Your UBackpackItem can also use the UObject derived class instead of the UInventoryComponent.

Thanks for the reply, and yes, this is exactly what I think will need to happen. I can keep all my inventory logic the same for the most part and just reference the class in each object that needs it.

As I started to look into making these changes, I realized I’m leveraging GetOwner() and GetOuter() quite a bit - which works fine in a Component, but I suspect I will have some refactoring to do in order to get the real ‘Outer’ of a UObject Inventory.

Looks like I have more studying to do :slight_smile: