Callback Functions configurable in a Details Panel

Some game play components requires call back functions (implemented in blueprints) which can be configured in the component’s details panel. I will provide an example for clarification: Say, you are implementing a quest system; it has an object ‘Quest’. Assume Quest objects are configurable to create any quests you could imagine: you would like to give the level designer the option to script some function in blueprints which is executed when a certain quest is completed, for instance, or some other special state in a quest is reached. For this, the quest object could have a call back function property.

The purpose of this thread is to discuss possible implementations this call back function property. Below I will provide discussions of some implementations that I have come up with but ultimately I would like us to be able to find improved versions which are more flexible.
For simplicity’s sake, we assume that a call back function has no parameters except for a ‘this’ pointer. I argue however that this restriction does not matter as, if we are passing a “this” pointer, the object pointed to could have public fields acting as parameters, which are set before the call back. Of course the mechanism would need to be defined but let us assume for discussion’s sake a protocol exists.

Option 1: FCallback Structure **
Type Definitions: We have a struct we name FCallback for reference; it has the fields following: UClass
ClassDefiningFunction, FString UFunctionName, AActor
ThisParameter. Furthermore, there is a function named Call(FCallback*) which is exposed as a blueprint node, which will call the callback.

How it works: * Any function which only has a ‘this’ pointer as parameter is clearly identified with a class and a function name. The implementation of Call(FCallback) would thus commence by iterating all UFunctions in ‘ClassDefiniingFunction’ attempting to find one with only a pointer parameter of type AActor* and the name ‘UFunctionName’. If found it will call ProcessEvent on ‘ThisParameter’.

Advantages: Call back functions can be defined independently from each other different from option 2 (see below). Also, any blueprint can define the call back function. As well, these are ‘true’ call back functions as we know them from C++ as they are similar to function pointers.

Drawbacks: It is required for an actor to be spawned in the level which has the type of the class we provide in the FCallback function. At the very least that is an overheat on memory; it also bloats the namespace of spawned actors. From a level designer’s perspective, configuring a call back function would be a series of complex steps in this approach: Creating a custom blueprint hosting the function, spawning said blueprint in a level, and providing correct class and function name in the structure. Assuming there are a lot of different call back functions defined in the said blueprint, then the level designer must look through all defined functions to find the correct one for the job each time.

Option 2: Universally defined ProcessCallback function
Type Definitions: We define a UFunction “ProcessCallback” marked as BlueprintNativeEvent in some class that will be overriden in blueprints. It takes exactly one parameter: int32 CallbackID. A class declaring this function could be a game instance, as it is always present, or a UActorComponent which is attached to the player pawn.

How it works: Each value of of the argument ‘CallbackID’ identifies a different callback function to call. In the implementation of ProcessCallback we use a ‘switch on’ node and each branch leads to the implementation of the specific call back. When the level designer wishes to specify a call back function in the details panel they need only specify the call back ID.

Advantages: All call back code is defined in a centralised place.

Drawbacks: The implementation of “ProcessCallback” may become very bloated. The level designer might still have to look up the correct function as described in option 1.

Option 3: Inheritance
Type definitions: Make every object that requires call back functions an actor component that declares a blueprint native or blueprint implementable event.

How it works: Simply call the declared function.

Advantages: By far the simplest, most straightforward option.

Drawbacks: The list of user classes increases signficantly as the need for many call back functions arises; the project is bloated with many different types which do not add much functionality but rather only define a call back. A bloated list of type increase the difficulty of maintaining the project. Additionally, this approach causes a memory overheat. Every component which must be attached to configuration comes with data from its parents; all this data is used for features not used. For only defining a call back function this presents a significant overheat.

Any ideas for alternative implementations and/or improvements to mine are welcome ;).