How to set up common delegates for unrelated classes?

Overview of my Project now:
I have 1 Blueprint class, 2 C++ classes, and an interface now:

  1. BP_MyCharacter
  2. AMoneyBag
  3. AMedKit
  4. ILoot

ILoot:

UINTERFACE(BlueprintType, Blueprintable, MinimalAPI)
class ULoot : public UInterface
{
	GENERATED_BODY()
};

class DETECTIVEINTERACTION_API ILoot
{
	GENERATED_BODY()

public:

	UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Interaction", meta=(HideSelfPin))
	void PickUp();
};

AMoneyBag:

UCLASS(Blueprintable, BlueprintType)
class AMoneyBag : public AActor, public ILoot
{
	GENERATED_BODY()

public:
	AMoneyBag();

	void PickUp_Implementation() override;

	DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPickUpMoneyBagSignature, in32, PickedMoney);
	UPROPERTY(BlueprintAssignable, BlueprintCallable)
	FOnPickUpMoneyBagSignature OnPickUp;

protected:

	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Money", meta=(AllowPrivateAccess))
	int32 Money;
};
void AMoneyBag::PickUp_Implementation()
{
	OnPickUp.Broadcast(Money);
	Destroy();
}

AMedicalKit:

UCLASS(Blueprintable, BlueprintType)
class AMedicalKit : public AActor, public ILoot
{
	GENERATED_BODY()
	
public:
	AMedicalKit();

	void PickUp_Implementation() override;

	DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPickUpMedKitSignature, int32, PickedHealth);
	UPROPERTY(BlueprintAssignable, BlueprintCallable)
	FOnPickUpMedKitSignature OnPickUp;

protected:

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Health", meta=(AllowPrivateAccess))
	int32 Health;
};
void AMedicalKit::PickUp_Implementation()
{
	OnPickUp.Broadcast(Health);
	Destroy();
}

In BeginPlay of BP_MyCharacter I iterate over all ILoot implementing objects and, with regular casts to AMoneyBag and AMedicalKit, bind to the delegates with events PickUpMoney and PickUpMedKit. As you can tell, this doesn’t scale very well:


The only shared logic between AMoneyBag and AMedicalKit is the “picking up” functionality consisting of an implementable function and specialized delegates. The types and other functions are different.

The question:
I wonder if there is any possible way to make it more generic. The part that I don’t like specifically is where I bind to all ILoot actors in the scene and then cast to specific classes. I want my classes to stay as decoupled as they can be, that’s why I’m trying to use interfaces and delegates as much as possible.

I am probably completely wrong

My guess is to use an abstract class for AMoneyBag and AMedicalKit and future “Loot” classes, though it seems like not the best solution. Seeking for an architectural advice.

That’s a great thought, but the problem is that the items that can be picked up can have different types. For example, if in the future I would like to add, say, a pickup-able weapon, then passing float won’t really work. That is why I’m really struggling to come up with a general solution (if it even exists). Maybe this interface-delegate approach is completely wrong?

Why not push the task to the component / target that is picking up the object.
Either character / inventory.
It’s their job to store the information. The item is just that, an item. It should have it’s own pickup interface and the inventory should handle any type of sorting / sending messages that things have been picked up etc.

You are putting too much responsibility on the items.

Use the common item interface to be picked up.

  • Add in a pickup function that takes in a container
  • have the container register the item, then the item itself can destroy it’s world actor
  • keeping the information about the item in the container via a struct.
2 Likes

Ok, so from what I understand, you suggest the Character/Component to decide how the item is stored AND destroyed on its own and let it control and know all the details about the item itself?

Anything that starts with “Get all actors …” makes me wanna roll on the ground.

It will be much more simpler if you keep it like this:

  1. Lootable items will implement ILoot.
  2. once character interacts with the Lootable item he/she will do so via the interface signature.
  3. you can simply pass in a reference to the player, i.e. Pickup(ACharacter* InstigatorOfAction).
  4. Now you can easily call the function which exists inside of your player via the Lootable Items implementation.

I hope this makes sense, I am kinda bad at making sentences.

1 Like

Thanks for your input. Everything does make sense, but my concern is that Lootable items mustn’t know anything about the calling Character. At least this is my feeling. If this is fine then great, but is this good practice to let the item add itself to the character? To be honest, I’m quite picky about design and I want to adopt the single responsibility principle.

Well then as mentioned above, you can have the character OR component do all the work and have pickups just a container for some data.

that way your character/component has the function that takes in the reference to item, and as mentioned above you can have a single parent class of Pickable Actor.

Alright, thanks @Raja.Haris @3dRaven @Ares9323 for your help, I really appreciate it. I think that I am going with the component solution after all.

1 Like