I’m relatively new to UE and can’t figure it out after having been wrestling with this for a while…
I want to provide a solution to designers who work with BPs so they can build their actor BPs flexibly. All these actors have to implement an interface with 3 functions/events so they can receive messages from the GameManager (an AActor who spawns, destroys and updates these Actor instances according to the data received from the server).
My approach uses a DataAsset which contains a list of structs where designers can pair FString IDs with UBlueprint*s in the Editor. The only restriction they have to follow is they have to implement an interface in the BPs that they want to use this way. The GameManager uses this interface to Initialize, Update and Destroy the Actors.
So the task is for the GameManager to spawn Actors using the UBlueprint* that it finds in the DataAsset and cast them to the interface to handle them during their lives:
// BPClass seems to be okay in the debugger. ClassGeneratedBy returns the right BP and it has the interface in the Interfaces and the 3 functions are also there in the FuncMap
UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(MyDataAssetRow.BPInstanceFromEditor->GeneratedClass);
// the actor spawns and appears on the screen
AActor* SpawnedActor = GetWorld()->SpawnActor(BPClass);
// this casting is not successful though
if (IInterfaceForGameManager* ShouldBeInterface = Cast<IInterfaceForGameManager>(SpawnedActor))
{
// would manage spawned actor via interface functions if the cast was successful
}
I suspect that I spawn the wrong way (tried many different versions, (templated and with more SpawnParameters) without any luck). I’ve seen examples where the Blueprint class is added to the manager as a UPROPERTY but it’s not possible in this case because there are too many different types…that’s why I use the DataAsset to keep track of the classes used.
I would appreciate the help…also if there’s a better pattern to solve the dynamic class management, I would also be curious about that.
Interface casts and blueprint don’t work well together unfortunately. In that case there’s nothing to actually cast to. Casts only work if the interface is implemented by a native type which then your blueprints would all be required to derive from. This is sometimes a reasonable restriction, but not always.
But there’s another option (because you have to be able to call those interface functions without casting) and that is to do this:
if (SpawnedActor->Implements<UInterfaceForGameManager>( ))
{
IInterfaceForGameManager::Execute_DoGameManagerThing( SpawnedActor );
}
That static Execute_DoGameManagerThing function is autogenerated by Unreal to have all the same parameters (plus the object input) and return value that you declared for your DoGameManagerThing function in the interface.
I question your need for the data asset at all. You’re just adding a level of indirection that you may not really need. At some point your designer calls a function and uses that string ID or specifies that ID as a property somewhere. You could just let them specify the class instead.
UPROPERTY( meta = (MustImplement = "/Script/Module.InterfaceForGameManager") )
TSoftClassPtr< AActor > or TSubclassOf< AActor> SpawnType;
The metadata markup from MustImplement will limit the selection of classes to those with the interface you’re interested in. So now the designer can just create a blueprint, implement that interface and plug it in whever they need without modifying some global lookup table of classes that can be spawned.
Plus you can ignore all that BlueprintGeneratedClass stuff and just do: GetWorld( )->SpawnActor( SpawnType.Get( ) ) or GetWorld( )->SpawnActor( SpawnType ) depending on whether or not you opt for the soft version or not.
Thank you @MagForceSeven. I’ve just implemented the interface check with the Implements<U.....>() way and the calls with the IInterface::Execute_..( Actor ) way. It works perfectly.
Just to be a good citizen: how ugly does this approach considered? Looks like a bit of a hack but a lot things are kinda hacky in UE due to the managed/processed C++ so not sure if it would be completely fine or seasoned UE devs would raise their eyebrows?
Regarding the second part of your answer. I think I will just keep the DataAsset because the designers don’t touch C++ and I want to keep the definition of these (ID, Blueprint) combinations fully to them (I can’t manage the UPROPERTYs in the code myself every time they amend these parings). The ID has to match with the IDs that come from the backend but I prefer them aligning with backend without having to fiddle with C++. So this DIY content management system with the DataAsset is a good solution for us. I will remember the MustImplement meta though…it can be very useful in another case.
Not at all ugly. It’s only thing you can do when your interface is allowed to be implemented by Blueprints (which is the default). It looks hacky because interface implementation by blueprint is weird/hacky at the engine level, so that’s going to translate into your project as well.
I’m not sure you understood my suggestion. I’m not suggesting you move anything to C++. I’m suggesting that you remove the ID->Blueprint lookup entirely and allow the Blueprint itself to be selected directly instead of the indirectly through an ID. Then you don’t need to maintain any lookup table and the Designers can still make and use blueprint that are compatible with your GameManager.
Any UProperty that they’re filling in as a string would instead by a SubclassOf/SoftObjectPtr with a MustImplements tag. You’re already providing those ID properties from native or else you wouldn’t be able to use them in your example. Also you get the benefit of a dropdown for selecting the value instead of a string input that could be typed in wrong.
If they’re using a function with a string input from blueprint, you can do the same thing with a SubclassOf/SoftObjectPtr and the MustImplements tag in a UPARAM macro to get the same filtered dropdown behavior.
I also have to clarify that I use the ID for another reason: the state of the actors comes from a complex backend calculation (long story…it’s not a game but a visualization and big part of the state of the things in the level are calculated/generated in the backend). The ID is coming in the payload and it’s used to identify the “kind” of the Actors. Every “Kind” has a Blueprint with their own abilities which handles all the possible states and make the Actor look and behave accordingly.
The GameManager loads the state and spawns the new Actors according to the IDs found in the payload and keeps sending state updates to them and eventually Destroys them. The Blueprints are paired with the IDs for this reason.
The designers add the Blueprints in the editor directly using the dropdown but they have to be paired up with their IDs which they fill in there by hand. (I get the list of valid IDs from the backend when the “game” is starting so the DataAsset can be validated and this would happen during development time so it’s safe enough.)
The DataAsset is defined as below. I will try to use TSubclassOF<AActor> instead of Blueprint* according to your suggestion but wondering if I had the nice dropdown in the DataAsset then in the Editor