Consumable Interactable Pattern
Type: Requires external gameplay system
This is the "safe pickup" pattern: the interactable only disappears if your own gameplay system confirms the reward or consumption succeeded.
Example
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Data/EDInteractionTypes.h"
#include "MyConsumableActor.generated.h"
class UArrowComponent;
class UEDInteractableComponent;
class USceneComponent;
class UStaticMeshComponent;
UCLASS()
class AMyConsumableActor : public AActor
{
GENERATED_BODY()
public:
AMyConsumableActor();
protected:
virtual void BeginPlay() override;
UPROPERTY(VisibleAnywhere)
TObjectPtr<USceneComponent> Root;
UPROPERTY(VisibleAnywhere)
TObjectPtr<UStaticMeshComponent> VisualMesh;
UPROPERTY(VisibleAnywhere)
TObjectPtr<USceneComponent> WidgetHolder;
UPROPERTY(VisibleAnywhere)
TObjectPtr<UArrowComponent> CenterArrow;
UPROPERTY(VisibleAnywhere)
TObjectPtr<UEDInteractableComponent> InteractableComponent;
UFUNCTION()
void HandleInteractionSuccessful(const FEDInteractionEventContext& Context);
};
#include "MyConsumableActor.h"
#include "Components/ArrowComponent.h"
#include "Components/EDInteractableComponent.h"
#include "Components/SceneComponent.h"
#include "Components/StaticMeshComponent.h"
#include "GameFramework/Pawn.h"
AMyConsumableActor::AMyConsumableActor()
{
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
VisualMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualMesh"));
VisualMesh->SetupAttachment(Root);
VisualMesh->ComponentTags.Add(FName(TEXT("DimensionComponent")));
WidgetHolder = CreateDefaultSubobject<USceneComponent>(TEXT("WidgetHolder"));
WidgetHolder->SetupAttachment(Root);
WidgetHolder->ComponentTags.Add(FName(TEXT("WidgetHolderComponent")));
CenterArrow = CreateDefaultSubobject<UArrowComponent>(TEXT("CenterArrow"));
CenterArrow->SetupAttachment(VisualMesh);
CenterArrow->ComponentTags.Add(FName(TEXT("CenterComponent")));
InteractableComponent = CreateDefaultSubobject<UEDInteractableComponent>(TEXT("InteractableComponent"));
}
void AMyConsumableActor::BeginPlay()
{
Super::BeginPlay();
InteractableComponent->OnInteractionSuccessful.AddDynamic(this, &AMyConsumableActor::HandleInteractionSuccessful);
}
void AMyConsumableActor::HandleInteractionSuccessful(const FEDInteractionEventContext& Context)
{
APawn* InstigatorPawn = Context.Instigator ? Context.Instigator->GetPawn() : nullptr;
if (!InstigatorPawn)
{
return;
}
// Placeholder for your own inventory/stat/progression system.
const bool bConsumed = TryConsumeOnPlayer(InstigatorPawn);
if (!bConsumed)
{
return;
}
Destroy();
}
Why this matters
This avoids deleting the actor when the actual gameplay reward fails.