icon안동민 개발노트

스켈레탈 메시, 애니메이션 시퀀스


 언리얼 엔진의 스켈레탈 메시 시스템과 애니메이션 시퀀스는 게임 내 캐릭터에 생동감을 불어넣는 핵심 요소입니다.

 이 절에서는 C++ 관점에서 이들의 기본 개념과 구현 방법을 살펴보겠습니다.

스켈레탈 메시의 구조

 스켈레탈 메시는 뼈대(skeleton)와 스킨(skin)으로 구성됩니다.

 C++에서 스켈레탈 메시 컴포넌트를 생성하고 접근하는 방법은 다음과 같습니다.

UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    AMyCharacter();
 
    UPROPERTY(VisibleAnywhere, Category = "Mesh")
    USkeletalMeshComponent* MeshComponent;
};
 
AMyCharacter::AMyCharacter()
{
    MeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("CharacterMesh"));
    MeshComponent->SetupAttachment(RootComponent);
}

본 계층 구조 접근

 스켈레탈 메시의 본 구조에 접근하여 특정 본의 위치나 회전을 조작할 수 있습니다.

void AMyCharacter::ManipulateBone(FName BoneName)
{
    USkeletalMeshComponent* SkelMesh = GetMesh();
    if (SkelMesh)
    {
        int32 BoneIndex = SkelMesh->GetBoneIndex(BoneName);
        if (BoneIndex != INDEX_NONE)
        {
            FTransform BoneTransform = SkelMesh->GetBoneTransform(BoneIndex);
            BoneTransform.SetRotation(FQuat::MakeFromEuler(FVector(0, 0, 45)));
            SkelMesh->SetBoneTransformByName(BoneName, BoneTransform, EBoneSpaces::ComponentSpace);
        }
    }
}

애니메이션 시퀀스 로드 및 재생

 애니메이션 시퀀스를 로드하고 재생하는 방법은 다음과 같습니다.

void AMyCharacter::PlayCustomAnimation()
{
    UAnimationAsset* AnimAsset = LoadObject<UAnimationAsset>(nullptr, TEXT("/Game/Animations/MyAnimation"));
    if (AnimAsset)
    {
        MeshComponent->PlayAnimation(AnimAsset, false);
    }
}

애니메이션 블루프린트와 C++ 코드의 상호작용

 애니메이션 블루프린트와 C++ 코드를 연동하려면 변수와 함수를 노출시켜야 합니다.

UCLASS()
class UMyAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
    float Speed;
 
    UFUNCTION(BlueprintCallable, Category = "Animation")
    void UpdateSpeed(float NewSpeed);
};
 
void UMyAnimInstance::UpdateSpeed(float NewSpeed)
{
    Speed = NewSpeed;
}

 C++ 코드에서 애니메이션 인스턴스에 접근하여 값을 업데이트할 수 있습니다.

void AMyCharacter::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
 
    UMyAnimInstance* AnimInstance = Cast<UMyAnimInstance>(MeshComponent->GetAnimInstance());
    if (AnimInstance)
    {
        float CurrentSpeed = GetVelocity().Size();
        AnimInstance->UpdateSpeed(CurrentSpeed);
    }
}

런타임에 애니메이션 속성 조정

 애니메이션의 재생 속도나 루프 설정 등을 런타임에 조정할 수 있습니다.

void AMyCharacter::AdjustAnimationProperties()
{
    UAnimationAsset* CurrentAnim = MeshComponent->GetAnimationAsset();
    if (CurrentAnim)
    {
        MeshComponent->SetPlayRate(2.0f); // 재생 속도를 2배로 설정
        MeshComponent->SetLooping(true); // 루프 설정
    }
}

애니메이션 노티파이 이벤트 처리

 애니메이션 노티파이 이벤트를 C++에서 처리하려면 다음과 같이 구현합니다.

UCLASS()
class UMyAnimNotifyState : public UAnimNotifyState
{
    GENERATED_BODY()
 
public:
    virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration) override;
    virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
};
 
void UMyAnimNotifyState::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration)
{
    AMyCharacter* Character = Cast<AMyCharacter>(MeshComp->GetOwner());
    if (Character)
    {
        Character->StartSpecialEffect();
    }
}
 
void UMyAnimNotifyState::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
    AMyCharacter* Character = Cast<AMyCharacter>(MeshComp->GetOwner());
    if (Character)
    {
        Character->EndSpecialEffect();
    }
}

성능 최적화 전략

  1. LOD (Level of Detail) 시스템 활용
void AMyCharacter::SetupLOD()
{
    USkeletalMeshComponent* SkelMesh = GetMesh();
    if (SkelMesh)
    {
        SkelMesh->SetForcedLOD(1);
        SkelMesh->SetStreamingDistanceMultiplier(1.5f);
    }
}
  1. 애니메이션 압축 설정

 애니메이션 에셋의 프로퍼티에서 압축 설정을 조정할 수 있습니다. C++에서는 다음과 같이 접근할 수 있습니다.

void AMyCharacter::SetAnimationCompression(UAnimSequence* AnimSequence)
{
    if (AnimSequence)
    {
        AnimSequence->CompressionSettings = FAnimationCompressionSettings();
        AnimSequence->CompressionSettings.MaxCompressionErrorPerTrack = 0.5f;
    }
}
  1. 애니메이션 업데이트 빈도 조절
void AMyCharacter::OptimizeAnimationUpdate()
{
    USkeletalMeshComponent* SkelMesh = GetMesh();
    if (SkelMesh)
    {
        SkelMesh->AnimationTickingRate = 30.0f; // 30 FPS로 애니메이션 업데이트
    }
}

대규모 캐릭터 시스템 설계 시 고려사항

  1. 애니메이션 인스턴스 풀링
class FAnimInstancePool
{
public:
    UAnimInstance* GetAnimInstance(USkeletalMeshComponent* MeshComponent)
    {
        if (Pool.Num() > 0)
        {
            UAnimInstance* Instance = Pool.Pop();
            Instance->InitializeAnimation();
            return Instance;
        }
        return NewObject<UAnimInstance>(MeshComponent);
    }
 
    void ReturnAnimInstance(UAnimInstance* Instance)
    {
        Instance->UninitializeAnimation();
        Pool.Push(Instance);
    }
 
private:
    TArray<UAnimInstance*> Pool;
};
  1. 애니메이션 블루프린트 복잡도 관리

 C++에서 복잡한 로직을 처리하고, 애니메이션 블루프린트는 간단하게 유지합니다.

UCLASS()
class UMyOptimizedAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
 
public:
    virtual void NativeUpdateAnimation(float DeltaSeconds) override;
 
private:
    void UpdateComplexLogic();
};
 
void UMyOptimizedAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
    Super::NativeUpdateAnimation(DeltaSeconds);
 
    UpdateComplexLogic();
}
  1. 애니메이션 리타겟팅 활용

 여러 캐릭터가 동일한 애니메이션을 공유할 수 있도록 리타겟팅을 활용합니다.

void AMyCharacter::SetupRetargeting()
{
    USkeletalMeshComponent* SkelMesh = GetMesh();
    if (SkelMesh)
    {
        USkeletalMesh* TargetMesh = LoadObject<USkeletalMesh>(nullptr, TEXT("/Game/Meshes/TargetMesh"));
        SkelMesh->SetSkeletalMesh(TargetMesh);
        SkelMesh->SetAnimation(TargetMesh->GetAnimationAsset());
    }
}

 스켈레탈 메시와 애니메이션 시스템은 언리얼 엔진의 강력한 기능 중 하나입니다. C++에서 이를 효과적으로 활용하면 높은 성능과 유연성을 갖춘 캐릭터 시스템을 구축할 수 있습니다. 애니메이션 블루프린트와의 연동, 런타임 속성 조정, 노티파이 이벤트 처리 등을 통해 복잡한 애니메이션 로직을 구현할 수 있습니다.

 성능 최적화를 위해 LOD 시스템, 애니메이션 압축, 업데이트 빈도 조절 등의 전략을 사용할 수 있습니다. 대규모 캐릭터 시스템을 설계할 때는 애니메이션 인스턴스 풀링, 복잡도 관리, 리타겟팅 활용 등을 고려해야 합니다.

 마지막으로, 스켈레탈 메시와 애니메이션 시스템을 효과적으로 사용하기 위해서는 지속적인 프로파일링과 최적화가 필요합니다. 게임의 요구사항과 타겟 플랫폼의 특성을 고려하여 적절한 tradeoff를 찾는 것이 중요합니다.