Animation Node, Entire Source for a Turn In Place Node
Contents
Overview
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.
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 :
- include "Runtime/Engine/Classes/Animation/AnimNodeBase.h"
- include "Runtime/Engine/Classes/Animation/InputScaleBias.h"
- include "Runtime/Engine/Classes/Animation/AnimNode_TwoWayBlend.h"