Animation Node, Entire Source for a Turn In Place Node

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 direction...

Updated about 3 years ago Edit Page Revisions

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.

File: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 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 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&amp; 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"