icon안동민 개발노트

애니메이션 블루프린트 기초


 애니메이션 블루프린트는 언리얼 엔진에서 캐릭터의 복잡한 애니메이션 로직을 시각적으로 구현할 수 있게 해주는 강력한 도구입니다.

 이 절에서는 애니메이션 블루프린트의 기본 구조와 C++에서 이를 제어하고 확장하는 방법을 살펴보겠습니다.

애니메이션 블루프린트의 기본 구조

 애니메이션 블루프린트는 크게 애님 그래프, 이벤트 그래프, 그리고 스테이트 머신으로 구성됩니다.

 애님 그래프

 애님 그래프는 포즈를 계산하고 블렌딩하는 주요 부분입니다. C++에서 애님 그래프의 결과에 접근하려면 다음과 같이 할 수 있습니다.

UCLASS()
class UMyAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
 
public:
    virtual void NativeUpdateAnimation(float DeltaSeconds) override;
 
private:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation", meta = (AllowPrivateAccess = "true"))
    float Speed;
};
 
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
    Super::NativeUpdateAnimation(DeltaSeconds);
 
    APawn* Pawn = TryGetPawnOwner();
    if (Pawn)
    {
        Speed = Pawn->GetVelocity().Size();
    }
}

 스테이트 머신

 스테이트 머신은 캐릭터의 다양한 상태를 관리합니다. C++에서 현재 스테이트를 확인하거나 변경하려면:

UCLASS()
class UMyAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintCallable, Category = "Animation")
    void SetCurrentState(FName NewState);
 
    UFUNCTION(BlueprintPure, Category = "Animation")
    FName GetCurrentState() const;
 
private:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation", meta = (AllowPrivateAccess = "true"))
    FName CurrentState;
};
 
void UMyAnimInstance::SetCurrentState(FName NewState)
{
    CurrentState = NewState;
}
 
FName UMyAnimInstance::GetCurrentState() const
{
    return CurrentState;
}

 블렌드 스페이스

 블렌드 스페이스는 여러 애니메이션을 부드럽게 혼합합니다. C++에서 블렌드 스페이스 파라미터를 조정하려면:

UCLASS()
class UMyAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
 
public:
    virtual void NativeUpdateAnimation(float DeltaSeconds) override;
 
private:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation", meta = (AllowPrivateAccess = "true"))
    float DirectionAngle;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation", meta = (AllowPrivateAccess = "true"))
    float Speed;
};
 
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
    Super::NativeUpdateAnimation(DeltaSeconds);
 
    APawn* Pawn = TryGetPawnOwner();
    if (Pawn)
    {
        FVector Velocity = Pawn->GetVelocity();
        Speed = Velocity.Size();
        DirectionAngle = FMath::Atan2(Velocity.Y, Velocity.X);
    }
}

C++에서 커스텀 애니메이션 노드 생성

 커스텀 애니메이션 노드를 생성하여 애니메이션 블루프린트의 기능을 확장할 수 있습니다.

UCLASS()
class MYGAME_API UAnimNode_MyCustomNode : public UAnimNode_Base
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Links)
    FPoseLink BasePose;
 
    virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override;
    virtual void CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) override;
    virtual void Update_AnyThread(const FAnimationUpdateContext& Context) override;
    virtual void Evaluate_AnyThread(FPoseContext& Output) override;
 
private:
    // 커스텀 로직을 위한 변수들
};
 
void UAnimNode_MyCustomNode::Evaluate_AnyThread(FPoseContext& Output)
{
    BasePose.Evaluate(Output);
    // 여기에 포즈 수정 로직 추가
}

애니메이션 블루프린트 인스턴스 접근 및 변수 조작

 게임플레이 코드에서 애니메이션 블루프린트 인스턴스에 접근하고 변수를 조작하는 방법:

UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    void UpdateAnimationState();
 
private:
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation", meta = (AllowPrivateAccess = "true"))
    UMyAnimInstance* AnimInstance;
};
 
void AMyCharacter::UpdateAnimationState()
{
    AnimInstance = Cast<UMyAnimInstance>(GetMesh()->GetAnimInstance());
    if (AnimInstance)
    {
        AnimInstance->SetCurrentState(FName("Running"));
        // 다른 변수들도 여기서 업데이트
    }
}

애니메이션 블루프린트와 게임플레이 코드의 효과적인 연동

  1. 이벤트 기반 통신
UCLASS()
class UMyAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
 
public:
    DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAnimationEventSignature);
 
    UPROPERTY(BlueprintAssignable, Category = "Animation")
    FOnAnimationEventSignature OnJumpStart;
 
    UFUNCTION(BlueprintCallable, Category = "Animation")
    void TriggerJumpStart();
};
 
void UMyAnimInstance::TriggerJumpStart()
{
    OnJumpStart.Broadcast();
}
  1. 인터페이스를 통한 통신
UINTERFACE(MinimalAPI, Blueprintable)
class UAnimationCommunicationInterface : public UInterface
{
    GENERATED_BODY()
};
 
class IAnimationCommunicationInterface
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Animation")
    void OnAnimationEvent(FName EventName);
};

애니메이션 시스템의 성능 최적화 기법

  1. LOD (Level of Detail) 시스템 활용
UCLASS()
class UMyAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
 
public:
    virtual void NativeUpdateAnimation(float DeltaSeconds) override;
 
private:
    void UpdateLOD();
};
 
void UMyAnimInstance::UpdateLOD()
{
    USkeletalMeshComponent* MeshComponent = GetOwningComponent();
    if (MeshComponent)
    {
        int32 LODLevel = MeshComponent->GetPredictedLODLevel();
        // LOD 레벨에 따라 애니메이션 복잡도 조절
    }
}
  1. 애니메이션 압축 설정
void AMyCharacter::SetupAnimationCompression()
{
    UAnimSequence* AnimSequence = LoadObject<UAnimSequence>(nullptr, TEXT("/Game/Animations/MyAnimation"));
    if (AnimSequence)
    {
        AnimSequence->CompressionSettings.TranslationCompressionFormat = ACF_Float96NoW;
        AnimSequence->CompressionSettings.RotationCompressionFormat = ACF_Float96NoW;
        AnimSequence->CompressionSettings.ScaleCompressionFormat = ACF_Float96NoW;
        AnimSequence->RequestSyncAnimRecompression();
    }
}

복잡한 애니메이션 로직의 디버깅 방법

  1. 로깅 활용
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
    Super::NativeUpdateAnimation(DeltaSeconds);
 
    UE_LOG(LogAnimation, Verbose, TEXT("Current State: %s, Speed: %f"), *GetCurrentState().ToString(), Speed);
}
  1. 비주얼 디버깅
void UMyAnimInstance::DrawDebug(UCanvas* Canvas, APlayerController* Controller)
{
    Super::DrawDebug(Canvas, Controller);
 
    FVector2D ScreenPosition(50.0f, 50.0f);
    FString DebugString = FString::Printf(TEXT("Current State: %s"), *GetCurrentState().ToString());
    FCanvasTextItem TextItem(ScreenPosition, FText::FromString(DebugString), GEngine->GetSmallFont(), FLinearColor::White);
    Canvas->DrawItem(TextItem);
}

대규모 프로젝트에서의 애니메이션 관리 전략

  1. 모듈화

 애니메이션 로직을 재사용 가능한 작은 단위로 분리합니다.

UCLASS()
class UAnimationModule_Locomotion : public UObject
{
    GENERATED_BODY()
 
public:
    void UpdateLocomotion(UMyAnimInstance* AnimInstance);
};
 
void UAnimationModule_Locomotion::UpdateLocomotion(UMyAnimInstance* AnimInstance)
{
    // 이동 관련 애니메이션 로직
}
  1. 데이터 주도 설계

 애니메이션 설정을 데이터 에셋으로 관리합니다.

UCLASS()
class UAnimationConfig : public UDataAsset
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, Category = "Animation")
    float WalkSpeed;
 
    UPROPERTY(EditAnywhere, Category = "Animation")
    float RunSpeed;
 
    UPROPERTY(EditAnywhere, Category = "Animation")
    UBlendSpace* LocomotionBlendSpace;
};
  1. 버전 관리 및 에셋 종속성 추적

 애니메이션 에셋의 버전을 관리하고 종속성을 추적합니다.

UCLASS()
class UAnimationVersionManager : public UObject
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintCallable, Category = "Animation")
    bool IsAnimationVersionCompatible(UAnimSequence* Animation, int32 RequiredVersion);
 
    UFUNCTION(BlueprintCallable, Category = "Animation")
    TArray<UAnimSequence*> GetDependentAnimations(UAnimSequence* Animation);
};

 애니메이션 블루프린트는 언리얼 엔진의 강력한 기능 중 하나로, 복잡한 애니메이션 로직을 효과적으로 구현할 수 있게 해줍니다. C++와의 연동을 통해 더욱 강력하고 유연한 애니메이션 시스템을 구축할 수 있습니다.

 성능 최적화를 위해 LOD 시스템을 활용하고, 애니메이션 압축 설정을 적절히 조정해야 합니다. 복잡한 애니메이션 로직을 디버깅할 때는 로깅과 비주얼 디버깅 도구를 활용하면 효과적입니다.

 대규모 프로젝트에서는 애니메이션 로직의 모듈화, 데이터 주도 설계, 그리고 체계적인 버전 관리가 중요합니다. 이를 통해 애니메이션 시스템의 확장성과 유지보수성을 높일 수 있습니다.

 마지막으로, 애니메이션 블루프린트와 C++ 코드의 균형을 잘 맞추는 것이 중요합니다. 복잡한 로직은 C++로 구현하고, 빠른 이터레이션이 필요한 부분은 블루프린트로 구현하는 등 각 도구의 장점을 살려 사용해야 합니다.