개인프로젝트/AFO_Refactor

[AFO_Refactor #0] 프로젝트 셋업 + 코드 분석 - 팀 프로젝트를 GAS 기반으로 리팩토링하기

Client Side 2026. 6. 9. 20:12

AFO_Refactor 개발일지 #0

프로젝트 셋업 + 코드 분석

GitHub · CLAUDE.md · 버그 9개 발견 · GAS 리팩토링 방향

🎯 왜 이 프로젝트를 시작했나

부트캠프 팀 프로젝트로 2v2 멀티플레이 액션 게임 AFO를 만들었다. Dedicated Server 기반에 5종 캐릭터, 팀 킬 스코어 방식으로 설계했고, 패키징까지 완료했다.

그런데 문제가 있었다.

  • 패키징 후 멀티플레이 플레이 중 크래시 발생
  • 팀원마다 코드 스타일이 달라 구조가 통일되지 않음
  • 스킬 DataTable 연동 미완성 (호출 타이밍 문제)
  • 서버 권한 처리가 일관되지 않음

취업 포트폴리오로 쓰기엔 부족했다. 그래서 혼자 처음부터 구조를 뜯어고치기로 했다.

💡 AI로 [시간단축] → 내가 [문제발견] → 내가 [구조개선] → [수치결과]

📋 프로젝트 개요

프로젝트명 AFO_Refactor
원본 팀 프로젝트 AFO (2v2 멀티플레이 액션)
엔진 Unreal Engine 5.5 / C++
네트워크 Dedicated Server
목표 GAS 기반 전투 시스템 리팩토링 + 멀티플레이 크래시 수정
개발 기간 약 4주 (Claude Code 활용)

🐙 GitHub 세팅

원본 팀 레포는 그대로 두고, 개인 레포 AFO_Refactor를 새로 생성했다.

LFS는 사용하지 않는다. Content 폴더를 아예 올리지 않기 때문이다. 면접관이 보는 건 코드이고, 에셋은 저작권 문제도 있어서 소스만 관리한다.

# .gitignore 핵심 항목
Content/           # 에셋 (용량 크고 저작권 문제)
Binaries/          # 컴파일 결과물
Intermediate/      # 중간 빌드 파일
Saved/             # 로그, 스크린샷
DerivedDataCache/  # 에셋 캐시

🔍 코드 분석 결과 — 발견한 버그 9개

Claude Code로 소스 전체를 분석했다. AI가 분석 → 내가 원인 판단 → 우선순위 결정 순서로 진행했다.

단순 코드 리뷰만으로 Critical 버그 4개를 포함해 총 9개의 문제를 발견했다.

🔴 Critical 1 — HandleSkillHitCheck 세미콜론 버그

위치: AFPlayerCharacter.cpp

bool bHit = GetWorld()->OverlapMultiByChannel(...);

if (bHit) return;  // ← 세미콜론! 충돌 시 바로 리턴
{
    for (auto& Result : OverlapResults)  // ← if와 분리된 블록, 항상 실행됨
    { ... }
}

결과: 스킬이 적에게 맞아도 데미지가 전혀 들어가지 않는다. 세미콜론 하나가 전체 스킬 시스템을 무력화했다.

🔴 Critical 2 — AnimNotify_AttackHit 클라이언트에서 DealDamage 직접 호출

위치: AFAnimInstance.cpp

// ❌ AnimNotify는 모든 클라이언트에서 발동됨
void UAFAnimInstance::AnimNotify_AttackHit()
{
    // HasAuthority() 체크 없음!
    Character->DealDamage();  // ← 클라이언트에서도 실행됨
}

결과: Dedicated Server 환경에서 클라이언트가 서버 전용 작업을 직접 실행 → 멀티플레이 크래시의 핵심 원인 중 하나.

🔴 Critical 3 — AFAurora::StopSprint 오타

위치: AFAurora.cpp

// ❌ StopSprint인데 StartSprint 호출
void AAFAurora::StopSprint(const FInputActionValue& Value)
{
    Super::StartSprint(Value);  // ← StopSprint여야 함
    bIsSprinting = false;
}

결과: 오로라 캐릭터는 스프린트 키를 떼도 속도가 원복되지 않는다.

🔴 Critical 4 — DataTable 로드 타이밍 버그

위치: AFMage.cpp, AFDarkKnight.cpp, AFWereWolf.cpp

// ❌ OnRep_PlayerState 시점에 DataTable이 아직 null일 수 있음
void AAFMage::OnRep_PlayerState()
{
    LoadMageData();  // ← SkillDataTable이 nullptr인 채로
}

void AAFMage::LoadMageData()
{
    // nullptr 체크 없이 바로 FindRow() 진입 → 크래시
    SkillDataTable->FindRow<FMageSkillData>(...);
}

결과: 클라이언트에서 스킬 아이콘이 뜨지 않거나, nullptr 역참조로 크래시. 스킬 슬롯 DataTable 연동이 끝내 안 됐던 근본 원인이다.

🟠 High 1 — AnimInstance 매 프레임 Cast 체인

위치: AFAnimInstance.cpp NativeUpdateAnimation()

// ❌ 매 프레임 Cast 3번 실행
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;

결과: 캐릭터 추가 시 이 함수도 수정해야 한다. 확장성 없는 구조 + 매 프레임 불필요한 Cast 비용.

🟠 High 2 — Multicast_NotifyDamage 팀 비교 로직 반대

위치: AFAttributeComponent.cpp

// ❌ 같은 팀 = 적 데미지로 표시되는 반전 로직
if (MyPS->GetTeamID() == InstigatorPS->GetTeamID())
{
    bIsEnemyDamage = true;  // ← 같은 팀인데 적 데미지?
}

결과: 아군을 공격하면 적 색상으로, 적을 공격하면 아군 색상으로 데미지 수치가 표시된다.

🟡 Medium 1 — UI EnsureUI 반복 타이머

위치: AFLobbyPlayerController.cpp

// ❌ UI가 붙은 후에도 0.2초마다 계속 실행
GetWorldTimerManager().SetTimer(
    EnsureUITimer, this,
    &ThisClass::SetupUIForCurrentMap,
    0.2f, true  // ← true = 무한 반복
);

결과: UI 셋업이 완료된 후에도 타이머가 멈추지 않는다. 이벤트/델리게이트 기반으로 교체 필요.

🟡 Medium 2 — 맵 이름 하드코딩

위치: AFLobbyPlayerController.cpp

// ❌ 맵 이름 문자열 직접 하드코딩
if (MapName == TEXT("AFOTeamSelect"))
if (MapName == TEXT("AFOCharacterSelectMap"))

결과: 맵 이름이 바뀌면 코드도 같이 수정해야 한다. DataAsset 또는 GameInstance 변수로 분리 필요.

🟡 Medium 3 — ApplySpeedBuff / AddShield 타이머 미정리

위치: 캐릭터 스킬 관련 코드

// ❌ 기존 타이머 정리 없이 재호출
// ClearTimer 없이 SetTimer 재호출
GetWorldTimerManager().SetTimer(
    SpeedBuffTimerHandle, ...
);

결과: 버프가 중첩 적용되거나 타이머가 꼬여서 버프 해제 타이밍이 부정확해진다.

📊 버그 요약

등급 버그 영향
🔴 Critical HandleSkillHitCheck 세미콜론 스킬 데미지 전혀 안 들어감
🔴 Critical AnimNotify_AttackHit HasAuthority 누락 멀티플레이 크래시
🔴 Critical AFAurora::StopSprint 오타 오로라 스프린트 속도 미복원
🔴 Critical DataTable nullptr 역참조 스킬 슬롯 미표시 / 크래시
🟠 High 매 프레임 Cast 체인 성능 + 확장성 문제
🟠 High 팀 비교 로직 반전 데미지 색상 반대로 표시
🟡 Medium UI 반복 타이머 불필요한 반복 실행
🟡 Medium 맵 이름 하드코딩 유지보수성 저하
🟡 Medium 타이머 미정리 패턴 버프 타이밍 부정확

🗺️ 리팩토링 방향 및 로드맵

Phase 작업 핵심 키워드
#1 Critical 버그 수정 (세미콜론, HasAuthority, 오타) 서버 권한, 스킬 판정
#2 Cast 체인 → Interface 교체 의존성 제거, 확장성
#3 GAS 도입 — ASC + AttributeSet GAS, 전투 시스템
#4 HP/Mana → GameplayEffect 교체 GAS, 리플리케이션
#5 스킬 → GameplayAbility 교체 GAS, 스킬 시스템
#6 DataTable 로드 타이밍 안정화 PossessedBy, nullptr 가드
#7 UI 타이머 → 이벤트 기반 교체 Delegate, 최적화
#8 드로우콜 측정 + 최적화 프로파일링, Before/After
#9 패키징 + 멀티플레이 크래시 최종 수정 Dedicated Server, 네트워크

🤖 CLAUDE.md 세팅

Claude Code가 이 프로젝트의 맥락을 이해하고 일관된 방향으로 작업할 수 있도록 CLAUDE.md를 루트에 작성했다.

IronBird 때와 달리 이번 CLAUDE.md의 핵심은 두 가지다.

  • 알려진 버그 목록 — 파일 위치와 원인까지 명시해서 Claude Code가 맥락 없이 엉뚱한 수정을 하지 않도록
  • GAS 도입 계획 — 교체 대상 클래스와 추가할 모듈을 미리 정의해서 작업 방향을 고정

📌 이번 작업에서 배운 것

코드 분석만으로도 포트폴리오 소재가 나온다.

새 기능을 추가하지 않아도 기존 코드에서 if (bHit) return; 같은 치명적인 버그를 발견하고, 원인을 정확히 짚어내는 것 자체가 실무 능력의 증명이다. 특히 팀 프로젝트 코드를 분석해서 "이 버그가 왜 생겼는지"까지 설명할 수 있으면, 면접관 입장에서 "이 사람은 남의 코드를 읽을 줄 안다"는 인상을 준다.

📌 다음 포스팅

[AFO_Refactor #1] Critical 버그 수정 — 세미콜론 하나가 스킬 시스템을 망가뜨린 이야기

HandleSkillHitCheck 수정 + AnimNotify HasAuthority 누락 수정 + 서버 권한 처리 전면 점검까지.