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);
// 추가적인 물리 시뮬레이션 로직
}
}
성능 고려사항
- 애니메이션 업데이트 빈도 최적화
void AMyCharacter::OptimizeAnimationUpdate()
{
USkeletalMeshComponent* Mesh = GetMesh();
if (Mesh)
{
Mesh->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
Mesh->bEnableUpdateRateOptimizations = true;
}
}
- LOD(Level of Detail) 시스템 활용
void AMyCharacter::SetupLOD()
{
USkeletalMeshComponent* Mesh = GetMesh();
if (Mesh)
{
Mesh->bEnableLODSwitch = true;
Mesh->MinLodModel = 0;
Mesh->ForcedLodModel = 0;
}
}
대규모 게임에서의 애니메이션 관리 전략
- 애니메이션 풀링 시스템 구현
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);
}
- 애니메이션 데이터 스트리밍
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 솔버, 물리 기반 애니메이션까지 다양한 기법을 활용할 수 있습니다.
애니메이션 그래프 로직을 C++로 확장하고 커스텀 애님 노드를 구현함으로써 더욱 복잡하고 특화된 애니메이션 시스템을 구축할 수 있습니다.