I am working on an inventory system and currently have a working implementation but I want to see if it is possible to refactor it in a way that will make it more flexible to work with.
Right now, I have items represented by UObjects so they can have functionality like OnEquip, OnDrop, OnUse, etc.
Inventories store a list of UItem and UItem inherits from UNetworkedObject (below) so they can be replicated:
UCLASS()
class SURVIVALGAME_API UNetworkedObject : public UObject
{
GENERATED_BODY()
protected:
// Allows the Object to get a valid UWorld from it's outer.
virtual UWorld* GetWorld() const override
{
if (const UObject* MyOuter = GetOuter())
{
return MyOuter->GetWorld();
}
return nullptr;
}
UFUNCTION(BlueprintPure)
AActor* GetOwningActor() const
{
return GetTypedOuter<AActor>();
}
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Add any Blueprint properties
// This is not required if you do not want the class to be "Blueprintable"
if (const UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass()))
{
BPClass->GetLifetimeBlueprintReplicationList(OutLifetimeProps);
}
}
virtual bool IsSupportedForNetworking() const override
{
return true;
}
virtual int32 GetFunctionCallspace(UFunction* Function, FFrame* Stack) override
{
check(GetOuter() != nullptr);
return GetOuter()->GetFunctionCallspace(Function, Stack);
}
// Call "Remote" (aka, RPC) functions through the actors NetDriver
virtual bool CallRemoteFunction(UFunction* Function, void* Parms, struct FOutParmRec* OutParms, FFrame* Stack) override
{
check(!HasAnyFlags(RF_ClassDefaultObject));
AActor* Owner = GetOwningActor();
UNetDriver* NetDriver = Owner->GetNetDriver();
if (NetDriver)
{
NetDriver->ProcessRemoteFunction(Owner, Function, Parms, OutParms, Stack, this);
return true;
}
return false;
}
};
And then UItem contains properties that can change at runtime, like amount, damage, etc. Here is the base UItem, which can be inherited from to add more data:
UCLASS(EditInlineNew, DefaultToInstanced)
class SURVIVALGAME_API UItem : public UNetworkedObject
{
GENERATED_BODY()
public:
UPROPERTY()
FOnItemUpdated OnItemUpdated;
UPROPERTY(EditAnywhere, Replicated, SaveGame)
TObjectPtr<UItemDefinition> ItemDefinition;
UPROPERTY(EditAnywhere, Replicated, ReplicatedUsing = OnRep_Amount, BlueprintReadOnly, SaveGame)
int32 Amount = 1;
UPROPERTY(EditAnywhere, Replicated, ReplicatedUsing = OnRep_InventoryPosition, BlueprintReadOnly, SaveGame)
int32 InventoryPosition = -1;
public:
virtual bool TryDrop(const FTransform& InTransform);
virtual bool CanStackWith(UItem* OtherItem);
/**
* @brief Attempts to stack another item into this one
* @param OtherItem The item to stack into this one
*/
virtual void CombineWith(UItem* OtherItem);
virtual EItemDragResult OnItemDragged(UItem* DraggedItem);
private:
UFUNCTION()
void OnRep_Amount();
UFUNCTION()
void OnRep_InventoryPosition();
protected:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};
As it stands, I store the inventory position of the item straight inside each item. This works, but I would rather not have the items be responsible for keeping track of where they are in an inventory.
I would prefer it if I could have inventories store an array of UInventorySlot (or similar) instead, which would handle the inventory position of items and allow for more granular functionality to be added to which items can be in which slots etc.
I have tried an approach like this, where UInventorySlot looks like this:
class UInventorySlot : public UNetworkedObject
{
UItem* Item; // can be null if the slot is empty
void SetItem(UItem* Item);
bool CanAcceptItem(Uitem* Item);
};
Then the inventory (an ActorComponent) stores an array of these slots based on the inventory’s size.
The trouble comes when trying to replicate. In the InventoryComponent I do something like this:
bool UInventoryComponent::ReplicateSubobjects(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)
{
bool bWroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
bWroteSomething |= Channel->ReplicateSubobjectList(InventorySlots, *Bunch, *RepFlags);
return bWroteSomething;
}
But that will only replicate the UInventorySlots and their properties, and since ReplicateSubobjects is not included for UObjects I cannot override it in UInventorySlot to simply replicate the slot’s item as a subobject.
TLDR: Is there a way to replicate these nested UObjects? Alternatively, could there be a different way to set this up that wouldn’t require nested UObjects?
Would love to hear any thoughts.