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 시스템, 애니메이션 압축, 업데이트 빈도 조절 등의 전략을 사용할 수 있습니다.

 캐릭터 시스템을 설계할 때는 애니메이션 인스턴스 풀링, 복잡도 관리, 리타겟팅 활용 등을 고려해야 합니다.