<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>tkdqls 님의 블로그</title>
    <link>https://tkd6966.tistory.com/</link>
    <description>tkdqls 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Sun, 14 Jun 2026 17:29:48 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>tkdqls</managingEditor>
    <item>
      <title>인벤토리 시스템 고도화 : 네이티브 태그 도입 및 파밍 동시 접근 제어</title>
      <link>https://tkd6966.tistory.com/104</link>
      <description>&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;  학습 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FGameplayTag의 계층 구조를 네이티브 시스템으로 구축하여 하드코딩된 문자열 의존성을 제거하고 데이터 무결성을 확보한다.&lt;/li&gt;
&lt;li&gt;퀵슬롯 무기 스왑 및 자동 장착 인터락 로직을 구현하여 인터랙션의 예외 처리를 정밀화한다.&lt;/li&gt;
&lt;li&gt;GameplayTagContainer 기반의 상태 제어 시스템을 구축하여 멀티플레이 환경에서의 동시 접근 제어 및 네트워크 파이프라인을 정립한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;  1. 인벤토리 구조 최적화: 네이티브 GameplayTags 도입&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;  문자열 파편화 방지 및 계층 구조 정립&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;기존에 하드코딩된 문자열(예: &quot;ItemType.Weapon&quot;)에 의존하던 방식은 오타 발생 위험이 크고, 아이템 계층 구조가 파편화되어 유지보수가 어려웠다. 이를 해결하기 위해 NCGameplayTags.h/cpp에 네이티브 태그를 등록하여 중앙화된 계층 구조를 구축하였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;네이티브 계층:&lt;/b&gt; Item.Equipment.Weapon, Item.Consumable.Heal 등 루트-자식 관계를 명확히 정의하였다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;성능 및 안정성:&lt;/b&gt; MatchesTag 함수를 활용하여 태그 계층을 비교함으로써 로직의 가독성을 높였으며, 단순 문자열 비교를 배제하여 런타임 하드코딩 오류를 100% 제거하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size26&quot;&gt;  2. 인벤토리 및 퀵슬롯 비즈니스 로직 고도화&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;  슬롯별 책임 분리 및 방어 로직&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;퀵슬롯 제약:&lt;/b&gt; 1~2번 슬롯(무기), 3번(힐), 4번(음식)으로 기능을 엄격히 분리하였다. 잘못된 아이템이 슬롯에 장착되는 논리적 오류를 방지하기 위해 GameplayTag 필터링 방어 로직을 적용하였다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;스왑 인터락(Interlock):&lt;/b&gt; 퀵슬롯 장착 해제 시, 현재 캐릭터 손에 들린 무기를 강제로 제거하여 맨손 상태로 전환하는 안전장치를 구현하여 상태 불일치를 방지하였다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,2,0&quot;&gt;자동 장착(Auto-Equip):&lt;/b&gt; AutoEquipItem 함수를 구현하여, 아이템 태그를 분석한 뒤 인벤토리 내 최적의 빈 슬롯을 스스로 찾아 장착하는 로직을 완성하였다. 이는 향후 UI 클릭 연동 기반의 핵심 백엔드 API로 활용된다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,3,0&quot;&gt;데이터 영속성:&lt;/b&gt; UNCSaveGame 클래스 내부에 퀵슬롯 데이터 구조를 통합하고, 컴포넌트 레벨에서 이를 디스크에 영구 저장/불러오기 하는 아키텍처를 구현하여 아이템 상태 보존 로직을 완성하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size26&quot;&gt;  3. 파밍 상자(LootBox) 시스템 확장 및 동시 접근 제어&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;  태그 기반 상태 관리 전환&lt;/h3&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;기존의 bool 플래그 방식은 상태 확장에 한계가 있었다. 이를 FGameplayTagContainer 기반의 상태 관리 방식으로 전면 전환하여, LootBox.State.BeingLooted와 같은 태그를 통해 상자의 현재 상태를 정교하게 추적하도록 개선하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;  멀티플레이어 동시 접근 방지 (Lock)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;멀티플레이 환경에서 여러 플레이어가 동시에 같은 상자를 여는 경합 조건(Race Condition)을 방지하기 위해 태그 기반 잠금(Lock) 시스템을 구현하였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;접근 제어:&lt;/b&gt; 상호작용 시 CanInteract와 Interact 로직에 BeingLooted 태그가 이미 활성화되어 있는지 확인하는 절차를 추가하였다. 태그가 존재하면 즉시 차단하여 동시 접근 버그를 원천 봉쇄하였다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0&quot;&gt;네트워크 파이프라인:&lt;/b&gt; * Client_OpenLootBoxUI: 클라이언트가 상자를 열 때 UI를 띄우도록 명령하는 RPC를 설계하였다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TakeItemFromLootBox: 서버 권한 측에서 아이템 이동 로직을 안전하게 처리하고, 검증된 결과값만 복제하여 데이터 불일치를 방지하는 로직을 구축하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size26&quot;&gt;  핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하드코딩 문자열을 Native GameplayTags로 대체하여 시스템 계층화와 오타 없는 안정적인 데이터 로직을 확보하였다.&lt;/li&gt;
&lt;li&gt;퀵슬롯의 슬롯별 책임 분리와 인터락 로직을 통해 아이템 사용의 안전성을 극대화하고, 자동 장착 및 데이터 영속성 로직으로 시스템 편의성을 높였다.&lt;/li&gt;
&lt;li&gt;파밍 상자의 상태를 GameplayTagContainer로 관리함으로써 멀티플레이어 환경에서의 동시 접근 제어(Locking)를 실현하고, 안정적인 서버-클라이언트 간 아이템 이동 RPC 구조를 완성하였다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프 Unreal_7기/본캠프</category>
      <author>tkdqls</author>
      <guid isPermaLink="true">https://tkd6966.tistory.com/104</guid>
      <comments>https://tkd6966.tistory.com/104#entry104comment</comments>
      <pubDate>Fri, 12 Jun 2026 20:13:14 +0900</pubDate>
    </item>
    <item>
      <title>OnConstruction SSOT 수립, 무기 스왑 인터락 및 복제 안전장치 설계</title>
      <link>https://tkd6966.tistory.com/103</link>
      <description>&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;  학습 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에디터 디테일 패널의 변수 리셋 버그를 해결하기 위해 OnConstruction 내부에서 단일 진실 공급원(SSOT) 구조를 확립한다.&lt;/li&gt;
&lt;li&gt;기존 무기 액터가 소켓에 잔상으로 남는 고아 액터(Orphan Actor) 문제를 인터락(Interlock) 설계를 통해 해결한다.&lt;/li&gt;
&lt;li&gt;네트워크 지연 환경(Ping)에서 상태 불일치로 인한 패킷 증발을 막기 위해 클라이언트 단 가드 조건문을 최적화한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;  1. OnConstruction 구조 개선: 단일 진실 공급원(Single Source of Truth)&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;  디테일 패널 데이터 롤백 현상과 원인&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;레벨에 배치된 액터 인스턴스의 변수 값을 에디터 디테일 패널에서 수정해도, 엔터를 누르는 순간 원래 값으로 리셋되는 현상이 발생하였다. 이는 에디터 타임 빌드 함수인 OnConstruction(생성 스크립트) 내부에서 특정 데이터를 기준으로 값을 강제로 덮어쓰고(Overwrite) 있을 때 발생하는 전형적인 데이터 동기화 오류다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;  수동 입력 허용 및 SSOT 확립&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;기존 클래스가 ItemRowHandle 구조체에 과도하게 종속되어 에디터 상에서의 수동 입력을 거부하던 정적 구조를 개편하였다. 사용자가 직접 입력한 고유 문자열 코드인 ItemID를 단일 진실 공급원(Single Source of Truth)으로 설정하였다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;ItemID 가공 데이터가 유효할 때 데이터 테이블 경로에서 행 데이터를 검색하고, 매핑된 ItemTypeTag와 스태틱 메쉬를 동적으로 로드하는 방식으로 변경하였다. 이로써 기획 데이터와 컴포넌트 간의 결합도를 낮추고 에디터 조작 편의성을 극대화하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjKupymiPyUAxUAAAAAHQAAAAAQvAg&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;void ANCItemActor::OnConstruction(const FTransform&amp;amp; Transform)
{
    Super::OnConstruction(Transform);
    
    if (!ItemID.IsNone())
    {
       UDataTable* LoadedItemDataTable = LoadObject&amp;lt;UDataTable&amp;gt;(nullptr, TEXT(&quot;/Game/NakwonClone/Blueprints/Item/ItemData/DT_ItemTypeData.DT_ItemTypeData&quot;));
       if (LoadedItemDataTable)
       {
          FItemData* FoundData = LoadedItemDataTable-&amp;gt;FindRow&amp;lt;FItemData&amp;gt;(ItemID, TEXT(&quot;ActorConstruction&quot;));
          if (FoundData)
          {
             ItemTypeTag = FoundData-&amp;gt;ItemTypeTag;
             if (ItemMesh &amp;amp;&amp;amp; FoundData-&amp;gt;ItemMesh) 
             {
                 ItemMesh-&amp;gt;SetStaticMesh(FoundData-&amp;gt;ItemMesh);
             }
          }
       }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size26&quot;&gt;  2. 무기 스왑(Swap) 인터락 설계 및 고아 액터(Orphan Actor) 방지&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;  고아 액터(Orphan Actor) 버그의 메커니즘&lt;/h3&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;기존 무기가 장착된 상태에서 명시적인 해제 과정 없이 곧바로 새 무기를 장착할 때 발생하는 비주얼 크래시 현상이다. 컴뱃 컴포넌트 내부의 액터 포인터 변수 주소는 새 무기의 메모리 주소로 정상 치환되지만, 기존 무기 액터는 캐릭터 손 소켓에 붙은 채 메모리에 둥둥 떠 있는 고아 객체(Orphan Actor)가 된다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;이 상태에서 맨손 전환(UnArm)을 호출하면 현재 포인터가 가리키는 최신 액터만 참조하여 소멸시키기 때문에, 이전에 소외된 옛날 무기가 캐릭터 손에서 절대 사라지지 않는 치명적인 비주얼 버그를 유발한다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;  인터락(Interlock) 상호 배제(Mutual Exclusion) 구현&lt;/h3&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;무기를 교체하기 직전, 현재 손에 무언가 파지 되어 있다면(CurrentEquippedSlotIndex != -1) 반드시 기존 무기를 청소하는 UnEquipWeapon을 선행 프로세스로 강제 실행하도록 설계하였다. 이를 통해 아키텍처적 상호 배제(Mutual Exclusion)를 달성하여 무기 잔상 문제를 해결하였다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;기획적으로도 1, 2번 단축키는 장착 및 무기 간 스왑만 전담하도록 제한하고, H키에만 맨손 전환(ForceUnArm) 명령을 독립 바인딩하여 유저 조작의 직관성을 높였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjKupymiPyUAxUAAAAAHQAAAAAQvQg&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;// 퀵슬롯 장비 사용 처리 분기문 내부
if (CurrentEquippedSlotIndex == QuickSlotIndex) return true; // 같은 무기면 입력 무시

if (CurrentEquippedSlotIndex != -1)
{
    NCCombatComp-&amp;gt;UnEquipWeapon(); // 다른 무기 소지 중일 시 선행 파괴 후 스왑 인터락 가동
}

NCCombatComp-&amp;gt;EquipWeapon(WeapontoEquip);
CurrentEquippedSlotIndex = QuickSlotIndex;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size26&quot;&gt;  3. 네트워크 레이턴시를 고려한 복제(Replication) 안전장치&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;23&quot; data-ke-size=&quot;size23&quot;&gt;  네트워크 지연(Ping)과 클라이언트 예측 불일치&lt;/h3&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;멀티플레이어 환경에서 클라이언트 유저가 단축키 연타 등으로 서버에 RPC 명령 패킷을 과도하게 쏟아낼 때, 클라이언트가 화면을 보고 예측(Prediction)하는 상태 값과 서버 Authority가 가진 실제 상태 값이 순간적으로 불일치하는 현상이 발생할 수 있다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size23&quot;&gt;  클라이언트 가드 조건문 제거를 통한 신뢰성 확보&lt;/h3&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;기존 UnEquipWeapon 함수 최상단 앞단에 배치되어 있던 if (!bIsEquipped) return;과 같은 로컬 가드 조건문이 지연 시간(Ping) 환경에서 치명적인 문제를 야기하였다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;네트워크 레이턴시로 인해 클라이언트가 본인의 장착 상태를 이미 해제된 것으로 일시 오해했을 때, 서버로 해제 요청 패킷 자체를 보내지 않고 로컬 단에서 가드문 확정 리턴을 해버리는 버그였다. 결국 서버 측 무기는 해제되지 않아 동기화가 완전히 깨지게 된다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;28&quot;&gt;앞단의 클라이언트 가드 조건문을 과감히 제거&lt;/b&gt;하였다. 유효성 및 안전 검사는 어차피 권한을 가진 서버 단 내부 로직(Internal_UnEquipWeapon)에서 철저하게 수행하므로, 앞단 레이어를 개방하여 네트워크 지연 상황에서도 예외 없이 해제 명령 패킷이 서버에 도달하도록 동기화 안정을 보장하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;30&quot; data-ke-size=&quot;size26&quot;&gt;  핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;31&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기획 데이터 롤백 현상을 방지하기 위해 ItemID 문자열을 최우선 기준으로 삼고 OnConstruction에서 에셋 명세를 실시간 로드하는 SSOT 아키텍처를 확립하였다.&lt;/li&gt;
&lt;li&gt;무기 스왑 시 포스트 액터 레퍼런스가 유실되어 소켓에 잔상이 남는 고아 객체 버그를 막고자 무기 교체 전 기존 무기 해제를 강제하는 인터락 시퀀스를 구현하였다.&lt;/li&gt;
&lt;li&gt;네트워크 지연 및 입력 오버랩 상황에서 RPC 패킷이 무단 유실되는 현상을 방지하기 위해 클라이언트 로컬 가드문을 소거하고 검증 권한을 서버 내부 함수로 일임하였다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프 Unreal_7기/본캠프</category>
      <author>tkdqls</author>
      <guid isPermaLink="true">https://tkd6966.tistory.com/103</guid>
      <comments>https://tkd6966.tistory.com/103#entry103comment</comments>
      <pubDate>Wed, 10 Jun 2026 21:20:15 +0900</pubDate>
    </item>
    <item>
      <title>파밍 상자 확률 시스템 구현 및 PlayerState 기반 인벤토리 아키텍처 리팩토링</title>
      <link>https://tkd6966.tistory.com/102</link>
      <description>&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;  학습 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의존성을 제거한 FNCLootDropData 모듈화 구조를 확립하고, 가중치 기반 랜덤 스폰 로직을 정립한다.&lt;/li&gt;
&lt;li&gt;언리얼 헤더 툴(UHT)의 인터페이스 가상 함수 처리 및 BlueprintNativeEvent 구현 규칙을 준수하여 컴파일 에러를 해결한다.&lt;/li&gt;
&lt;li&gt;세션 유지 및 멀티플레이 동기화 무결성을 위해 인벤토리 컴포넌트의 소유권을 Character에서 PlayerState로 리팩토링한다.&lt;/li&gt;
&lt;li&gt;아키텍처 개편에 따른 UI 위젯의 데이터 참조 포인터를 교정하고 드래그 오퍼레이션 분기 처리를 고도화한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;  1. 파밍 상자(Loot Box) 확률형 스폰 시스템 구현&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;  드랍 테이블 구조체 분리 (FNCLootDropData)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;아이템 ID, 드랍 가중치(Weight), 최소/최대 수량, 속성 태그를 관리하는 전용 구조체를 설계하였다. 이를 특정 액터 클래스 내부 헤더에 종속시키지 않고 독립된 NCLootDropData.h 파일로 분리하여 모듈화하였다. 향후 몬스터 처치 보상이나 필드 드랍 오브젝트 등 타 시스템에서 의존성 인클루드 꼬임 없이 깔끔하게 재사용할 수 있는 구조적 확장성을 확보하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;  가중치 기반 랜덤 룩업 알고리즘 (GenerateLoot)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;데이터 테이블에서 가중치 데이터를 순회하며 누적 합산(Total Weight)을 구한 뒤, FMath::FRandRange를 통해 의사 난수를 추출하여 확률적으로 아이템을 선별하는 로직을 구현하였다.&lt;/p&gt;
&lt;div data-hveid=&quot;3&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;zephir&quot;&gt;&lt;code&gt;// NCLootBoxActor.cpp 가중치 선택 로직 알고리즘
void ANCLootBoxActor::GenerateLoot()
{
    if (!LootDataTable) return;

    TArray&amp;lt;FNCLootDropData*&amp;gt; AllRows;
    LootDataTable-&amp;gt;GetAllRows(TEXT(&quot;FetchLootData&quot;), AllRows);

    float TotalWeight = 0.f;
    for (const auto* Row : AllRows) { TotalWeight += Row-&amp;gt;DropWeight; }

    float DiceRoll = FMath::FRandRange(0.f, TotalWeight);
    float CurrentWeightSum = 0.f;

    for (const auto* Row : AllRows)
    {
        CurrentWeightSum += Row-&amp;gt;DropWeight;
        if (DiceRoll &amp;lt;= CurrentWeightSum)
        {
            // 당첨된 아이템 ID와 수량을 코어 인벤토리 함수에 전달
            int32 SpawnQuantity = FMath::RandRange(Row-&amp;gt;MinQuantity, Row-&amp;gt;MaxQuantity);
            InventoryComponent-&amp;gt;AddItem(Row-&amp;gt;ItemID, SpawnQuantity);
            break;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;추출된 데이터 결과물은 기존에 구축해 둔 만능 데이터 함수인 UNCInventoryBaseComponent::AddItem과 연동하였다. 이를 통해 별도의 추가 예외 처리 없이도 내부 컨테이너 배열 안에서 자동 스택 병합 및 빈 슬롯 그리드 할당이 유기적으로 수행되도록 설계하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size26&quot;&gt;  2. 언리얼 인터페이스(Interface) UHT 컴파일 에러 해결&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;  BlueprintNativeEvent 상속 선언 문법 오류&lt;/h3&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;UINTERFACE를 통해 구현된 상호작용 인터페이스를 자식 액터 클래스에 상속하고 구현하는 과정에서 언리얼 헤더 툴(UHT) 빌드 오류가 발생하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;  원인 분석 및 해결 규격 정립&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;원인 1:&lt;/b&gt; 인터페이스 헤더 선언 시 C++ 표준 규격처럼 virtual void Interact();와 같이 가상 함수 키워드를 명시하였다. 하지만 언리얼 엔진의 BlueprintNativeEvent 지정을 받은 함수는 UHT가 빌드 타임에 자동으로 내부 가상 함수 파이프라인을 생성하므로, 개발자가 virtual을 중복 선언하면 헤더 툴 충돌 에러가 발생한다. 인터페이스 원본 헤더에서 해당 키워드를 소거하여 해결하였다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,1,0&quot;&gt;원인 2:&lt;/b&gt; 상속받는 구체 클래스(시민 액터) 선언부에서 원본 명세인 Interact()를 한 번 더 기술하여 컴파일러 시그니처 락이 발생하였다. BlueprintNativeEvent 속성을 오버라이드할 때는 원본 함수가 아닌 엔진 복제 규격인 _Implementation 접미사를 붙여 선언 및 구현해야 구조가 완성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-hveid=&quot;6&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// NCLootBoxActor.h 정석적인 오버라이드 명세
virtual void Interact_Implementation() override;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size26&quot;&gt;  3. 핵심 아키텍처 리팩토링: 인벤토리 소유권 이사 (Character ➔ PlayerState)&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;  액터 수명주기(Skeletal Life Cycle)에 따른 데이터 휘발 문제&lt;/h3&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;기존 시스템은 ANCPlayerCharacter 내부에 인벤토리 컴포넌트가 결합되어 있었다. 이는 익스트랙션 서바이벌 장르 특성상 캐릭터 사망 후 리스폰(Destroy 후 Spawn) 시 힙 메모리에 있던 가방 데이터가 통째로 증발하는 치명적인 아키텍처 결함이 존재하였다. 또한 캐릭터 액터의 잦은 소멸과 생성은 멀티플레이어 환경에서 네트워크 레플리케이션 패킷 안정성을 떨어뜨린다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;23&quot; data-ke-size=&quot;size23&quot;&gt;  PlayerState 중심의 지속성 데이터 레이어 구축&lt;/h3&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;레벨 전이 및 캐릭터 액터 소멸 시에도 세션이 유지되는 동안 영속성을 지니는 ANCPlayerState 클래스로 인벤토리 컴포넌트를 이관 배치(CreateDefaultSubobject 위치 수정)하였다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;캐릭터는 덩치 큰 인벤토리 데이터를 직접 소유하지 않으며, 오직 PlayerInventoryRef 포인터 변수를 통해 주소 값만 바인딩하여 바라보는 약참조 구조로 변경하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;26&quot; data-ke-size=&quot;size23&quot;&gt;  서버-클라이언트 투 트랙(Two-Track) 포인터 바인딩 동기화&lt;/h3&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;캐릭터 액터가 생성되고 컨트롤러가 배정되는 생명주기 시점에 맞추어 양방향 포인터 안전 바인딩을 구현하였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;28&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;28,0,0&quot;&gt;서버 권한 (Authority):&lt;/b&gt; 캐릭터가 서버에 의해 정상possessed 되는 시점인 PossessedBy(AController* NewController) 가상 함수 내에서 PlayerState를 취득하여 인벤토리 주소를 연결한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;28,1,0&quot;&gt;로컬 클라이언트 (Autonomous Proxy):&lt;/b&gt; 복제 패킷이 도달하여 내부 주소가 동기화되는 런타임 시점인 OnRep_PlayerState() 콜백 함수 내에서 인벤토리 주소를 동적으로 갱신한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-path-to-node=&quot;29&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-path-to-node=&quot;29,0&quot; data-ke-size=&quot;size16&quot;&gt;디자인 패턴 인사이트: &quot;캐릭터는 물리적 껍데기(렌트카)일 뿐이며, 진짜 세션 데이터(내 지갑)는 PlayerState가 전담한다.&quot; 사망 시 모든 장비를 월드에 떨어뜨려야 하는 기획이더라도 데이터 연산은 PlayerState에서 통제하고, 소멸 직전 시체 상자(LootBox) 오브젝트로 데이터를 안전하게 이관(TransferItemTo)한 뒤 컴포넌트를 청소하는 구조가 멀티플레이 아키텍처 관점에서 훨씬 무결함을 실증하였다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;31&quot; data-ke-size=&quot;size26&quot;&gt;  4. 리팩토링 후속 조치 및 UI 블루프린트 트러블슈팅&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;32&quot; data-ke-size=&quot;size23&quot;&gt;  UI 주소 레퍼런스 단절 해결&lt;/h3&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;컴포넌트 소유권이 이동함에 따라 기존 UMG 위젯 내에서 Get Player Pawn ➔ Get InventoryComponent 형태로 경로를 탐색하던 모든 블루프린트 로직이 컴파일 에러를 뿜으며 무력화되었다. 데이터 허브 타깃 노드를 Get Player State로 전환하고 Get Component by Class 노드를 연동하여 런타임 갱신 에러를 완벽히 해결하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;34&quot; data-ke-size=&quot;size23&quot;&gt;  캐스팅 오류 우회를 통한 장비 장착 스왑 로직 고도화&lt;/h3&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;슬롯 드래그 인터랙션 시, 일반 가방 아이템 정보가 담긴 BP_ItemDragDrop 객체와 이미 장비창에 올라가 있던 정보인 BP_EquipmentDragDrop 객체가 단일 UI 타깃 내에서 충돌하며 조건 누락이 발생하였다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;이를 정교하게 정류하기 위해 UI 파트 팀원과 협업하여 &lt;b data-index-in-node=&quot;31&quot; data-path-to-node=&quot;36&quot;&gt;Cast Failed(캐스팅 실패 분기 체인)&lt;/b&gt; 구조를 수립하였다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;37&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;OnDrop 이벤트 진입 시 먼저 BP_ItemDragDrop으로 캐스팅을 시도한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,1,0&quot;&gt;성공 시:&lt;/b&gt; 가방에서 장비창으로 아이템을 신규 장착하는 비즈니스 C++ 함수를 가동한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,2,0&quot;&gt;실패(Cast Failed) 시:&lt;/b&gt; 하위 체인으로 BP_EquipmentDragDrop 캐스팅을 재수행한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,3,0&quot;&gt;성공 시:&lt;/b&gt; 장비창 슬롯 간의 위치 교환 혹은 내부 스왑 연산 구문을 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해 입력 장치 오작동을 차단하고 예외 처리를 정밀화하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;40&quot; data-ke-size=&quot;size26&quot;&gt;  핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;41&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NCLootDropData.h 구조체 모듈화를 통해 결합도를 낮추고 FRandRange 기반의 가중치 랜덤 스폰 시스템을 확립하였다.&lt;/li&gt;
&lt;li&gt;BlueprintNativeEvent 사용 시 virtual 명시를 제거하고 부모 명세인 _Implementation 함수만 기술하여 UHT 컴파일 락을 해결하였다.&lt;/li&gt;
&lt;li&gt;캐릭터 사망 시 데이터 유실을 방지하기 위해 인벤토리 컴포넌트 소유권을 PlayerState로 리팩토링하여 네트워크 무결성을 획득하였다.&lt;/li&gt;
&lt;li&gt;PossessedBy와 OnRep_PlayerState 시점에 맞춰 주소 값을 할당하여 멀티플레이 환경의 양방향 런타임 참조 구조를 완성하였다.&lt;/li&gt;
&lt;li&gt;UI 드롭 핸들러에 Cast Failed 폭포수 분기문을 설계하여 가방 에셋과 장비창 에셋 간의 다형성 조작 예외 처리를 제어하였다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프 Unreal_7기/본캠프</category>
      <author>tkdqls</author>
      <guid isPermaLink="true">https://tkd6966.tistory.com/102</guid>
      <comments>https://tkd6966.tistory.com/102#entry102comment</comments>
      <pubDate>Tue, 9 Jun 2026 20:48:34 +0900</pubDate>
    </item>
    <item>
      <title>UMG Drag &amp;amp; Drop 이벤트 처리의 함정과 인벤토리 세이브/로드 아키텍처</title>
      <link>https://tkd6966.tistory.com/101</link>
      <description>&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;  학습 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UMG 드래그 앤 드롭 시스템의 리턴 값(True/False)에 따른 엔진 내부 마우스 이벤트 처리 메커니즘을 파악한다.&lt;/li&gt;
&lt;li&gt;상이한 인벤토리 컴포넌트 인스턴스 간 데이터 이관을 위한 TransferItemTo 파이프라인 및 멀티 캐스팅 UI 동기화를 구현한다.&lt;/li&gt;
&lt;li&gt;USaveGame 시스템을 아키텍처에 통합하여 레벨 전이 시 데이터 영속성(Persistence)을 보장하는 저장/로드 프레임워크를 수립한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;  1. UMG Drag &amp;amp; Drop 이벤트의 Return Value (True vs False)의 함정&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;  마우스 드롭 이벤트 취소 시 액터 유실 버그 분석&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;UI 인벤토리 슬롯 간에 아이템 스왑(Swap) 조건이 맞지 않아 백엔드 C++ 로직이 false를 반환했을 때, 이를 위젯 블루프린트 On Drop 함수의 Return Value에 그대로 매핑하여 전달할 경우, 스왑이 조용히 취소되는 것이 아니라 아이템이 월드 바닥에 강제로 드롭(Drop)되어 버리는 심각한 비조작성 버그가 발생하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;  UMG 마우스 이벤트 버블링 및 캔슬 아키텍처 원인&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;언리얼 엔진 UMG 프레임워크의 구조적 특성 때문이다. On Drop 오버라이드 함수가 False를 반환하면, 엔진 컴파일러는 &quot;해당 UI 위젯이 마우스 드롭 이벤트를 소비(Handle)하지 않고 통과시켰다&quot;라고 판단한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 마우스가 UI 가방 영역이 아닌 '허공(월드 뷰포트)'에 놓였다고 간주하여 On Drag Cancelled 이벤트를 강제로 트리거한다. 당시 프로젝트 구조는 드래그 캔슬 시 화면 밖에 아이템을 버리는 로직(Server_DropItem)과 묶여 있었기에 연쇄 버그로 이어졌다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;  방어적 UI 리턴 설계 규칙&lt;/h3&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;슬롯 UI 위젯은 내부 백엔드 로직의 성공/실패 여부와 무관하게, 마우스 단에서 '드롭'이라는 인터랙션 행위 자체를 정상적으로 접수했다면 무조건 Return Value를 True로 고정하여 이벤트를 소모시켜야 한다. 데이터 테이블 검증 실패나 스왑 불가 등의 예외 처리는 백엔드 데이터(배열)를 건드리지 않는 방식으로 엔진 내부에서 조용히 리턴(Early Return) 처리해야 뷰포트 화면의 예기치 못한 오작동을 원천 봉쇄할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size26&quot;&gt;  2. 인벤토리 컴포넌트 간의 데이터 이동 (Move vs Transfer)&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;  단일 컴포넌트 제어와 다중 컴포넌트 통신의 차이&lt;/h3&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;가방 내부의 슬롯 이동용으로 설계된 MoveItem 함수는 동일한 메모리 배열 내에서 인덱스 교환만 처리하므로 타겟 인스턴스를 고려할 필요가 없다. 하지만 플레이어 가방(Inventory)과 외부 창고(Stash) 시스템처럼 독립된 두 개의 인벤토리 컴포넌트 간에 물품을 교환할 때는 단순 내부 인덱스 스왑 구조를 사용할 수 없다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;  TransferItemTo 매커니즘 구현&lt;/h3&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;상이한 컴포넌트 간 안전한 데이터 이관을 위해 TransferItemTo(TargetInventory, FromIndex, ToIndex) 함수를 신규 설계하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwigz6vKnfeUAxUAAAAAHQAAAAAQwQk&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// NCInventoryBaseComponent.cpp 내부 이관 코어 로직
bool UNCInventoryBaseComponent::TransferItemTo(UNCInventoryBaseComponent* TargetInventory, int32 FromIndex, int32 ToIndex)
{
    if (!TargetInventory || !Items.IsValidIndex(FromIndex)) return false;

    // 1. 출발지 데이터를 목적지 임시 버퍼로 얕은 복사
    FInventorySlot ItemToTransfer = Items[FromIndex];

    // 2. 목적지 컴포넌트의 가드 조건(빈칸, 동일 아이템 병합, 타 아이템 스왑)에 맞게 수량 분배 함수 호출
    if (TargetInventory-&amp;gt;ReceiveItemFrom(ItemToTransfer, ToIndex))
    {
        // 3. 목적지 적재가 완수되면 출발지 메모리 데이터를 안전하게 제거
        ClearSlot(FromIndex);

        // 4. 독립된 두 UI 위젯 컴포넌트를 동시에 갱신하기 위해 양측 확성기(Delegate) 호출
        this-&amp;gt;OnInventoryUpdated.Broadcast();
        TargetInventory-&amp;gt;OnInventoryUpdated.Broadcast();
        return true;
    }
    return false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;두 인벤토리의 소유 주체가 다를 수 있으므로 데이터 변경 즉시 원본 인스턴스(this)와 목적지 인스턴스(TargetInventory)의 OnInventoryUpdated 멀티캐스트 델리게이트를 양측 모두 브로드캐스트하여 클라이언트 화면의 동기화 무결성을 유지하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size26&quot;&gt;  3. USaveGame 시스템을 활용한 데이터 영속성(Persistence) 유지&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;23&quot; data-ke-size=&quot;size23&quot;&gt;  레벨 전환(Level Transition) 시 임시 메모리 휘발 문제&lt;/h3&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;로비 씬에서 창고와 가방 데이터를 정교하게 정돈하더라도, 실제 인게임 레벨로 진입(OpenLevel)하는 순간 기존 월드와 컴포넌트의 임시 RAM 메모리 데이터는 모두 가비지 컬렉팅되어 파괴된다. 캐릭터의 성장 데이터 및 가방 스탯을 영구 보존하기 위해 하드디스크에 바이너리로 기록되는 구조가 필수적이다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size23&quot;&gt;  USaveGame 기반 영속성 프레임워크 설계&lt;/h3&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;엔진 내장 파일 저장 아키텍처인 USaveGame 라이브러리를 상속받는 전용 세이브 객체 NCSaveGame C++ 클래스를 구축하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwigz6vKnfeUAxUAAAAAHQAAAAAQwgk&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// NCSaveGame.h 구조체 배열 직렬화 정의
UCLASS()
class UNCSaveGame : public USaveGame
{
    GENERATED_BODY()
public:
    // 인벤토리 배열 구조체를 통째로 시리얼라이즈 변수로 등록
    UPROPERTY()
    TArray&amp;lt;FInventorySlot&amp;gt; SavedInventoryItems;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;28&quot; data-ke-size=&quot;size23&quot;&gt;  PlayerState 중심의 직렬화/역직렬화(Serialization) 세이브 가이드&lt;/h3&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;레벨 전이 과정에서도 소멸되지 않고 데이터 허브 역할을 수행하는 고유 PlayerState 클래스를 직렬화 제어 주체로 설정하였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;30&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;30,0,0&quot;&gt;Save 시퀀스:&lt;/b&gt; 로비 씬 종료 및 레벨 이동 트리거가 당겨지는 시점에 UGameplayStatics::SaveGameToSlot을 호출하여 인벤토리 컴포넌트의 구조체 배열(TArray&amp;lt;FInventorySlot&amp;gt;) 데이터를 로컬 디스크의 바이너리 슬롯 파일로 영구 직렬화 보존한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;30,1,0&quot;&gt;Load 시퀀스:&lt;/b&gt; 새로운 인게임 레벨이 생성되고 PlayerState의 BeginPlay() 라이프사이클 타이밍이 도래하면 UGameplayStatics::LoadGameFromSlot을 실행한다. 세이브 파일 존재 여부에 따라 조건 분기를 처리한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;30,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;30,1,1,0,0&quot;&gt;세이브 파일 존재 시:&lt;/b&gt; 세이브 데이터 내부의 배열을 역직렬화하여 인벤토리 컴포넌트에 안전하게 덮어씌운다(SetItemsArray).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;30,1,1,1,0&quot;&gt;최초 접속(파일 누락) 시:&lt;/b&gt; 공백 상태의 초기화 가방 함수(InitializeInventory)를 분기 구동하여 런타임 가드 락을 예방한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;32&quot; data-ke-size=&quot;size26&quot;&gt;  핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;33&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UMG On Drop 함수가 False를 리턴하면 마우스 이벤트를 소모하지 않은 것으로 간주하여 On Drag Cancelled 연쇄 버그(아이템 무단 버림 등)를 유발하므로 무조건 True를 반환해 차단해야 한다.&lt;/li&gt;
&lt;li&gt;가방-창고 간 상이한 컴포넌트 통신을 제어하기 위해 얕은 복사 후 가중치를 분배하는 TransferItemTo 인터페이스를 구현하고 양측 델리게이트를 개별 호출하여 UI를 갱신한다.&lt;/li&gt;
&lt;li&gt;레벨 전이 시 발생하는 메모리 휘발 한계는 USaveGame을 상속받은 직렬화 데이터 클래스를 통해 하드디스크 바이너리 포맷으로 아카이빙하여 극복한다.&lt;/li&gt;
&lt;li&gt;데이터 세이브/로드의 핵심 생명주기는 소멸성이 짙은 액터가 아닌, 네트워크 무결성을 지닌 PlayerState 클래스의 BeginPlay 시점 등에서 동기화 분기를 처리해야 안전하다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프 Unreal_7기/본캠프</category>
      <author>tkdqls</author>
      <guid isPermaLink="true">https://tkd6966.tistory.com/101</guid>
      <comments>https://tkd6966.tistory.com/101#entry101comment</comments>
      <pubDate>Mon, 8 Jun 2026 20:33:39 +0900</pubDate>
    </item>
    <item>
      <title>데이터 주도형(Data-Driven) 인벤토리 리팩터링 및 에디터 작업 프로세스 최적화</title>
      <link>https://tkd6966.tistory.com/100</link>
      <description>&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;  학습 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인벤토리 슬롯 데이터에 데이터 테이블 Key 값을 연동하여 런타임 동적 에셋 복원 및 데이터 주도형 아키텍처를 수립한다.&lt;/li&gt;
&lt;li&gt;FDataTableRowHandle 및 리플렉션 한정자를 활용하여 레벨 디자이너의 휴먼 에러를 원천 차단하는 방어적 C++ 클래스를 설계한다.&lt;/li&gt;
&lt;li&gt;OnConstruction 라이프사이클 함수를 오버라이드하여 에디터 뷰포트 내 실시간 위지윅(WYSIWYG) 데이터 동기화 시스템을 구현한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;  1. Data-Driven 인벤토리 아키텍처 개편&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;  추상 데이터 기반 에셋 복원의 한계점 분석&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;기존 시스템은 FGameplayTag만을 기반으로 아이템의 카테고리를 분류하였다. 이로 인해 아이템을 필드에 재드롭(Server_DropItem)하는 시점에 해당 아이템이 본래 소유하고 있던 고유 스태틱 메쉬(예: 특정 장비의 고유 외형)가 무엇인지 역산해내지 못하고 기본 베이스 껍데기로만 스폰되는 아키텍처적 한계가 존재하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;  ItemID 연동을 통한 동적 메쉬 스폰 메커니즘&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;인벤토리 최소 단위 구조체인 FInventorySlot이 데이터 테이블의 고유 식별자인 ItemID(Row Name)를 직접 기억하도록 데이터 구조를 개편하였다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;아이템 버리기 함수(Server_DropItem_Implementation)가 호출되면, 슬롯에 저장되어 있던 ItemID를 Key값으로 삼아 DT_ItemData 테이블을 탐색(Lookup)한다. 이후 추출된 행 데이터 내부의 스태틱 메쉬 레퍼런스를 기본 마네킹 액터인 NCItemActor에게 동적으로 주입하여 원본 외형을 완벽하게 복원 및 스폰하도록 로직을 고도화하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size26&quot;&gt;  2. 휴먼 에러 방지를 위한 C++ 변수 및 제약 조건 설계&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;  FDataTableRowHandle을 활용한 데이터 무결성 확보&lt;/h3&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;레벨 디자이너나 기획자가 월드 뷰포트에 아이템 액터를 배치하고 고유 식별자를 문자열(FName, FString) 형태로 직접 타이핑하게 할 경우, 마침표 누락이나 대소문자 오타 등으로 인해 데이터베이스 조회 연산이 실패하는 휴먼 에러가 필연적으로 발생한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;이를 방지하기 위해 C++ 헤더에 데이터 테이블과 행을 명시적으로 결합하는 FDataTableRowHandle 구조체를 도입하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjR-eHduu-UAxUAAAAAHQAAAAAQzwM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Item Setting&quot;)
FDataTableRowHandle ItemDataHandle;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;이 구조체를 선언하면 언리얼 에디터 디테일 패널상에 텍스트 입력창 대신 등록된 데이터 테이블의 모든 Row Name을 직관적으로 선택할 수 있는 &lt;b data-index-in-node=&quot;81&quot; data-path-to-node=&quot;17&quot;&gt;'드롭다운 컴보 박스'&lt;/b&gt; 목록이 생성되므로 데이터 입력 무결성을 원천 확보할 수 있다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size23&quot;&gt;  VisibleAnywhere를 통한 데이터 변조 방지 (Read-Only)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;ItemID나 ItemTypeTag와 같이 데이터 테이블 룩업 완료 후 런타임에 결정되는 핵심 원본 변수들을 사용자가 에디터에서 임의로 수정하여 싱크가 깨지는 현상을 방지해야 한다. 이를 위해 해당 속성들을 읽기 전용으로 제약하는 VisibleAnywhere 매크로를 적용하여 사용자는 눈으로 데이터 검증만 수행하고 직접적인 값 수정은 불가능하도록 방어 코드를 설계하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size26&quot;&gt;  3. OnConstruction 오버라이드를 통한 위지윅(WYSIWYG) 환경 구축&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size23&quot;&gt;  에디터 타임 빌드 시퀀스의 필요성&lt;/h3&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;기존의 BeginPlay() 기반 에셋 할당 로직은 시뮬레이션 버튼(Play)을 누르기 전까지는 월드에 배치된 아이템 액터가 실제 도끼인지, 회복 포션인지 뷰포트상에서 외형적으로 확인할 수 없어 레벨 디자인의 직관성을 저해하는 요소였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size23&quot;&gt;  실시간 뷰포트 동기화 구현&lt;/h3&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;액터가 월드에 배치되거나 디테일 패널의 컴포넌트 변수 수치가 변경될 때 에디터 타임에서 즉시 트리거되는 라이프사이클 함수인 OnConstruction을 오버라이드하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjR-eHduu-UAxUAAAAAHQAAAAAQ0AM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// NCItemActor.h 선언부
virtual void OnConstruction(const FTransform&amp;amp; Transform) override;

// NCItemActor.cpp 구현부
void ANCItemActor::OnConstruction(const FTransform&amp;amp; Transform)
{
    Super::OnConstruction(Transform);

    if (ItemDataHandle.DataTable &amp;amp;&amp;amp; !ItemDataHandle.RowName.IsNone())
    {
        // 에디터 타임에서 드롭다운 수정을 감지하여 즉시 테이블 데이터 파싱
        FItemData* RowData = ItemDataHandle.DataTable-&amp;gt;FindRow&amp;lt;FItemData&amp;gt;(ItemDataHandle.RowName, TEXT(&quot;OnConstructionFetch&quot;));
        if (RowData &amp;amp;&amp;amp; MeshComponent)
        {
            // 플레이 버튼을 누르지 않아도 뷰포트에 실시간 메쉬 변경 반영
            MeshComponent-&amp;gt;SetStaticMesh(RowData-&amp;gt;ItemStaticMesh);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;기획자가 디테일 패널에서 아이템 ID를 클릭하는 순간, 백엔드에서 데이터 테이블을 즉각 서치하여 SetStaticMesh를 구동하므로 레벨 디자이너가 인게임 결과를 실시간으로 예측하며 배치할 수 있는 고효율 작업 환경을 완성하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;29&quot; data-ke-size=&quot;size26&quot;&gt;  핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;30&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;슬롯 구조체가 태그를 넘어 테이블 고유 Key인 ItemID를 추적하도록 개편하여 동적 외형 복원 파이프라인을 구축하였다.&lt;/li&gt;
&lt;li&gt;FDataTableRowHandle 구조체를 연동하여 에디터 내 데이터 입력을 드롭다운 방식으로 규격화하고 오타 버그를 원천 배제하였다.&lt;/li&gt;
&lt;li&gt;VisibleAnywhere 지정을 통해 기획 데이터의 수동 변조 가능성을 차단하고 시스템 내부 데이터의 안전성을 확보하였다.&lt;/li&gt;
&lt;li&gt;OnConstruction 함수 캐싱을 활용해 에디터 런타임 이전에 메쉬 데이터를 즉각 동기화하는 위지윅 레벨 디자인 아키텍처를 완수하였다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프 Unreal_7기/본캠프</category>
      <author>tkdqls</author>
      <guid isPermaLink="true">https://tkd6966.tistory.com/100</guid>
      <comments>https://tkd6966.tistory.com/100#entry100comment</comments>
      <pubDate>Fri, 5 Jun 2026 22:48:54 +0900</pubDate>
    </item>
    <item>
      <title>멀티플레이 인벤토리 - 드래그 앤 드롭 구현 및 데이터&amp;middot;액터 분리 구조의 이해</title>
      <link>https://tkd6966.tistory.com/99</link>
      <description>&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;  학습 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UI 위젯의 드래그 오퍼레이션 파이프라인을 구축하여 아이템 드래그 앤 드롭(Drag &amp;amp; Drop) 기능을 구현한다.&lt;/li&gt;
&lt;li&gt;HasAuthority 가드 클로즈의 제약을 파악하고, Server RPC 구조로 전환하여 멀티플레이어 환경의 액터 스폰 권한을 제어한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;인벤토리 시스템의 본질인 '데이터와 실물 액터의 분리 구조'를 이해하고 이를 기반으로 한 아키텍처를 분석한다.&lt;/li&gt;
&lt;li&gt;MVP 완성 이후의 구조적 확장성을 위해 슬롯 데이터 내 원본 클래스 정보를 복원하는 리팩터링 방향을 수립한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;  1. C++ 함수 블루프린트 리플렉션 노출&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;  리플렉션 지정자 누락 이슈&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;C++ 내부에서 정상적으로 구현한 슬롯 데이터 반영 함수인 SetSlotData가 블루프린트 위젯 에디터의 노드 검색 창에서 식별되지 않는 현상이 발생하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;  원인 및 해결 방법&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;언리얼 엔진 컴파일러에게 블루프린트 노출 허가를 주지 않아 발생한 문제다. 가상 머신 및 리플렉션 시스템이 해당 메서드를 인식하고 블루프린트 가상 노드로 파싱할 수 있도록, 헤더 파일(.h)의 선언부에 BlueprintCallable 지정을 명시하여 해결하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiMsuPlv-iUAxUAAAAAHQAAAAAQ3QE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, Category = &quot;Inventory UI&quot;)
void SetSlotData(FGameplayTag ItemTag, int32 Quantity);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size26&quot;&gt;  2. 드래그 오퍼레이션의 데이터 흐름(Data Flow) 정상화&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;  드래그 오퍼레이션 내부 데이터 유실 현상&lt;/h3&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;슬롯 위젯에서 아이템을 드래그하여 월드 뷰포트에 드롭하는 인터랙션까지는 정상 도달하였으나, 버려진 아이템의 세부 정보인 태그와 수량이 None 및 0으로 소실되는 현상이 관찰되었다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;  원인 및 해결 방법&lt;/h3&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;On Drag Detected 함수 내에서 우편배달부 역할을 하는 Create Drag Drop Operation 노드를 생성할 때, 슬롯이 이미 들고 있던 C++ 원본 변수들을 입력 핀들에 연결하지 않고 비워두어 빈 상자만 전송된 것이 원인이었다. 슬롯에 캐싱되어 있던 CurrentItemTag와 CurrentQuantity 변수를 블루프린트 생성 노드의 입력 핀에 다이렉트로 바인딩하여 데이터 흐름을 정상화하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size26&quot;&gt;  3. 멀티플레이어 권한(Authority) 처리와 연쇄 버그 수정&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size23&quot;&gt;  클라이언트 입력 무시 및 상호작용(Loot) 락아웃 현상&lt;/h3&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트 프록시 화면에서 아이템을 드래그하여 바닥에 드롭할 때 아무런 반응이 없고, 이 과정에서 정상 작동하던 F 키 상호작용(LootItem) 기능까지 갑자기 먹통이 되는 연쇄 버그가 발생하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;  HasAuthority 가드 클로즈 원인 분석&lt;/h3&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;C++ 컴포넌트 내부 코어 로직 최상단에 배치된 네트워크 방어용 가드 클로즈 조건문으로 인해 발생하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiMsuPlv-iUAxUAAAAAHQAAAAAQ3gE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;if (!GetOwner()-&amp;gt;HasAuthority()) return false;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;권한이 없는 로컬 클라이언트 단에서 아이템 줍기/버리기 함수를 직접 호출하자, 위 방어 코드에 걸려 서버가 요청을 무시하고 패킷 신호가 로컬 단에서 멸실되는 구조적 문제가 원인이었다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size23&quot;&gt;  Server RPC 구조로의 파이프라인 개선&lt;/h3&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;멀티플레이어 환경에서 월드에 액터를 스폰(SpawnActor)하거나 가방 데이터를 영구 수정하는 작업은 반드시 &lt;b data-index-in-node=&quot;62&quot; data-path-to-node=&quot;26&quot;&gt;서버 권한&lt;/b&gt;을 가진 주체가 실행해야 한다. 따라서 클라이언트의 로컬 직접 조작 방식을 전면 배제하고, 서버에 대리 실행을 요청하는 &lt;b data-index-in-node=&quot;134&quot; data-path-to-node=&quot;26&quot;&gt;Server RPC&lt;/b&gt; 아키텍처로 개편하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiMsuPlv-iUAxUAAAAAHQAAAAAQ3wE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// NCInventoryBaseComponent.h 핵심 선언부
UFUNCTION(Server, Reliable)
void Server_DropItem(FGameplayTag ItemTag, int32 Quantity);

// NCInventoryBaseComponent.cpp 실 구현부
void UNCInventoryBaseComponent::Server_DropItem_Implementation(FGameplayTag ItemTag, int32 Quantity)
{
    // 서버 권한(Authority) 내부에서 안전하게 데이터 차감 및 월드 스폰 로직 처리
    if (DecreaseItem(ItemTag, Quantity))
    {
        SpawnItemInWorld(ItemTag, Quantity);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&quot;클라이언트의 입력 검출 및 요청 ➔ Server RPC 송신 ➔ 서버의 무결성 검증 및 실행 ➔ 복제(Replication)를 통한 전체 클라이언트 동기화&quot; 파이프라인을 정립하여 모든 권한 문제를 해결하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;30&quot; data-ke-size=&quot;size26&quot;&gt;  4. 인벤토리 데이터와 실물 액터 분리의 이해 (핵심 수확)&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;31&quot; data-ke-size=&quot;size23&quot;&gt;  아이템 재드롭 시 원본 외형 유실 현상&lt;/h3&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;레벨에 도끼 스태틱 메쉬를 가진 BP_NCItemActor를 배치해 두고 상호작용으로 획득했다가 다시 버렸을 때, 원래 형태인 도끼가 아니라 컴포넌트에 세팅해 둔 기본 베이스 메쉬(큐브 등)로만 스폰되어 떨어지는 현상이 발견되었다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;33&quot; data-ke-size=&quot;size23&quot;&gt;  인벤토리 아키텍처의 본질 파악&lt;/h3&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;&quot;월드에 배치된 개별 아이템을 주웠으니 가방에도 액터 객체 자체가 들어가서 그대로 뱉어낼 것&quot;이라는 직관과 달리, &lt;b data-index-in-node=&quot;64&quot; data-path-to-node=&quot;34&quot;&gt;언리얼의 인벤토리는 실물 액터가 아닌 '추상적인 데이터(태그, 수량)'만 기억하는 공간&lt;/b&gt;이라는 아키텍처의 본질을 깨달았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;35&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,0,0&quot;&gt;기존 로직의 한계:&lt;/b&gt; 아이템을 주울 때 액터에서 ItemTypeTag와 Quantity 데이터만 쏙 빼서 가방 배열에 글자로 적어두고, 실물 액터는 Destroy()로 파괴해 버리는 구조였다. 그러다 보니 버릴 때(DropItem) 가방 구조체를 열어봐도 '장비, 1개'라는 단순 정보만 있을 뿐, 이게 원래 무슨 블루프린트(도끼)였는지 알 길이 없어 시스템에 등록된 기본 껍데기(BaseItemActorClass)로만 스폰되었던 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;37&quot; data-ke-size=&quot;size26&quot;&gt;  5. 백로그 및 확장성 리팩터링 설계 (Next Steps)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;현재 단계는 최소 기능 구현(MVP)의 증명을 목표로 하므로, 고정된 베이스 메쉬 클래스로 드롭 프로세스가 완벽하게 동기화되는 것까지만 확인하고 스킵하였다. 추후 주웠던 원본 아이템 모습 그대로 완벽하게 복원하여 뱉어내기 위해 아래와 같이 단일 책임 기반 리팩터링 방향을 정립하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiMsuPlv-iUAxUAAAAAHQAAAAAQ4AE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 1. 인벤토리 구조체 데이터 확장
USTRUCT(BlueprintType)
struct FInventorySlot
{
    GENERATED_BODY()

    UPROPERTY()
    FGameplayTag ItemTag;

    UPROPERTY()
    int32 Quantity;

    // 원래 어떤 블루프린트였는지 저장할 클래스 타입 정보 기억 변수 추가
    UPROPERTY()
    TSubclassOf&amp;lt;class ANCItemActor&amp;gt; ItemClass; 
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;40&quot; data-ke-size=&quot;size23&quot;&gt;  데이터 캡슐화 및 동적 스폰 시퀀스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;41&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,0,0&quot;&gt;아이템 획득 단계 (LootItem):&lt;/b&gt; 아이템을 주울 때 데이터와 함께 ItemToLoot-&amp;gt;GetClass()를 통해 원본 블루프린트(예: BP_Axe) 정보를 가져와 가방 배열 슬롯 구조체에 함께 메모한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,1,0&quot;&gt;아이템 버리기 단계 (Server_DropItem):&lt;/b&gt; 고정된 베이스 클래스로 스폰하는 비효율을 걷어내고, 구조체 슬롯에 저장해 두었던 Items[SlotIndex].ItemClass 레퍼런스를 기반으로 SpawnActor 하도록 동적 스폰 구조를 고도화한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;43&quot; data-ke-size=&quot;size26&quot;&gt;  핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;44&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UI 위젯의 변수 데이터를 DragDropOperation 입력 핀에 명시적으로 바인딩하여 인게임 드래그 시 데이터 유실을 완전 차단하였다.&lt;/li&gt;
&lt;li&gt;HasAuthority() 검증 레이어로 인한 클라이언트 차단 현상은 Server RPC 구조로 전환하여 데이터 주도권을 서버에 집중시켰다.&lt;/li&gt;
&lt;li&gt;언리얼 인벤토리 아키텍처는 실물 객체가 아닌 '추상적 데이터(태그/수량)'를 관리하는 공간임을 이해하고 액터 파괴 및 생성 흐름을 파악하였다.&lt;/li&gt;
&lt;li&gt;기획적 확장성(다양한 아이템 외형 복원)을 확보하기 위해 슬롯 구조체 내부에 TSubclassOf&amp;lt;ANCItemActor&amp;gt; 메타 변수를 선언하는 차세대 백로그 로직을 정립하였다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프 Unreal_7기/본캠프</category>
      <author>tkdqls</author>
      <guid isPermaLink="true">https://tkd6966.tistory.com/99</guid>
      <comments>https://tkd6966.tistory.com/99#entry99comment</comments>
      <pubDate>Tue, 2 Jun 2026 21:10:39 +0900</pubDate>
    </item>
    <item>
      <title>캐릭터 상호작용 시스템 아키텍처 구현 및 포스트 프로세스 최적화</title>
      <link>https://tkd6966.tistory.com/98</link>
      <description>&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;  학습 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장르적 UX 특성을 고려하여 상호작용 검출 방식을 Sphere Overlap 기반 최단 거리 타겟팅 구조 구현&lt;/li&gt;
&lt;li&gt;언리얼 엔진 5의 IWYU 정책에 따른 컴파일 에러를 해결하고, 데이터 불일치 및 변수 초기화 누락으로 인한 런타임 버그 방지&lt;/li&gt;
&lt;li&gt;해상도 독립적인 포스트 프로세스 외곽선 머티리얼을 설계하고, 데이터 타입 불일치 에러를 해결하여 렌더링 파이프라인 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;  1. 상호작용 스캔 방식 구현: Sphere Overlap 기반 타겟팅&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;  크로스헤어 조준 방식의 한계 및 UX 피로도 분석&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;기존 카메라 에임(Crosshair) 기반의 레이캐스트(LineTrace/Sweep) 방식은 플레이어에게 매번 정밀한 조준을 요구한다. 이는 아이템 파밍이 빈번하게 일어나는 액션 RPG나 서바이벌 장르 특성상 유저의 피로도를 극심하게 높이는 원인이 된다. 장르적 특성과 UX 편의성을 극대화하기 위해 검출 아키텍처의 전면 개편을 단행하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;  Sphere Overlap 및 최단 거리 타겟팅 알고리즘 구현&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;캐릭터의 중심 위치를 기준으로 일정 반경(InteractionSearchRadius)의 구체 영역을 전방위 검사하는 OverlapMultiByChannel 방식을 채택하였다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;레이더망 내에 복수의 아이템 액터가 동시에 검출될 경우, 루프를 돌며 캐릭터와의 물리적 거리(FVector::Dist)를 계산한다. 이 중 가장 가까운(Closest) 대상 하나만을 인터랙션 타겟으로 캡슐화하고 하이라이트 레이어로 넘겨주도록 연산 시퀀스를 최적화하였다.&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;11&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-path-to-node=&quot;11,0&quot; data-ke-size=&quot;size16&quot;&gt;장르적 성찰: 게임의 핵심 재미와 장르적 흐름에 따라 백엔드의 타겟팅 및 충돌 검출 아키텍처를 유연하게 변경해야 설계 무결성을 유지할 수 있음을 깨달았다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size26&quot;&gt;  2. C++ IWYU 최적화 이슈 해결 (에러 C2027)&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;  정의되지 않은 형식 사용 에러 발생&lt;/h3&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;OverlapMulti 로직을 구현하고 컴파일을 수행하는 과정에서 아래와 같은 치명적인 빌드 에러가 발생하였다.&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;16&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-path-to-node=&quot;16,0&quot; data-ke-size=&quot;size16&quot;&gt;Error C2027: 정의되지 않은 형식 'FOverlapResult'을(를) 사용했습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;  원인 및 해결 방법&lt;/h3&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;언리얼 엔진 5의 &lt;b data-index-in-node=&quot;10&quot; data-path-to-node=&quot;18&quot;&gt;IWYU(Include What You Use)&lt;/b&gt; 정책으로 인해 발생한 현상이다. 컴파일러가 해당 전방 선언 구조체의 멤버 변수나 크기를 파싱해야 하는 시점에, 실제 구조체의 명세가 담긴 헤더 파일이 참조되지 않아 발생한 빌드 블로킹이었다. 소스 코드 소스 파일(.cpp) 상단에 구조체가 정의된 아래 헤더를 명시적으로 포함하여 해결하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjZsOHy8OWUAxUAAAAAHQAAAAAQ4wI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;#include &quot;Engine/OverlapResult.h&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size26&quot;&gt;  3. 아이템 파밍(LootItem) 파이프라인 디버깅 및 데이터 초기화 세팅&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size23&quot;&gt;  인터페이스 신호 도달 후 런타임 먹통 현상&lt;/h3&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;키 입력 이벤트(E키)부터 인터페이스 호출 단계까지는 디버그 로그가 정상 출력되며 패킷 신호가 완벽하게 도달했음에도, 월드의 아이템 액터가 파괴되지 않고 가방 인벤토리 배열에도 아이템이 추가되지 않는 논리적 오류가 발생하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size23&quot;&gt;  원인 분석 및 디버깅&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;25&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,0,0&quot;&gt;원인 1 (데이터 테이블 Key 불일치):&lt;/b&gt; 아이템에 부여된 속성 태그(ItemTypeTag)의 문자열과 실제 데이터 테이블(DT_ItemData)에 등록된 Row Name이 오타 등의 이유로 일치하지 않았다. 이로 인해 내부 데이터베이스 조회 메서드가 false를 뱉으며 시퀀스가 중단되었다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,1,0&quot;&gt;원인 2 (C++ 멤버 변수 초기화 누락):&lt;/b&gt; C++ 헤더 파일 내에서 아이템 수량 변수를 선언할 때 기본값 할당을 누락하였다(int32 Quantity;). 이로 인해 메모리에 쓰레기 값 대신 0이 기본으로 할당되었고, 인벤토리 시스템의 상위 방어 가드 조건문이 이를 &quot;0개짜리 무효 아이템&quot;으로 판단하여 데이터 삽입 및 액터 파괴 작업을 거부한 것으로 판명되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;26&quot; data-ke-size=&quot;size23&quot;&gt;  방어적 코드 설계 (휴먼 에러 방지)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;기획자나 레벨 디자이너가 월드에 에셋을 무작위로 배치하더라도 오작동이 일어나지 않도록, C++ 헤더 선언부에서 변수의 최소 기본값을 강제 고정하는 방어적 구조를 수립하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjZsOHy8OWUAxUAAAAAHQAAAAAQ5AI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Item Data&quot;)
int32 Quantity = 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;30&quot; data-ke-size=&quot;size26&quot;&gt;  4. 포스트 프로세스 머티리얼 (Custom Stencil 외곽선) 구현 및 최적화&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;31&quot; data-ke-size=&quot;size23&quot;&gt;  이슈 1: 해상도 종속성에 따른 외곽선 두께 변조 현상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;32&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32,0,0&quot;&gt;문제:&lt;/b&gt; UV 좌표를 0.002와 같은 고정된 정적 실수치(실수 오프셋)로 밀어내어 텍셀 외곽선을 검출할 경우, 출력 해상도(1080p vs 4K)의 픽셀 밀도 차이에 따라 외곽선의 물리적 두께가 달라져 비주얼 왜곡이 발생하였다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32,1,0&quot;&gt;해결:&lt;/b&gt; 머티리얼 에디터 내에서 SceneTexelSize 노드를 도입하여 현재 디스플레이 해상도 기준의 정확한 &lt;b data-index-in-node=&quot;62&quot; data-path-to-node=&quot;32,1,0&quot;&gt;1픽셀 물리 크기&lt;/b&gt;를 동적으로 역산하였다. 여기에 사용자 정의 파라미터(LineThickness)를 승산 처리함으로써 모든 뷰포트 환경에서 일정한 픽셀 두께를 유지하는 &lt;b data-index-in-node=&quot;155&quot; data-path-to-node=&quot;32,1,0&quot;&gt;해상도 독립적(Resolution-Independent) 외곽선 셰이더&lt;/b&gt;를 완성하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;33&quot; data-ke-size=&quot;size23&quot;&gt;  이슈 2: 데이터 타입 다형성 위배에 따른 컴파일 에러&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;34&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;34,0,0&quot;&gt;문제:&lt;/b&gt; 머티리얼 수식 연산 중 아래와 같은 타입 불일치 에러와 함께 셰이더 컴파일이 거부되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-path-to-node=&quot;35&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-path-to-node=&quot;35,0&quot; data-ke-size=&quot;size16&quot;&gt;Arithmetic between types float4 and float3 are undefined&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;36&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,0,0&quot;&gt;원인:&lt;/b&gt; 화면 버퍼를 샘플링하는 SceneTexture 노드는 투명도(Alpha)를 포함한 float4(RGBA) 구조를 반환하는 반면, 외곽선 지정을 위해 추가한 컬러 파라미터 변수는 float3(RGB) 구조를 출력하여 벡터 차원 불일치로 인한 사칙연산 불가 오류였다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,1,0&quot;&gt;해결:&lt;/b&gt; ComponentMask 노드를 파이프라인 중간에 배치하여 원본 렌더 타깃 픽셀 데이터에서 알파(A) 채널을 소거하고 R, G, B 채널만 명시적으로 추출(float4 ➔ float3)하였다. 데이터 규격을 3차원 벡터로 통일시켜 에러를 해결하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;38&quot; data-ke-size=&quot;size26&quot;&gt;  핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;39&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유저 피로도를 최소화하기 위해 상호작용 탐색 구조를 에임 방식에서 캐릭터 중심의 Sphere Overlap 기반 최단 거리 검출 방식으로 개편&lt;/li&gt;
&lt;li&gt;FOverlapResult 구조체 사용을 위해 Engine/OverlapResult.h 헤더를 명시적으로 포함하여 IWYU 빌드 에러 완화&lt;/li&gt;
&lt;li&gt;변수 초기화 누락(Quantity = 1) 및 데이터 테이블 Key 매핑 관계를 교체하여 데이터 누락 현상을 원천 방어&lt;/li&gt;
&lt;li&gt;포스트 프로세스 단계에서 SceneTexelSize 및 ComponentMask 노드를 연동하여 해상도 독립적이고 연산 오류가 없는 커스텀 스텐실 외곽선 머티리얼 인스턴스(MI) 시스템 구축&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프 Unreal_7기/본캠프</category>
      <author>tkdqls</author>
      <guid isPermaLink="true">https://tkd6966.tistory.com/98</guid>
      <comments>https://tkd6966.tistory.com/98#entry98comment</comments>
      <pubDate>Mon, 1 Jun 2026 20:45:47 +0900</pubDate>
    </item>
    <item>
      <title>멀티플레이 인벤토리 코어 시스템 구축 및 네트워크 데이터 흐름 검증</title>
      <link>https://tkd6966.tistory.com/97</link>
      <description>&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;  학습 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HasAuthority 가드 클로즈 및 OnRep_ 콜백의 네트워크 특성을 이해하고 Server RPC 데이터 흐름 정립&lt;/li&gt;
&lt;li&gt;FGameplayTag 문자열 기반 데이터 테이블 탐색 로직의 데이터 무결성 검증&lt;/li&gt;
&lt;li&gt;FindStackableSlot 자동 병합 알고리즘의 방어 코드와 예외 처리 시퀀스 실증&lt;/li&gt;
&lt;li&gt;단일 책임 원칙(SRP)에 따라 데이터 차감은 인벤토리에 캡슐화하고, 시각적 처리는 멀티캐스트 델리게이트로 위임하는 확장형 API 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;  1. 멀티플레이어 네트워크 리플리케이션(Replication)의 함정과 해결&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;  클라이언트 데이터 갱신 누락 현상&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트 환경에서 아이템 획득(Add) 키 입력 이벤트를 트리거했으나, 관련 로그가 출력되지 않고 인벤토리 컨테이너 데이터가 전혀 갱신되지 않는 현상이 발생하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;  원인 분석&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;HasAuthority() 가드 클로즈:&lt;/b&gt; C++ 코어 레벨에서 데이터 변조(핵)를 방지하기 위해 서버 권한(HasAuthority())이 없는 클라이언트 프록시의 데이터 직접 수정 접근을 완벽하게 가드 차단하고 있었다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;OnRep_ 콜백의 실행 특성:&lt;/b&gt; 언리얼 엔진 리플리케이션 시스템에서 OnRep_ 콜백 함수는 오직 &lt;b data-index-in-node=&quot;55&quot; data-path-to-node=&quot;9,1,0&quot;&gt;클라이언트(Proxy) 단에서만 자동 실행&lt;/b&gt;된다. 서버(Authority) 측 본인의 데이터가 변경될 때는 이 콜백이 자동으로 트리거되지 않으므로 서버 로컬 UI 갱신 로직이 누락되는 구조적 특성이 존재한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size23&quot;&gt;  해결 방법&lt;/h3&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;블루프린트 입력 이벤트를 C++ 인벤토리 조작 함수에 직접 바인딩하지 않고, 중간에 &lt;b data-index-in-node=&quot;47&quot; data-path-to-node=&quot;11&quot;&gt;Run on Server (Reliable)&lt;/b&gt; 속성이 부여된 커스텀 이벤트(Server RPC) 파이프라인을 거치도록 데이터 흐름을 교정하였다. 클라이언트가 요청하면 서버가 안전하게 데이터를 검증&amp;middot;수정하고, 변동된 값이 다시 클라이언트들에게 복제(Replicate)되도록 구조적 안정성을 확보하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size26&quot;&gt;  2. 데이터 테이블 탐색 로직과 문자열 매칭&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;  FindRow 탐색 실패 이슈&lt;/h3&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;C++ 내부에서 FindRow() 함수를 통해 아이템 데이터베이스를 조회할 때, 데이터가 존재함에도 지속적으로 false를 반환하며 아이템 정보를 로드하지 못하는 문제가 발생하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;  원인 및 해결&lt;/h3&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;언리얼 엔진의 FGameplayTag를 Convert하여 데이터 테이블의 행 이름(Row Name)과 매칭할 때, 문자열이 단 1글자의 오타(예: 마침표 . 누락 또는 대소문자 불일치)도 없이 완벽하게 일치해야 함을 확인하였다. 데이터 테이블의 Row Name 포맷을 ItemType.Consumable 등의 태그 문자열 구조와 정확히 1:1로 일치하도록 교정하여 정상 룩업(Lookup)을 완료하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size26&quot;&gt;  3. C++ 방어 로직과 자동 병합(Auto-Stack) 알고리즘 검증&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size23&quot;&gt;  Move 로직의 리턴 false 현상&lt;/h3&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;0번 슬롯에 아이템을 획득한 후 1번 슬롯으로 이동(Move)시키고, 다시 0번에 아이템을 재획득한 뒤 1번 슬롯으로 복사 이동을 재시도했을 때, 두 번째 Move 시퀀스에서 지속적으로 false가 반환되는 현상이 관찰되었다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size23&quot;&gt;  예외 처리 알고리즘의 무결성 실증&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;23&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,0,0&quot;&gt;분석:&lt;/b&gt; 코드의 논리적 결함이 아닌, 인벤토리 시스템에 내장된 FindStackableSlot(자동 병합 알고리즘)이 의도대로 정교하게 작동한 결과였다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,1,0&quot;&gt;시퀀스 흐름:&lt;/b&gt; 두 번째로 아이템을 획득할 때, 시스템은 아이템을 빈 슬롯(0번)에 새로 할당하지 않고, 이미 동일한 태그를 가진 1번 슬롯의 기존 아이템 스택에 수량을 자동으로 합산(Combine)시켰다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,2,0&quot;&gt;결론:&lt;/b&gt; 결과적으로 0번 슬롯은 계속 비어있는 상태(IsEmpty() == true)를 유지했으므로, 빈 슬롯에서 아이템을 옮기려 한 두 번째 Move 로직이 상위 가드 조건문에서 안전하게 방어 처리된 정상적인 동작임을 검증하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size26&quot;&gt;  4. 확장성을 고려한 단일 책임 원칙(SRP)과 델리게이트 활용&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;26&quot; data-ke-size=&quot;size23&quot;&gt;  핵심 데이터 로직의 내부 캡슐화&lt;/h3&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;아이템 사용(UseQuickSlot) 및 파밍(LootItem) 시 발생하는 핵심 비즈니스 로직(물약 등의 소모품 수량 차감 및 슬롯 비우기)을 메인 인벤토리 컴포넌트 내부에서 중앙 제어하도록 설계하여 데이터 무결성을 확보하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;28&quot; data-ke-size=&quot;size23&quot;&gt;  멀티캐스트 델리게이트를 통한 시각/스탯 처리 위임 (Decoupling)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;인벤토리 컴포넌트가 캐릭터의 물리적 스탯 변동(체력 회복)이나 외형적 변화(무기 장착 애니메이션 몽타주 재생)까지 직접 처리하는 것은 단일 책임 원칙에 위배된다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;이를 분리하기 위해 C++ 레벨에서 FOnItemUsedSignature (동적 멀티캐스트 델리게이트)를 선언하였다. 인벤토리는 데이터 연산 완료 후 해당 델리게이트를 브로드캐스트(Broadcast)하기만 하고, 실제 시각적/체력적 변화는 이를 구독하는 캐릭터 블루프린트 파트에서 유연하게 수신하여 처리할 수 있도록 견고한 통합 API를 제공하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;32&quot; data-ke-size=&quot;size26&quot;&gt;  후속 작업&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;33&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;완성된 C++ 백엔드 델리게이트(OnInventoryUpdated)를 UMG(블루프린트 위젯)와 바인딩하여 실제 화면에 인벤토리 그리드를 시각화한다.&lt;/li&gt;
&lt;li&gt;아이템 드래그 앤 드롭 UI 기능 연동을 수행하여 데이터와 시각적 인터랙션을 통합한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;35&quot; data-ke-size=&quot;size26&quot;&gt;  핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;36&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트의 불법 데이터 변조를 막기 위해 HasAuthority() 검증을 거치는 Server RPC 보안 파이프라인 정립&lt;/li&gt;
&lt;li&gt;FGameplayTag 문자열 데이터 구조와 데이터 테이블 Row Name의 1:1 매칭 구조를 정교화하여 탐색 오류 해결&lt;/li&gt;
&lt;li&gt;FindStackableSlot이 유효 스택을 찾아 선병합함에 따라 발생하는 슬롯 공백 상태와, 이에 따른 Move 함수의 방어 예외 처리가 정상 작동함 실증&lt;/li&gt;
&lt;li&gt;백엔드 데이터 관리(Inventory)와 프론트엔드 비주얼 표현(Character ABP)의 결합도를 낮추기 위해 동적 멀티캐스트 델리게이트 기반의 분업 아키텍처 완성&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프 Unreal_7기/본캠프</category>
      <author>tkdqls</author>
      <guid isPermaLink="true">https://tkd6966.tistory.com/97</guid>
      <comments>https://tkd6966.tistory.com/97#entry97comment</comments>
      <pubDate>Fri, 29 May 2026 20:49:44 +0900</pubDate>
    </item>
    <item>
      <title>인벤토리 UI 최적화, 협업 아키텍처 및 MVP 구현 전략</title>
      <link>https://tkd6966.tistory.com/96</link>
      <description>&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;  학습 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ReplicatedUsing(OnRep)&lt;span&gt; 콜백과 델리게이트를 연동하여 멀티플레이 환경에서 인벤토리 UI의 불필요한 전체 갱신을 막고 데이터 변화에만 반응하는 최적화 구조 구축&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;단일 책임 원칙(SRP)에 근거하여 입력 처리(Character)와 데이터 처리(Inventory) 역할을 엄격히 분리(Decoupling)함으로써 객체지향 협업 아키텍처 수립&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;백엔드 및 네트워크 동기화가 완료된 C++ 부모 클래스(&lt;/span&gt;ANCItemActor&lt;span&gt;)를 설계하여 기획/디자인 파트와의 효율적인 작업 파이프라인 구축&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;프로젝트 초기 단계에서 복잡한 시스템 참조 대신 빠르고 확실한 테스트가 가능한 MVP(최소 기능 제품) 마인드셋 기반의 토글 및 필터링 로직 구현&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;  1. UI 갱신 최적화와 리플리케이트 콜백(ReplicatedUsing)&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;  ReplicatedUsing과 델리게이트 연동을 통한 데이터-UI 동기화&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;서버 권한(Authority) 측에서 인벤토리 데이터 배열이 변동될 때,&lt;/span&gt;&lt;span&gt; 이를 클라이언트(Proxy)에게 즉시 통지하고 클라이언트의 UMG UI를 자동으로 갱신하기 위해 &lt;/span&gt;OnRep&lt;span&gt; 콜백 함수와 &lt;/span&gt;델리게이트(Delegate)&lt;span&gt; 파이프라인을 구축하였다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;  배열 독립성에 기반한 갱신 오버헤드 최소화 (최적화)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;메인 인벤토리(&lt;/span&gt;Items&lt;span&gt;)와 플레이어 전용 퀵슬롯(&lt;/span&gt;QuickSlots&lt;span&gt;)은 물리적으로 독립적인 메모리 배열 구조를 가진다.&lt;/span&gt;&lt;span&gt; 따라서 데이터 변화를 통지하는 델리게이트(확성기)도 별도로 할당해야 불필요한 전체 갱신 오버헤드를 막을 수 있음을 체감하였다.&lt;/span&gt;&lt;span&gt; 퀵슬롯 전용 &lt;/span&gt;OnRep_QuickSlots&lt;span&gt; 콜백을 추가 구현하여 메인 가방 데이터가 변할 때는 퀵슬롯 UI가 반응하지 않고,&lt;/span&gt;&lt;span&gt; 오직 퀵슬롯 데이터가 변할 때만 해당 위젯 영역이 갱신되도록 렌더링 부하를 최적화하였다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;  2. 단일 책임 원칙(SRP)에 기반한 협업 아키텍처 (아이템 파밍)&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;  인벤토리 컴포넌트의 입력 처리 의존성 제거 (Decoupling)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;인벤토리 컴포넌트 내부에서 플레이어의 'F키(상호작용)' 입력을 직접 바인딩하여 파밍 로직을 처리하도록 구현할 경우,&lt;/span&gt;&lt;span&gt; 객체지향적 설계 원칙에 위배되며 캐릭터 클래스가 가진 다른 상호작용 입력(문 열기,&lt;/span&gt;&lt;span&gt; NPC 대화 등)과 키보드 입력 레퍼런스가 충돌할 위험이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;  라인트레이스 위임 및 HasAuthority 검증&lt;/h3&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;역할 분리를 위해 인벤토리 컴포넌트는 키 입력 바인딩을 완전히 제거하고,&lt;/span&gt;&lt;span&gt; 오직 외부에서 호출 가능한 &lt;/span&gt;LootItem(ANCItemActor* ItemToLoot)&lt;span&gt; 데이터 처리 함수만 노출하였다.&lt;/span&gt;&lt;span&gt; 플레이어 캐릭터 클래스가 전방 라인트레이스(Raycast)를 통해 월드 액터를 탐색하고,&lt;/span&gt;&lt;span&gt; 파밍 대상 액터 레퍼런스를 인벤토리 컴포넌트의 &lt;/span&gt;LootItem&lt;span&gt;으로 넘겨주도록 역할을 위임하여 깃허브 코드 및 런타임 로직 충돌을 원천 차단하였다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;  3. 확장성을 고려한 아이템 부모 클래스(ANCItemActor) 설계&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size23&quot;&gt;  C++ 기반의 멀티플레이어 뼈대 확립&lt;/h3&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;월드에 스폰될 아이템들의 공통 C++ 부모 클래스(&lt;/span&gt;ANCItemActor&lt;span&gt;)를 구현하였다.&lt;/span&gt;&lt;span&gt; 생성자 단계에서 &lt;/span&gt;bReplicates = true&lt;span&gt;를 선언하고,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;GameplayTag&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;Quantity&lt;span&gt; 변수 등에 &lt;/span&gt;Replicated&lt;span&gt; 마킹 및 &lt;/span&gt;GetLifetimeReplicatedProps&lt;span&gt; 설정을 완료하여 데디케이티드 서버 환경에서 완벽하게 작동하는 멀티플레이어 환경을 구축하였다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size23&quot;&gt;  기획/디자인 파트와의 효율적 작업 파이프라인 수립&lt;/h3&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;프로그래머는 백엔드 논리 및 네트워크 동기화가 완전히 검증된 '뼈대' 데이터만 C++로 제공하고,&lt;/span&gt;&lt;span&gt; 아이템 기획자나 디자이너는 이 클래스를 상속받은 블루프린트에서 &lt;/span&gt;스태틱 메시&lt;span&gt;와 물리 효과(&lt;/span&gt;Simulate Physics&lt;span&gt;)를 자유롭게 추가하여 아이템을 커스터마이징할 수 있는 협업 구조를 확립하였다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;23&quot; data-ke-size=&quot;size26&quot;&gt;  4. MVP(최소 기능 제품) 마인드셋을 적용한 퀵슬롯 하드코딩&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size23&quot;&gt;  빠르고 확실한 테스트를 위한 임시 구조 채택&lt;/h3&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1~2번(무기),&lt;/span&gt;&lt;span&gt; 3~4번(소모품) 퀵슬롯 제한 로직을 구현하는 초기 단계에서,&lt;/span&gt;&lt;span&gt; 유연하지만 연산이 복잡한 데이터 테이블 참조 방식 대신 C++ 조건문을 통한 인덱스 기반 태그 필터링(&lt;/span&gt;MatchesTag&lt;span&gt;)과 스왑(&lt;/span&gt;Swap&lt;span&gt;) 방식을 전격 채택하였다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;26&quot; data-ke-size=&quot;size23&quot;&gt;  기술적 성찰&lt;/h3&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;프로젝트 극 초기 단계에서는 시스템의 완벽한 유연성보다는,&lt;/span&gt;&lt;span&gt; 핵심 코어 논리를 빠르고 확실하게 검증하고 테스트할 수 있는 '원자적 커밋' 구조를 만드는 MVP 마인드셋이 생산성에 얼마나 결정적인지 이해하였다.&lt;/span&gt;&lt;span&gt; 향후 데이터 기반 시스템으로 마이그레이션하더라도 현재의 빠른 프로토타이핑은 가치가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;29&quot; data-ke-size=&quot;size26&quot;&gt;  후속 작업&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;30&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;완성된 C++ 백엔드 델리게이트(&lt;/span&gt;OnInventoryUpdated&lt;span&gt;)를 UMG(블루프린트 위젯)와 바인딩하여 실제 화면에 인벤토리 그리드를 시각화한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;아이템 드래그 앤 드롭 UI 기능 연동을 수행하여 데이터와 시각적 인터랙션을 통합한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;32&quot; data-ke-size=&quot;size26&quot;&gt;  핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;33&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ReplicatedUsing(OnRep)&lt;span&gt; 콜백과 델리게이트를 분리하여 가방 데이터와 퀵슬롯 데이터가 독립적으로 UI에 반응하는 최적화 구조 완성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;단일 책임 원칙(SRP)에 근거하여 입력 처리 권한을 캐릭터에 집중시키고 데이터 처리 권한을 인벤토리에 위임하여 유연한 협업 아키텍처 수립&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;C++ 레벨에서 네트워크 동기화가 완료된 아이템 부모 클래스(&lt;/span&gt;ANCItemActor&lt;span&gt;)를 설계하여 기획 및 디자인 파트와의 병렬 작업 생산성 확보&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;프로젝트 초기 코어 논리 검증을 위해 빠르고 직관적인 MVP 마인드셋 기반의 하드코딩 토글 및 필터링 로직 구현&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프 Unreal_7기/본캠프</category>
      <author>tkdqls</author>
      <guid isPermaLink="true">https://tkd6966.tistory.com/96</guid>
      <comments>https://tkd6966.tistory.com/96#entry96comment</comments>
      <pubDate>Thu, 28 May 2026 20:58:07 +0900</pubDate>
    </item>
    <item>
      <title>게임플레이 태그 기반 인벤토리 C++ 아키텍처 및 코어 로직 구현</title>
      <link>https://tkd6966.tistory.com/95</link>
      <description>&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;  학습 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다형성(Polymorphism)을 활용하여 컨테이너와 플레이어 모두 대응 가능한 상속 기반 인벤토리 컴포넌트 설계&lt;/li&gt;
&lt;li&gt;FGameplayTag 및 데이터 테이블을 연동하여 하드코딩 없는 데이터 주도형 아이템 시스템 구축&lt;/li&gt;
&lt;li&gt;멀티플레이어 환경에서 아이템 복사 부정행위를 방지하는 스택(Stack) 제어 로직 및 Server RPC 보안 파이프라인 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;  1. 상속(Inheritance) 기반의 인벤토리 아키텍처 설계&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;  다형성을 통한 코드 중복 및 메모리 낭비 방지&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;플레이어의 퀵슬롯 기능과 필드 상자의 파밍 기능을 하나의 컴포넌트에 통합할 경우, 불필요한 메모리 점유와 스파게티 코드가 양산되는 문제가 발생한다. 이를 해결하기 위해 객체 지향 프로그래밍의 상속 구조를 도입하였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;NCInventoryBaseComponent (부모):&lt;/b&gt; GridSize 기반 슬롯 생성, 아이템 입출력(Add/Remove), 서버 동기화 등 가방으로서의 공통 코어 로직을 전담한다. 일반 상자나 파밍 컨테이너에 부착하여 재사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;NCPlayerInventoryComponent (자식):&lt;/b&gt; 부모 클래스를 상속받아, 플레이어 캐릭터만 전용으로 사용하는 4칸짜리 QuickSlots 배열 및 인터페이스 기능만 확장하여 구현한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size26&quot;&gt;  2. 게임플레이 태그(Gameplay Tag) 전면 도입 및 데이터 주도 설계&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;  구조적 통일성 및 오타 위험 방지&lt;/h3&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;아이템 분류 및 ID 체계를 기존의 FName이나 Enum 방식에서 FGameplayTag 구조로 전면 교체하였다. 이는 프로젝트 내 캐릭터 상태 제어 시스템과의 데이터 아키텍처 통일성을 확보하고, 하드코딩으로 인한 오타 에러를 사전에 방지하기 위함이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;태그 예시:&lt;/b&gt; Item.Consumable.Potion, Item.Equipment.Weapon&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;  기획 친화적 데이터 구조 수립&lt;/h3&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;FItemData 구조체를 정의할 때 팀 C++ 코딩 컨벤션에 맞춰 에셋 참조 변수에 TObjectPtr를 엄수하여 적용하였다. 이를 기반으로 DT_ItemData 데이터 테이블을 구축하여, 프로그래머의 코드 수정 및 재컴파일 없이 기획 파트에서 에디터 조율만으로 아이템별 최대 스택 수량(MaxStackSize) 등을 실시간으로 제어할 수 있는 구조를 완성하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;  3. 아이템 입출력 (CRUD) 및 스택(Stack) 시스템 C++ 구현&lt;/h2&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;아이템 개수 관리 책임 및 로직을 개별 아이템 객체 클래스가 아닌 인벤토리 메인 매니저 로직으로 이관하여 구조를 캡슐화하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size23&quot;&gt;  AddItem (아이템 추가) 시퀀스&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터 테이블을 조회하여 해당 아이템 태그의 MaxStackSize를 룩업한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0&quot;&gt;FindStackableSlot:&lt;/b&gt; 인벤토리 배열 내에서 동일한 태그를 가졌으면서 아직 최대 수량에 도달하지 않은 여유 슬롯이 있는지 탐색하여 남은 수량을 먼저 합산(Stacking)한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,2,0&quot;&gt;FindEmptySlot:&lt;/b&gt; 기존 슬롯이 모두 만석이거나 스택이 불가능한 장비류 항목일 경우, 완전히 비어 있는 새로운 칸을 찾아 남은 수량을 할당한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;  RemoveItem (아이템 차감) 시퀀스&lt;/h3&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;인덱스 및 지정 수량을 넘겨받아 차감을 진행하며, 연산 결과 수량이 0에 도달하면 슬롯의 식별자를 FGameplayTag::EmptyTag로 변경하여 완전 초기화 처리를 수행한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size26&quot;&gt;  4. 트러블슈팅 (Troubleshooting)&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size23&quot;&gt;  이슈 1: 인벤토리 컴포넌트 데이터의 클라이언트 동기화 실패&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;26&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,0,0&quot;&gt;원인:&lt;/b&gt; 가방 내부의 인벤토리 데이터 배열 변수에 Replicated 마킹을 지정했음에도 불구하고, 정작 배열을 소유한 인벤토리 컴포넌트 자체의 네트워크 통신 속성이 비활성화되어 있어 0 Array Elements 상태로 누락되었다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,1,0&quot;&gt;해결:&lt;/b&gt; 부모 클래스인 ANCInventoryBaseComponent 생성자 내부에 컴포넌트 리플리케이션을 선언하는 아래 코드를 추가하여 네트워크 파이프라인을 활성화하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjQ_Y-judmUAxUAAAAAHQAAAAAQkQE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;SetIsReplicatedByDefault(true);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;28&quot; data-ke-size=&quot;size23&quot;&gt;  이슈 2: 클라이언트가 아이템 획득 시 데이터가 삭제되는 롤백 현상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;29&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;29,0,0&quot;&gt;원인:&lt;/b&gt; 클라이언트 로컬(클라이언트 프록시) 환경에서만 인벤토리 배열 값을 직접 조작하여 발생한 현상이다. 서버 권한(Authority) 측은 이 변화를 비정상적인 데이터 변조(핵 행위)로 판정하고, 서버가 가진 원본 데이터(빈 가방)를 다시 클라이언트에 강제로 덮어씌워 롤백시켰다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;29,1,0&quot;&gt;해결:&lt;/b&gt; 입력 이벤트 발생 시 로컬 가방을 직접 조작하는 로직을 완전히 걷어냈다. 대신 &lt;b data-index-in-node=&quot;48&quot; data-path-to-node=&quot;29,1,0&quot;&gt;Server RPC&lt;/b&gt;를 구현하여 서버에 데이터 처리를 정석으로 요청하도록 구조를 변경하였다. 서버 내에서 HasAuthority() 검증을 거친 후 안전하게 아이템 데이터를 추가 및 복제하도록 수정하여 아이템 복사 버그를 원천 차단하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;33&quot; data-ke-size=&quot;size26&quot;&gt;  5. 후속 작업 (Next Steps)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;34&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에디터에서 배열 데이터 수량(Quantity)이 의도대로 스택형(3 ➔ 6 ➔ 9)으로 정상 누적 출력되는지 최종 검증한다.&lt;/li&gt;
&lt;li&gt;백엔드 C++ 인벤토리 배열 데이터를 UI 화면에 1:1 슬롯 그리드로 바인딩하여 뿌려주는 UMG 위젯 화면 연동 작업을 준비한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;36&quot; data-ke-size=&quot;size26&quot;&gt;  핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;37&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 지향 설계에 따라 공통 가방 기능(Base)과 플레이어 특화 기능(Player)을 상속 구조로 분리하여 모듈성을 확보하였다.&lt;/li&gt;
&lt;li&gt;FGameplayTag 매핑 및 데이터 테이블 기반 룩업 방식을 통해 에디터 친화적인 데이터 주도형 아이템 시스템을 정립하였다.&lt;/li&gt;
&lt;li&gt;SetIsReplicatedByDefault(true) 및 Server RPC 보안 검증 파이프라인을 구축하여 멀티플레이 데이터 무결성을 증명하였다.&lt;/li&gt;
&lt;li&gt;데이터 변조 핵 방지를 위해 모든 데이터 변동 연산 권한을 서버(HasAuthority)에 집중시키는 안전한 인벤토리 코어 로직을 완성하였다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프 Unreal_7기/본캠프</category>
      <author>tkdqls</author>
      <guid isPermaLink="true">https://tkd6966.tistory.com/95</guid>
      <comments>https://tkd6966.tistory.com/95#entry95comment</comments>
      <pubDate>Wed, 27 May 2026 21:20:41 +0900</pubDate>
    </item>
  </channel>
</rss>