TIL - 재화 드랍 테스트

Kyu_·2026년 6월 17일

30주차

목록 보기
3/5

재화 드랍 테스트

목표

PIE 멀티 환경에서 재화 드랍 시스템을 빠르게 테스트할 수 있는 콘솔 치트명령 구현


문제 1. GameMode의 UFUNCTION(Exec)가 콘솔에서 실행되지 않음

현상

NSRunGameModeUFUNCTION(Exec) 함수를 만들고 콘솔(~)에서 호출해도 아무일도 일어나지 않음, 기존 로그만 출력되는 현상

원인

언리얼 콘솔 Exec 명령의 라우팅체인은

LocalPlayer -> PlayerController -> Pawn -> HUD -> PlayerCameraManager -> PlayerInput -> CheatManager -> GameInstance

콘솔(~) Exec 명령은 GameMode로 라우팅되지 않는다는 사실, GameMode는 이 체인에 없음 그래서 Debug_SpawnCurrency를 쳐도 함수 자체가 호출되지 않는문제였다.

해결방안

  1. APlayerController로 옮기기(깔끔)
  2. CheatManager 만들기 (나중에 사용할 수 있지 않을까?)

CheatManager쪽으로 가기로 했다. 추후에 다른 테스트 할때도 사용할 수 있지 않을까해서
그래서 CheatManager에서 Exec함수를 정의, NSPlayerController 생성자에서 CheatClass = UNSCheatManager::StaticClass()로 등록


문제 2. 클라에서 콘솔 입력시 "Command not recognized" 오류

현상

호스트 콘솔에서는 Debug_SpawnCurrency가 정상 실행 되지만, 클라 콘솔에서는 명령 자체를 인식 못함.

원인

UCheatManager는 엔진 기본 동작상 서버/스탠드얼론에서만 생성됨 (APlayerController::AddCheatsNetMode != NM_Client 조건으로 가드됨), 순수 클라 PlayerController에는 CheatManager가 아예 생성되지 않아서 Exec 명령을 인식하지 못했음

해결

NSPlayerController::BeginPlay에서 로컬 컨트롤러인 경우 EnabledCheats()를 명시적으로 호출, 이 함수가 내부적으로 AddCheats(true)를 실행하여 클라에도 CheatManager를 강제 생성

void ANSPlayerController::BeginPlay()
{
    ...
    // 테스트용 임시 코드 - 드롭 테이블 연동 후 삭제
    EnableCheats();
    ...
}

문제 3. 클라에서 치트를 실행해도 호스트 발밑 재화가 생성되지 않음

현상

CheatManager가 클라에서도 인식된 뒤, 클라 콘솔에서 Debug_SpawnCurrency를 실행하면 호스트 캐릭터위치에 재화가 생성되지 않음, 클라 캐릭터 위치에만 생성됨

원인

RegisterDropHasServerAuthority()가드가 있어 서버 권한에서만 실행됨. CheatManager는 클라 로컬에서 동작하므로 클라의 GetPlayerControllerIterator()는 로컬 컨트롤러만 보고, 드랍도 서버에 반영되지 않았음

해결

CheatManager에서 드랍 로직을 직접 실행하지 않고, ServerRPC를 통해 반드시 서버 권한에서 실행되도록 설계

클라 콘솔 입력
-> UNSCheatManager::Debug_SpawnCurrency() (클라 로컬)
-> ANSPlayerController::Server_DebugSpawnCurrency() (Server RPC -> 서버로 전송)
-> 서버에서 모든 PlayerController순회 -> RegisterDrop
-> 각 CurrencyReplicationProxy -> Client RPC -> 각 클라에서 LocalPickup 스폰

문제 4. 공통·스킬재화를 드랍했는데 메시가 안 보임

현상

Debug_SpawnCurrency 하나로 셋 다 드랍했더니 임시재화만 메시가 보이고, 공통/스킬은 줍기 로그는 떠도 메시가 안 보임

원인

치트가 셋 다 Grade1로 등록했는데, UNSCurrencyVisualData의 행 조회(FindVisual)는 CurrencyType + Grade 둘 다 일치해야 함. 설계상 임시재화만 등급(Grade1~)을 쓰고 공통/스킬은 Grade::None 이라, FindVisual(Common, Grade1)은 매칭 행이 없어 nullptr → 메시 미설정

해결

치트를 타입별로 분리(Debug_SpawnTemp / Debug_SpawnCommon / Debug_SpawnSkill)하고, 공통/스킬은 ENSCurrencyGrade::None으로 등록. → VisualData 조회 키와 일치


문제 5. 인런 중 획득한 영구재화도 임시재화처럼 HUD에 실시간 표시로 변경

배경

임시재화는 복제되는 Wallet에 들어가 HUD에 실시간 반영되지만, 공통/스킬재화는 서버 전용 PendingPermanent TMap에만 쌓여 클라가 볼 수 없음

검토한 두 방법

  • 방법 A (Wallet 미러): AddRunPermanent에서 Pending 누적 + AddToWallet로 복제 경로에도 미러. 진실 원본은 Pending 유지. 커밋 로직 무변경
  • 방법 B (Wallet 일원화): Pending TMap 제거, 전부 Wallet에 저장. 커밋이 Wallet을 읽고 비움

결정 → 방법 A

커밋 = 런 종료(게임오버) 시점이라, A의 단점이던 "커밋 후 Wallet 정리"가 무의미해짐(어차피 런 경계에서 리셋/파괴). Pending·Wallet은 같은 함수에서 같은 양을 더하므로 드리프트 불가, 진실 원본(Pending)을 안 건드리는 게 가장 좋아보였음


문제6. 아웃런 진입 시 지갑초기화

배경

Seamless Travel구조라서 PlayerState가 무조건 초기화된다는 보장이없어서 지갑을 추가적으로 초기화해주는 함수를 만들었다.


최종 구조

[호스트/클라 콘솔] Debug_SpawnCurrency
  → UNSCheatManager::Debug_SpawnCurrency()
  → ANSPlayerController::Server_DebugSpawnCurrency()  ← Server RPC
  → UNSCurrencyDropSubsystem::RegisterDrop() × N명  (서버)
  → ANSCurrencyReplicationProxy::SendSpawnEvent() × N명
  → Client_SpawnCurrency() × N명  ← Client RPC
  → ANSLocalCurrencyPickup 각자 로컬 스폰

관련 파일:

파일역할
Core/Cheat/NSCheatManager.h/.cppExec 치트 정의
Core/PlayerController/NSPlayerController.h/.cppCheatClass 등록, Server RPC 구현, EnableCheats
System/Subsystem/NSCurrencyDropSubsystem.cppRegisterDrop (서버 권한 전용)
Progression/Currency/NSCurrencyReplicationProxy.cppClient RPC로 각 클라에 이벤트 전달
Progression/Currency/NSLocalCurrencyPickup.cpp클라 로컬 픽업 액터

삭제 예정 (드롭 테이블 연동 후)

  • Core/Cheat/NSCheatManager.h/.cpp — 전체
  • NSPlayerControllerServer_DebugSpawnCurrency 선언/구현, EnableCheats() 호출, 관련 인클루드
  • NSPlayerController::BeginPlayEnableCheats() 한 줄

변경 사항

  • 재화(Currency) 시스템 구현 (서버 레지스트리 + 클라 로컬 비주얼)
    • UNSCurrencyDropSubsystem: 서버 전용 드랍 레지스트리
    • ANSCurrencyReplicationProxy: 플레이어당 1개, owner-only 복제로 Wallet 변경 전달
    • ANSLocalCurrencyPickup: 클라 로컬 비주얼 액터 (서버 비존재)
    • UNSCurrencyWalletComponent: FastArraySerializer 기반 지갑 (키: FGameplayTag)
    • PlayerState ServerRPC: 오버랩 시 서버 Wallet 적립
  • 영구 재화 적립 구조: 런 중 PendingBucket 누적 → 런 종료 시
    CommitRunPermanent(Multiplier) (클리어 1.0 / 전멸 0.5)
  • ProgressComponent에 영구 재화 저장 및 HUD 연동
  • GameMode에 Proxy 세팅 연결
  • 디버그용 UNSCheatManager 추가 (재화 강제 지급 콘솔 커맨드)

테스트 방식

  1. NSCurrencyVisualData를 상속받는 DA생성
  2. DA 세팅
    스크린샷 2026-06-17 161219
  • Currency.Common/ Temp / Skill 존재, Grade는 Temp만 1~3등급으로 지정해주면 됨
  • Scale은 크기
  1. NSCurrencyReplicationProxy를 상속받는 BP생성
  2. Currency > Visual Data에 만든 DA 지정
    스크린샷 2026-06-17 161128
  3. Debug 함수 실행
    스크린샷 2026-06-17 161600
  • SpawnTemp : 임시 재화 생성
  • SpawnSkill : 스킬 재화 생성
  • SpawnCommon : 공통 재화 생성
  • Spawn함수들은 각플레이어 앞에 재화 생성
  • CommitPermanent : 영구재화(스킬/공통)를 아웃런에 적립 (실제로는 런 종료시에 호출, 클리어/전멸)

참고 사항

  • 투사체 시스템(ProjectileManagerComponent + ReplicationProxy + ProjectileVisual) 패턴을 그대로 미러링한 구조
  • 드랍 확률/수치 밸런스(UNSCurrencyDropTable)는 별도 담당자 작업 예정
  • 런 종료 commit 훅(OpenRunEndVote 경로)은 아직 미연결 — 허브 복귀 확정 시점에 연결 필요

추후 구현 사항

  • 드롭 테이블 구현 후 재화 스폰 위치 구현 필요
  • 파츠 쪽도 똑같이 몬스터 사망에서 구현 필요

0개의 댓글