In this lesson, you will learn how to use delegate interfaces to create "contracts" between systems, allowing them to communicate without ever needing to know about each other’s internal code.
The Problem: Tight Coupling
In many projects, developers accidentally create "Spaghetti Code" by having systems reference each other directly. For example, if your UInventorySystem calls PlayerCharacter->Heal(), the inventory system is now permanently stuck to the player class. If you ever want to use that inventory system in a vehicle or a turret, you have to rewrite the code.
The Solution: Delegate Interfaces
Instead of the Inventory talking to the Player, we define a Delegate Interface—a shared header file containing a delegate definition. The Inventory system simply "broadcasts" an event saying, "I just used a health potion," and it doesn't care who is listening.
By using this approach, any system—be it a Player, an NPC, or even a Debug Logging tool—can subscribe to that delegate. The Inventory system remains perfectly "decoupled," meaning it can exist and function in any context without needing to include the headers of the objects listening to it.
Practical Implementation
First, define your interface in a shared header:
DECLARE_MULTICAST_DELEGATE_OneParam(FOnItemUsedDelegate, float);
class IConsumableInterface {
public:
virtual FOnItemUsedDelegate& GetOnItemUsedDelegate() = 0;
};
By having your actors implement this interface, you create a standard "contract." The Inventory system checks if an actor implements IConsumableInterface. If they do, it binds to the delegate without needing to know if the target is a APlayerCharacter or a ABaseEnemy.
This architecture turns your systems into independent modules. If you want to change how the Player heals, you update the Player. If you want to add a new sound effect when an item is used, you create a new UAudioSystem that listens to the delegate, leaving the UInventorySystem untouched and bug-free.
Key Takeaway: Use delegate interfaces to replace direct object references with communication contracts, ensuring your game systems remain modular, reusable, and easy to refactor.