🎯 핵심 목표
객체가 언제 생성되고, 언제 사라지며, 누가 관리하는지가 명확한 구조
“누가 이 이벤트를 처리하는지 몰라도 동작하는” 구조
🔑 핵심 키워드
이벤트 시스템
메시지 시스템
델리게이트 브로드캐스트
GameMode / GameState / Subsystem 역할 분리
✏️ 정리
기존에는
“이 상태가 바뀌면 → 저 객체를 찾아서 → 함수 호출”
같은 강한 결합 구조를 쓰기 쉬웠다.
하지만 규모가 커질수록 이 방식은 유지보수가 급격히 어려워진다.
그래서 오늘은 구조를 이렇게 정리했다:
GameMode
게임의 규칙과 흐름만 관리 (FSM)
직접 UI나 월드 오브젝트를 건드리지 않음
GameState
현재 상태를 “사실”로 보관
상태 변화 시 델리게이트로 브로드캐스트
각 객체 (SpawnVolume, Widget 등)
GameState의 이벤트를 구독만 함
누가 상태를 바꿨는지는 알 필요 없음
👉 결과적으로
“상태를 바꾸는 쪽”과 “상태에 반응하는 쪽”이 완전히 분리됨
🎯 구현 의도
웨이브 중 플레이어가 현재 진행 상황을 월드 안에서 직관적으로 확인
UI와 월드 로직의 결합 최소화
🧱 구조 요약
WidgetComponent
World Space
Billboard 모드 (항상 카메라를 바라봄)
CoinCounterWidget
GameState의 OnCoinCountChanged 구독
텍스트만 갱신
🔄 흐름
GameMode에서 웨이브 상태 변경
GameState가 OnWaveStateChanged 브로드캐스트
SpawnVolume
웨이브 시작 → 위젯 Visible
웨이브 종료 → Hidden
코인 획득 시
GameState가 OnCoinCountChanged 브로드캐스트
위젯은 "3 / 10" 같은 텍스트만 갱신
📌 중요 포인트
SpawnVolume은 UI의 내부 구조를 모름
Widget은 코인을 누가 증가시켰는지 모름
→ 완전한 단방향 의존
런타임 비용 증가
에디터에서 미리보기 불가
레벨 디자이너와 협업 어려움
코드가 환경 디테일까지 책임짐
✅ 서브레벨 방식의 장점
환경은 에셋
코드는 언제 전환할지만 결정
UGameplayStatics::UnloadStreamLevel(this, "Wave1_SubLevel", FLatentActionInfo());
UGameplayStatics::LoadStreamLevel(this, "Wave2_SubLevel", true, true, FLatentActionInfo());
❓ 왜 버프 시스템이 필요했는가
기존 아이템 구조:
즉시 효과 → 끝
하지만 이런 요구가 생김:
“5초간 이동속도 감소”
“중첩 시 배율 적용”
👉 시간 + 중첩 개념을 가진 객체가 필요
🧩 아키텍처 설계
1️⃣ 버프는 UObject
액터에 종속되지만, 액터 그 자체는 아님
수명과 로직을 독립적으로 관리
2️⃣ BuffComponent가 중앙 관리
캐릭터가 USpartaBuffComponent 보유
모든 버프 인스턴스를 배열로 관리
Tick에서 수명 갱신
3️⃣ 토큰 기반 중첩
단순 덮어쓰기 ❌
각 버프 인스턴스가 자기 토큰을 발급
만료 시 자기 토큰만 회수
Speed = BaseSpeed × 0.5 × 0.5 × 0.5
👉 중첩/해제 모두 안전하게 처리 가능
🔄 버프 수명 주기 핵심 인사이트
OnFirstStart
“효과가 처음 등장했을 때 한 번만”
파티클, 블랙보드 등록
OnStart
매 인스턴스마다
토큰 발급
OnEnd
매 인스턴스 종료
토큰 회수
OnLastEnd
모든 인스턴스 종료 시
파티클 제거, 정리
📌 이 구조 덕분에
시각 효과 / 수치 효과 / 중첩 로직이 깔끔하게 분리됨
🎯 목적
데이터 접근의 단일 창구
레벨 전환과 무관한 데이터 유지
반복 순회 제거
🧠 구조적 판단
GameInstance Subsystem 사용
시작 시 DataTable 로드 후 캐싱
TMap으로 정리해서 O(1) 접근
WaveCache[LevelIndex][WaveIndex];
기존 문제
타이머 기반 흐름
상태가 늘어날수록 조건 분기 지옥
해결
상태를 클래스로 승격
Enter / Update / Exit 명확화
None → Begin → Progress → End → Intermission → Begin
설계 원칙
SRP: 상태는 자기 책임만
OCP: 새 상태 추가 시 기존 코드 수정 최소화
타이머, 리소스는 Enter/Exit에서 관리
❓ dump 파일 디버깅이 왜 중요한가
이전까지 크래시는 보통 이렇게 받아들였다.
“어디서 터졌는지 모르겠다”
“재현 안 되면 어쩔 수 없다”
“로그 좀 더 찍어볼까…”
하지만 오늘 알게 된 건,
dump 파일은 ‘운 나쁘게 터진 흔적’이 아니라 ‘이미 정답이 들어 있는 자료’라는 점이었다.
🛠 실제 디버깅 절차 정리
Visual Studio에서 dump 파일 열기
프로세스 선택 시
→ unreal.exe 선택
심볼(Symbol) 경로 설정
엔진 심볼
Engine/Binaries/Win64
프로젝트 심볼
Project/Binaries/Win64
심볼 로드가 정상적으로 되면
Call Stack이 깨지지 않고
엔진 함수 + 프로젝트 함수까지 연결해서 확인 가능
📌 이 과정의 핵심 포인트
심볼이 없으면
함수 이름 ❌
파일 위치 ❌
라인 번호 ❌
심볼이 있으면
“어디서 터졌는지”
“그 시점에 어떤 상태였는지”
“엔진 코드 → 내 코드 흐름”을 연결해서 추적 가능
즉,
크래시를 ‘추측’이 아니라 ‘추적’의 영역으로 끌어오는 작업이다.
🧠 관점의 변화
이제 크래시를 이렇게 보게 됐다.
❌ “왜 터졌지…?”
✅ “이 dump에 어떤 정보가 들어있지?”
👉 로그를 늘리는 것보다
👉 dump를 제대로 읽는 게 훨씬 강력한 디버깅 수단이라는 걸 체감했다.
1️⃣ 이력서: “기술 나열”에서 “문제 해결 기록”으로
❌ 흔한 실수
Unreal Engine 사용
C++
GAS
FSM
→ 이건 ‘무엇을 썼는지’만 있고, ‘왜 썼는지’가 없다.
✅ 권장 구조: 문제 → 접근 → 적용 → 결과
이력서는 설명서가 아니라 결과 보고서에 가깝다.
예시 관점
어떤 문제가 있었는가?
그 문제를 왜 그렇게 판단했는가?
어떤 기술을 선택했는가?
그 결과 무엇이 개선되었는가?
✍️ 예시 비교
❌ Before
Unreal Engine으로 게임 제작, GAS 사용
✅ After
웨이브 기반 전투 구조에서 상태 분기 로직이 복잡해지는 문제를 해결하기 위해
FSM 기반 Wave State Machine을 설계하고,
Delegate 기반 이벤트 브로드캐스트 구조로 UI 및 월드 오브젝트와의 결합도를 제거함.
그 결과 상태 추가 시 기존 코드 수정 없이 확장 가능하도록 개선함.
📌 같은 기술이라도
“왜 필요했는지”가 드러나면 평가가 완전히 달라진다.
2️⃣ “언리얼 사용” vs “언리얼로 무엇을 설계했는가”
❌ 언리얼 엔진 사용
❌ 블루프린트 + C++
이건 너무 넓고, 너무 추상적이다.
대신 이렇게 써야 한다:
GAS 기반 Attribute / Effect 설계
Delegate 기반 이벤트 아키텍처
GameMode / GameState / Subsystem 책임 분리
DataTable 캐싱 구조 설계
UObject 기반 버프 시스템 구현
👉 엔진을 ‘사용했다’가 아니라
엔진의 ‘개념을 이해하고 설계에 활용했다’는 인상을 주는 게 핵심
3️⃣ 자기소개서 작성법 – 단계별 접근
자기소개서는 “한 번에 잘 쓰는 글”이 아니다.
단계를 나눠야 한다.
✏️ 초벌 – 생각 다 쏟아내기
문장 퀄리티 ❌
구조 ❌
중복 ❌
👉 대신
했던 고민
실패
막혔던 지점
선택의 이유
전부 쏟아낸다.
✂️ 중벌 – 구조 정리
비슷한 이야기 묶기
흐름 만들기
불필요한 기술 나열 제거
이 단계에서 처음으로
“읽는 사람 입장”을 고려하기 시작한다.
✨ 종벌 – 검증 단계
다른 사람에게 보여주기
하루 지나서 다시 읽기
소리 내서 읽어보기
📌 소리 내서 읽으면 바로 들린다.
말이 안 되는 문장
주장만 있고 근거 없는 문단
내가 아닌 ‘이력서용 나’의 문장들