I have a custom FAnimNode the has a custom UStruct type (called BoneMapping) as a property. The custom UStruct has a property that is a TArray of another custom UStruct type (BoneTarget). If I use the Make BoneMapping node in the AnimGraph and pass in an array of BoneTargets the blueprint compiles just fine:
If, however, I connect a variable of type BoneMapping to the AnimNode, the editor crashes upon compile:
This is the code for BoneMapping:
USTRUCT(BlueprintType)
struct FBoneTarget
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString Source;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FName Target;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool TranslationEnabled = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool RotationEnabled = true;
FBoneTarget()
{
Source = FString();
Target = FName();
}
FString GetSource()
{
return Source;
}
FName GetTarget()
{
return Target;
}
bool GetTranslationEnabled()
{
return TranslationEnabled;
}
bool GetRotationEnabled()
{
return RotationEnabled;
}
};
USTRUCT(BlueprintType)
struct FBoneMapping
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
TArray<FBoneTarget> BoneTargets;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString TestString;
TArray<FBoneTarget> GetBoneTargets()
{
return BoneTargets;
}
FBoneMapping()
{
BoneTargets = TArray<FBoneTarget>();
TestString = FString();
}
};
and here’s the code for the AnimNode:
AnimGraphNode_SkeletalMeshStream.h
USTRUCT()
struct FAnimNode_SkeletalMeshStream : public FAnimNode_Base
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Links)
FPoseLink DefaultPose;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Motion Capture", meta = (PinShownByDefault))
FSkeletalMeshConnection Connection;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Motion Capture", meta = (PinShownByDefault))
FBoneMapping BoneMapping;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Motion Capture", meta = (PinHiddenDefault))
FString Subject;
public:
virtual void Initialize(const FAnimationInitializeContext& Context) override;
virtual void CacheBones(const FAnimationCacheBonesContext& Context) override;
virtual void Update(const FAnimationUpdateContext& Context) override;
virtual void Evaluate(FPoseContext& Output) override;
public:
FAnimNode_SkeletalMeshStream();
protected:
bool WorldIsGame;
bool isFrameOK;
AActor* OwningActor;
USkeletalMeshComponent* OwningSkeleton;
};
UCLASS()
class UAnimGraphNode_SkeletalMeshStream : public UAnimGraphNode_Base
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category = Settings)
FAnimNode_SkeletalMeshStream Node;
public:
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual FLinearColor GetNodeTitleColor() const override;
virtual FString GetNodeCategory() const override;
protected:
virtual FString GetControllerDescription() const;
};
AnimGraphNode_SkeletalMeshStream.cpp
FAnimNode_SkeletalMeshStream::FAnimNode_SkeletalMeshStream() : FAnimNode_Base()
{
}
void FAnimNode_SkeletalMeshStream::Initialize(const FAnimationInitializeContext& Context)
{
DefaultPose.Initialize(Context);
//Editor or Game?
UWorld* TheWorld = Context.AnimInstance->GetWorld();
if (!TheWorld) return;
WorldIsGame = (TheWorld->WorldType == EWorldType::Game || TheWorld->WorldType == EWorldType::PIE);
//Get the Actor Owner
OwningActor = Context.AnimInstance->GetSkelMeshComponent()->GetOwner();
OwningSkeleton = Context.AnimInstance->GetSkelMeshComponent();
if (WorldIsGame) {
}
}
void FAnimNode_SkeletalMeshStream::Evaluate(FPoseContext& Context)
{
if (WorldIsGame) {
int32 numBones = BoneMapping.GetBoneTargets().Num();
for (int i = 0; i < numBones; ++i)
{
FName sourceName = FName(*BoneMapping.BoneTargets[i].GetSource());
FName targetName = BoneMapping.BoneTargets[i].GetTarget();
USkeletalMeshComponent* skel = Connection.SourceSkel;
if (skel) {
int32 sourceIndex = skel->GetBoneIndex(sourceName);
FQuat sourceRot = skel->GetBoneQuaternion(sourceName, EBoneSpaces::ComponentSpace);
FCompactPoseBoneIndex targetIndex = FCompactPoseBoneIndex(Context.Pose.GetBoneContainer().GetPoseBoneIndexForBoneName(targetName));
Context.Pose[targetIndex].SetRotation(sourceRot);
}
}
return;
}
DefaultPose.Evaluate(Context);
}
void FAnimNode_SkeletalMeshStream::CacheBones(const FAnimationCacheBonesContext& Context)
{
}
void FAnimNode_SkeletalMeshStream::Update(const FAnimationUpdateContext& Context)
{
EvaluateGraphExposedInputs.Execute(Context);
DefaultPose.Update(Context);
}