스켈레탈 메시, 애니메이션 시퀀스
언리얼 엔진의 스켈레탈 메시 시스템과 애니메이션 시퀀스는 게임 내 캐릭터에 생동감을 불어넣는 핵심 요소입니다.
이 절에서는 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();
}
}
성능 최적화 전략
- LOD (Level of Detail) 시스템 활용
void AMyCharacter::SetupLOD()
{
USkeletalMeshComponent* SkelMesh = GetMesh();
if (SkelMesh)
{
SkelMesh->SetForcedLOD(1);
SkelMesh->SetStreamingDistanceMultiplier(1.5f);
}
}
- 애니메이션 압축 설정
애니메이션 에셋의 프로퍼티에서 압축 설정을 조정할 수 있습니다. C++에서는 다음과 같이 접근할 수 있습니다.
void AMyCharacter::SetAnimationCompression(UAnimSequence* AnimSequence)
{
if (AnimSequence)
{
AnimSequence->CompressionSettings = FAnimationCompressionSettings();
AnimSequence->CompressionSettings.MaxCompressionErrorPerTrack = 0.5f;
}
}
- 애니메이션 업데이트 빈도 조절
void AMyCharacter::OptimizeAnimationUpdate()
{
USkeletalMeshComponent* SkelMesh = GetMesh();
if (SkelMesh)
{
SkelMesh->AnimationTickingRate = 30.0f; // 30 FPS로 애니메이션 업데이트
}
}
대규모 캐릭터 시스템 설계 시 고려사항
- 애니메이션 인스턴스 풀링
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;
};
- 애니메이션 블루프린트 복잡도 관리
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();
}
- 애니메이션 리타겟팅 활용
여러 캐릭터가 동일한 애니메이션을 공유할 수 있도록 리타겟팅을 활용합니다.
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 시스템, 애니메이션 압축, 업데이트 빈도 조절 등의 전략을 사용할 수 있습니다.
캐릭터 시스템을 설계할 때는 애니메이션 인스턴스 풀링, 복잡도 관리, 리타겟팅 활용 등을 고려해야 합니다.