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 누락 수정 + 서버 권한 처리 전면 점검까지.
'개인프로젝트 > AFO_Refactor' 카테고리의 다른 글
| [AFO_Refactor #1] Critical 버그 수정 - AnimNotify 권한 누락, Aurora 오타, 팀 비교 반전 (0) | 2026.06.10 |
|---|