Download

Is there an equivalent of casting for structs/value types?

I’m trying to get my inventory system working with structs, where each category of item is a child of some common base class:



USTRUCT(Blueprintable)
struct FItemBase {
	GENERATED_BODY()

		UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
		FName itemID;

		UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
		FString itemName;

		virtual void Use();
		
	bool operator==(const FItemBase& rhs) const {
		return itemID == rhs.itemID;
	}
};

USTRUCT(Blueprintable)
struct FItemConsumable : public FItemBase {
	GENERATED_BODY()

		float Healing;

	virtual void Use() override {
		playerHP += Healing;
	};

};

This works really, really well in theory, but I’m running into a bit of an antipattern when it comes to actually storing items in someone’s inventory. The advantage of using inheritance to deal with unique item functionality is that I store everything as a generic FItemBase, and trust that my unique overrides will ensure that each item functions as intended. However, since I can’t cast an FItemBase to a child class, I ended up using a custom enumeration and doing something like this every time an item is used:


		TArray<FItemConsumable> consumableList;
		TArray<FItemDeployable> deployList;
		TArray<FItemWearable> wearList;

		UseItem(FItemBase item) {

		Switch(item.itemEnum) {
			case EItemEnum::Consumable:
				consumableList.Add(item);
				break;
			case EItemEnum::Deployable:
				deployList.Add(item);
				break;
			case EItemEnum::Wearable:
				wearList.Add(item);
				break;
		}

	}

That’s an incredibly bad way to do things, but I’ve been having a huge problem thinking of an alternative that would let me store everything in a single TArray<FItemBase> and access its child values on demand; is there any way to handle this that I’m not aware of?

In the Unreal world, USTRUCT and UCLASS are quite different unlike the real (haha) world. If I remember correctly, USTRUCT cannot be referenced and can only be copied around. this wouldn’t be so ideal. Just based on that you should use class which I know you can use Cast<Type> to cast it or reinterpret_cast<type>. You could try to use static_cast<ChildStruct>(ItemBase) and see if that works?

Hmm, that definitely works in some cases, but I’m kind of nervous about trying to force a value type to work like a reference type when UE’s paradigm obviously doesn’t intend for it… the way I suggested above kinda works, so if UE C++ truly doesn’t like you to use structs like classes, it might be cleaner and simpler all around to just keep my current method and accept that the getters and setters will be clumsy and do a lot of heavy lifting.

Another thing to consider that would probably favour the UCLASS approach is that (from my latest 4.9.1 experience) you cannot pass a derived struct type into a function that takes the base struct type as an argument. Of course that is only relevant if you intend to use Blueprint with it…



USTRUCT(Blueprintable)
struct FBaseUstruct
{
	GENERATED_BODY()

	virtual bool DoSomething()
	{
		return false;
	}
};

USTRUCT(Blueprintable)
struct FDerivedUstruct: public FBaseUstruct
{
	virtual bool DoSomething() override
	{
		return true;
	}
};


//Blueprint will prevent you from passing an FDerivedStruct for the 'Struct' parameter
bool SomeFunctionLibrary::SomeBlueprintCallableFunction(FBaseUstruct Struct)
{
	return Struct.DoSomething();
}

So here’s the really obvious question then- if I favor the UCLASS approach, isn’t there going to be a performance hit due to using so many actors? Let’s say I use AInfo as my parent class, since it doesn’t call tick, register itself with physics, or contain a scene component that gives it a physical location… I’m still creating one per item, and if a character has dozens and dozens of random trinkets on them, I’m running NewObject() and spawning dozens of actors every time they spawn in a scene.

You could store your items as a TArray of structs where each struct has a TSubclassOf<AItem> and an amount-integer. In your item-class you could then define and override a Use-function. When using the item, you could then get the default object of the class inside the struct and use it to do whatever you want with your item. That’s how I am currently doing it and it works really well :slight_smile: If I want to add a new type of item, I just add a new class deriving from AItem and override the use-function. The only problem with it is that you can’t change the properties of the item per-instance and have those changes carried over to your inventory. But I’m sure that could be done as well somehow if necessary.