AnimGraph 실행 흐름
UAnimInstance, NativeUpdate, FAnimInstanceProxy의 내부 동작과 멀티스레드 평가 메커니즘을 심층 분석합니다.
UAnimInstance 아키텍처
Animation Blueprint의 C++ 기반 클래스 UAnimInstance의 구조와 라이프사이클
UAnimInstance는 Animation Blueprint의 C++ 백엔드입니다. USkeletalMeshComponent마다 하나의 UAnimInstance가 존재하며, 게임플레이 데이터를 읽어 AnimGraph에 전달하고, 애니메이션 평가 결과를 관리합니다.
// 커스텀 AnimInstance 클래스
UCLASS()
class UMyAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
// 초기화 - 컴포넌트 설정 시 한 번 호출
virtual void NativeInitializeAnimation() override;
// 매 프레임 게임 스레드에서 호출
virtual void NativeUpdateAnimation(float DeltaSeconds) override;
// AnimGraph 평가 완료 후 게임 스레드에서 호출
virtual void NativePostEvaluateAnimation() override;
UPROPERTY(BlueprintReadOnly, Category = "Animation")
float Speed = 0.f;
UPROPERTY(BlueprintReadOnly, Category = "Animation")
float Direction = 0.f;
UPROPERTY(BlueprintReadOnly, Category = "Animation")
bool bIsInAir = false;
};
FAnimInstanceProxy와 스레드 안전성
AnimGraph 노드가 워커 스레드에서 안전하게 실행되기 위한 프록시 패턴
FAnimInstanceProxy는 UAnimInstance의 데이터를 워커 스레드에서 안전하게 접근하기 위한 프록시 구조체입니다. AnimGraph 노드는 UAnimInstance가 아닌 FAnimInstanceProxy를 통해서만 데이터에 접근합니다.
| 메서드 | 스레드 | 역할 |
|---|---|---|
PreUpdate() |
Game Thread | UAnimInstance에서 데이터를 Proxy로 복사 |
Update(DeltaTime) |
Worker Thread | AnimGraph 노드 Update 호출, Montage 틱 |
Evaluate(Output) |
Worker Thread | AnimGraph 노드 트리 평가, FCompactPose 생성 |
PostUpdate() |
Game Thread | Proxy 결과를 UAnimInstance로 복사 |
AnimGraph 노드의 Update_AnyThread와 Evaluate_AnyThread에서는 UAnimInstance에 직접 접근하면 안 됩니다. 이 함수들은 워커 스레드에서 실행될 수 있으며, 게임 스레드의 UAnimInstance 데이터와 경합(race condition)이 발생합니다. 반드시 FAnimInstanceProxy를 통해 데이터에 접근하세요.
// 커스텀 AnimInstanceProxy
struct FMyAnimInstanceProxy : public FAnimInstanceProxy
{
FMyAnimInstanceProxy() = default;
FMyAnimInstanceProxy(UAnimInstance* InAnimInstance)
: FAnimInstanceProxy(InAnimInstance) {}
// 게임 스레드에서 호출 - UAnimInstance 데이터 복사
virtual void PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) override
{
Super::PreUpdate(InAnimInstance, DeltaSeconds);
UMyAnimInstance* MyAnim = Cast<UMyAnimInstance>(InAnimInstance);
CachedSpeed = MyAnim->Speed;
CachedDirection = MyAnim->Direction;
}
// 워커 스레드에서 안전하게 사용 가능한 캐시 데이터
float CachedSpeed = 0.f;
float CachedDirection = 0.f;
};
// GetProxyOnAnyThread / GetProxyOnGameThread 사용
FAnimInstanceProxy& Proxy = AnimInstance->GetProxyOnGameThread<FMyAnimInstanceProxy>();
AnimGraph 노드 트리 평가
FAnimNode_Base를 루트로 하는 AnimGraph 노드 트리의 평가 메커니즘
AnimGraph는 FAnimNode_Base를 상속하는 노드들의 트리 구조입니다. 최종 출력에서 시작하여 트리를 역방향으로 순회하며, 각 노드가 순차적으로 포즈를 계산하고 블렌딩합니다.
Initialize_AnyThread
노드 초기화. 필요한 리소스 할당, 본 참조 캐시. AnimGraph 로드 시 한 번 호출됩니다.
Update_AnyThread
매 프레임 노드 상태 업데이트. DeltaTime 적용, 웨이트 계산, Blend 진행 등. 워커 스레드에서 실행됩니다.
Evaluate_AnyThread
실제 포즈 계산. 입력 포즈를 받아 처리 후 출력 포즈를 FPoseContext에 기록합니다.
CacheBones_AnyThread
LOD 변경 시 호출. 새 LOD의 RequiredBones에 맞게 본 인덱스를 재매핑합니다.
// 커스텀 AnimNode 구현 예시
USTRUCT(BlueprintInternalUseOnly)
struct FAnimNode_ScaleBySpeed : public FAnimNode_Base
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Links)
FPoseLink BasePose; // 입력 포즈 링크
UPROPERTY(EditAnywhere, Category = Settings)
float SpeedScale = 1.f;
// 노드 업데이트 (워커 스레드 가능)
virtual void Update_AnyThread(const FAnimationUpdateContext& Context) override
{
BasePose.Update(Context);
// DeltaTime 기반 로직
}
// 포즈 평가 (워커 스레드 가능)
virtual void Evaluate_AnyThread(FPoseContext& Output) override
{
BasePose.Evaluate(Output);
// 루트 본 스케일 수정
FCompactPoseBoneIndex RootIndex(0);
FTransform& RootTransform = Output.Pose[RootIndex];
RootTransform.ScaleTranslation(SpeedScale);
}
};
NativeUpdate와 Blueprint 연동
C++ NativeUpdate와 Blueprint EventGraph의 협력 패턴
실무에서는 C++ NativeUpdateAnimation에서 성능 민감한 데이터 수집을 수행하고, Blueprint EventGraph에서는 시각적 로직이나 간단한 분기 처리를 담당하는 하이브리드 패턴이 일반적입니다.
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
APawn* OwnerPawn = TryGetPawnOwner();
if (!OwnerPawn) return;
// 속도 계산 (C++에서 효율적으로)
FVector Velocity = OwnerPawn->GetVelocity();
Speed = Velocity.Size2D();
// 이동 방향 계산
FRotator ActorRotation = OwnerPawn->GetActorRotation();
Direction = CalculateDirection(Velocity, ActorRotation);
// 공중 여부
ACharacter* Character = Cast<ACharacter>(OwnerPawn);
if (Character)
{
bIsInAir = Character->GetCharacterMovement()->IsFalling();
}
}
// Blueprint에서 접근 가능한 프로퍼티로 노출
// AnimBP EventGraph에서 추가 로직 수행 가능
UE5에서는 Property Access 시스템을 통해 AnimGraph 노드에서 직접 프로퍼티를 바인딩할 수 있습니다. EventGraph를 거치지 않고 AnimGraph 핀에서 바로 Character.Movement.Velocity 같은 경로로 접근하면, Blueprint VM 오버헤드를 줄이고 멀티스레드 평가와의 호환성을 높일 수 있습니다.
이 함수가 false를 반환하면 애니메이션 업데이트가 워커 스레드로 완전히 오프로드됩니다. 게임 스레드에서의 즉시 업데이트가 필요하지 않다면 false를 반환하도록 오버라이드하여 게임 스레드 부하를 줄일 수 있습니다.
핵심 요약
- UAnimInstance는 Animation Blueprint의 C++ 백엔드로, NativeUpdate/BlueprintUpdate/PostEvaluate 라이프사이클을 따른다.
- FAnimInstanceProxy는 워커 스레드에서 AnimGraph를 안전하게 평가하기 위한 프록시 패턴이다.
- AnimGraph 노드(FAnimNode_Base)는 Initialize/Update/Evaluate/CacheBones 가상 함수로 동작한다.
- AnimGraph는 출력에서 입력 방향으로 역방향 트리 순회하며 포즈를 계산한다.
- C++ NativeUpdate에서 데이터 수집, Blueprint에서 시각적 로직을 처리하는 하이브리드 패턴이 권장된다.
도전 과제
배운 내용을 직접 실습해보세요
Animation Blueprint를 생성하고, Event Graph에서 캐릭터의 Velocity를 가져와 Speed 변수에 저장하세요. AnimGraph에서 이 변수를 사용하여 Idle/Walk 애니메이션을 전환하는 기본 그래프를 구성하세요.
Blueprint에서 NativeUpdateAnimation 이벤트를 오버라이드하여 매 프레임 캐릭터 상태(Speed, IsFalling, Direction)를 업데이트하세요. Try Get Pawn Owner로 캐릭터를 캐스팅하고, 변수를 AnimGraph에서 참조하세요.
C++에서 UAnimInstance를 상속한 커스텀 AnimInstance 클래스를 만들고, NativeUpdateAnimation에서 FAnimInstanceProxy를 활용하여 워커 스레드 안전한 변수 업데이트를 구현하세요. Property Access 노드를 사용하여 게임 스레드 접근 없이 AnimGraph에서 직접 변수를 읽는 패턴도 적용하세요.