creating and using a blendspace in c++

Hello all,

i’m trying to automatize the animation of (a lot of) pawns and would like not to have to define ~100 blendspaces and animblueprint by hand in the editor.

If i can get it to work, i’ll just have to create blueprints inheriting from this class and set the skeletal mesh and animations.

This subject seems to pop up every now and then, there are a few post on the answerhub, and even a wiki by Rama, but all of them seem to assume that the BlendSpace is created in the editor.

As you can see, i’m trying several methods (via the skeletal mesh component and the anim instance) to animate the mesh with the blendspace, none of them works so far.
I tried everything i could find related to animation and blendspace in the api documentation.

I can spawn the blueprint, but the mesh remains static so obviously i miss something.

I tried to set this in the blueprint, and i can see the debuging flow correctly going through everything.

I can’t check visually the blendspace, so i’m a bit lost and dry now.

Here’s the code:


void AYagPawn::BeginPlay()
{
	Super::BeginPlay();
	
	...]

	// creating the BS (SMC_00 is a USkeletalMeshComponent)
	ThisBlendSpace1D = NewObject<UBlendSpace1D>(this, UBlendSpace1D::StaticClass());
	ThisBlendSpace1D->SetSkeleton(SMC_00->SkeletalMesh->Skeleton);

	// setting a parameter for the BS
	FBlendParameter BP_Speed;
	BP_Speed.DisplayName = "Speed";
	BP_Speed.GridNum = 4;
	BP_Speed.Min = 0.f;
	BP_Speed.Max = 100.f;
	ThisBlendSpace1D->UpdateParameter(0, BP_Speed);

	// adding two sample (at 0 and 100)
	// IdleSequence and MoveSequence are UAnimSequence to be set in the BP inheriting this pawn class
	FBlendSample IdleSample = FBlendSample(IdleSequence, FVector(0.f, 0.f, 0.f), false);
	FBlendSample MoveSample = FBlendSample(MoveSequence, FVector(100.f, 0.f, 0.f), false);
	ThisBlendSpace1D->AddSample(IdleSample);
	ThisBlendSpace1D->AddSample(MoveSample);

	// first method 
	SMC_00->PlayAnimation(ThisBlendSpace1D, true);

	// second method
	SMC_00->SetAnimation(ThisBlendSpace1D);
	SMC_00->Play(true);

	// third method
	SMC_00->OverrideAnimationData(ThisBlendSpace1D, true, true, 0.f, 1.f);

	// fourth method
	BS_AnimInstance = Cast<UAnimSingleNodeInstance>(SMC_00->GetAnimInstance());
	BS_AnimInstance->SetAnimationAsset(ThisBlendSpace1D);
	BS_AnimInstance->PlayAnim(true, 1.f, 0.f);
}


void AYagPawn::Tick( float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	...]

	// update BS with Speed (a float)
	BS_AnimInstance->SetBlendSpaceInput(FVector(Speed, 0.f, 0.f));

	// useless in principle but i tried
	SMC_00->OverrideAnimationData(ThisBlendSpace1D, true, true, 0.f, 1.f);
}

And here is the variables section in the BP:

3c5aab19701ae88fd69695b65e1bdee907319749.jpeg

As you can see, the BS is still empty (normal, it will be created in the begin play function at runtime) and both animations have default values.

Any idea ?

Thanks

Cedric

EDIT: i realized i wasn’t setting the animation mode, so i added this in the constructor:


SMC_00->SetAnimationMode(EAnimationMode::AnimationSingleNode);

But it still doesn’t work.

Edit: I should have read your entire question. I’m interested in this so I’m going to help you get the bottom of it! You’re probably missing an update call somewhere, or you need to serialize the result to an asset or something. I’ll report back.

Here’s a compiled simple animation blueprint. It uses a blendspace called “Strafe_IP” and has two variables: Right, Forward. Note that the Character class doesn’t appear to call or update the animation - it only sets it initially.



UCLASS(config=Engine, Blueprintable, BlueprintType, meta=(ReplaceConverted="/Game/Blueprints/BPA_Test.BPA_Test_C", OverrideNativeName="BPA_Test_C"))
class UBPA_Test : public UAnimInstance
{
public:
	GENERATED_BODY()
	
	UPROPERTY(meta=(OverrideNativeName="AnimGraphNode_Root_6D3BF226443B80030387A5AADD092A5D"))
	FAnimNode_Root Root;
	
	UPROPERTY(meta=(OverrideNativeName="AnimGraphNode_BlendSpacePlayer_44691A814B28B34EF79F7B8F71402E07"))
	FAnimNode_BlendSpacePlayer BlendSpacePlayer;
	
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta=(DisplayName="Right", Category="Default", OverrideNativeName="Right"))
	float Right;
	
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta=(DisplayName="Forward", Category="Default", OverrideNativeName="Forward"))
	float Forward;
	
	UBPA_Test(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
	static void __CustomDynamicClassInitialization(UDynamicClass* InDynamicClass);
	static void __StaticDependencies_CommonAssets(TArray<FBlueprintDependencyData>& AssetsToLoad);
};




UBPA_Test::UBPA_Test(const FObjectInitializer& ObjectInitializer) : Super()
{
	if(HasAnyFlags(RF_ClassDefaultObject) && (UBPA_Test::StaticClass() == GetClass()))
		UBPA_Test::__CustomDynamicClassInitialization(CastChecked<UDynamicClass>(GetClass()));

	Root.Result.LinkID = 1;
	BlendSpacePlayer.BlendSpace = CastChecked<UBlendSpaceBase>(CastChecked<UDynamicClass>(UBPA_Test::StaticClass())->UsedAssets[0], ECastCheckedType::NullAllowed);
	BlendSpacePlayer.EvaluateGraphExposedInputs.CopyRecords = TArray<FExposedValueCopyRecord> ();
	BlendSpacePlayer.EvaluateGraphExposedInputs.CopyRecords.AddUninitialized(2);
	FExposedValueCopyRecord::StaticStruct()->InitializeStruct(BlendSpacePlayer.EvaluateGraphExposedInputs.CopyRecords.GetData(), 2);
	
	auto& _Right = BlendSpacePlayer.EvaluateGraphExposedInputs.CopyRecords[0];
	_Right.SourcePropertyName = FName(TEXT("Right"));
	_Right.DestProperty = FindFieldChecked<UFloatProperty>(FAnimNode_BlendSpacePlayer::StaticStruct(), TEXT("X"));
	_Right.Size = 4;
	
	auto& _Forward = BlendSpacePlayer.EvaluateGraphExposedInputs.CopyRecords[1];
	_Forward.SourcePropertyName = FName(TEXT("Forward"));
	_Forward.DestProperty = FindFieldChecked<UFloatProperty>(FAnimNode_BlendSpacePlayer::StaticStruct(), TEXT("Y"));
	_Forward.Size = 4;
	
	Right = 0.000000f;
	Forward = 0.000000f;
}

void UBPA_Test::__CustomDynamicClassInitialization(UDynamicClass* InDynamicClass)
{
	ensure(0 == InDynamicClass->ReferencedConvertedFields.Num());
	ensure(0 == InDynamicClass->MiscConvertedSubobjects.Num());
	ensure(0 == InDynamicClass->DynamicBindingObjects.Num());
	ensure(0 == InDynamicClass->ComponentTemplates.Num());
	ensure(0 == InDynamicClass->Timelines.Num());
	ensure(nullptr == InDynamicClass->AnimClassImplementation);
	
	InDynamicClass->AssembleReferenceTokenStream();
	FConvertedBlueprintsDependencies::FillUsedAssetsInDynamicClass(InDynamicClass, &__StaticDependencies_DirectlyUsedAssets);
	
	auto AnimClassData = NewObject<UAnimClassData>(InDynamicClass, TEXT("AnimClassData"));
	AnimClassData->TargetSkeleton = CastChecked<USkeleton>(CastChecked<UDynamicClass>(UBPA_Test::StaticClass())->UsedAssets[1], ECastCheckedType::NullAllowed);
	AnimClassData->RootAnimNodeIndex = 1;
	AnimClassData->RootAnimNodeProperty = InDynamicClass->FindStructPropertyChecked(TEXT("Root"));
	AnimClassData->AnimNodeProperties = TArray<UStructProperty*> ();
	AnimClassData->AnimNodeProperties.Reserve(2);
	AnimClassData->AnimNodeProperties.Add(InDynamicClass->FindStructPropertyChecked(TEXT("Root")));
	AnimClassData->AnimNodeProperties.Add(InDynamicClass->FindStructPropertyChecked(TEXT("BlendSpacePlayer")));
	InDynamicClass->AnimClassImplementation = AnimClassData;
}

void UBPA_Test::__StaticDependencies_CommonAssets(TArray<FBlueprintDependencyData>& AssetsToLoad)
{
	const TCHAR* __Local__3 = TEXT("/Game/MovementAnimsetPro/BlendSpaces");
	const TCHAR* __Local__4 = TEXT("/Game/Mannequin/Character/Mesh");
	FBlueprintDependencyData LocAssets] =
	{
		FBlueprintDependencyData(__Local__3, TEXT("Strafe_IP"), TEXT("Strafe_IP"), TEXT("/Script/Engine"), TEXT("BlendSpace")),
		FBlueprintDependencyData(__Local__4, TEXT("UE4_Mannequin_Skeleton"), TEXT("UE4_Mannequin_Skeleton"), TEXT("/Script/Engine"), TEXT("Skeleton")),
	};
	for(auto& LocAsset : LocAssets) { AssetsToLoad.Add(LocAsset); } 
}

struct FRegisterHelper__UBPA_Test
{
	FRegisterHelper__UBPA_Test()
	{
		FConvertedBlueprintsDependencies::Get().RegisterClass(TEXT("/Game/Blueprints/BPA_Test"), &UBPA_Test::__StaticDependenciesAssets);
	}
	static FRegisterHelper__UBPA_Test Instance;
};

FRegisterHelper__UBPA_Test FRegisterHelper__UBPA_Test::Instance;


Hey Duke22,

Thanks !

Yes, what i need is to click the “apply parameters changes” button of the blendspace in c++.

I made a short video to show this:

I create a brand new BS and assign it to a Skeletal Mesh Component.

When i run the code, you can see there is no animation. I can play many times (i do it twice in the video), animations won’t show.

But when i open the BS in the editor, you can see the code has done its work and the samples are correctly defined.

I can then just save and from now on animations show up.

So as you guessed, i really just need to figure out how to “apply the parameter changes and save” from c++.

A clever fellow figured out how to do this for BlendSpace1D here:
https://answers.unrealengine.com/questions/514838/how-do-you-properly-use-the-blend-space-addsample.html

Problems are:

  • i’m not too much a fan of using large portions of the engine code, future and maintenance are not garantied
  • 2 dimensionnal blendspaces are more complicated than 1D ones and i still couldn’t see what i really neeed from the engine code.

So i’d like a less hackish method, but i’m afraid this button is not exposed in the api currently.

That could maybe make a Feature Request. I’ll see where this thread goes :slight_smile:

Cheers

Cedric

EDIT: i forgot to mention that for the moment i stopped trying to create a BS from scratch and work with a editor created BS. But i suspect when this problem is solved, creating a BS entirely in code should work as well.

Is there any others interested in this?

I aim to create a Animation Blueprint alternative to make it available to multiple character in the editor.

So I also came across this thread. But when I want to implement AnimGraph,
DynamicClass::FindStructPropertyChecked() is not an available option for me anymore, because it refer to a AnimGraphNode.

Does anyone have some experience about how to implement AnimGraph in an AnimInstance in C++ ?
Please share with us! Thank you.

1 Like