포스트

TIL 2026-04-24

TIL 2026-04-24

2026-04-24 Void 프로젝트 TPS 피벗 + 8번 과제 레포 구축 + 팀플 기여 가이드

목차


오늘 한 일 요약

  1. Void GDD TPS 피벗 — 쿼터뷰에서 3인칭 TPS로 시점 전환. game-concept.md의 9개 섹션 재작성 (피치·필러·MDA·Core Loop·비주얼·레퍼런스·기술 리스크·Feature List)
  2. 8번 과제 구현계획 재조정 — 원 10일 일정 → 오늘 Day 1로 재시작 7일 일정 (8번과제_구현계획.md §6·§9 재작성)
  3. 8번 과제 GitHub 레포 생성NBC_JangSik_Assignment8 Private 레포 생성 + 초기 3커밋 push
  4. gitignore Content 화이트리스트 패턴 도입Content/VoidUnreal/만 추적, 외부 에셋팩 전부 무시
  5. AVOIDPlayerCharacter TPS 전환 — SpringArm 1400→300, 숄더뷰 오프셋, bUseControllerRotationYaw=true 기반으로 Move/Look 재작성
  6. 팀프로젝트 CONTRIBUTING.md + PR 템플릿 작성 — GitHub Flow 전략, Conventional Commits, 담당 영역 문서화
  7. 팀플 레포 master → main 브랜치 전환 — 표준 맞추기, GitHub default branch 변경, 원격 master 삭제
  8. 언리얼 수업 — DamageType 커스터마이징UDamageType 상속으로 화상/임펄스 타입 분리, GetDefaultObject<T>()로 CDO 파라미터 접근
  9. 언리얼 수업 — 비동기 Trace 시스템UWorld::AsyncLineTraceByChannel + FTraceDelegate 콜백 패턴 실습 (D:\Unreal\NBC_Master)
  10. 다음주 과제 수신 — 필수: 샷건 + 커스텀 반동 시스템 / 도전: 비동기 Trace AI 탐지 (마감 5/1)


Void GDD TPS 피벗 — 9개 섹션 재작성

피벗 결정 배경

원래 고민: game-concept.md는 쿼터뷰 기반으로 설계되어 있는데, 팀프로젝트(Ch3)가 TPS로 결정됐다. 본인 역할(C — 전투/히트 판정/히트스톱/처형)에서 쌓을 TPS 타격감 자산을 Void 본편으로 이식하려면 시점을 TPS로 통일해야 재사용이 자연스럽다.

검토 결과: 장르(익스트랙션)·필러(Noise is Currency)·레퍼런스(타르코프·좀보이드)는 대부분 유지 가능. 시점 관련 문장만 교체하면 되므로 전면 재작성이 아닌 부분 재작성 스코프.

재작성 섹션 정리

섹션주요 변경
상단 헤더“TPS Revision” 명시, 최종수정일 기록
Elevator Pitch“quarter-view” → “third-person” / “쿼터뷰” → “3인칭 TPS”
Core IdentityGenre: Top-Down 45° → Over-the-Shoulder. 레퍼런스 재정렬 (Tarkov 1순위 승격, 낙원 복원, Hunt:Showdown·SM2 추가, SKYHILL 제거)
Unique Hook“TPS 좀비 PvE 익스트랙션 + 소음 + 차량 은신처” 교집합 — Tarkov·Hunt는 PvP 중심이라는 차별점 명시
Pillar 3 (Tactical Over Twitch)“쿼터뷰 넓은 시야” 전제 제거. 제한된 시야 + 정보 축적 기반 전술로 재정의
MDA / Dynamics숄더 스위치·ADS FOV·코너 피킹 항목 추가
Core Loop (Moment-to-Moment)“조준보행”, “소리로 먼저 읽는다”, 문 진입 판단 추가
Visual Identity Anchor타르코프식 환경 동화 철학으로 전면 재작성. 위협/목적지/루팅 3층위 Visibility Hierarchy 정립 + Anti-Pattern 명시
Technical/Design/Market Risks“쿼터뷰 카메라 리그” → “TPS 숄더뷰 + ADS 트랜지션”, “어두운 실내 Lumen 성능” 추가
Implementation Feature ListCharacter/Combat 테이블에 숄더 스위치·ADS·코너 피킹 추가. 은신을 Stretch → Essential 승격

핵심 설계 변화 3가지

1. “정보 우위” 중심으로 Pillar 재정의

쿼터뷰는 시야 자체가 전술 정보였다. TPS는 시야가 좁기 때문에 같은 “전술 우선” 필러를 유지하려면 정보의 출처가 바뀌어야 한다.

1
2
3
4
쿼터뷰 Pillar 3: 넓은 시야로 상황 판단 → 전략 행동
    ↓
TPS Pillar 3: 소음 단서 / 청각 HUD / 사전 정찰 마킹 / 환경 스토리텔링
             으로 제한된 시야 너머를 읽고 → 정보 우위로 승부

시야 이점 → 정보 이점으로 승부 수단 교체. 필러의 정신은 유지되고 구현 방식만 바뀐다.

2. 타르코프식 환경 동화 — Visibility Hierarchy

원래 Visual Identity는 “모든 오브젝트가 즉시 식별 가능”이 원칙이었는데, TPS 피벗하면서 타르코프식 “오브젝트를 환경에 녹여서 플레이어가 능동적으로 탐색하게” 만드는 철학으로 전환. 단, 모든 걸 숨기면 게임 진행이 막히므로 3층위 분리:

층위처리
위협 (좀비)실루엣 + 움직임 + 소리로 감지 가능 — 완전 동화 금지 (즉사이므로 생존 정보)
목적지 (익스트랙션 존)고정 위치 + 시각 마커 + 청각 큐
루팅 오브젝트환경과 동화 — 밝은 하이라이트·아이콘 절대 금지

루팅만 동화하는 발견 보조 장치:

  • 상호작용 거리 내 근접 시 희미한 Press-E 프롬프트
  • 플래시라이트로 비추면 미세한 반짝임 — Pillar 1(“Noise is Currency”)와 연결 (라이트는 소음 비용 없는 탐색 수단)
  • 구역별 루팅 오브젝트 시각 언어 일관 (학습 곡선을 재플레이 가치로 전환)

3. 장르 포지션 재정의

Unique Hook을 “쿼터뷰 + 소음 + 익스트랙션 조합이 시장에 없음”에서 “TPS 좀비 PvE 익스트랙션”으로 바꾸면서 경쟁작 구도 재정렬:

1
2
3
4
5
6
Tarkov  : TPS 익스트랙션 but PvP 중심
Hunt    : TPS 익스트랙션 but PvP 중심
좀보이드 : 소음 철학 but 쿼터뷰 + 비익스트랙션
낙원    : TPS 서바이벌 but 익스트랙션 루프 없음
    ↓
Void    : TPS + 좀비 PvE + 소음 + 익스트랙션 + 차량 은신처


8번 과제 구현계획 재조정 — 7일 일정

원 계획: 4/21~4/30, 10일 실제 상황: 4/21~4/23 3일간 팀프로젝트 킥오프·CS 면접 준비에 시간 소모해서 미착수 → 오늘(4/24)이 실질적 Day 1

재조정 일정 (TPS 기준, 4/24 시작)

Day날짜핵심 작업완료 기준
14/24 목Git init + TPS PlayerCharacter + IA 5개 + BP + PIETPS WASD 걷기 + 마우스 카메라
24/25 금좀비 AI (BT/BB) + 근접/원거리 사격 + Noise→AIPerception좀비 1마리 추적·공격·처치
34/26 토SpawnVolume + WaveData + GameMode + Inventory 무게 + 픽업Wave 1→2 자동 전환, 무게 변화
44/27 일Wave 3 차량 수리 + ADS 조준 + 차량 탑승 카메라부품 3개 후 탈출 성공 화면
54/28 월WBP_HUD (6개 게이지) + 메뉴 3종 + 입력 모드 전환HUD 실시간 바인딩
64/29 화디버프 3종 HUD + UI 애니메이션 + 3D 위젯 인디케이터도전 과제 전부
74/30 수밸런싱 + 시연 영상 + README + 태그 + 제출제출

압축 포인트: 이미 4/20에 D:/Unreal/VoidUnreal에 C++ 36개 파일 구조를 만들어뒀기 때문에 원 계획의 “4/21~4/23 뼈대 구축” 3일분을 Day 1 하루로 압축 가능.



8번 과제 레포 구축 — NBC_JangSik_Assignment8

전략: 복사본 없이 기존 D:\Unreal\VoidUnreal에서 바로 git init. 로컬 폴더명·프로젝트명·클래스 접두사 전부 VoidUnreal / VOID 유지. GitHub 레포명만 NBC_JangSik_Assignment8로 제출. 4/30에 v0.1-assignment8 태그로 스냅샷 박제 후 main에서 Void 본편 개발 계속.

작업 순서

1
2
3
4
5
6
7
cd "D:/Unreal/VoidUnreal"
git init -b main
git add .gitignore README.md
git commit -m "Initial: .gitignore + README for Void Tier 0 (Assignment 8)"
git add .vsconfig Config Content Source VoidUnreal.png VoidUnreal.uproject
git commit -m "Add: VoidUnreal UE5 project baseline — 36 C++ files"
gh repo create NBC_JangSik_Assignment8 --private --source=. --remote=origin --push

Content 화이트리스트 gitignore 패턴

처음엔 문제가 있는 에셋팩 폴더명을 개별로 ignore했는데, 앞으로 새 에셋팩을 가져올 때마다 gitignore 수정하는 유지비용이 생긴다. 화이트리스트로 전환:

# Content 화이트리스트: Content/VoidUnreal/ 만 추적
Content/*
!Content/VoidUnreal/
!Content/VoidUnreal/**

검증: git check-ignore -v로 각 경로가 의도대로 동작하는지 확인.

경로결과
Content/Fantastic_Dungeon_Pack/...무시 (Content/*)
Content/MPTopdownKit/...무시
Content/VoidUnreal/Blueprints/BP.uasset추적 (!Content/VoidUnreal/** 예외)
Content/NewAssetPack/... (가상)자동 무시 — 새 에셋팩 추가 시 gitignore 수정 불필요

대용량 파일 차단 → gitignore 재설계

첫 push 시 GitHub가 100MB 제한으로 거부:

1
2
remote: error: File Content/Fantastic_Dungeon_Pack/maps/..._BuiltData.uasset is 578.39 MB;
this exceeds GitHub's file size limit of 100.00 MB

해결 순서:

  1. .gitignoreContent/Fantastic_Dungeon_Pack/, Content/MPTopdownKit/ 추가
  2. git rm -r --cached 로 인덱스에서 제거 (로컬 파일은 유지)
  3. git commit --amend --no-edit — 같은 커밋을 수정해서 대용량 파일이 아예 이력에 없던 것처럼 만듦 (마지막 커밋이었기에 가능)
  4. 재 push 성공
  5. 이후 화이트리스트 패턴으로 전환 — 앞으로 새 에셋팩 추가해도 자동 처리

배운 점: UE 프로젝트는 첫 커밋 전에 gitignore를 제대로 세팅해야 한다. 한 번 커밋되면 이력에서 파일을 지우기가 번거로워진다 (amend는 마지막 커밋에만 가능, 중간 커밋은 rebase나 git filter-repo 필요).



TPS PlayerCharacter 코드 전환

기존 AVOIDPlayerCharacter는 쿼터뷰 기반이라 카메라 리그·이동 계산을 TPS로 교체. 학습 노트 챕터2-3 (ASpartaCharacter 방식)과 동일한 패턴으로 맞춰서 나중에 본편 확장 때 일관성 유지.

쿼터뷰 → TPS 숄더뷰 차이점

요소쿼터뷰 (이전)TPS 숄더뷰 (변경 후)
SpringArm ArmLength1400300
SpringArm RotationPitch=-45° 고정컨트롤러 회전 따라감
bUsePawnControlRotationfalsetrue
bInherit*전부 false기본 (true)
SocketOffset없음(0, 50, 50) — 오른쪽 어깨 오프셋
Camera Lag없음Enable, Speed 15
bUseControllerRotationYawfalsetrue (캐릭터가 카메라 방향 따라 회전)
Move 방향 계산CameraBoom Yaw 기준Controller Yaw 기준 (GetControlRotation().Yaw)
Look 함수비어있음 (쿼터뷰는 회전 불필요)AddControllerYawInput + AddControllerPitchInput 구현
기본 이동속도600450 (Noise 필러 전제로 보수적)

Move 함수 리팩터링 요약

이전 (쿼터뷰)CameraBoom 월드 Yaw 기준:

1
2
3
const FRotator CameraYaw(0.0f, CameraBoom->GetComponentRotation().Yaw, 0.0f);
const FVector ForwardDir = FRotationMatrix(CameraYaw).GetUnitAxis(EAxis::X);
AddMovementInput(ForwardDir, Input.Y);

변경 후 (TPS) — 캐릭터 자체 Forward 벡터 기준 (bUseControllerRotationYaw=true라 카메라와 정렬됨):

1
2
3
4
5
const FVector2D MoveInput = Value.Get<FVector2D>();
if (!FMath::IsNearlyZero(MoveInput.X))
    AddMovementInput(GetActorForwardVector(), MoveInput.X);
if (!FMath::IsNearlyZero(MoveInput.Y))
    AddMovementInput(GetActorRightVector(), MoveInput.Y);

왜 이게 가능한가: bUseControllerRotationYaw=true면 캐릭터가 컨트롤러(=카메라) Yaw를 매 프레임 따라간다. 결과적으로 캐릭터의 GetActorForwardVector()와 컨트롤러의 전방 벡터가 일치 → Controller->GetControlRotation() 없이도 동일 효과. 코드가 더 간결해진다.



팀프로젝트 기여 가이드 수립

CONTRIBUTING.md를 루트에 두는 이유

GitHub는 저장소 루트의 특정 파일명을 자동 감지해 UI에 통합한다:

파일명자동 표시 위치
README.md저장소 홈
CONTRIBUTING.mdIssue/PR 작성 화면 상단 “Contributing guidelines” 링크 자동 생성
.github/PULL_REQUEST_TEMPLATE.mdPR 작성 시 본문 자동 주입
.github/ISSUE_TEMPLATE/*.mdIssue 생성 시 선택지

기존 팀 README는 Docs/roles.md, Docs/asset-candidates.md 분리 패턴이었지만 CONTRIBUTING만큼은 루트에 둬야 GitHub 자동 연동 효과를 얻는다. 팀원이 PR 만들 때 링크가 뜨는 것과 README 어딘가에 묻혀 있는 것은 규칙 준수율이 완전히 다르다.

수립한 규칙 요약

영역규칙
브랜치 전략GitHub Flow (main + feat/*·fix/*·refactor/*·chore/*)
브랜치 명명<type>/<scope>-<desc> 영문 소문자 + 하이픈
커밋 메시지Conventional Commits — <type>(<scope>): <subject>
scopeplayer / ai / wave / card / combat / hud / level / vfx / sfx / anim
병합 전략feat/* → main은 Squash Merge (히스토리 깔끔)
PR 규칙리뷰어 1명 이상, 체크리스트 템플릿, 본인 PR 자기 머지 금지
담당 영역A~E 5명의 주 작업 폴더 명시
에셋 네이밍SM_, SK_, M_, MI_, T_, AM_, BP_, WBP_, DA_, DT_

PR 템플릿 — .github/PULL_REQUEST_TEMPLATE.md

1
2
3
4
5
6
7
8
9
10
11
## 변경 요약
## 담당 시스템
## 테스트 방법
## 스크린샷 / 영상
## 체크리스트
- [ ] 로컬 빌드 성공
- [ ] PIE 검증 완료
- [ ] 브랜치·커밋 컨벤션 준수
- [ ] 에셋 네이밍 규칙 준수
- [ ] 담당 영역 외 파일 수정 시 사전 공유
## 관련 이슈

GitHub에서 PR 만들 때 이 본문이 자동으로 채워진다. 팀원들이 매번 같은 정보를 누락 없이 제공하게 강제하는 가장 간단한 수단.

master → main 브랜치 전환

팀플 레포가 master 기본 브랜치였는데, 8번 과제 레포(main)와 일관성 유지 + GitHub 표준 맞추기 위해 전환:

1
2
3
4
5
6
7
8
9
10
11
# 1. 로컬 브랜치 이름 변경
git branch -m master main

# 2. 새 main 브랜치 push + upstream 설정
git push -u origin main

# 3. GitHub default branch 변경 (gh CLI)
gh repo edit GoldBoll/NBC_Ch3_TeamProject --default-branch main

# 4. 원격 master 브랜치 삭제
git push origin --delete master

순서 중요: ③ (default branch 변경)을 ④ (master 삭제) 전에 해야 한다. 순서 바꾸면 “기본 브랜치는 삭제 불가” 에러.

팀원 공지 문구도 같이 준비 (이미 클론 받았을 수 있으니):

1
2
3
4
git fetch origin
git branch -m master main
git branch -u origin/main main
git remote set-head origin -a


언리얼 수업 — DamageType · Trace 시스템

실습 프로젝트: D:\Unreal\NBC_Master\Source\NBC_Master

오늘 수업 주제는 데미지 타입 커스터마이징Trace 시스템의 동기/비동기 차이였다. 두 개념은 다음주 과제(샷건 반동 + 비동기 AI 탐지)의 기반 기술이다.

TakeDamage 오버라이드 + 커스텀 DamageType

AActor에는 기본 TakeDamage 가상함수가 있고, 데미지 원인 타입을 구별하려면 UDamageType을 상속한 클래스를 만들어 구분자로 쓴다.

UFireDamageType — 화상 속성을 추가 데이터로 보유:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
UCLASS()
class NBC_MASTER_API UFireDamageType : public UDamageType
{
    GENERATED_BODY()
public:
    UFireDamageType();

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
    float BurnDuration;    // 화상 지속시간

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
    float ArmorPenetration; // 방어구 관통률
};

UFireDamageType::UFireDamageType()
{
    BurnDuration = 5.f;
    ArmorPenetration = 0.2f;
}

UMyTestDamageType — 물리 임펄스 기반 데미지:

1
2
3
4
5
6
UMyTestDamageType::UMyTestDamageType()
{
    DamageImpulse = 5000.f;        // 히트 시 물리 임펄스 크기
    bScaleMomentumByMass = true;   // 질량에 따라 임펄스 스케일
    bCausedByWorld = true;         // 월드 원인(낙하·환경) 데미지 표시
}

ANBC_MasterCharacter::TakeDamage 오버라이드DamageEvent.DamageTypeClass로 타입 분기:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
float ANBC_MasterCharacter::TakeDamage(
    float DamageAmount, FDamageEvent const& DamageEvent,
    AController* EventInstigator, AActor* DamageCauser)
{
    float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);

    // DamageTypeClass는 UClass* — GetDefaultObject로 CDO 꺼냄
    const UFireDamageType* FireDamage =
        DamageEvent.DamageTypeClass->GetDefaultObject<UFireDamageType>();

    if (FireDamage)
    {
        ActualDamage *= (1.f + FireDamage->ArmorPenetration);
        // TODO: 화상 이펙트, 사운드 재생
        UE_LOG(LogTemp, Warning, TEXT("ByWorld Damage Received"));
    }

    if (EventInstigator)
    {
        UE_LOG(LogTemp, Warning, TEXT("Im Enemy!"));
    }

    return ActualDamage;
}

핵심 포인트:

  • DamageTypeClass->GetDefaultObject<T>() — CDO를 캐스팅해 타입의 기본값 파라미터에 접근 (1강에서 배운 CDO 패턴 활용)
  • GetDefaultObject<UFireDamageType>() 결과가 nullptr이 아니면 “이 데미지는 화상 계열”로 판정
  • 여러 DamageType을 chain해 조건별 효과 분기 가능 (화상·관통·폭발 등)

동기 Trace vs 비동기 Trace

ATraceTest 액터에서 두 방식을 모두 실습.

동기 TraceUKismetSystemLibrary::LineTraceMulti (호출 프레임 내 결과 반환):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void ATraceTest::StartSingleTarce()
{
    TArray<FHitResult> HitResult;
    TArray<AActor*> ActorsToIgnore;
    ActorsToIgnore.Add(this);

    UKismetSystemLibrary::LineTraceMulti(
        GetWorld(),
        GetActorLocation(),
        GetActorForwardVector() * 1000.f + GetActorLocation(),
        UEngineTypes::ConvertToTraceType(ECC_Visibility),
        false,                          // bTraceComplex
        ActorsToIgnore,
        EDrawDebugTrace::ForOneFrame,
        HitResult,
        true,
        FLinearColor::Red,
        FLinearColor::Green
    );
}

비동기 TraceUWorld::AsyncLineTraceByChannel (콜백으로 나중에 결과 수신):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void ATraceTest::StartAsyncTrace()
{
    // 1. 델리게이트에 콜백 바인딩
    FTraceDelegate TraceDelegate;
    TraceDelegate.BindUObject(this, &ATraceTest::OnAsyncTraceCompleted);

    // 2. 응답 파라미터 — 어떤 채널에 Block할지
    FCollisionResponseParams ResponseParams;
    ResponseParams.CollisionResponse.WorldDynamic = ECR_Block;

    // 3. 쿼리 파라미터 — 자기 자신 무시, 복잡 충돌 체크 끔
    FCollisionQueryParams QueryParams;
    QueryParams.AddIgnoredActor(this);
    QueryParams.bTraceComplex = false;

    // 4. 비동기 요청 제출 (반환 즉시, 결과는 다음 프레임 이후 콜백)
    GetWorld()->AsyncLineTraceByChannel(
        EAsyncTraceType::Multi,
        GetActorLocation(),
        GetActorForwardVector() * 1000.f + GetActorLocation(),
        ECC_Visibility,
        QueryParams,
        ResponseParams,
        &TraceDelegate
    );
}

void ATraceTest::OnAsyncTraceCompleted(const FTraceHandle& Handle, FTraceDatum& Data)
{
    for (const FHitResult& Hit : Data.OutHits)
    {
        AActor* HitActor = Hit.GetActor();
        GEngine->AddOnScreenDebugMessage(-1, 0.f, FColor::Green,
            FString::Printf(TEXT("Multi Hit Actor : %s"), *HitActor->GetName()));

        DrawDebugSphere(GetWorld(), Hit.ImpactPoint, 20.f, 12, FColor::Green, false, 2.f);

        UGameplayStatics::ApplyPointDamage(
            HitActor, 50.f,
            GetActorForwardVector(),
            Hit,
            GetInstigatorController(),
            this,
            UMyTestDamageType::StaticClass()
        );
    }
}

동기 vs 비동기 비교:

항목동기 Trace (LineTraceMulti)비동기 Trace (AsyncLineTraceByChannel)
결과 시점호출 즉시 반환다음 프레임(이상) 뒤 콜백
호출 스레드메인 스레드 블로킹백그라운드 처리
적합한 상황1회성 판정, 총알 히트스캔지속 스캔, AI 시야 판정, 많은 수 동시
결과 전달반환값 TArray<FHitResult>FTraceDelegate 콜백 → FTraceDatum.OutHits
프레임 부하Trace 수 × 복잡도분산됨, 프레임 시간 보장
주의사항매 프레임 호출 시 부하 누적결과가 “과거” 상태일 수 있음 (1~2 프레임 지연)

왜 AI에는 비동기 Trace가 적합한가: 적이 10마리 있고 각자 플레이어를 찾는 Trace를 매 틱 돌리면, 동기 방식은 메인 스레드에서 10번의 물리 쿼리가 직렬 실행돼 프레임이 튄다. 비동기는 병렬 분산되고 결과를 콜백으로만 처리하므로 프레임 시간이 안정적이다. 대신 “지금 바로 반응”이 아니라 “1~2 프레임 뒤 반응”이라는 걸 AI 로직이 감안해야 함.

ApplyPointDamage 체인

비동기 Trace가 히트한 액터에 데미지를 전달하는 흐름:

1
2
3
4
5
6
7
8
9
AsyncLineTrace (결과 수신)
    ↓ FHitResult
ApplyPointDamage(HitActor, 50.f, ..., UMyTestDamageType::StaticClass())
    ↓
HitActor::TakeDamage(DamageEvent)
    ↓
DamageEvent.DamageTypeClass->GetDefaultObject<UMyTestDamageType>()
    ↓ 타입 확인 후 분기
화상 처리 / 폭발 처리 / 일반 처리

왜 DamageType을 StaticClass()로 넘기나:

  • 데미지 타입은 타입 식별자 + 기본 파라미터 묶음 역할
  • 인스턴스를 만들 필요가 없으므로 CDO(StaticClass가 반환하는 UClass*)만 전달
  • 받는 쪽은 GetDefaultObject<T>()로 CDO의 기본값을 읽음 → 1강 CDO 패턴의 실전 활용


다음주 과제 — 샷건 + 비동기 Trace AI

마감: 2026-05-01 (금) 주의: 마감일이 팀플 시작일과 겹침 → 이번주 안에 필수 과제 완료 목표

필수 과제 — 샷건 + 총 반동 시스템

  • 샷건 제작 (기본 발사 로직)
  • 총 반동(Recoil) 구현 — 발사 시 카메라/무기 반동
  • 총마다 커스터마이징 가능하도록 설계
    • 총기별 데이터 분리 (반동 세기·패턴·회복 속도 등)
    • DataAsset / DataTable 또는 UWeaponConfig 같은 데이터 구조로 확장 가능하게
    • 단일 무기 클래스에서 파라미터 교체만으로 다른 총처럼 동작하도록

도전 과제 — 비동기 Trace 기반 적 AI 탐지

  • 적 캐릭터가 비동기 Trace(Async Line/Sphere Trace) 로 플레이어 탐지
  • UWorld::AsyncLineTraceByChannel / AsyncSweep 활용
  • 델리게이트 콜백으로 결과 수신 → 탐지 상태 갱신
  • 동기 Trace 대비 프레임 블로킹 없이 성능 확보

구현·설계는 별도 구현계획 파일로 분리 예정. 여기는 문제 정의만 기록.



오늘 배운 것 정리

  1. 컨셉 피벗은 “전면 재작성”이 아니라 “의존 섹션만 교체”로 범위 관리. TPS 피벗에서도 필러·레퍼런스·장르 DNA 대부분이 유지 가능했다. 피벗 전에 “어느 섹션이 시점에 의존하는지” 먼저 매핑하면 작업량이 1/3로 줄어든다.

  2. UE 프로젝트는 첫 커밋 전에 gitignore가 완성돼 있어야 한다. 한 번 커밋된 대용량 파일은 --amend (마지막 커밋만) 또는 filter-repo (중간 커밋) 같은 이력 재작성을 강요한다. 오늘 다행히 마지막 커밋이라 amend로 해결했지만 중간 커밋이었으면 훨씬 복잡했을 것.

  3. bUseControllerRotationYaw=true면 Move 계산이 극단적으로 단순해진다. 캐릭터 Forward 벡터가 곧 카메라 방향과 일치하므로 GetActorForwardVector() 하나로 끝. 컨트롤러 회전 벡터를 매번 읽을 필요 없다. 쿼터뷰(캐릭터와 카메라가 독립 회전)와 TPS(캐릭터가 카메라 따라감)의 근본 차이가 코드 복잡도에 직결된다.

  4. gitignore 화이트리스트 패턴(Content/* + !Content/VoidUnreal/**)은 에셋팩이 많은 UE 프로젝트에서 유지보수 비용을 0에 가깝게 만든다. 새 에셋팩이 생길 때마다 gitignore 수정하는 루프를 제거.

  5. CONTRIBUTING.md는 “어디에 두느냐”가 내용만큼 중요하다. 루트에 두면 GitHub가 PR 화면에서 자동으로 링크를 보여준다 — 사실상 유일하게 팀원이 실제로 읽게 만드는 방법. Docs/contributing.md로 숨기면 아무도 안 읽는다.

  6. GitHub Flow는 1인 프로젝트에도 팀 프로젝트에도 통한다. GitFlow의 develop 브랜치는 통합 테스트 공간이 필요한 다인 팀에서만 의미가 있고, 혼자 또는 5인 이하에선 main + feat/*로 충분하다. 불필요한 의례를 없애는 게 핵심.

  7. 오늘 결정은 3개 레포에 걸쳐 일관성을 확보했다. Claude-Code-Game-Studios GDD(TPS로 재작성) → NBC_JangSik_Assignment8 코드(TPS 카메라 리그) → NBC_Ch3_TeamProject 규칙(CONTRIBUTING+main). 한 결정이 다른 레포로 파급될 때 끊김 없이 이어지는 구조가 나중에 자산 이식을 가능하게 만든다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.