UPROPERTY not updating from Blueprint connection

I am trying to build some custom AnimGraph nodes. I have gotten them to compile and run, but have had some issues when using them in my Blueprints. When I create custom UPROPERTYs on the node and connect to them using Blueprints, the values do not get to the c++. When querying the property, I get the default value specified at construction time.

I have created a very simple node to demonstrate what I’m talking about. On it there is a float UPROPERTY called TestAttr. In the various functions, I print the value of TestAttr to the log. When I create this node in a Blueprint and connect an input to TestAttr, I don’t get the values, though they show in the editor when hovering over the connection.

Can someone please tell me what I’m doing wrong?

Here’s the code:

AnimNode_TestNode.h

#pragma once

#include "Animation/AnimNodeBase.h"
#include "AnimInstanceProxy.h"
#include "AnimNode_TestNode.generated.h"

USTRUCT()
struct FAnimNode_TestNode : public FAnimNode_Base
{
	GENERATED_USTRUCT_BODY()

	// Input Pose
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Links)
		FPoseLink Pose;

	// Test Attribute
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Settings, meta = (PinShownByDefault))
		mutable float TestAttr;

	FAnimNode_TestNode();

	// FAnimNode_Base interface
	virtual void GatherDebugData(FNodeDebugData& DebugData) final;
	virtual void Initialize(const FAnimationInitializeContext& Context) final;
	virtual void CacheBones(const FAnimationCacheBonesContext& Context)  final;
	virtual void Update(const FAnimationUpdateContext& Context) final;
	virtual void Evaluate(FPoseContext& Output) final;
	// End of FAnimNode_Base interface

};

AnimNode_TestNode.cpp

#include "dDNAToolsPrivatePCH.h"
#include "AnimNode_TestNode.h"
#include "AnimationRuntime.h"
#include "AnimInstanceProxy.h"

/////////////////////////////////////////////////////
// TestNode

FAnimNode_TestNode::FAnimNode_TestNode()
	: FAnimNode_Base(),
	TestAttr(0.0f)
{
	UE_LOG(LogTemp, Error, TEXT("Constructor: %f"), TestAttr);
}

void FAnimNode_TestNode::GatherDebugData(FNodeDebugData& DebugData)
{
	FString DebugLine = DebugData.GetNodeName(this);

	DebugData.AddDebugItem(DebugLine);
}

void FAnimNode_TestNode::Initialize(const FAnimationInitializeContext& Context)
{
	FAnimNode_Base::Initialize(Context);

	Pose.Initialize(Context);

	UE_LOG(LogTemp, Warning, TEXT("Initialize: %f"), TestAttr);
}

void FAnimNode_TestNode::CacheBones(const FAnimationCacheBonesContext& Context)
{
	FAnimNode_Base::CacheBones(Context);

	Pose.CacheBones(Context);

	UE_LOG(LogTemp, Warning, TEXT("Cache: %f"), TestAttr);
}

void FAnimNode_TestNode::Update(const FAnimationUpdateContext& Context)
{
	FAnimNode_Base::Update(Context);

	Pose.Update(Context);

	UE_LOG(LogTemp, Warning, TEXT("Update: %f"), TestAttr);
}

void FAnimNode_TestNode::Evaluate(FPoseContext& Output)
{
	UE_LOG(LogTemp, Warning, TEXT("Evaluate: %f"), TestAttr);

	Pose.Evaluate(Output);

	UE_LOG(LogTemp, Warning, TEXT("Evaluate End: %f"), TestAttr);

}

AnimGraphNode_TestNode.h

#pragma once

#include "AnimGraphNode_Base.h"
#include "AnimNode_TestNode.h"
#include "AnimGraphNode_TestNode.generated.h"

UCLASS()
class UAnimGraphNode_TestNode : public UAnimGraphNode_Base
{
	GENERATED_UCLASS_BODY()

	UPROPERTY(EditAnywhere, Category = Settings)
	FAnimNode_TestNode Node;

public:

	// UEdGraphNode Interface
	virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const final;
	virtual FText GetTooltipText() const final;
	virtual FLinearColor GetNodeTitleColor() const final;
	virtual FString GetNodeCategory() const final;
	virtual void CreateOutputPins() final;
	// End of UEdGraphNode Interface

protected:

	virtual FString GetControllerDescription() const { return FString(); }

};

AnimGraphNode_TestNode.cpp

#include "dDNAToolsEditorPCH.h"

#include "AnimGraphNode_TestNode.h"
#include "AnimationGraphSchema.h"

UAnimGraphNode_TestNode::UAnimGraphNode_TestNode(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{ }


FLinearColor UAnimGraphNode_TestNode::GetNodeTitleColor() const
{
	return FLinearColor(0, 12, 12, 1);
}

FText UAnimGraphNode_TestNode::GetTooltipText() const
{
	return FText::FromString("Test Node");
}


FString UAnimGraphNode_TestNode::GetNodeCategory() const
{
	return FString("Testing");
}

FText UAnimGraphNode_TestNode::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	return FText::FromString(GetControllerDescription());
}

//Node Output Pin (Change at own RISK!)
void UAnimGraphNode_TestNode::CreateOutputPins()
{
	const UAnimationGraphSchema* Schema = GetDefault<UAnimationGraphSchema>();
	CreatePin(EGPD_Output, Schema->PC_Struct, TEXT(""), FPoseLink::StaticStruct(), /*bIsArray=*/ false, /*bIsReference=*/ false, TEXT("OutputPose"));
}

Ok, so I dove into this again and figured out what’s going wrong here. I am putting up these notes in case anyone else hits this particular snag.

Basically, during AnimGraph node update, you must call

EvaluateGraphExposedInputs.Execute(Context);

This updates the input properties. This is not mentioned in many of the online tutorials. For example, A new, community-hosted Unreal Engine Wiki - Announcements - Epic Developer Community Forums or Creating Custom Animation Nodes - Unreal Engine and numerous ones at 3rd party sites I visited while trying to resolve this.

Interestingly, the second link above has, in the Comments, links to several pages which include the code above in their examples, though none that I’ve found call this out as important.

So anyway, this is in case anyone else runs into this. Maybe they’ll have an easier time of it for this thread existing.