icon안동민 개발노트

C++에서 애니메이션 제어하기


 언리얼 엔진에서 C++ 코드를 통해 캐릭터 애니메이션을 직접 제어하는 것은 높은 수준의 커스터마이징과 최적화를 가능하게 합니다. 이 절에서는 다양한 애니메이션 제어 기법을 살펴보겠습니다.

기본적인 애니메이션 제어 기법

 애니메이션 인스턴스 조작

 애니메이션 인스턴스를 통해 애니메이션의 기본적인 파라미터를 제어할 수 있습니다.

UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    void UpdateAnimationParameters();
 
private:
    UPROPERTY()
    UMyAnimInstance* AnimInstance;
};
 
void AMyCharacter::UpdateAnimationParameters()
{
    AnimInstance = Cast<UMyAnimInstance>(GetMesh()->GetAnimInstance());
    if (AnimInstance)
    {
        AnimInstance->Speed = GetVelocity().Size();
        AnimInstance->bIsInAir = GetMovementComponent()->IsFalling();
    }
}

 몽타주 재생

 몽타주를 사용하여 특정 애니메이션 시퀀스를 재생할 수 있습니다.

UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintCallable, Category = "Animation")
    void PlayAttackMontage();
 
private:
    UPROPERTY(EditDefaultsOnly, Category = "Animation")
    UAnimMontage* AttackMontage;
};
 
void AMyCharacter::PlayAttackMontage()
{
    if (AttackMontage)
    {
        PlayAnimMontage(AttackMontage);
    }
}

 블렌드 스페이스 파라미터 조정

 블렌드 스페이스의 파라미터를 동적으로 조정하여 부드러운 애니메이션 전환을 구현할 수 있습니다.

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

고급 애니메이션 제어 기법

 프로시저럴 애니메이션 구현

 프로시저럴 애니메이션을 통해 동적으로 애니메이션을 생성할 수 있습니다.

UCLASS()
class UMyProceduralAnimationComponent : public UActorComponent
{
    GENERATED_BODY()
 
public:
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
 
private:
    void UpdateBoneTransforms();
};
 
void UMyProceduralAnimationComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
 
    UpdateBoneTransforms();
}
 
void UMyProceduralAnimationComponent::UpdateBoneTransforms()
{
    USkeletalMeshComponent* SkeletalMesh = GetOwner()->FindComponentByClass<USkeletalMeshComponent>();
    if (SkeletalMesh)
    {
        FTransform BoneTransform = SkeletalMesh->GetBoneTransform(BoneIndex);
        // 여기서 BoneTransform을 수정
        SkeletalMesh->SetBoneTransformByName(BoneName, BoneTransform, EBoneSpaces::ComponentSpace);
    }
}

 런타임 IK (Inverse Kinematics) 솔버 활용

 IK를 사용하여 캐릭터의 팔다리를 동적으로 조정할 수 있습니다.

UCLASS()
class UMyIKComponent : public UActorComponent
{
    GENERATED_BODY()
 
public:
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
 
private:
    void SolveIK();
};
 
void UMyIKComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
 
    SolveIK();
}
 
void UMyIKComponent::SolveIK()
{
    USkeletalMeshComponent* SkeletalMesh = GetOwner()->FindComponentByClass<USkeletalMeshComponent>();
    if (SkeletalMesh)
    {
        FTransform EffectorTransform = CalculateEffectorTransform();
        SkeletalMesh->SetMorphTarget(MorphTargetName, MorphTargetValue);
        // IK 솔버 로직 구현
    }
}

 애니메이션 커브를 이용한 동적 파라미터 제어

 애니메이션 커브를 사용하여 애니메이션 중 특정 파라미터를 동적으로 제어할 수 있습니다.

UCLASS()
class UMyAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
 
public:
    virtual void NativeUpdateAnimation(float DeltaSeconds) override;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
    float CurveValue;
};
 
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
    Super::NativeUpdateAnimation(DeltaSeconds);
 
    CurveValue = GetCurveValue(FName("MyCurve"));
    // CurveValue를 사용하여 애니메이션 파라미터 조정
}

애니메이션 그래프 로직 확장

 커스텀 애님 노드 클래스 구현

 커스텀 애님 노드를 구현하여 애니메이션 그래프의 기능을 확장할 수 있습니다.

UCLASS()
class MYGAME_API UAnimNode_MyCustomNode : public UAnimNode_Base
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, 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;
};
 
void UAnimNode_MyCustomNode::Evaluate_AnyThread(FPoseContext& Output)
{
    BasePose.Evaluate(Output);
    // 여기서 포즈 수정 로직 구현
}

 물리 기반 애니메이션과 스켈레탈 컨트롤 활용

 물리 시뮬레이션과 스켈레탈 컨트롤을 조합하여 더 자연스러운 애니메이션을 구현할 수 있습니다.

UCLASS()
class UMyPhysicsAnimComponent : public UActorComponent
{
    GENERATED_BODY()
 
public:
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
 
private:
    void UpdatePhysicsAnimation();
};
 
void UMyPhysicsAnimComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
 
    UpdatePhysicsAnimation();
}
 
void UMyPhysicsAnimComponent::UpdatePhysicsAnimation()
{
    USkeletalMeshComponent* SkeletalMesh = GetOwner()->FindComponentByClass<USkeletalMeshComponent>();
    if (SkeletalMesh)
    {
        SkeletalMesh->SetAllBodiesBelowSimulatePhysics(BoneName, true);
        // 추가적인 물리 시뮬레이션 로직
    }
}

성능 고려사항

  1. 애니메이션 업데이트 빈도 최적화
void AMyCharacter::OptimizeAnimationUpdate()
{
    USkeletalMeshComponent* Mesh = GetMesh();
    if (Mesh)
    {
        Mesh->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
        Mesh->bEnableUpdateRateOptimizations = true;
    }
}
  1. LOD(Level of Detail) 시스템 활용
void AMyCharacter::SetupLOD()
{
    USkeletalMeshComponent* Mesh = GetMesh();
    if (Mesh)
    {
        Mesh->bEnableLODSwitch = true;
        Mesh->MinLodModel = 0;
        Mesh->ForcedLodModel = 0;
    }
}

대규모 게임에서의 애니메이션 관리 전략

  1. 애니메이션 풀링 시스템 구현
UCLASS()
class UAnimationPoolManager : public UObject
{
    GENERATED_BODY()
 
public:
    UAnimInstance* GetAnimInstance(USkeletalMeshComponent* MeshComponent);
    void ReturnAnimInstance(UAnimInstance* Instance);
 
private:
    TArray<UAnimInstance*> Pool;
};
 
UAnimInstance* UAnimationPoolManager::GetAnimInstance(USkeletalMeshComponent* MeshComponent)
{
    if (Pool.Num() > 0)
    {
        UAnimInstance* Instance = Pool.Pop();
        Instance->InitializeAnimation();
        return Instance;
    }
    return NewObject<UAnimInstance>(MeshComponent);
}
 
void UAnimationPoolManager::ReturnAnimInstance(UAnimInstance* Instance)
{
    Instance->UninitializeAnimation();
    Pool.Add(Instance);
}
  1. 애니메이션 데이터 스트리밍
UCLASS()
class UAnimationStreamingManager : public UObject
{
    GENERATED_BODY()
 
public:
    void StreamInAnimation(TSoftObjectPtr<UAnimSequence> AnimationToStream);
    void StreamOutAnimation(UAnimSequence* AnimationToUnload);
 
private:
    TArray<UAnimSequence*> LoadedAnimations;
};
 
void UAnimationStreamingManager::StreamInAnimation(TSoftObjectPtr<UAnimSequence> AnimationToStream)
{
    if (AnimationToStream.IsValid())
    {
        LoadedAnimations.Add(AnimationToStream.Get());
    }
    else
    {
        AnimationToStream.LoadSynchronous();
        LoadedAnimations.Add(AnimationToStream.Get());
    }
}
 
void UAnimationStreamingManager::StreamOutAnimation(UAnimSequence* AnimationToUnload)
{
    LoadedAnimations.Remove(AnimationToUnload);
    AnimationToUnload->ConditionalBeginDestroy();
}

 C++를 통한 애니메이션 제어는 언리얼 엔진에서 높은 수준의 커스터마이징과 최적화를 가능하게 합니다. 기본적인 애니메이션 인스턴스 조작부터 복잡한 프로시저럴 애니메이션, IK 솔버, 물리 기반 애니메이션까지 다양한 기법을 활용할 수 있습니다.

 성능 최적화를 위해서는 애니메이션 업데이트 빈도 조절, LOD 시스템 활용, 그리고 효율적인 메모리 관리가 중요합니다. 대규모 게임에서는 애니메이션 풀링 시스템과 데이터 스트리밍을 구현하여 리소스를 효율적으로 관리해야 합니다.

 애니메이션 그래프 로직을 C++로 확장하고 커스텀 애님 노드를 구현함으로써 더욱 복잡하고 특화된 애니메이션 시스템을 구축할 수 있습니다. 이를 통해 게임의 요구사항에 맞는 고유한 애니메이션 솔루션을 개발할 수 있습니다.

 마지막으로, C++와 블루프린트의 장점을 적절히 조합하여 사용하는 것이 중요합니다. 성능 크리티컬한 부분은 C++로 구현하고, 빠른 이터레이션이 필요한 부분은 블루프린트로 구현하는 등 각 도구의 강점을 살려 효율적인 애니메이션 시스템을 구축해야 합니다.