TIL - 파츠 시스템 및 아웃런파츠 NPC 끝

Kyu_·2026년 7월 3일

32주차

목록 보기
5/5

1. 파츠 데이터 구조 정의

Source/NeoSanctum/Data/Part/NSPartTypes.h

파츠 관련 핵심 구조체가 모여있는 파일. 세 단계로 진화함.

  1. 최초: FNSPartDefinitionRow(DataTable row 구조체) 신설 — Definition(DA 소프트 참조), PartSlot, bCanReroll, UnlockCost, ValueRange, bEnabled. 파츠 수치를 DA가 아니라 DT에 두기로 한 결정의 시작점.
  2. FNSPartSlotRow 추가 + ENSPartSlot enum 폐기 → FGameplayTag PartSlot: 슬롯을 고정 enum(Body/Arm/Leg 3개)이 아니라 GameplayTag로 바꿔서, 슬롯 자체도 DT(FNSPartSlotRow: SlotTag, UnlockCost, bUnlockedByDefault, bEnabled)로 데이터화. 슬롯 언락 개념(비용, 기본 해금 여부)이 이때 처음 생김. FNSPartData::Slot도 enum → tag로 변경.

Source/NeoSanctum/Data/Part/NSPartDefinition.h

DA(DataAsset) 쪽에 있던 PartSlot/bCanReroll/ValueRange를 삭제. 위 DT 구조체로 옮기면서, DA는 PartName/EffectClass/PartMesh/Icon 같은 "정체성·에셋 참조"만 남기고 수치는 전부 DT로 위임. (계기: "수치는 DataTable, DataAsset은 구조/참조만"이라는 프로젝트 규칙)


2. 데이터 로드 파이프라인 — NSDataSubsystem

Source/NeoSanctum/Core/GameInstance/Subsystem/NSDataSubsystem.h/.cpp

세 단계로 발전:

  1. BuildPartRowCache() 신설 — DT를 읽어 TMap<FPrimaryAssetId, FNSPartDefinitionRow> CachedPartRowsByDefId에 캐싱, GetPartRow()/GetAllPartRows()로 조회 제공. OnOutGameReferenceAssetsLoaded()에서 호출.
  2. DT 에셋 참조를 처음엔 NSDataSubsystem 안에 직접(TObjectPtr<UDataTable> PartDefinitionTable) 뒀다가, 곧바로 UNSDataSettings(신설한 UDeveloperSettings)로 이관 — OnOutGamePrimaryAssetsLoaded()를 새로 만들어 Settings의 소프트 참조를 비동기 로드 후 캐시 빌드.
  3. 최종적으로 UNSDataSettings 자체를 폐기하고 NSCommonDataConfig(기존에 있던 공용 데이터 설정 DA)로 재이관. 이유: 다른 모든 데이터가 "페이즈 전용 PrimaryDataAsset + AssetBundle" 패턴을 쓰는데 파츠만 UDeveloperSettings(프로젝트 세팅, 페이즈 개념 없음)를 써서 구조가 어긋나 있었음. OnCommonAssetsLoaded()에서 BuildPartRowCache()/BuildSlotRowCache()(슬롯 DT용으로 같이 신설) 호출하도록 정리. GetSlotRow()/GetAllSlotRows()도 이때 추가.

Source/NeoSanctum/Data/Config/NSCommonDataConfig.h

PartsBaseStatTable, PartsSlotBaseStatTable 필드 추가 (AssetBundles="CommonData") — 파츠 DT 2개를 여기로 옮겨받는 자리.

Source/NeoSanctum/Data/Util/NSDataSettings.h/.cpp (삭제)

잠깐 만들었다가 위 이유로 완전히 삭제. Config/DefaultGame.ini의 관련 섹션도 같이 제거.

Source/NeoSanctum/NeoSanctum.Build.cs

NSDataSettings(UDeveloperSettings 상속) 만들 때 "DeveloperSettings" 모듈 의존성 추가. (참고: NSDataSettings 자체는 나중에 삭제됐지만 이 모듈 의존성 라인은 diff상 그대로 남아있음 — 다른 데서 안 쓰면 정리 대상)


3. 파츠 조회 유틸 — NSPartUtils

Source/NeoSanctum/Progression/Part/NSPartUtils.h/.cpp

  • ResolvePartRow(WorldContextObject, DefId) 신설 — NSDataSubsystem::GetPartRow()를 경유하는 조회 헬퍼. PurchasePart/EquipPart/RerollStat/RollValueForRarity 등 여러 곳에서 DA를 직접 로드하는 대신 이걸 거치게 됨.
  • 후속으로 ResolvePartDefinition(WorldContextObject, TSoftObjectPtr<UNSPartDefinition>) 오버로드 추가 — 기존엔 FNSPartData를 받는 버전만 있었는데, 카탈로그 선택(FNSPartDefinitionRow::Definition)과 장착중 조회(FNSPartSaveData::Definition) 양쪽에서 "소프트포인터 → 캐시 경유 Definition"이 필요해져서 공용화. 기존 FNSPartData 버전은 이 오버로드에 위임하도록 리팩터.

4. 서버 진행 로직 — NSProgressionSubsystem

Source/NeoSanctum/Core/GameInstance/Subsystem/NSProgressionSubsystem.h/.cpp

파츠 구매/장착/슬롯 언락의 실질적인 백엔드. 여러 단계로 완성됨:

  • PurchasePart(Definition, Rarity) — 처음엔 Cost를 파라미터로 직접 받았으나, DT에서 Row->UnlockCost를 읽어오는 구조로 변경(LoadSynchronous 제거가 핵심 동기). 최종 버전엔 슬롯 언락 여부 체크(IsSlotUnlocked)도 추가되어, 슬롯이 안 열려있으면 구매 자체가 실패하도록 함.
  • SetEquippedPart(CharacterId, Definition, Rarity) / GetEquippedPart(CharacterId) — 장착 저장/조회. Definition이 null이면 해제로 처리(장착 해제 기능이 이 널 체크 하나로 동작).
  • IsPartOwned, GetCommonCurrency, GetLastSelectedCharacterId — 조회용.
  • UnlockSlot(CharacterId, Slot) / IsSlotUnlocked / GetSlotUnlockCost — 슬롯 언락 로직. 재화 체크 실패, Row 없음, 이미 해금됨 등 각 실패 분기에 진단 로그 추가(원래 로그가 하나도 없어서 재화 부족 때문에 언락이 안 되는 걸 못 찾고 있었음).

Source/NeoSanctum/Progression/Save/NSPermanentSaveGame.h

  • FNSCharacterSaveData::UnlockedSlots(TSet<FGameplayTag>) 추가 — 슬롯 언락 상태 저장.
  • OwnedParts(계정 공유 파츠 인벤토리) 필드 위치 정리.

5. 드랍/장착 실행 로직

Source/NeoSanctum/Progression/Reward/NSRewardHandler.h/.cpp

MakePartDataFromDropResult() — 드랍 시 파츠 수치를 만들 때 LoadSynchronous()로 DA를 강제 동기 로드하던 걸 제거하고, NSPartUtils::ResolvePartRow()(캐시 경유)로 대체. 이 함수가 UWorld* 인자를 새로 받도록 시그니처 변경(서브시스템 접근에 필요).

Source/NeoSanctum/Progression/Part/NSPartEquipComponent.h/.cpp

실제로 GAS 이펙트를 붙이고 캐릭터에 파츠를 장착하는 컴포넌트.

  • EquipPart/RerollStat/RollValueForRarityDef->PartSlot/Def->ValueRange를 직접 읽던 걸, DT 이관 이후 NSPartUtils::ResolvePartRow()로 대체.
  • 슬롯 타입을 ENSPartSlot enum에서 FGameplayTag로 전면 전환 (delegate 시그니처 FNSOnPartChanged, HasEquippedPart, GetEquippedPart, FindPart, DropPartInSlot, RemovePartEffects, RemoveGEForSlot, RemoveAbilitiesForSlot, ApplyPartEffect, Internal_ApplyGE, OnEffectLoaded, GrantAbilities, OnAbilitiesLoaded, RerollStat, UpgradeRarity, 관련 TMap 전부 포함).

Source/NeoSanctum/Character/Component/NSPartVisualComponent.h/.cpp

캐릭터 몸에 파츠 메시를 실제로 붙이는 컴포넌트(리더포즈 방식). 위와 같은 이유로 슬롯을 enum → tag로 전환. 이 과정에서 하드코딩되어 있던 { ENSPartSlot::Body, ENSPartSlot::Arm, ENSPartSlot::Leg } 순회를 DataSS->GetAllSlotRows() 기반 순회로 바꿔서, 슬롯이 DT에 추가/삭제되어도 코드 수정 없이 대응 가능하게 함 (BindToEquipComponent, EnsureSlotComponents, GetSlotMeshComp, HandlePartChanged, UpdateSlotVisual, ClearSlotVisual).


6. 인런 HUD 파츠 패널

Source/NeoSanctum/UI/Part/NSPartPanelWidget.h/.cpp

원래 BodySlotButton/ArmSlotButton/LegSlotButton 3개를 WBP에 고정 배치해서 쓰던 걸, SlotButtonContainer(빈 패널) + SlotButtonTemplate(버튼 1개짜리 템플릿)로 바꿔서 BuildSlotButtons()가 슬롯 DT를 읽어 런타임에 동적 생성하도록 변경. ApplySlot/HandlePartChanged도 슬롯 태그 기반으로 전환. OnOutGameDataReady()를 새로 붙여서 DT 로드 완료 시점에 맞춰 버튼을 빌드.


7. 파츠 3D 프리뷰

Source/NeoSanctum/Progression/Part/NSPartPreviewStage.h/.cpp (신규)

파츠 상세 패널의 실시간 3D 프리뷰 전용 액터. 월드 밖 먼 위치에 스폰되어 자기 자신만 캡처하는 SceneCaptureComponent2D + 조명 2개(Key/Rim) + 배경판(BackdropComponent)으로 구성. SetPreviewMesh()로 메시 교체, 드래그로 AddManualYaw(), 휠로 AddZoom(). WarmupAllPartMeshes()로 파츠샵 진입 전 모든 파츠 메시를 미리 로드해 첫 프레임에 저해상도 텍스처가 안 뜨게 함.

관련 콘텐츠

  • BP_PartPreviewStage.uasset — 위 액터의 BP
  • M_PartPreview.uasset — RenderTarget을 UI Image에 뿌리는 머티리얼
  • M_PartPreviewBackDrop.uasset, T_PreviewBackDrop.uasset — 배경을 거점 사진으로 교체 (Unlit 머티리얼 + 엔진 Plane UV 반전 보정)

8. 파츠샵 UI — 카탈로그/상세 패널

Source/NeoSanctum/UI/Part/NSPartDetailWidget.h/.cpp (신규)

카탈로그 후보와 장착중인 파츠 양쪽에서 공용으로 쓰는 상시 설명 패널.

  • SetupFromDefinition(Row, Def) — 카탈로그 후보(이름/슬롯/비용/리롤가능)
  • SetupFromEquipped(SaveData, Def) — 장착중 파츠(이름/등급/효과값)
  • SetupFromSlotLock(SlotRow) — 잠긴 슬롯 정보(슬롯명/언락비용), 3D 프리뷰 없음
  • ClearDetail(), SetPreviewTarget(Stage), ClearPreview() — 프리뷰 연결/해제
  • NativeConstruct()에서 ClearPreview() 호출 — 초기 상태에 빈 흰색 프리뷰 이미지가 남아있던 버그 수정
  • 마우스 드래그/휠 이벤트를 받아 NSPartPreviewStageAddManualYaw/AddZoom 호출
  • 모든 텍스트에 "이름 :", "슬롯 :", "효과 :" 등 라벨 포맷 적용, 슬롯 태그를 leaf 이름으로 축약해서 표시(GetSlotLeafName), 효과 수치는 소수점 1자리로 포맷(FormatEffectValue)

Source/NeoSanctum/UI/Part/NSPartCatalogEntryWidget.h/.cpp (신규)

카탈로그 개별 항목. 클릭 가능한 SelectButton + 아이콘 비동기 로드(OnDefinitionLoaded) + SelectedIndicator(선택 시에만 보이는 반투명 오버레이, SetIsSelected(bool))로 구성. 클릭하면 Owner->SelectCatalogPart(StoredRow, this) 호출.

Source/NeoSanctum/UI/Part/Button/NSPartSlotButton.h/.cpp

원래 HUD 파츠 패널(WBP_PartsButton) 전용이던 걸 파츠샵의 "장착중" 3박스에도 재사용. SelectedHighlight(선택 표시 오버레이) + SetHighlighted(bool) 추가 — CommonUI 자체 Selected 스타일 기능을 시도했다가 적용이 안 되는 문제가 계속돼서, 카탈로그와 동일한 수동 오버레이 방식으로 통일.


9. 파츠샵 메인 위젯 — NSPartEquipWidget

Source/NeoSanctum/UI/Interaction/NSPartEquipWidget.h/.cpp

이 브랜치에서 가장 많이 바뀐 파일. 세 번의 큰 리팩터를 거침.

1단계 — 컴포넌트 직접 호출 → ProgressionSubsystem 경유
원래 EquipSelectedPart(int32 SelectableIndex)가 배열 인덱스로 파츠를 골라 UNSPartEquipComponent::Server_RequestEquip을 직접 호출하던 구조를, RequestUnlockPart(Definition)/RequestEquipPart(Definition)NSProgressionSubsystem(세이브 기반 구매/장착)을 경유하도록 전면 교체.

2단계 — 선택 모델 도입 (카탈로그 클릭 → 중앙 패널 → 장착 버튼)
항목별 개별 버튼+hover 방식을 버리고, BuildPartEntries()BodyListContainer/ArmListContainer/LegListContainer 3분할 카탈로그 구성, SelectCatalogPart(Row)로 선택 시 중앙 패널(SelectedDetailWidget) 갱신, OnEquipButtonClicked()로 구매/장착 확정. 왼쪽 "장착중" 3박스는 SelectEquippedSlot(SlotTag)로 클릭한 슬롯에 장착된 것만 표시.

3단계 — 슬롯 언락/해제를 같은 선택 흐름에 통합 (이번 세션 핵심 작업)

  • ENSPartSelectionMode(None/Part/SlotUnlock/UnequipPart) 신설 — 중앙 패널/버튼이 가리키는 대상을 하나의 상태로 관리
  • RefreshEquippedDisplay() — 왼쪽 패널을 슬롯버튼 클릭과 무관하게 "현재 장착 파츠"만 상시 표시하도록 단순화 (기존 SelectEquippedSlot을 대체)
  • RefreshEquippedSlotButton() — 왼쪽 3개 슬롯버튼(NSPartSlotButton)에 실제 장착 파츠의 아이콘/이름/수치를 표시
  • OnSlotButtonClicked(SlotTag) — 슬롯버튼 3개 공용 처리: 잠긴 슬롯 클릭 시 언락 정보 표시, 해금+장착된 슬롯 클릭 시 해제 정보 표시, 해금+빈 슬롯은 무반응
  • RefreshEquipButton() — 슬롯해금/해제/슬롯해금필요/구매/장착 5가지 상태로 확장
  • RequestUnequipPart() 신설 — null Definition으로 SetEquippedPart 호출해 해제
  • IsSlotEquipped(SlotTag) — 이 슬롯에 지금 뭐가 장착돼있는지 BP에서도 조회 가능하게 노출 (슬롯버튼 활성 표시용)
  • RefreshSelectionHighlights() — 카탈로그 선택/슬롯버튼 선택 하이라이트가 서로 배타적으로 켜지고 꺼지도록 정리
  • 죽어있던 NSPartSlotEntryWidget/SlotListContainer(별도 슬롯 언락 리스트, WBP에 배치가 안 돼서 한 번도 작동한 적 없던 코드) 완전 삭제

10. 상호작용/컨트롤러 자잘한 수정

Source/NeoSanctum/Interaction/Component/NSInteractionComponent.cpp

Client_OnInteractApproved_ImplementationANSInteractableNPCBase면 무조건 PC의 OpenInteractionWidget을 호출하던 걸, NPC->GetInteractionWidgetClass()가 있을 때만 그렇게 하도록 조건 추가. 위젯 클래스가 없는 NPC(예: 직접 OnInteract를 처리하는 캐릭터 선택 NPC)는 기존 Execute_OnInteract 폴백 경로를 타게 함. (원인: 파츠샵/펫 NPC는 위젯 클래스 방식, 캐릭터 선택 NPC는 직접 호출 방식으로 서로 다른데 공용 컴포넌트가 한쪽만 가정하고 있었음)

Source/NeoSanctum/Core/PlayerController/NSPlayerController.cpp

TogglePauseMenu()if (ActiveInteractionWidget) { CloseInteractionWidget(); return; } 추가 — ESC를 눌렀을 때 상호작용 위젯(파츠샵 등)이 열려있으면 일시정지 메뉴보다 그것부터 닫히게 함.


11. 개발용 치트

Source/NeoSanctum/Core/Cheat/NSCheatManager.h/.cpp

Debug_SetCommonCurrency() 신규 — 슬롯 언락/파츠 구매 테스트용으로 공통 재화를 즉시 10000으로. NSProgressionSubsystem이 참조하는 것과 동일한 캐시된 세이브 객체 값만 메모리에서 직접 바꾸는 방식(디스크 저장·서버 동기화 없음 — 테스트 목적상 그럴 필요가 없어서 최대한 단순하게 처리).


12. 데이터 콘텐츠 (Content/)

  • DT_PartDefinition.uasset, DT_PartSlot.uasset — 위 DT 구조체들의 실제 데이터
  • Data/Reward/Part/DataAsset/ — 파츠별 DA(Arm 7종, Body 2종, Leg 3종)
  • Data/Reward/Part/Icon/ — 파츠별 아이콘 텍스처
  • DA_CommonDataConfig.uassetPartsBaseStatTable/PartsSlotBaseStatTable에 위 DT 연결
  • WBP_PartCatalogEntry.uasset, WBP_PartDetail.uasset, WBP_PartSlotButton.uasset, WBP_PartEquip.uasset(UI/Interaction/Parts/로 위치 이동) — 파츠샵 UI 에셋 일체
  • WBP_PartsButton.uasset, WBP_PartPanel.uasset — HUD 파츠 패널 쪽 에셋 (슬롯 동적 생성 대응)
  • BP_Style_PartSlotButton.uasset — CommonButtonStyle 시도했다가 실제로는 안 쓰게 된 에셋 (수동 하이라이트 방식으로 대체됨, 정리 대상인지 확인 필요)
  • WBP_PartSlotEntry.uasset — 삭제된 NSPartSlotEntryWidget이 쓰던 에셋 (C++ 클래스가 없어졌으므로 같이 정리 대상)

확인이 필요한 것들

  • Content/NeoSanctum/Interaction/NPC/BP_PartsNPC.uasset, Content/NeoSanctum/UI/HUD/WBP_HUD.uasset(develop과 충돌 후 develop 버전 채택) — 이 브랜치에서 실제로 뭘 바꾸려 했는지 파악 못 함
  • BP_Style_PartSlotButton.uasset, WBP_PartSlotEntry.uasset — 안 쓰는 게 확실하면 삭제 권장

파츠 프리뷰 배경 변경하기

머티리얼을 만들고 파츠 프리뷰 화면으로 적절한곳에서 언리얼 스크린샷을 찍어서 머티리얼에 넣고 실제로 3D에셋이 보일때 뒤 배경으로 설정하였다.

파츠 슬롯 해금

파츠 슬롯이 해금이 안되는문제가 있어서 로그로 우선 문제파악을 들어갔다. -> 로직은 정상인데 재화가 없어서 언락이 안되고 있었음
Tab눌렀을때 파츠가 출력되는것처럼 파츠장착 위젯에서도 해당 파츠 슬롯을 재사용하는식으로 변경했음 -> 다 임시라서 우선 이름 :, 효과 : 이런식으로 표현

AI 한테 질문

문제1. 파츠 장착을 했을때 정보가 이상함 (2.779가 뭘말하는건지 모르겠음), 파츠 이름 또 다른 정보들을 출력해야 될거같음 그리고 무슨 정보인지 알 수 있게 이름 : , 효과 : 이런식으로 들어가야 할거같음(가운데 파츠 디테일도 똑같이)
문제2. 슬롯쪽에 해금된 슬롯의 경우 슬롯색이 흰색으로 변해서 활성화된 느낌을 주어야할거같음
문제3. 장착한 슬롯이 슬롯버튼에 갱신이 안됨

선택한 느낌이 안듬

파츠나 슬롯을 클릭했을때 선택되어있다는 느낌이 안듬

0개의 댓글