Hello, there seems to be some issue in implementing interface methods in an abstract class.
Here is my interface:
class SHOOTER_API IInventorySlot
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
UFUNCTION(Category = "Inventory Slots")
virtual void Drop() = 0;
UFUNCTION(Category = "Inventory Slots")
virtual bool IsEmpty() = 0;
Here is my abstract class:
UCLASS(Abstract)
class SHOOTER_API UInventorySlotBase : public UObject, public IInventorySlot
{
GENERATED_BODY()
public:
UInventorySlotBase(const FObjectInitializer& ObjectInitializer);
UFUNCTION(Category = "Inventory Slots")
virtual void Drop();
UFUNCTION(Category = "Inventory Slots")
virtual bool IsEmpty();
Each function in the abstract base class above is implemented in the .cpp file with appropriate local logic, including IsEmpty(), which is going to throw as pure-virtual below.
Here is my implementing class:
UCLASS()
class SHOOTER_API UPrimarySlot : public UInventorySlotBase
{
GENERATED_BODY()
public:
UPrimarySlot(const FObjectInitializer& ObjectInitializer);
Finally, here is the class where these are used:
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SHOOTER_API UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UInventoryComponent();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
virtual void AddSlottableToSlots(TScriptInterface<ISlottable> slottableToAdd);
protected:
virtual int GetIndexForSlotType(SlotType slotType);
virtual void InitInventorySlots();
TArray<TScriptInterface<IInventorySlot>> InventorySlots;
TScriptInterface<IInventorySlot> MeleeSlot;
TScriptInterface<IInventorySlot> PrimarySlot;
TScriptInterface<IInventorySlot> SecondarySlot;
and its cpp:
void UInventoryComponent::InitInventorySlots()
{
PrimarySlot = CreateDefaultSubobject<UPrimarySlot>(TEXT("PrimarySlot"));
SecondarySlot = CreateDefaultSubobject<USecondarySlot>(TEXT("SecondarySlot"));
InventorySlots.Add(PrimarySlot);
InventorySlots.Add(SecondarySlot);
}
void UInventoryComponent::AddSlottableToSlots(TScriptInterface<ISlottable> slottableToAdd)
{
if (slottableToAdd)
{
SlotType type = slottableToAdd->GetSlotType();
int index = GetIndexForSlotType(type);
TScriptInterface<IInventorySlot> slot = InventorySlots[index];
if (!(slot->IsEmpty()))
{
slot->Drop();
}
slot->Insert(slottableToAdd);
}
}
That last class is an actor component. The blueprint for said actor calls on construction a code path that eventually hits “AddSlottableToSlots”, and when hitting “compile”, I get this error:
Fatal error:
[File:D:/build/++UE5/Sync/Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformMisc.cpp] [Line: 476] Pure virtual function being called
UnrealEditor_Core!PureCallHandler() [D:\build\++UE5\Sync\Engine\Source\Runtime\Core\Private\Windows\WindowsPlatformMisc.cpp:478]
VCRUNTIME140
UnrealEditor_Shooter!UInventoryComponent::AddSlottableToSlots() [C:\pathToMyProject\Source\Shooter\Inventory\InventoryComponent.cpp:102]
where line 102 is
if (!(slot->IsEmpty()))
It seems like the base class implementation of IsEmpty() is not being found. I have tried every combo of using “override” and marking as “UFUNCTION()” or not.
I would like this pattern:
Interface (defines, well, interface of callable functions) → abstract base class (gives definitions that can be overridden if necessary) → implementation classes. Is this pattern not tenable in unreal engine?
I am professionally used to enterprise C#, so I am well-versed in OOP and interfaces and have strong opinions about code cleanliness and organization. What I am less sure is how a multi-level inheritance would or would not work in UE C++. Any help or similarly elegant alternate patterns would be appreciated.