Source/NeoSanctum/Data/Part/NSPartTypes.h파츠 관련 핵심 구조체가 모여있는 파일. 세 단계로 진화함.
FNSPartDefinitionRow(DataTable row 구조체) 신설 — Definition(DA 소프트 참조), PartSlot, bCanReroll, UnlockCost, ValueRange, bEnabled. 파츠 수치를 DA가 아니라 DT에 두기로 한 결정의 시작점.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.hDA(DataAsset) 쪽에 있던 PartSlot/bCanReroll/ValueRange를 삭제. 위 DT 구조체로 옮기면서, DA는 PartName/EffectClass/PartMesh/Icon 같은 "정체성·에셋 참조"만 남기고 수치는 전부 DT로 위임. (계기: "수치는 DataTable, DataAsset은 구조/참조만"이라는 프로젝트 규칙)
NSDataSubsystemSource/NeoSanctum/Core/GameInstance/Subsystem/NSDataSubsystem.h/.cpp세 단계로 발전:
BuildPartRowCache() 신설 — DT를 읽어 TMap<FPrimaryAssetId, FNSPartDefinitionRow> CachedPartRowsByDefId에 캐싱, GetPartRow()/GetAllPartRows()로 조회 제공. OnOutGameReferenceAssetsLoaded()에서 호출.NSDataSubsystem 안에 직접(TObjectPtr<UDataTable> PartDefinitionTable) 뒀다가, 곧바로 UNSDataSettings(신설한 UDeveloperSettings)로 이관 — OnOutGamePrimaryAssetsLoaded()를 새로 만들어 Settings의 소프트 참조를 비동기 로드 후 캐시 빌드.UNSDataSettings 자체를 폐기하고 NSCommonDataConfig(기존에 있던 공용 데이터 설정 DA)로 재이관. 이유: 다른 모든 데이터가 "페이즈 전용 PrimaryDataAsset + AssetBundle" 패턴을 쓰는데 파츠만 UDeveloperSettings(프로젝트 세팅, 페이즈 개념 없음)를 써서 구조가 어긋나 있었음. OnCommonAssetsLoaded()에서 BuildPartRowCache()/BuildSlotRowCache()(슬롯 DT용으로 같이 신설) 호출하도록 정리. GetSlotRow()/GetAllSlotRows()도 이때 추가.Source/NeoSanctum/Data/Config/NSCommonDataConfig.hPartsBaseStatTable, PartsSlotBaseStatTable 필드 추가 (AssetBundles="CommonData") — 파츠 DT 2개를 여기로 옮겨받는 자리.
Source/NeoSanctum/Data/Util/NSDataSettings.h/.cpp (삭제)잠깐 만들었다가 위 이유로 완전히 삭제. Config/DefaultGame.ini의 관련 섹션도 같이 제거.
Source/NeoSanctum/NeoSanctum.Build.csNSDataSettings(UDeveloperSettings 상속) 만들 때 "DeveloperSettings" 모듈 의존성 추가. (참고: NSDataSettings 자체는 나중에 삭제됐지만 이 모듈 의존성 라인은 diff상 그대로 남아있음 — 다른 데서 안 쓰면 정리 대상)
NSPartUtilsSource/NeoSanctum/Progression/Part/NSPartUtils.h/.cppResolvePartRow(WorldContextObject, DefId) 신설 — NSDataSubsystem::GetPartRow()를 경유하는 조회 헬퍼. PurchasePart/EquipPart/RerollStat/RollValueForRarity 등 여러 곳에서 DA를 직접 로드하는 대신 이걸 거치게 됨.ResolvePartDefinition(WorldContextObject, TSoftObjectPtr<UNSPartDefinition>) 오버로드 추가 — 기존엔 FNSPartData를 받는 버전만 있었는데, 카탈로그 선택(FNSPartDefinitionRow::Definition)과 장착중 조회(FNSPartSaveData::Definition) 양쪽에서 "소프트포인터 → 캐시 경유 Definition"이 필요해져서 공용화. 기존 FNSPartData 버전은 이 오버로드에 위임하도록 리팩터.NSProgressionSubsystemSource/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.hFNSCharacterSaveData::UnlockedSlots(TSet<FGameplayTag>) 추가 — 슬롯 언락 상태 저장.OwnedParts(계정 공유 파츠 인벤토리) 필드 위치 정리.Source/NeoSanctum/Progression/Reward/NSRewardHandler.h/.cppMakePartDataFromDropResult() — 드랍 시 파츠 수치를 만들 때 LoadSynchronous()로 DA를 강제 동기 로드하던 걸 제거하고, NSPartUtils::ResolvePartRow()(캐시 경유)로 대체. 이 함수가 UWorld* 인자를 새로 받도록 시그니처 변경(서브시스템 접근에 필요).
Source/NeoSanctum/Progression/Part/NSPartEquipComponent.h/.cpp실제로 GAS 이펙트를 붙이고 캐릭터에 파츠를 장착하는 컴포넌트.
EquipPart/RerollStat/RollValueForRarity가 Def->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).
Source/NeoSanctum/UI/Part/NSPartPanelWidget.h/.cpp원래 BodySlotButton/ArmSlotButton/LegSlotButton 3개를 WBP에 고정 배치해서 쓰던 걸, SlotButtonContainer(빈 패널) + SlotButtonTemplate(버튼 1개짜리 템플릿)로 바꿔서 BuildSlotButtons()가 슬롯 DT를 읽어 런타임에 동적 생성하도록 변경. ApplySlot/HandlePartChanged도 슬롯 태그 기반으로 전환. OnOutGameDataReady()를 새로 붙여서 DT 로드 완료 시점에 맞춰 버튼을 빌드.
Source/NeoSanctum/Progression/Part/NSPartPreviewStage.h/.cpp (신규)파츠 상세 패널의 실시간 3D 프리뷰 전용 액터. 월드 밖 먼 위치에 스폰되어 자기 자신만 캡처하는 SceneCaptureComponent2D + 조명 2개(Key/Rim) + 배경판(BackdropComponent)으로 구성. SetPreviewMesh()로 메시 교체, 드래그로 AddManualYaw(), 휠로 AddZoom(). WarmupAllPartMeshes()로 파츠샵 진입 전 모든 파츠 메시를 미리 로드해 첫 프레임에 저해상도 텍스처가 안 뜨게 함.
BP_PartPreviewStage.uasset — 위 액터의 BPM_PartPreview.uasset — RenderTarget을 UI Image에 뿌리는 머티리얼M_PartPreviewBackDrop.uasset, T_PreviewBackDrop.uasset — 배경을 거점 사진으로 교체 (Unlit 머티리얼 + 엔진 Plane UV 반전 보정)Source/NeoSanctum/UI/Part/NSPartDetailWidget.h/.cpp (신규)카탈로그 후보와 장착중인 파츠 양쪽에서 공용으로 쓰는 상시 설명 패널.
SetupFromDefinition(Row, Def) — 카탈로그 후보(이름/슬롯/비용/리롤가능)SetupFromEquipped(SaveData, Def) — 장착중 파츠(이름/등급/효과값)SetupFromSlotLock(SlotRow) — 잠긴 슬롯 정보(슬롯명/언락비용), 3D 프리뷰 없음ClearDetail(), SetPreviewTarget(Stage), ClearPreview() — 프리뷰 연결/해제NativeConstruct()에서 ClearPreview() 호출 — 초기 상태에 빈 흰색 프리뷰 이미지가 남아있던 버그 수정NSPartPreviewStage의 AddManualYaw/AddZoom 호출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 스타일 기능을 시도했다가 적용이 안 되는 문제가 계속돼서, 카탈로그와 동일한 수동 오버레이 방식으로 통일.
NSPartEquipWidgetSource/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에 배치가 안 돼서 한 번도 작동한 적 없던 코드) 완전 삭제Source/NeoSanctum/Interaction/Component/NSInteractionComponent.cppClient_OnInteractApproved_Implementation이 ANSInteractableNPCBase면 무조건 PC의 OpenInteractionWidget을 호출하던 걸, NPC->GetInteractionWidgetClass()가 있을 때만 그렇게 하도록 조건 추가. 위젯 클래스가 없는 NPC(예: 직접 OnInteract를 처리하는 캐릭터 선택 NPC)는 기존 Execute_OnInteract 폴백 경로를 타게 함. (원인: 파츠샵/펫 NPC는 위젯 클래스 방식, 캐릭터 선택 NPC는 직접 호출 방식으로 서로 다른데 공용 컴포넌트가 한쪽만 가정하고 있었음)
Source/NeoSanctum/Core/PlayerController/NSPlayerController.cppTogglePauseMenu()에 if (ActiveInteractionWidget) { CloseInteractionWidget(); return; } 추가 — ESC를 눌렀을 때 상호작용 위젯(파츠샵 등)이 열려있으면 일시정지 메뉴보다 그것부터 닫히게 함.
Source/NeoSanctum/Core/Cheat/NSCheatManager.h/.cppDebug_SetCommonCurrency() 신규 — 슬롯 언락/파츠 구매 테스트용으로 공통 재화를 즉시 10000으로. NSProgressionSubsystem이 참조하는 것과 동일한 캐시된 세이브 객체 값만 메모리에서 직접 바꾸는 방식(디스크 저장·서버 동기화 없음 — 테스트 목적상 그럴 필요가 없어서 최대한 단순하게 처리).
DT_PartDefinition.uasset, DT_PartSlot.uasset — 위 DT 구조체들의 실제 데이터Data/Reward/Part/DataAsset/ — 파츠별 DA(Arm 7종, Body 2종, Leg 3종)Data/Reward/Part/Icon/ — 파츠별 아이콘 텍스처DA_CommonDataConfig.uasset — PartsBaseStatTable/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눌렀을때 파츠가 출력되는것처럼 파츠장착 위젯에서도 해당 파츠 슬롯을 재사용하는식으로 변경했음 -> 다 임시라서 우선 이름 :, 효과 : 이런식으로 표현
문제1. 파츠 장착을 했을때 정보가 이상함 (2.779가 뭘말하는건지 모르겠음), 파츠 이름 또 다른 정보들을 출력해야 될거같음 그리고 무슨 정보인지 알 수 있게 이름 : , 효과 : 이런식으로 들어가야 할거같음(가운데 파츠 디테일도 똑같이)
문제2. 슬롯쪽에 해금된 슬롯의 경우 슬롯색이 흰색으로 변해서 활성화된 느낌을 주어야할거같음
문제3. 장착한 슬롯이 슬롯버튼에 갱신이 안됨
파츠나 슬롯을 클릭했을때 선택되어있다는 느낌이 안듬