Because I have plenty of enemies with different types, I made a data driven enemy class which means there is only one enemy class for most enemy types and use different data assets to spawn different types of enemies. But behavior tree is an annoying problem because there are some blackbaord keys that are actually config variables like PatrolSpeed or Weapon range. One way to get around this is to acess those value from my pawn which holds the data asset that contains these variables. But another more elegant way I believe is to override blackboard value based on data asset.
For example, this is what I want to achieve.
class UEnemyConfig : public UPrimaryDataAsset
{
...
UPROPERTY(EditAnywhere)
TMap<FBlackboardKeySelector, float> OverrideParameters;
}
And in data asset editor, TMap<FBlackboardKeySelector, float> OverrideParameters will be an editable property like in BT. I can make sure an UEnemyConfig only uses one BT and one BB.
Of course I can use TMap<FName, float> OverrideParameters to manual input that key name but I want it to be editable so that is won’t cause any typo.
You can’t use FBlackboardKeySelector as a hash key directly as it’s missing the GetTypeHash function to generate a readable entry for the hash map key.
You could maybe try extending the struct with the needed function.
8 months past and I have decided to use reflection to achieve. The core idea is read parameters from an object(usually the Pawn or the AIController which uses BT), and then paste into the corresponding blackboard key parameters which share the same type and the same variable name. Although I still need to be careful about typo, as it requires the parameter name in the Pawn class identical to the key name in the blackboard, it’s better than TMap<FName, float> solution as I don’t need too many code to support a lot of types and it’s extendable(such as UBlackboardKeyType_GameplayTag which is defined in an experimental plugin)
Pseudo code will be like:
TMap<FName, FProperty*> PropertyMap;
for(TFieldIterator<FProperty> It(PawnClass); It; ++It)
{
PropertyMap.Add(It->NamePrivate, *It);
}
for (auto& Key : BlackboardAsset.Keys)
{
if (FProperty** PropertyPtr = PropertyMap.Find(Key.EntryName))
{
FProperty* Property = *PropertyPtr;
if (PropertyClass == FIntProperty::StaticClass()
&& Key.KeyType == UBlackboardKeyType_Int::StaticClass())
{
Do some copy
}
else if (...)
}
}
With a TMap mapping field class and Blackboard Key Type to a function pointer, the code will be much shorter than the pseudo code.