AFO_Refactor 개발일지 #2
매 프레임 Cast 체인 제거
bIsSprinting 베이스 클래스 이동 · AnimInstance Cast 3번 → 0번 · 파생 클래스 정리
🔍 문제 — 매 프레임 Cast 3번
AFAnimInstance.cpp의 NativeUpdateAnimation()은 매 프레임 실행되는 함수다. 여기서 캐릭터 타입을 판별하기 위해 Cast를 3번 연속으로 시도하고 있었다.
BEFORE — NativeUpdateAnimation() 내부
// ❌ 매 프레임 Cast 3번 실행
if (AAFPlayerCharacter* BaseChar = Cast<AAFPlayerCharacter>(OwnerCharacter))
{
if (AAFDarkKnight* DK = Cast<AAFDarkKnight>(BaseChar))
bIsSprinting = DK->bIsSprinting;
else if (AAFMage* Mage = Cast<AAFMage>(BaseChar))
bIsSprinting = Mage->bIsSprinting;
else if (AAFAurora* Aurora = Cast<AAFAurora>(BaseChar))
bIsSprinting = Aurora->bIsSprinting;
else
bIsSprinting = false;
}
bIsSprinting = bIsSprinting && (GroundSpeed > 3.f);
문제는 두 가지였다.
① 성능
Cast는 런타임 타입 체크(RTTI)다. 60fps 기준 초당 180번 실행된다. 캐릭터 수가 늘어날수록 비례해서 증가한다.
② 확장성
캐릭터가 추가될 때마다 AnimInstance 코드도 수정해야 한다. AnimInstance가 모든 캐릭터 클래스에 의존하는 구조 — 전형적인 OCP(개방-폐쇄 원칙) 위반이다.
🔎 원인 분석
bIsSprinting이 각 파생 클래스에 따로 선언되어 있었다.
// AFDarkKnight.h bool bIsSprinting = false; // AFMage.h bool bIsSprinting = false; // AFAurora.h bool bIsSprinting = false;
베이스 클래스인 AFPlayerCharacter에는 없고, 파생 클래스마다 따로 선언했기 때문에 AnimInstance에서 이 값을 읽으려면 각 타입으로 Cast해야만 했다.
✅ 해결 — bIsSprinting 베이스 클래스로 이동
Cast 체인이 필요한 근본 이유를 제거했다. bIsSprinting을 AFPlayerCharacter로 올리고, 파생 클래스의 중복 선언을 전부 제거했다.
Step 1 — AFPlayerCharacter.h에 bIsSprinting 추가
// AFPlayerCharacter.h UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Movement") bool bIsSprinting = false;
Step 2 — AFPlayerCharacter.cpp StartSprint/StopSprint에 플래그 설정
void AAFPlayerCharacter::StartSprint(const FInputActionValue& value)
{
if (!bCanSprint) return;
if (GetCharacterMovement())
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
bUseControllerRotationYaw = false;
GetCharacterMovement()->bOrientRotationToMovement = true;
bIsSprinting = true;
}
void AAFPlayerCharacter::StopSprint(const FInputActionValue& value)
{
if (!bCanSprint) return;
if (GetCharacterMovement())
GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
bIsSprinting = false;
}
Step 3 — AFAnimInstance Cast 체인 제거
AFTER
// Cast 체인 전체 삭제, 한 줄로 교체 bIsSprinting = OwnerCharacter ? OwnerCharacter->bIsSprinting : false; bIsSprinting = bIsSprinting && (GroundSpeed > 3.f);
Step 4 — 파생 클래스 정리
AFMage, AFAurora에서 이제 불필요해진 코드를 제거했다.
// AFMage.h / AFAurora.h — 제거 bool bIsSprinting = false; // ← 삭제 (베이스로 올라감) // AFMage.cpp / AFAurora.cpp — StartSprint/StopSprint override 제거 // bIsSprinting 설정만 하던 override는 더 이상 필요 없음
📊 Before / After 비교
| Before | After | |
|---|---|---|
| 매 프레임 Cast 횟수 | 3번 | 0번 |
| 캐릭터 추가 시 AnimInstance 수정 | 필요 | 불필요 |
| bIsSprinting 선언 위치 | 파생 클래스 3곳에 중복 | 베이스 클래스 1곳 |
| AnimInstance의 캐릭터 클래스 의존성 | DarkKnight, Mage, Aurora 직접 참조 | AFPlayerCharacter만 참조 |
📌 이번 작업에서 배운 것
Cast 남발은 의존성 문제의 신호다.
AnimInstance가 DarkKnight, Mage, Aurora를 직접 알고 있다는 건 설계가 잘못됐다는 신호다. AnimInstance는 캐릭터의 구체적인 타입을 몰라야 한다. 공통 데이터를 베이스로 올리는 것만으로 의존성이 끊기고, Cast도 사라진다.
NativeUpdateAnimation은 비용에 민감하다.
60fps 기준 초당 60번 실행된다. 여기서 Cast를 하면 캐릭터 수 × 60번이 된다. 모바일 환경에서는 이 비용이 PC보다 크게 느껴진다. Tick/Update 계열 함수에서는 항상 "꼭 필요한가?"를 먼저 물어봐야 한다.
📌 다음 포스팅
[AFO_Refactor #3] GAS 도입 — AbilitySystemComponent + AttributeSet 셋업
기존 UAFAttributeComponent 기반 HP/Mana 처리를 GAS AttributeSet으로 교체하는 첫 단계. ASC를 PlayerState에 붙이는 구조 설계부터 시작.
'개인프로젝트 > AFO_Refactor' 카테고리의 다른 글
| [AFO_Refactor #1] Critical 버그 수정 - AnimNotify 권한 누락, Aurora 오타, 팀 비교 반전 (0) | 2026.06.10 |
|---|---|
| [AFO_Refactor #0] 프로젝트 셋업 + 코드 분석 - 팀 프로젝트를 GAS 기반으로 리팩토링하기 (0) | 2026.06.09 |