Files
UnrealEngine/Engine/Source/Runtime/AnimGraphRuntime/Public/AnimCustomInstanceHelper.h
2025-05-18 13:04:45 +08:00

293 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
This is a binding/unbinding functions for custom instances, specially for Sequencer tracks
This is complicated because Sequencer Animation Track and ControlRig Tracks could be supported through this interface
and it encapsulates lots of complications inside.
You can use one Animation Track - it's because this track doesn't take input from other pose, so this is always source
You can use multiple ControlRig Tracks - this is because ControlRig could take inputs from other sources
However this is not end of it. The way sequencer works is to allow you to add/remove anytime or any place.
So this behaves binding/unbinding depending on if you're source (Animation Track) or not (ControlRig).
If you want to be used by Animation Track, you should derive from ISequencerAnimationSupport and implement proper interfaces.
Now, you want to support layering, you'll have to support DoesSupportDifferentSourceAnimInstance to be true, and allow it to be used as source input.
1. ControlRigLayerInstance : this does support different source anim instance, and use it as a source of animation
2. AnimSequencerInstance: this does not support different source anim instance, this acts as one.
The code is to support, depending what role you have, you can be bound differently, so that you don't disturb what's currently available
=============================================================================*/
#pragma once
#include "Animation/AnimInstance.h"
#include "AnimSequencerInstance.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/World.h"
#include "SequencerAnimationSupport.h"
class FAnimCustomInstanceHelper
{
public:
/**
* Called to bind a typed UAnimCustomInstance to an existing skeletal mesh component
* @return the current (or newly created) UAnimCustomInstance
*/
template<typename InstanceClassType>
static InstanceClassType* BindToSkeletalMeshComponent(USkeletalMeshComponent* InSkeletalMeshComponent, bool& bOutWasCreated)
{
bOutWasCreated = false;
// make sure to tick and refresh all the time when ticks
// @TODO: this needs restoring post-binding
InSkeletalMeshComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
#if WITH_EDITOR
InSkeletalMeshComponent->SetUpdateAnimationInEditor(true);
InSkeletalMeshComponent->SetUpdateClothInEditor(true);
#endif
TArray<USceneComponent*> ChildComponents;
InSkeletalMeshComponent->GetChildrenComponents(true, ChildComponents);
for (USceneComponent* ChildComponent : ChildComponents)
{
USkeletalMeshComponent* ChildSkelMeshComp = Cast<USkeletalMeshComponent>(ChildComponent);
if (ChildSkelMeshComp)
{
ChildSkelMeshComp->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
#if WITH_EDITOR
ChildSkelMeshComp->SetUpdateAnimationInEditor(true);
ChildSkelMeshComp->SetUpdateClothInEditor(true);
#endif
}
}
// we use sequence instance if it's using anim blueprint that matches. Otherwise, we create sequence player
// this might need more check - i.e. making sure if it's same skeleton and so on,
// Ideally we could just call NeedToSpawnAnimScriptInstance call, which is protected now
const bool bShouldCreateCustomInstance = ShouldCreateCustomInstancePlayer(InSkeletalMeshComponent);
UAnimInstance* CurrentAnimInstance = InSkeletalMeshComponent->AnimScriptInstance;
// See if we have SequencerInterface from current instance
ISequencerAnimationSupport* CurrentSequencerInterface = Cast<ISequencerAnimationSupport>(CurrentAnimInstance);
const bool bCurrentlySequencerInterface = CurrentSequencerInterface != nullptr;
const bool bCreateSequencerInterface = InstanceClassType::StaticClass()->ImplementsInterface(USequencerAnimationSupport::StaticClass());
bool bSupportDifferentSourceAnimInstance = false;
if (bCreateSequencerInterface)
{
InstanceClassType* DefaultObject = InstanceClassType::StaticClass()->template GetDefaultObject<InstanceClassType>();
ISequencerAnimationSupport* SequencerSupporter = Cast<ISequencerAnimationSupport>(DefaultObject);
bSupportDifferentSourceAnimInstance = SequencerSupporter && SequencerSupporter->DoesSupportDifferentSourceAnimInstance();
}
// if it should use sequence instance and current one doesn't support Sequencer Interface, we fall back to old behavior
if (bShouldCreateCustomInstance && !bCurrentlySequencerInterface)
{
// this has to wrap around with this because we don't want to reinitialize everytime they come here
// SetAnimationMode will reinitiaize it even if it's same, so make sure we just call SetAnimationMode if not AnimationCustomMode
if (InSkeletalMeshComponent->GetAnimationMode() != EAnimationMode::AnimationCustomMode)
{
InSkeletalMeshComponent->SetAnimationMode(EAnimationMode::AnimationCustomMode);
}
if (Cast<InstanceClassType>(InSkeletalMeshComponent->AnimScriptInstance) == nullptr || !InSkeletalMeshComponent->AnimScriptInstance->GetClass()->IsChildOf(InstanceClassType::StaticClass()))
{
InstanceClassType* SequencerInstance = NewObject<InstanceClassType>(InSkeletalMeshComponent, InstanceClassType::StaticClass());
InSkeletalMeshComponent->AnimScriptInstance = SequencerInstance;
InSkeletalMeshComponent->AnimScriptInstance->InitializeAnimation();
bOutWasCreated = true;
return SequencerInstance;
}
// if it's the same type it's expecting, returns the one
else if (InSkeletalMeshComponent->AnimScriptInstance->GetClass()->IsChildOf(InstanceClassType::StaticClass()))
{
return Cast<InstanceClassType>(InSkeletalMeshComponent->AnimScriptInstance);
}
}
else
{
// if it's the same type it's expecting, returns the one
if (CurrentAnimInstance->GetClass()->IsChildOf(InstanceClassType::StaticClass()))
{
return Cast<InstanceClassType>(InSkeletalMeshComponent->AnimScriptInstance);
}
// if currently it is sequencer interface, check to see if
else if (bCurrentlySequencerInterface)
{
// this is a case where SequencerInstance is created later, currently it has SequencerInteface
UAnimInstance* CurSourceInstance = CurrentSequencerInterface->GetSourceAnimInstance();
// if no source, create new one, and assign the new instance if current sequencer interface supports
if (CurSourceInstance == nullptr)
{
// if current doesn't have source instance and if it does support different source animation
if (CurrentSequencerInterface->DoesSupportDifferentSourceAnimInstance())
{
// create new one requested, and set to new source
InstanceClassType* NewSequencerInstance = NewObject<InstanceClassType>(InSkeletalMeshComponent, InstanceClassType::StaticClass());
//if it's sequencer inteface, create one, and assign
NewSequencerInstance->InitializeAnimation();
CurrentSequencerInterface->SetSourceAnimInstance(NewSequencerInstance);
bOutWasCreated = true;
return NewSequencerInstance;
}
else
{
UE_LOG(LogAnimation, Warning, TEXT("Currently Sequencer doesn't support Source Instance. They're not compatible to work together."));
}
}
// if source is same as what's requested, just return this.
else if (CurSourceInstance->GetClass()->IsChildOf(InstanceClassType::StaticClass()))
{
// nothing to do?
// it has already same type
return Cast<InstanceClassType>(CurSourceInstance);
}
// if this doesn't support different source anim instances, but the new class does
// see if we could switch it up
else if (bCreateSequencerInterface && bSupportDifferentSourceAnimInstance && !CurrentSequencerInterface->DoesSupportDifferentSourceAnimInstance())
{
InstanceClassType* NewInstance = NewObject<InstanceClassType>(InSkeletalMeshComponent, InstanceClassType::StaticClass());
ISequencerAnimationSupport* NewSequencerInterface = Cast<ISequencerAnimationSupport>(NewInstance);
check(NewSequencerInterface);
if (NewSequencerInterface->DoesSupportDifferentSourceAnimInstance())
{
InSkeletalMeshComponent->AnimScriptInstance = NewInstance;
ensureAlways(NewInstance != CurSourceInstance);
NewSequencerInterface->SetSourceAnimInstance(CurSourceInstance);
InSkeletalMeshComponent->AnimScriptInstance->InitializeAnimation();
bOutWasCreated = true;
return Cast<InstanceClassType>(InSkeletalMeshComponent->AnimScriptInstance);
}
}
}
// if requested one is support sequencer animation?
else if (bCreateSequencerInterface && bSupportDifferentSourceAnimInstance)
{
InstanceClassType* NewInstance = NewObject<InstanceClassType>(InSkeletalMeshComponent, InstanceClassType::StaticClass());
ISequencerAnimationSupport* NewSequencerInterface = Cast<ISequencerAnimationSupport>(NewInstance);
check(NewSequencerInterface);
if (NewSequencerInterface->DoesSupportDifferentSourceAnimInstance())
{
InSkeletalMeshComponent->AnimScriptInstance = NewInstance;
NewSequencerInterface->SetSourceAnimInstance(CurrentAnimInstance);
InSkeletalMeshComponent->AnimScriptInstance->InitializeAnimation();
bOutWasCreated = true;
return Cast<InstanceClassType>(InSkeletalMeshComponent->AnimScriptInstance);
}
}
}
return nullptr;
}
/** Called to unbind a UAnimCustomInstance to an existing skeletal mesh component */
template<typename InstanceClassType>
static void UnbindFromSkeletalMeshComponent(USkeletalMeshComponent* InSkeletalMeshComponent)
{
#if WITH_EDITOR
InSkeletalMeshComponent->SetUpdateAnimationInEditor(false);
InSkeletalMeshComponent->SetUpdateClothInEditor(false);
#endif
if (InSkeletalMeshComponent->GetAnimationMode() == EAnimationMode::Type::AnimationCustomMode)
{
UAnimInstance* AnimInstance = InSkeletalMeshComponent->GetAnimInstance();
// if same type, we're fine
InstanceClassType* SequencerInstance = Cast<InstanceClassType>(AnimInstance);
if (SequencerInstance)
{
bool bClearAnimScriptInstance = true;
if(ISequencerAnimationSupport* SequencerInterface = Cast<ISequencerAnimationSupport>(AnimInstance))
{
if (SequencerInterface->DoesSupportDifferentSourceAnimInstance())
{
UAnimInstance* SourceAnimInstance = SequencerInterface->GetSourceAnimInstance();
// if we have source, replace with it
if (SourceAnimInstance)
{
// clear before you remove
SequencerInterface->SetSourceAnimInstance(nullptr);
InSkeletalMeshComponent->AnimScriptInstance = SourceAnimInstance;
bClearAnimScriptInstance = false;
}
}
}
if(bClearAnimScriptInstance)
{
InSkeletalMeshComponent->ClearAnimScriptInstance();
}
}
else // if not, we'd like to see if SequencerSupport
{
ISequencerAnimationSupport* SequencerInterface = Cast<ISequencerAnimationSupport>(AnimInstance);
if (SequencerInterface && SequencerInterface->DoesSupportDifferentSourceAnimInstance())
{
UAnimInstance* SourceInstance = SequencerInterface->GetSourceAnimInstance();
SequencerInstance = Cast<InstanceClassType>(SourceInstance);
if (SequencerInstance)
{
SequencerInterface->SetSourceAnimInstance(nullptr);
}
// this can be animBP, we want to return that
else if (SourceInstance)
{
SequencerInterface->SetSourceAnimInstance(nullptr);
InSkeletalMeshComponent->AnimScriptInstance = SourceInstance;
}
}
}
}
else if (InSkeletalMeshComponent->GetAnimationMode() == EAnimationMode::Type::AnimationBlueprint)
{
UAnimInstance* AnimInstance = InSkeletalMeshComponent->GetAnimInstance();
ISequencerAnimationSupport* SequencerInterface = Cast<ISequencerAnimationSupport>(AnimInstance);
if (SequencerInterface && SequencerInterface->DoesSupportDifferentSourceAnimInstance())
{
UAnimInstance* SourceInstance = SequencerInterface->GetSourceAnimInstance();
if (SourceInstance)
{
SequencerInterface->SetSourceAnimInstance(nullptr);
InSkeletalMeshComponent->AnimScriptInstance = SourceInstance;
AnimInstance = SourceInstance;
}
}
if (AnimInstance)
{
const TArray<UAnimInstance*>& LinkedInstances = const_cast<const USkeletalMeshComponent*>(InSkeletalMeshComponent)->GetLinkedAnimInstances();
for (UAnimInstance* LinkedInstance : LinkedInstances)
{
// Sub anim instances are always forced to do a parallel update
LinkedInstance->UpdateAnimation(0.0f, false, UAnimInstance::EUpdateAnimationFlag::ForceParallelUpdate);
}
AnimInstance->UpdateAnimation(0.0f, false);
}
// Update space bases to reset it back to ref pose
InSkeletalMeshComponent->RefreshBoneTransforms();
InSkeletalMeshComponent->RefreshFollowerComponents();
InSkeletalMeshComponent->UpdateComponentToWorld();
}
// if not game world, don't clean this up
if (InSkeletalMeshComponent->GetWorld() != nullptr && InSkeletalMeshComponent->GetWorld()->IsGameWorld() == false)
{
InSkeletalMeshComponent->ClearMotionVector();
}
}
private:
/** Helper function for BindToSkeletalMeshComponent */
static ANIMGRAPHRUNTIME_API bool ShouldCreateCustomInstancePlayer(const USkeletalMeshComponent* SkeletalMeshComponent);
};