Are events the right way to implement this pattern?

I’m building all functionality in my game to be super-aggressively component-driven: characters don’t take damage from attacks unless they have a Health component to hear TakeDamage, they have no carrying capacity unless they have an Inventory component, no ability to use light switches unless they have an Interact component, and so forth. This lets me compartmentalize functionality in a way that makes it very simple to add or modify features later, but I’m having a heck of a time deciding how these components should communicate with the ACharacter they’re attached to.

Off the top of my head, events seem like the obvious way to do this- each Character gets an event for every possible verb (attack, take damage, use item, open inventory), the input manager/AI controller calls myVerb.Broadcast(), and individual components are in charge of binding themselves to the appropriate event in their parent Actor.

The downside I’m quickly finding is that Actors get really bloated, really quickly- at a bare minimum I’m going to have a press/release event for each key, and dozens, possibly hundreds of events for every single possible verb the characters’ action lexicons will contain. Once it’s configured this is pretty bulletproof, but actually configuring and curating the event system seems like it could be a nightmare.

So is there a better design pattern I’m missing that would let me compartmentalize everything the way I’m discussing, or is biting the bullet and committing to managing a very large event system likely the best way to do this?

I think maybe you are being too input focussed here. Why would a health component receive input for instance? It simply registers for “Take Damage” events and sends out “Current Health Value Change” events.

Normally my component architecture uses a central message publisher/subscriber event manager built into the actor, although you could just as easily make that a component and have every component you build simply cache a weak pointer to the message publisher component at postinitialize time.

Generally, they should communicate through the passing of events this way, but there’s nothing to say you can’t simply have components query for other components and use their references directly. As long as they have a method of handling failure (i.e. using messages if component cannot be cached).

What makes things a bit tricky, is that Unreal Engine doesn’t REALLY use components properly yet. Too much inheritance is left in the core of Actors and the like that some of the fundamental aspects of component architectures don’t actually work (try deleting a component from a C++ based actor at runtime and see why).

So currently its a bit of a compromise. But yeah, events and lots of them. Not so many “input” ones per-se. But it can get quite complex for sure. The advantage is you can develop with the narrow scope of one component at a time.

So I really like your method of centering it on a central manager, especially since it relieves the project from the need to ensure that everyone who uses this system inherits from a base class where the event hierarchy lives. If you’ll bear with me though, I have a few very rudimentary questions about implementation:

  1. Am I correct that instead of calling broadcast events directly from the issuing component, it’s generally worth going to the trouble of making the events private, and for each verb give the manger some function:

void BroadcastDamage(){
DamageEvent.Broadcast();
}

  1. I feel silly for having trouble figuring this out, but I’ve been staring at Events | Unreal Engine Documentation and I can’t for the life of me figure out how to bind a function to an event once it’s been created- say I have some manager class UComponentManager, and do this:


DECLARE_EVENT(UComponentManager,FCharacterVerb) //All character actions get binded to the CharacterVerb event type 

UCLASS()
class CODEPROJ_API UComponentManager : public UActorComponent
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	UComponentManager ();
	FCharacterVerb StartAttackVerb; //this broadcasts to everything that runs when the attack key is depressed
};


How the heck do I actually teach my Combat component to bind its OnAttack function to StartAttackVerb, so it runs when that event fires from UComponentManager?

Your event manager component would typically have publish and subscribe methods. So lets say you have a TArray of hash,delegate pairs. Each event you want to be able to use, you register as an event that can be subscribed to (lets say there’s a method RegisterEvent on the component). Other components that want to receive nofication of the event being fired, get hold of the manager component and subscribe to the event with a method that gets called back. Components that want to call the event, simply call Publish on the event they want to send.

So the Event Manager becomes something like:




class AEventPublisherComponent: public AActorComponent
{
           EventHashID GetHashForEventName(String name);
           bool RegisterEvent(EventHashID hashid);
           bool PublishEvent(EventHashID hashid,AActorComponent *sender, void *pData);
           bool SubscribeEvent(EventHashID hashid,EventDelegateMethodPtr callbackmethodptr);

          // storage for hash/delegate pairs (TArray? TMap?).
}


Then in terms of usage, one component would simply Publish the events it needs to and Register for the ones it wants to be aware of. The downside of this approach is that then ALL components end up being dependant on this publisher/subscriber class wherever it is. But that is mitigated by the fact that once it is up and running and debugged it generally won’t change much. It also has the advantage that then components are not tightly coupled, but instead are coupled only by the intermediate Event manager.

Now I’ve seen both central singleton based “manager” classes and localised per-actor type message handling. In fact my last engine I built had both together. But the core is the same, pass events through a third party and try and keep components from knowing anything about each other directly. Unless you absolutely must have speed, in which case simply store a weak pointer to the class that you need and have some way of ensuring the dependency at least at compile time.

Thank you for the illustration, that makes a lot more sense! Making each of the three main methods return a bool is for error trapping purposes, right? So a component is instantly aware if an attempt to register something fails?

Two quick questions occur reading this: the second argument that SubscribeEvent takes, the DelegateMethodPr, that’s a pointer directly to the function that the subscribing component is trying to subscribe to the event whose hash they’re passing, right? What exact data type is callbackmethodptr? I’m a bit murky as to how you actually pass the function as an argument.

Second, can you recommend a reference/further reading on creating the EventHashID enum and GetHashForEventName function? I’m just learning about hashes this week, and I don’t know where to begin looking into translating data>hash.

the delegatemethodptr is literally just the type of the method that binds to the delegate, so it depends on what delegate type you use. have a look at the DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams macros in UE4 for examples of delegate declarations.

One way you might make it easier, is to return the delegate itself from RegisterEvent and SubscribeEvent (so pass the HashID value and get the delegate for that hash back, then just Bind it as you usually would). But normally I’d pass back a bool to suggest if the Hash value was invalid or any other error. It would be very rare for anything to return false though. You might want to have various methods for checking if things are bound to a given Hash already and to Unbind their callback methods too. But in general you rely on the Delegate for all that functionality. So really all the Event manager class is, is a container to a set of hash/delegate pairs. Because the delegate is the bit responsible for doing the indirection from publisher->subscriber. Its kind of a pity that C++ doesn’t have built in delegate functionality thats easier to use too. Like in C# or other modern languages (yeah there’s C++11 and all but the syntax is not nice!).

Hmm, hash functions are pretty common, but i’d recommend “Game Engine Architecture” by Jason Gregory as a good book to read for general stuff like that (and overall useful discussion of game engine architecture itself) as he’s a Naughty Dog guy and goes over most of this ground. Essentially I would use a method that turns a string into a unique hash number. Then call the various events by hash rather than by string. Although for convenience you might also just create methods with string usage as well (i.e. pass the string and do the hashing internally to the method). I’m pretty sure there’s a string hashing method somewhere in the Unreal Engine, but can’t for the life of me remember what its called.

Alright excellent, thank you massively for the help and references! I’ve been smashing my head over this all week :slight_smile:

Edit: Huh, I just found that the engine has a hash function, uint32 GetTypeHash(const YourType& TypeVar)… do you have any idea if that alone is sufficient?

Hmm, that doesn’t sound right. You want one that takes a string and gives a hash value. I guess they use the GetTypeHash for hashing types for blueprint variables or something.

That would explain why it’s behaving oddly then. I think there might not be hashing built in to the engine, as that’s all I’m finding in the docs, but I’m a little surprised- it’s a common enough utility that you would think they’d add even a lackluster method.

Edit: Last questions, because it’s 99% implemented, but I still don’t understand a few things:

First off, what’s the purpose of having the subscribe function issue a callback when it already has the bool to tell the subscribing component whether or not it was successful?

Second, why does Publish come with a pointer to the sender? Is it just for debugging purposes, if something gets weird and the publisher wants to know who’s issuing calls?

Finally, what in the sam hill is the syntax to feed a function to something that’s expecting a delegate? I have the method finished:



bool UEventPublisher::SubscribeEvent(FString hashID, FEventPublisherEvent callbackmethodptr){
	eventStorage.Add(hashID, callbackmethodptr);
	return true;
}

But I can’t for the life of me work out how to use it; eventPublisher->SubscribeEvent(“Attack”, TestFunction); throws an error, and every variation of function pointer and delegate I try seems to get rejected.

The sender information is just one way of passing details. Its really up to you to consider what you need to be able to send. Typical message information might be something like: Who sent it, a few parameters worth of data, the event type, the intended receiver id. I’ve typically used messagetype, sender id, receiver id, uint32 value, void * dataptr and the like. Pretty much like a normal windows message. Another option is to have a key/value pair data parameter storage class and pass a pointer to an instance of that. But you have to be careful with pointers obviously.

The method pointer thing is a bit complicated syntax wise. Have a look at how Delegates declared with the DECLARE_DYNAMIC_MULTICAST_* syntax are specified and bound. Because that’s essentially what you’re doing. My suggestion was to return the delegate from the Subscribe call and then simply bind the Delegate as normal rather than trying to figure out the syntax for how to pass the method to be bound as a parameter to the function. But you might want to actually read the macro spec for the DECLARE_DYNAMIC_MULTICAST_* type delegate declarations to give you some info if you prefer to actually work it out.

Remember you can’t just bind any old function, it has to match the prototype required for the delegate. So if the delegate takes two parameters, your testfunction must take two parameters for instance.

If I recall, there MIGHT be an implementation of some of this kind of stuff in AIMessage, but it seemed a bit clunky the last time I looked.

Have a read of the Jason Gregory book and maybe have a look at how C# does its delegates and delegate binding to show why I find it kind of horrible to do the same stuff in C++. In my own engine, I used a thing called “FastDelegate” which I got implementation from CodeProject.com I believe. C++ delegates at the time were a complete cludge.

Okay, so I think I’m almost done here- I looked up how to make function pointers, and changed Subscribe to use pointers instead of delegates:

typedef void (UEventPublisher::*FunctionPtrType)(void);
FunctionPtrType myPtr;
myPtr = &MyClass::TestFunc;

bool UEventPublisher::SubscribeEvent(FString hashID, FunctionPtrType callbackmethodptr){ //Subscribe asks the system to inform a specific function in the component when a specific event broadcasts

if (eventStorage[hashID].IsBound()){ //If there is a record of this ID
eventStorage[hashID].Add((this->*(callbackmethodptr))); //This line still produces an error
}

That last line is still bugging out on me, however- if I got the pointer from an external class I can’t use this->*, and it’s still popping an error, “Pointer to a bound function may only be used to call the function”.