Animation Node, Entire Source for a Turn In Place Node

From Epic Wiki

Overview

Original Author ( )

Dear Community,

Here is my entire code for a Turn In Place animation node!

This node detects when there is little to no velocity, but the character is changing directions constantly.

This code shows you my method of accessing the player character from within the animation node code.

TurnInPlacenode.jpg

Animation USTRUCT

.h

// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.

#pragma once
#include "AnimNode_VictoryTurnInPlace.generated.h"

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

	/** Base Pose*/
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Links)
	FPoseLink BasePose;
	
	/** Turning In Place! */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Links)
	FPoseLink TurnPose;
	
	/** How Quickly to Blend In/Out of Turn Pose */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Links, meta=(PinShownByDefault))
	float TurnBlendDuration;
	
	/** What Amount of Turn Per Tick Qualifies for Maximum Turn Blending? Anything less per tick will result in slower Turn Blending. Result: If player turns slowly, the turn blend blends in slowly, and ramps up smoothly to max turn blend as player turns faster. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Links, meta=(PinShownByDefault))
	float TurnSpeedModifierMAX;
	
	/** The Lower This Number The Faster The Turn In Place Anim Will Activate */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Links, meta=(PinShownByDefault))
	float TurnSensitivity;
	
	/** The Lower This Number The Faster The Turn In Place Anim Will Activate */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Links, meta=(PinShownByDefault) )
	float MoveSensitivity;
	
	/** Seeing this in the log can help you decided what TurnSpeedModifierMAX to use  */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Logs)
	float ShowTurnRotationChangePerTick;
	
// FAnimNode_Base interface
public:
	
	// FAnimNode_Base interface
	virtual void Initialize(const FAnimationInitializeContext& Context) 	OVERRIDE;
	virtual void Update(const FAnimationUpdateContext & Context) 		OVERRIDE;
	virtual void Evaluate(FPoseContext& Output) 							OVERRIDE;
	// End of FAnimNode_Base interface

//~~~ Constructor ~~~
public:
	
	FAnimNode_VictoryTurnInPlace();
	
//Functions
protected:
	void DetermineUseTurnPose();
	void UpdateBlendAlpha();
	
protected:	
	
	//Our very own Blend node, yay! (makes this all super clear)
	FAnimationNode_TwoWayBlend OurVeryOwnBlend;
	
	AActor * OwningActor;
	FVector PrevLoc;
	FVector CurLoc;
	float PrevYaw;
	float CurYaw;
	float TurnAmountThisTick;
	bool WorldIsGame;
	
	//~~~ Blending ~~~
	float BlendDurationMult; //blend slower if moving slower
	float InternalBlendDuration; //divided the input by 100 just cause it looks better that way
	float BlendAlpha;
	bool BlendingIntoTurnPose; //false = blending out of
	
	FVector2D RangeIn;
	FVector2D RangeOut;
};

.CPP

// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.

#include "VictoryGame.h"

//#include "AnimationRuntime.h"

FAnimNode_VictoryTurnInPlace::FAnimNode_VictoryTurnInPlace()
	: FAnimNode_Base()
	, TurnBlendDuration(4.f)
	, TurnSpeedModifierMAX(4.333)
	, TurnSensitivity(0.777f)
	, MoveSensitivity(25.f)
{
	WorldIsGame = false;
	BlendDurationMult = 1;
	InternalBlendDuration = TurnBlendDuration / 100;
	
	RangeIn = FVector2D(0, TurnSpeedModifierMAX);
	RangeOut = FVector2D(0, 1);
	
	ShowTurnRotationChangePerTick = false;
}

void FAnimNode_VictoryTurnInPlace::Initialize(const FAnimationInitializeContext & Context) 
{
	//Init the Inputs
	BasePose.Initialize(Context);
	TurnPose.Initialize(Context);
	
	//Get the Actor Owner
	OwningActor = Context.AnimInstance-> GetSkelMeshComponent()->GetOwner(); 
	
	//Editor or Game?
	UWorld * TheWorld = Context.AnimInstance->GetWorld();
	if (!TheWorld) return;
	//~~~~~~~~~~~~~~~~
	
	WorldIsGame = (TheWorld->WorldType == EWorldType::Game);
	
	//~~~
	
	//~~~ Init the Blend ~~~
	OurVeryOwnBlend.A = BasePose;
	OurVeryOwnBlend.B = TurnPose;
	OurVeryOwnBlend.Initialize(Context);
}

void FAnimNode_VictoryTurnInPlace::DetermineUseTurnPose()
{
	//Delta time
	//Context.GetDeltaTime();
	
	//Get Current
	CurYaw = OwningActor->GetActorRotation().Yaw;
	CurLoc =  OwningActor->GetActorLocation();
	
	//~~~ Choose Turn Pose or Base Pose ~~~
		//Yaw Delta Amount
	TurnAmountThisTick = FMath::Abs(CurYaw - PrevYaw);
	if (TurnAmountThisTick < TurnSensitivity)
	{
		BlendingIntoTurnPose = false;
	}
	
	//Turning Amount is Sufficient and Movement is slow enough
	else if(FVector::DistSquared(CurLoc, PrevLoc) < MoveSensitivity)
	{
		BlendingIntoTurnPose = true;
	}
	
	//~~~ Save Previous ~~~
	PrevYaw = CurYaw;
	PrevLoc = CurLoc;
	
	//Log the Change in Rotation Per Tick
	if(ShowTurnRotationChangePerTick) UE_LOG(LogAnimation, Warning, TEXT("turn difference per tick,  %f"), TurnAmountThisTick);
	
	//~~~ Calc Blend Mult ~~~

	//In case this gets modified during game time
	RangeIn.Y = TurnSpeedModifierMAX;
	
	//Mapped Range
	BlendDurationMult = FMath::GetMappedRangeValue(RangeIn, RangeOut, TurnAmountThisTick);
}
void FAnimNode_VictoryTurnInPlace::UpdateBlendAlpha()
{
	if (BlendingIntoTurnPose)
	{
		if (BlendAlpha >= 1) BlendAlpha = 1;
		else BlendAlpha += InternalBlendDuration * BlendDurationMult; //modify blend-in by speed of turning
	}
	
	//Blending out
	else 
	{
		if (BlendAlpha <= 0) BlendAlpha = 0;
		else BlendAlpha -= InternalBlendDuration;
	}
}
void FAnimNode_VictoryTurnInPlace::Update(const FAnimationUpdateContext & Context)
{
	//EDITOR
	//Editor mode? just use the base pose
	if (!WorldIsGame)
	{
		BlendAlpha = 0;
	}
	
	//GAME
	//Actually in Game so the Owner Instance Should Exist
	else
	{
		//Try Again if not found
		if (!OwningActor) OwningActor = Context.AnimInstance->GetSkelMeshComponent()->GetOwner(); 
		
		//Not found
		if (!OwningActor) 
		{
			UE_LOG(LogAnimation, Warning, TEXT("FAnimNode_VictoryTurnInPlace::Update() Owning Actor was not found"));
			return;
			//~~~~~~~~~~~~~~~~~~~
		}
	
		//~~~ Determine Use Turn Pose ~~~
		DetermineUseTurnPose();
		
		//~~~ Calc Blend Alpha ~~~
		UpdateBlendAlpha();
	}
	
	//~~~ Do Updates ~~~

	//At end of Blend, only evaluate 1, save resources
	//**************************************************************************
	// FPoseLinkBase::Update Active Pose - this is what makes the glowing line thing happen and animations loop
	//**************************************************************************
	if (BlendAlpha >= 1) TurnPose.Update(Context);
	else if (BlendAlpha <= 0) BasePose.Update(Context);
	
	//Currently Blending
	else
	{		
		//Blend node below handles this now
		//BasePose.Update(Context);
		//TurnPose.Update(Context);
		
		//~~~ Update the Blend ~~~
		OurVeryOwnBlend.Alpha = BlendAlpha;
		OurVeryOwnBlend.Update(Context);
	}
	
	//***************************************
	// Evaluate Graph, see AnimNode_Base, AnimNodeBase.h
	EvaluateGraphExposedInputs.Execute(Context);
	//***************************************
}

void FAnimNode_VictoryTurnInPlace::Evaluate(FPoseContext & Output)
{
	//~~~ Fully In Base Pose ~~~
	if(BlendAlpha <= 0) BasePose.Evaluate(Output);
	
	//~~~ Fully In Turn Pose ~~~
	else if (BlendAlpha >= 1) TurnPose.Evaluate(Output);
	
	//~~~ Currently Blending ~~~
	else
	{
		OurVeryOwnBlend.Evaluate(Output);
	}	
}

World Is Game

Please note the use of WorldIsGame.

In the editor, there is no instanced version of the Character, so I do not run that part of the code.

Here is how you can determine if your node is running in the Editor preview or in the actual game!

WorldIsGame = (TheWorld->WorldType == EWorldType::Game);

Animation Graph Node

.H

// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "AnimGraphDefinitions.h"
#include "Kismet2/BlueprintEditorUtils.h"

#include "AnimGraphNode_VictoryTurnInPlace.generated.h"

//Whole point of this is to be wrapper for node struct
//		so it depends on it, and that node must compile first
//		for type to be recognized

UCLASS(MinimalAPI, dependson=FAnimNode_VictoryTurnInPlace)
class UAnimGraphNode_VictoryTurnInPlace : public UAnimGraphNode_Base
{
	GENERATED_UCLASS_BODY()

	UPROPERTY(EditAnywhere, Category=Settings)
	FAnimNode_VictoryTurnInPlace Node;

public:
	// UEdGraphNode interface
	virtual FString GetNodeTitle(ENodeTitleType::Type TitleType) const OVERRIDE;
	virtual FLinearColor GetNodeTitleColor() const OVERRIDE;
	virtual FString GetNodeCategory() const OVERRIDE;
	// End of UEdGraphNode interface

protected:
	// UAnimGraphNode_SkeletalControlBase interface
	virtual FString GetControllerDescription() const;
	// End of UAnimGraphNode_SkeletalControlBase interface
};

.CPP

// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.

#include "VictoryGame.h"

/////////////////////////////////////////////////////
// UAnimGraphNode_VictoryTurnInPlace

UAnimGraphNode_VictoryTurnInPlace::UAnimGraphNode_VictoryTurnInPlace(const FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
}

//Title Color!
FLinearColor UAnimGraphNode_VictoryTurnInPlace::GetNodeTitleColor() const 
{ 
	return FLinearColor(0,12,12,1);
}

//Node Category
FString UAnimGraphNode_VictoryTurnInPlace::GetNodeCategory() const
{
	return FString("Victory Anim Nodes");
}
FString UAnimGraphNode_VictoryTurnInPlace::GetControllerDescription() const
{
	return TEXT("~~~ Victory Turn In Place ~~~");
}

FString UAnimGraphNode_VictoryTurnInPlace::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	FString Result = *GetControllerDescription();
	Result += (TitleType == ENodeTitleType::ListView) ? TEXT("") : TEXT("\n");
	return Result;
}

Enjoy!

( )

In UE 4.7 you need adequate include headers for FAnim* classes :

  1. include "Runtime/Engine/Classes/Animation/AnimNodeBase.h"
  2. include "Runtime/Engine/Classes/Animation/InputScaleBias.h"
  3. include "Runtime/Engine/Classes/Animation/AnimNode_TwoWayBlend.h"