[Replicating TArray of UObjects] Cannot get TArray<UItemObject*> to replicate from within custom ActorComponent.

Hello,

I’m having an issue with getting a TArray of custom ItemObjects to replicate after the initial client load (it does when a client loads, but does not replicate after that). I am successfully adding ItemObjects to the TArray within the InventoryComponent on the server, but it does not replicate to clients. That is to say, the server’s edition of the TArray contains the ItemObject while the client’s does not, and no replication occurs.

I’m slowly learning UE and networking so correct me if I’m wrong, but I thought the proper approach to an inventory system like this would be to have the server add the item to the inventory, and update the clients via replication. Perhaps I somehow have to manually tell the server to replicate and it does not automatically detect the change in the array?

Anyway, here’s my setup. I have aggressively thrown in the “Replicated” UPROPERTY specifier even if I’m not sure I need it in case that was the issue:

I created UItemObject’s that are derived from UObjects. They just have some basic additional data and are not too complicated:

UCLASS(Blueprintable)
class UItemObject : public UObject
{
// GENERATED BODY and some stuff above, example property here:
private:
    UPROPERTY(meta=(ExposeOnSpawn), BlueprintGetter=GetDimensions, Replicated)
    FIntPoint Dimensions;
public:
    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty> & OutLifetimeProps) const override;
};

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

I created a UInventoryComponent that is derived from UActorComponent that contains a replicated TArray of UItemObject*s. The ReplicatedUsing callback function just prints to screen for debugging so I can see when replication occurs:

UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent), Blueprintable)
class UInventoryComponent : public UActorComponent
{
public:
    // TODO: Add Anti-Cheat with "Validation"
    UFUNCTION(Server, Reliable, BlueprintCallable)
        void ServerTryAddItem(UItemObject* ItemToAdd);
protected:
    UPROPERTY(BlueprintReadWrite, ReplicatedUsing=TempCheckReplication)
        TArray<UItemObject*> Items;
public:
    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty> & OutLifetimeProps) const override;
};

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

As of now, the InventoryComponent is only added to a AMainCharacter C++ class that is derived from ACharacter. AMainCharacter has a replicated InventoryComponent added to it:

UCLASS()
class AMainCharacter : public ACharacter
{
public:
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Components, meta = (AllowPrivateAccess = "true"), Replicated)
    TObjectPtr<class UInventoryComponent> InventoryComponent;
    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty> &OutLifetimeProps) const override;
};

AMainCharacter::AMainCharacter()
{
    InventoryComponent = CreateDefaultSubobject<UInventoryComponent>("InventoryComponent");
}

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

Any advice would be appreciated, I feel as though I need something as simple as marking the InventoryComponent dirty for replication, or that I’m missing some other function override like GetLifetimeReplicatedProps that’s necessary for replication that I can’t seem to find.

I’ve also taken a look at Replicating custom subobject which is UPROPERTY of ActorComponent but there doesn’t seem to be an updated solution there.

In the mean time, I’m reading up on replication and seeing what steps I’ve missed, and reading through https://jambax.co.uk/replicating-uobjects/.

check the article " Advanced: Generic replication of Actor Subobjects" in this Wiki Replication - Old UE4 Wiki

you need AActor owner of UObjects with overridden functions in the AActor owner and UObject class as well

Thanks for the advice, taking a look through and will follow up.

I was able to resolve my issue by adding each UItemObject* in the Array as a registered subobject. This is my understanding of the problem, so if you see logical errors here please correct me as I want to have as complete of an understanding as possible.

Basically the TArray is an Array of pointers that is sized at BeginPlay and who’s pointers are set to null or to a UItemObject to represent a spatially based inventory. If I would change let’s say Items[2] to a new item on the server, it would not replicate to the clients unless I used something like Items.Add() or Items.Remove() because the array would not be marked dirty unless it was changing size. Simply modifying a pointer within the TArray would not cause it to be detected as dirty and kick off a replication. I tried using the new Push Model system to MARK_PROPERTY_AS_DIRTY but that caused me to have all sorts of linker issues and I abandoned that solution even though it’s my belief it may have potentially worked.

What resolved my issue was following this page and adding each UItemObject as a registered subobject as shown below:

void UInventoryComponent::AddItemAt(UItemObject *ItemToAdd, FIntPoint TopLeftTile)
{
    // irrelevant code above
    AddReplicatedSubObject(ItemToAdd);
    /// irrelevant code below
}

As described in the documentation, in order to enable the SubObject you must also set bReplicateUsingRegisteredSubObjectList = true; in the constructor of your ActorComponent and override AllowActorComponentToReplicate(//...) within your Actor.

I also overrode IsSupportedForNetworking() (simply returns true) for the UItemObject but I can’t say for certain if that’s necessary.

Another thing worth mentioning is that this only caused the UPROPERTYs that I specified within the UItemObject’s overridden GetLifeTimeReplicatedProps() function to replicate properly. I have a Blueprint class that derives from UItemObject with more replicated variables, but until I added the below segment the Blueprint variables were not replicated properly. I was able to arrive to this solution by following the replicating uobjects jambax page I linked earlier.

void UItemObject::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    DOREPLIFETIME(UItemObject, Dimensions);
    DOREPLIFETIME(UItemObject, bIsRotated);
    DOREPLIFETIME(UItemObject, Name);

    if (const UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass()))
    {
        BPClass->GetLifetimeBlueprintReplicationList(OutLifetimeProps);
    }
}

This of course requires each of the above mentioned UPROPERTYs to have a Replicated specifier.

Hope this helps anyone who has this issue in the future, and please read subsequent comments in case some of my statements are incorrect.

2 Likes

I guess we are following similar structure for the inventory. Great job for figuring it out, it fixed my problem! ^^

1 Like

Happy to help :slight_smile:

I am also making an actor component for a spatial inventory system (we are all doing it based on Reid’s channel I bet, been trying to solve the replication issue for a while), you said there is irrelevant code above and below the AddItemAt function, however, I’ve been struggling to implement it correctly. Could you show your code implementation for that function?