<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>미피디버거</title>
    <link>https://moung051.tistory.com/</link>
    <description>ʕっ&amp;bull;ᴥ&amp;bull;ʔっ
Unreal Engine</description>
    <language>ko</language>
    <pubDate>Tue, 18 Aug 2026 12:28:36 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코딩하는미피</managingEditor>
    <image>
      <title>미피디버거</title>
      <url>https://tistory1.daumcdn.net/tistory/8419713/attach/5e53794b95664b38ae59b1171a808e11</url>
      <link>https://moung051.tistory.com</link>
    </image>
    <item>
      <title>[TIL] 벨트 상호작용 및 슬롯 이동 기능 개선 | 최종 팀프로젝트</title>
      <link>https://moung051.tistory.com/130</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;컨베이어 벨트 상호작용 위젯 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨베이어 벨트도 플레이어가 상호작용할 수 있도록 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벨트가 설비와 연결되어 있어도, 사용자가 벨트가 어느 설비 슬롯과 연결되어 있는지 직접 확인하거나 변경하기 어려웠다. 그래서 벨트에 상호작용하면 연결 설정 위젯이 열리도록 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1783601022417&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;컨베이어 벨트 상호작용
&amp;rarr; 연결 대상 확인
&amp;rarr; 사용할 output 슬롯 선택
&amp;rarr; 선택된 슬롯 기준으로 아이템 이동&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;슬롯 미할당 상태 표시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벨트가 설비와 연결되어 있어도 아직 입출력 슬롯이 할당되지 않은 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서는 플레이어 입장에서 벨트가 왜 제대로 동작하지 않는지 알기 어렵다. 그래서 벨트에 연결 슬롯이 지정되지 않은 경우, 월드 상에 느낌표 표시가 뜨도록 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1783601087697&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;벨트가 설비와 연결됨
&amp;rarr; 하지만 output 슬롯이 지정되지 않음
&amp;rarr; 느낌표 표시

슬롯이 정상적으로 지정됨
&amp;rarr; 느낌표 숨김&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인벤토리와 설비 슬롯 간 우클릭 이동&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 인벤토리에서 설비 input 슬롯이나 보일러 fuel 슬롯으로 아이템을 넣을 때 주로 드래그 앤 드롭을 사용했다. 기능은 동작했지만, 제작 과정에서 재료를 반복적으로 넣고 빼야 하는 상황에서는 조작이 번거로웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우클릭으로 처리할 수 있도록 개선했다.&lt;/p&gt;
&lt;pre id=&quot;code_1783599785908&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;인벤토리 &amp;rarr; 설비 input 슬롯
설비 input 슬롯 &amp;rarr; 인벤토리
설비 output 슬롯 &amp;rarr; 인벤토리
인벤토리 &amp;rarr; 보일러 fuel 슬롯
보일러 fuel 슬롯 &amp;rarr; 인벤토리&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이동 방향은 크게 두 가지로 나누었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;슬롯&amp;nbsp;&amp;rarr;&amp;nbsp;인벤토리 &lt;br /&gt;2.&amp;nbsp;인벤토리&amp;nbsp;&amp;rarr;&amp;nbsp;슬롯&lt;/p&gt;
&lt;p data-end=&quot;1804&quot; data-start=&quot;1735&quot; data-ke-size=&quot;size16&quot;&gt;슬롯에서 인벤토리로 빼는 경우에는 클릭 대상이 KOFactorySlotWidget이기 때문에 슬롯 위젯 안에서 처리했다.&lt;/p&gt;
&lt;p data-end=&quot;1898&quot; data-start=&quot;1806&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;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FactorySlotWidget 공통화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설비 input 슬롯, output 슬롯, 보일러 fuel 슬롯은 모두 KOFactorySlotWidget을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬롯에서 인벤토리로 이동하는 처리는 MoveCurrentSlotItemToInventory() 하나로 통합했다.&lt;br /&gt;실제 추출 방식만 Mode에 따라 다르게 처리하도록 분리했다.&lt;/p&gt;
&lt;pre id=&quot;code_1783599881133&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;switch (Mode)
{
case EKOFactorySlotMode::Fuel:
    // 보일러 연료 버퍼에서 추출
    break;

case EKOFactorySlotMode::ProcessorInput:
    // 설비 input 버퍼에서 추출
    break;

case EKOFactorySlotMode::ProcessorOutput:
    // 설비 output 버퍼에서 추출
    break;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인벤토리에서 슬롯으로 넣기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설비 input 슬롯에 넣을 때는 TryInsertItem()을 사용하고, 보일러 fuel 슬롯에 넣을 때는 TryInsertFuel()을 사용한다. 하지만 전체 흐름은 동일했다.&lt;/p&gt;
&lt;pre id=&quot;code_1783599916128&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;인벤토리에서 아이템 제거
&amp;rarr; 현재 슬롯에 넣기 시도
&amp;rarr; 못 들어간 수량은 인벤토리에 복구
&amp;rarr; 슬롯 UI 갱신&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KOFactorySlotWidget에 TryMoveInventorySlotToThis()를 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 함수는 현재 슬롯의 Mode에 따라 다음처럼 동작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1783599942213&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (Mode == EKOFactorySlotMode::Fuel)
{
    // 보일러 fuel 슬롯에 연료 투입
}
else if (Mode == EKOFactorySlotMode::ProcessorInput)
{
    // 설비 input 슬롯에 재료 투입
}
else
{
    // output 슬롯은 아이템을 받을 수 없음
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;각 클래스의 역할 정리&lt;/h4&gt;
&lt;pre id=&quot;code_1783599985593&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;KOFactorySlotWidget
&amp;rarr; 실제 아이템 이동 처리

KOFactoryProcessorWidget
&amp;rarr; 인벤토리 우클릭 이벤트를 input 슬롯에 전달

KOFactoryProducerWidget
&amp;rarr; 인벤토리 우클릭 이벤트를 fuel 슬롯에 전달&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 드래그 앤 드롭 중심이었던 아이템 이동을 우클릭으로도 처리할 수 있게 개선했고, fuel/input/output 슬롯의 이동 로직을 KOFactorySlotWidget 중심으로 공통화했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬롯 자체에서 처리할 수 있는 기능은 KOFactorySlotWidget에 두고, 부모 위젯은 인벤토리 우클릭 이벤트를 받아 적절한 슬롯으로 전달하도록 분리했다.&lt;/p&gt;</description>
      <category>Unreal 최종 팀프로젝트</category>
      <category>Unreal_C++</category>
      <category>팀 프로젝트</category>
      <author>코딩하는미피</author>
      <guid isPermaLink="true">https://moung051.tistory.com/130</guid>
      <comments>https://moung051.tistory.com/130#entry130comment</comments>
      <pubDate>Thu, 9 Jul 2026 21:47:14 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 튜토리얼 퀘스트 시스템 | 최종 팀프로젝트</title>
      <link>https://moung051.tistory.com/129</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;초반 진행 단계를 퀘스트 형태로 나누고, 각 행동이 완료될 때 다음 단계로 넘어가도록 구조를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀘스트 시스템은 &lt;b&gt;DataTable 기반&lt;/b&gt;으로 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 퀘스트는 ID를 가지고 있으며, 제목, 목표 설명, 완료 조건, 목표 아이템 또는 설비 ID, 다음 퀘스트 ID를 데이터로 관리한다. 이를 통해 퀘스트 순서를 바꾸거나 목표를 수정할 때 코드 수정 없이 DataTable에서 조정할 수 있도록 했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;퀘스트 진행 구조&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀘스트는 현재 진행 중인 CurrentQuestId를 기준으로 동작한다.&lt;br /&gt;플레이어가 특정 행동을 하면 각 시스템에서 퀘스트 서브시스템에 알림을 보내고, 퀘스트 서브시스템은 현재 퀘스트의 완료 조건과 비교한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어&amp;nbsp;행동&amp;nbsp;발생 &lt;br /&gt;&amp;rarr;&amp;nbsp;QuestGuideSubsystem에&amp;nbsp;Notify&amp;nbsp;호출 &lt;br /&gt;&amp;rarr;&amp;nbsp;현재&amp;nbsp;퀘스트의&amp;nbsp;CompleteType&amp;nbsp;/&amp;nbsp;TargetId&amp;nbsp;비교 &lt;br /&gt;&amp;rarr;&amp;nbsp;조건이&amp;nbsp;맞으면&amp;nbsp;퀘스트&amp;nbsp;완료 &lt;br /&gt;&amp;rarr;&amp;nbsp;보상&amp;nbsp;지급 &lt;br /&gt;&amp;rarr;&amp;nbsp;다음&amp;nbsp;퀘스트로&amp;nbsp;이동&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;진행도 카운트 처리&lt;/h4&gt;
&lt;p data-end=&quot;1580&quot; data-start=&quot;1499&quot; data-ke-size=&quot;size16&quot;&gt;일부 퀘스트는 한 번의 행동으로 끝나지 않는다.&lt;br /&gt;예를 들어 몬스터 4마리 처치 퀘스트는 몬스터를 한 마리 잡을 때마다 진행도가 증가해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;1661&quot; data-start=&quot;1582&quot; data-ke-size=&quot;size16&quot;&gt;이를 위해 퀘스트 데이터에 RequiredCount를 두고, 퀘스트 서브시스템 내부에서 CurrentProgress를 관리하도록 했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;몬스터 1마리 처치 &amp;rarr; CurrentProgress + 1&lt;/li&gt;
&lt;li&gt;CurrentProgress &amp;lt; RequiredCount &amp;rarr; 아직 퀘스트 진행 중&lt;/li&gt;
&lt;li&gt;CurrentProgress &amp;gt;= RequiredCount &amp;rarr; 퀘스트 완료&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;퀘스트 보상 처리&lt;/h4&gt;
&lt;p data-end=&quot;1964&quot; data-start=&quot;1871&quot; data-ke-size=&quot;size16&quot;&gt;퀘스트 완료 시 보상을 지급하는 기능도 추가했다.&lt;br /&gt;예를 들어 몬스터 4마리 처치 후 보일러를 지급하거나, 특정 제작 단계를 완료한 뒤 컨베이어를 지급하는 방식이다.&lt;/p&gt;
&lt;p data-end=&quot;2008&quot; data-start=&quot;1966&quot; data-ke-size=&quot;size16&quot;&gt;보상 역시 코드에 직접 작성하지 않고 퀘스트 데이터에 넣을 수 있도록 했다.&lt;/p&gt;
&lt;p data-end=&quot;2008&quot; data-start=&quot;1966&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2008&quot; data-start=&quot;1966&quot; data-ke-size=&quot;size16&quot;&gt;퀘스트 완료&lt;/p&gt;
&lt;p data-end=&quot;2008&quot; data-start=&quot;1966&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; Rewards 배열 확인&lt;/p&gt;
&lt;p data-end=&quot;2008&quot; data-start=&quot;1966&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 인벤토리에 보상 아이템 지급&lt;/p&gt;
&lt;p data-end=&quot;2008&quot; data-start=&quot;1966&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 다음 퀘스트로 이동&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스킬 해금 제한&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜토리얼 흐름상 스킬은 특정 시점 이후에만 해금되어야 했다.&lt;br /&gt;그래서 퀘스트 데이터에 스킬 해금 가능 여부를 두고, 현재 퀘스트가 스킬 해금을 허용하지 않는 상태라면 스킬 해금을 막도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 퀘스트에서 스킬 해금 불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 스킬 해금 버튼 실패 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 퀘스트에서 스킬 해금 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 스킬 해금 진행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 퀘스트 완료 조건 검사&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;퀘스트 기반 게이트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀘스트 진행에 따라 특정 구간을 열어주는 게이트도 함께 구현했다.&lt;br /&gt;게이트는 RequiredCompletedQuestId를 가지고 있고, 해당 퀘스트가 완료되었는지 확인해서 열림/닫힘 상태를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요 퀘스트 미완료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 게이트 닫힘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 플레이어 통과 불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요 퀘스트 완료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 게이트 열림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 플레이어 통과 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시각적으로는 Niagara 안개 이펙트를 사용해 막힌 길처럼 보이도록 했고, 퀘스트가 완료되면 안개가 사라지면서 길이 열리는 방식으로 구현했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DataTable 기반 퀘스트 데이터 구성&lt;/li&gt;
&lt;li&gt;CurrentQuestId 기반 현재 퀘스트 관리&lt;/li&gt;
&lt;li&gt;CompleteType / TargetId 기반 완료 조건 검사&lt;/li&gt;
&lt;li&gt;RequiredCount를 이용한 진행도 카운트 처리&lt;/li&gt;
&lt;li&gt;퀘스트 완료 시 보상 지급&lt;/li&gt;
&lt;li&gt;각 시스템에서 Notify 함수로 퀘스트 진행 연결&lt;/li&gt;
&lt;li&gt;스킬 해금 시점 제한&lt;/li&gt;
&lt;li&gt;완료 퀘스트 기반 게이트 개방&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Unreal 최종 팀프로젝트</category>
      <category>Unreal_C++</category>
      <category>팀 프로젝트</category>
      <author>코딩하는미피</author>
      <guid isPermaLink="true">https://moung051.tistory.com/129</guid>
      <comments>https://moung051.tistory.com/129#entry129comment</comments>
      <pubDate>Wed, 8 Jul 2026 20:37:42 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 세이브 로드 시스템 | 최종 팀프로젝트</title>
      <link>https://moung051.tistory.com/128</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;세이브 로드 구조 정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세이브 로드 시스템은 UKOSaveGame에 저장 데이터를 모아두고, UKOSaveSubsystem에서 저장과 로드를 처리하는 방식으로 구현했다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 50.9285%; height: 211px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 14.4533%; height: 22px;&quot;&gt;저장 대상&lt;/td&gt;
&lt;td style=&quot;width: 28.273%; height: 22px;&quot;&gt;저장 내용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 14.4533%; height: 21px;&quot;&gt;플레이어&lt;/td&gt;
&lt;td style=&quot;width: 28.273%; height: 21px;&quot;&gt;위치, 회전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 14.4533%; height: 21px;&quot;&gt;인벤토리&lt;/td&gt;
&lt;td style=&quot;width: 28.273%; height: 21px;&quot;&gt;슬롯 아이템 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 14.4533%; height: 21px;&quot;&gt;장비&lt;/td&gt;
&lt;td style=&quot;width: 28.273%; height: 21px;&quot;&gt;장착 무기, 방어구&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 14.4533%; height: 21px;&quot;&gt;설비&lt;/td&gt;
&lt;td style=&quot;width: 28.273%; height: 21px;&quot;&gt;설비 종류, 위치, 그리드 점유 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 14.4533%; height: 21px;&quot;&gt;설비 내부 상태&lt;/td&gt;
&lt;td style=&quot;width: 28.273%; height: 21px;&quot;&gt;선택 레시피, 입력/출력 버퍼, 제작 진행률&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 14.4533%; height: 21px;&quot;&gt;연료 설비&lt;/td&gt;
&lt;td style=&quot;width: 28.273%; height: 21px;&quot;&gt;연료 아이템, 연료 개수, 연료 소모 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 14.4533%; height: 21px;&quot;&gt;보스&lt;/td&gt;
&lt;td style=&quot;width: 28.273%; height: 21px;&quot;&gt;생존 여부, 위치, 체력, 전투 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 14.4533%; height: 21px;&quot;&gt;몬스터&lt;/td&gt;
&lt;td style=&quot;width: 28.273%; height: 21px;&quot;&gt;처치 여부 또는 현재 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 14.4533%; height: 21px;&quot;&gt;채집물&lt;/td&gt;
&lt;td style=&quot;width: 28.273%; height: 21px;&quot;&gt;이미 획득했는지 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p 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;style5&quot; /&gt;
&lt;h4 data-end=&quot;1094&quot; data-start=&quot;994&quot; data-ke-size=&quot;size20&quot;&gt;저장 흐름&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장 흐름은 모든 대상이 거의 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 월드 상태 확인&lt;br /&gt;&amp;rarr; 저장할 대상 순회&lt;br /&gt;&amp;rarr; 대상의 고유 ID 확인&lt;br /&gt;&amp;rarr; 필요한 상태값 저장&lt;br /&gt;&amp;rarr; SaveGame 슬롯에 기록&lt;/p&gt;
&lt;p data-end=&quot;1381&quot; data-start=&quot;1314&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 객체를 나중에 다시 같은 상태로 만들기 위해 필요한 최소한의 정보가 무엇인가?&amp;rdquo;를 기준으로 저장 데이터를 정리했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로드 흐름&lt;/h4&gt;
&lt;p data-end=&quot;1449&quot; data-start=&quot;1393&quot; data-ke-size=&quot;size16&quot;&gt;로드할 때는 단순히 값을 다시 넣는 것이 아니라, 월드를 저장 당시 상태에 맞게 다시 구성해야 했다.&lt;/p&gt;
&lt;p data-end=&quot;1637&quot; data-start=&quot;1470&quot; data-ke-size=&quot;size16&quot;&gt;저장 파일 확인&lt;br /&gt;&amp;rarr; 저장 데이터 불러오기&lt;br /&gt;&amp;rarr; 플레이어 상태 복원&lt;br /&gt;&amp;rarr; 인벤토리와 장비 복원&lt;br /&gt;&amp;rarr; 기존 설비 상태 정리&lt;br /&gt;&amp;rarr; 저장된 설비 다시 스폰&lt;br /&gt;&amp;rarr; 그리드 점유 정보 복원&lt;br /&gt;&amp;rarr; 설비 내부 상태 복원&lt;br /&gt;&amp;rarr; 보스 상태 복원&lt;br /&gt;&amp;rarr; 몬스터 상태 복원&lt;br /&gt;&amp;rarr; 채집물 획득 여부 반영&lt;/p&gt;
&lt;p data-end=&quot;1726&quot; data-start=&quot;1639&quot; data-ke-size=&quot;size16&quot;&gt;특히 설비는 기존 월드에 남아 있는 액터와 저장된 액터가 겹칠 수 있기 때문에, 기존 설비를 제거하고 저장 데이터 기준으로 다시 생성하는 방식으로 처리했다.&lt;/p&gt;
&lt;p data-end=&quot;1802&quot; data-start=&quot;1728&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;style5&quot; /&gt;
&lt;h4 data-end=&quot;1943&quot; data-start=&quot;1927&quot; data-section-id=&quot;tpevbg&quot; data-ke-size=&quot;size20&quot;&gt;구현하면서 중요했던 부분&lt;/h4&gt;
&lt;p data-end=&quot;2005&quot; data-start=&quot;1945&quot; data-ke-size=&quot;size16&quot;&gt;저장 대상을 하나씩 따로 보는 것이 아니라, 공통된 흐름으로 정리하였다.&lt;/p&gt;
&lt;p data-end=&quot;2031&quot; data-start=&quot;2007&quot; data-ke-size=&quot;size16&quot;&gt;모든 저장 대상은 다음 구조를 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2080&quot; data-start=&quot;2033&quot;&gt;고유 ID가 있는가&lt;/li&gt;
&lt;li data-end=&quot;2080&quot; data-start=&quot;2033&quot;&gt;현재 상태가 무엇인가&lt;/li&gt;
&lt;li data-end=&quot;2080&quot; data-start=&quot;2033&quot;&gt;로드할 때 다시 복원할 수 있는가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2129&quot; data-start=&quot;2082&quot; data-ke-size=&quot;size16&quot;&gt;이 기준으로 보면 설비, 보스, 몬스터, 채집물은 모두 같은 방식으로 다룰 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2222&quot; data-start=&quot;2131&quot; data-ke-size=&quot;size16&quot;&gt;설비는 FactoryId로 다시 스폰하고, 보스와 몬스터는 저장된 생존 상태를 기준으로 복원한다. 채집물은 획득 여부를 저장해 로드 후 다시 나타날지 결정한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-end=&quot;2229&quot; data-start=&quot;2224&quot; data-section-id=&quot;1melx8&quot; data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;p data-end=&quot;2400&quot; data-start=&quot;2308&quot; data-ke-size=&quot;size16&quot;&gt;플레이어, 인벤토리, 장비처럼 플레이어에게 직접 연결된 데이터뿐만 아니라, 설치된 설비, 보스, 몬스터, 채집물처럼 월드에 존재하는 객체들의 상태까지 함께 저장했다.&lt;/p&gt;
&lt;p data-end=&quot;2512&quot; data-start=&quot;2402&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해 로드했을 때 플레이어가 어디에 있었는지뿐만 아니라, 어떤 설비를 설치했는지, 어떤 보스를 처치했는지, 어떤 몬스터가 사라졌는지, 어떤 채집물을 이미 획득했는지까지 유지할 수 있게 되었다.&lt;/p&gt;</description>
      <category>Unreal 최종 팀프로젝트</category>
      <category>Unreal_C++</category>
      <category>팀 프로젝트</category>
      <author>코딩하는미피</author>
      <guid isPermaLink="true">https://moung051.tistory.com/128</guid>
      <comments>https://moung051.tistory.com/128#entry128comment</comments>
      <pubDate>Mon, 6 Jul 2026 19:52:07 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 컨베이어 벨트 포트 선택 | 최종 팀프로젝트</title>
      <link>https://moung051.tistory.com/127</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;컨베이어 벨트가 설비와 연결될 때, 단순히 모든 아이템을 주고받는 것이 아니라 &lt;b&gt;선택한 입출력 포트의 아이템만 이동하도록&lt;/b&gt; 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 컨베이어 벨트를 설비 옆에 설치하면 설비의 OutputBuffer에 들어 있는 아이템이 구분 없이 벨트로 이동했다. 예를 들어 설비의 출력이 청동판과 석탄 가루 두 가지라면, 사용자가 석탄 가루만 빼내고 싶어도 컨베이어 벨트에는 모든 output 아이템이 섞여 나오는 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨베이어 벨트 설치 후 열리는 연결 위젯에서 사용자가 특정 input 또는 output 슬롯을 선택하고, 벨트가 그 선택 정보를 기준으로 아이템을 이동하도록 구조를 정리했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;컨베이어 벨트 연결 위젯의 역할 정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨베이어 벨트 연결 위젯은 벨트를 설비의 어느 포트에 연결할지 선택하는 UI다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설비의 레시피가 다음과 같다고 하면,&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Input  : 청동, 석탄
Output : 청동판, 석탄 가루
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벨트가 설비에 아이템을 넣는 방향이면 Input 슬롯이 표시되고, 설비에서 아이템을 빼내는 방향이면 Output 슬롯이 표시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 사용자는 벨트를 설치한 뒤 다음과 같은 선택을 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Input 1 : 청동
Input 2 : 석탄
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Output 1 : 청동판
Output 2 : 석탄 가루
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 선택하는 것은 설비의 특정 포트 슬롯이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;const FKOFactoryPortSlot PortSlot(Kind, Index, ItemHint);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Kind는 Input 또는 Output이고, Index는 포트 번호, ItemHint는 해당 포트에 표시되는 아이템 정보다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;선택한 포트 정보를 벨트에 저장&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬롯을 클릭하면 위젯에서는 선택한 포트 정보를 컨베이어 벨트에 넘긴다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;Belt-&amp;gt;BindToMachinePort(Building, ClickedSlot);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 벨트는 선택한 포트 정보를 내부에 저장한다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;BoundMachine   = Machine;
BoundKind      = Slot.Kind;
BoundPortIndex = Slot.PortIndex;
BoundItemId    = Slot.ItemId;
bHasSelectedPort = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 값은 BoundItemId와 bHasSelectedPort다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BoundItemId는 이 벨트가 어떤 아이템 포트에 연결되었는지를 의미한다. 예를 들어 석탄 가루 output 슬롯을 선택했다면 BoundItemId에는 석탄 가루 아이템 ID가 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bHasSelectedPort는 사용자가 실제로 슬롯을 선택했는지 여부를 나타낸다. 이 값이 false라면 아직 포트 선택이 완료되지 않은 상태이므로, 벨트가 설비에서 아이템을 꺼내면 안 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Output 포트 선택 전에는 아이템을 꺼내지 않도록 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 문제는 위젯에서 슬롯을 선택하기 전에 벨트가 먼저 동작하면서 설비의 모든 output 아이템을 가져오는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 막기 위해 벨트가 설비의 Output 포트와 연결될 예정인 상태라면, 포트 선택이 끝나기 전에는 아이템을 꺼내지 않도록 했다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;if (!bHasSelectedPort || BoundItemId.IsNone())
{
    return;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Output 바인딩 상태인데 아직 슬롯을 선택하지 않았거나 선택한 아이템 정보가 없다면, 벨트는 설비에서 아무 아이템도 가져오지 않는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;선택한 Output 아이템만 꺼내도록 수정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 로직에서는 설비에서 아이템을 가져올 때 일반적인 PopOutputItem()을 사용했다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Src-&amp;gt;PopOutputItem(Pulled)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 특정 아이템을 지정하지 않고 output 버퍼에서 꺼낼 수 있는 아이템을 가져오는 구조다. 그래서 석탄 가루 슬롯을 선택해도 청동판까지 함께 벨트로 나오는 문제가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정 후에는 Output 포트에 바인딩된 벨트라면 BoundItemId 기준으로만 아이템을 꺼내도록 했다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;if (Proc-&amp;gt;TryExtractItem(BoundItemId, 1) == 1)
{
    Slots[0] = FKOConveyorItem(BoundItemId);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 석탄 가루 output 슬롯을 선택하면, 해당 벨트는 석탄 가루만 꺼낸다. 청동판은 같은 설비의 output에 존재하더라도 이 벨트로 이동하지 않는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Output 바인딩 후 일반 Pull 로직으로 내려가지 않도록 수정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정 과정에서 중요한 부분은 Output 바인딩 처리 후 일반 Pull 로직으로 이어지지 않게 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Output 포트에 바인딩된 벨트는 선택된 아이템만 꺼내야 한다. 그런데 해당 분기 이후에 다시 일반 PopOutputItem() 로직이 실행되면, 결국 모든 output 아이템이 다시 섞여 나올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Output 바인딩 분기에서는 처리가 끝난 뒤 바로 빠져나오도록 했다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;if (BoundKind == EKOPortKind::Output &amp;amp;&amp;amp; BoundMachine.IsValid())
{
    if (!bHasSelectedPort || BoundItemId.IsNone())
    {
        return;
    }

    if (UKOFactoryProcessorComponent* Proc =
        BoundMachine-&amp;gt;FindComponentByClass&amp;lt;UKOFactoryProcessorComponent&amp;gt;())
    {
        if (Proc-&amp;gt;TryExtractItem(BoundItemId, 1) == 1)
        {
            Slots[0] = FKOConveyorItem(BoundItemId);
        }
    }

    return;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Output 포트에 연결된 벨트는 항상 선택한 아이템만 처리하고, 일반 Source Pull 로직으로 넘어가지 않는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Input 포트 선택 시 동작&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Input 포트를 선택한 경우에는 벨트가 설비에 아이템을 넣는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Input 슬롯에서 청동을 선택하면, 이 벨트는 설비의 청동 입력 포트에 연결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 벨트 끝에 도착한 아이템이 청동일 때만 설비에 넣어야 한다. 따라서 Input 바인딩에서도 BoundItemId를 검사하는 구조가 필요하다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;const bool bCanPushToBoundInput =
    bHasSelectedPort &amp;amp;&amp;amp;
    !BoundItemId.IsNone() &amp;amp;&amp;amp;
    Slots[TailIdx].ItemId == BoundItemId;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조건을 통해 청동 Input 포트에 연결된 벨트는 청동만 설비에 투입하고, 다른 아이템은 해당 포트로 들어가지 않게 만들 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ESC로 위젯을 닫았을 때 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트 선택 위젯에서 ESC를 눌러 닫는 경우도 고려했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESC를 눌렀을 때는 설비와 닿아 있다는 정보는 유지하되, 선택한 포트 정보만 비우는 방식으로 처리하는 것이 적절하다고 판단했다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;BoundPortIndex = INDEX_NONE;
BoundItemId = NAME_None;
bHasSelectedPort = false;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 벨트는 여전히 해당 설비의 Output 방향에 닿아 있는 상태로 남아 있고, bHasSelectedPort가 false이기 때문에 아이템을 꺼내지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, ESC로 닫은 상태는 다음과 같이 해석된다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;벨트는 설치되어 있음
설비와 닿아 있음
하지만 포트는 선택되지 않음
따라서 설비에서 아이템을 꺼내지 않음
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;최종 동작 흐름&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨베이어 벨트와 설비의 연결 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;벨트 설치
&amp;rarr; 설비와 닿아 있으면 연결 위젯 열림
&amp;rarr; 벨트는 포트 선택 대기 상태가 됨
&amp;rarr; 선택 전에는 설비에서 아이템을 꺼내지 않음
&amp;rarr; 사용자가 Output 슬롯 선택
&amp;rarr; BoundItemId에 선택 아이템 저장
&amp;rarr; 이후 해당 아이템만 벨트로 이동
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 설비의 output이 다음과 같을 때,&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Output : 청동판, 석탄 가루
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 석탄 가루를 선택하면:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;BoundItemId = 석탄 가루
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 컨베이어 벨트는 석탄 가루만 설비에서 꺼낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 ESC로 위젯을 닫으면:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;bHasSelectedPort = false
BoundItemId = None
&lt;/code&gt;&lt;/pre&gt;
&lt;p 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;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업을 통해 컨베이어 벨트가 단순히 설비의 output을 모두 가져오는 구조에서 벗어나, 사용자가 선택한 input/output 포트 기준으로 동작하도록 개선했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 BoundItemId와 bHasSelectedPort를 기준으로 벨트의 이동 로직을 제어하면서, 포트 선택 전에는 아이템이 흐르지 않고, 선택 후에는 해당 아이템만 이동하도록 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 컨베이어 벨트와 설비 간의 연결이 더 명확해졌고, 여러 output을 가진 설비에서도 원하는 아이템만 분리해서 운송할 수 있게 하였다.&lt;/p&gt;</description>
      <category>Unreal 최종 팀프로젝트</category>
      <category>Unreal_C++</category>
      <category>팀 프로젝트</category>
      <author>코딩하는미피</author>
      <guid isPermaLink="true">https://moung051.tistory.com/127</guid>
      <comments>https://moung051.tistory.com/127#entry127comment</comments>
      <pubDate>Wed, 24 Jun 2026 21:05:17 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 컨베이어 벨트 고스트 프리뷰 방향 표시 | 최종 팀프로젝트</title>
      <link>https://moung051.tistory.com/126</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;컨베이어 벨트를 설치할 때 보이는 고스트 프리뷰에 방향 화살표를 추가하고, 이 화살표가 실제 설치 후 컨베이어의 흐름 방향과 일치하도록 수정했다.&lt;/p&gt;
&lt;p 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;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기존 문제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 컨베이어 고스트 프리뷰에 화살표를 표시하는 방식만 생각했다.&lt;br /&gt;현재 설치하려는 건물이 컨베이어라면 화살표를 보이고, 컨베이어가 아니라면 숨기는 구조였다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;if (bIsConveyorPreview)
{
    CurrentPreviewActor-&amp;gt;ShowDirectionArrow();
}
else
{
    CurrentPreviewActor-&amp;gt;HideDirectionArrow();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 &amp;ldquo;컨베이어일 때 화살표를 보여준다&amp;rdquo;는 목적은 달성할 수 있었다.&lt;br /&gt;하지만 실제 컨베이어는 설치 후 주변 벨트나 설비를 보고 흐름 방향을 자동으로 결정한다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;Belt-&amp;gt;ApplyPlacementFlow(bCornerFlipPlacement);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 프리뷰 화살표는 기본 방향을 가리키고 있는데, 실제 설치 후에는 주변 연결 상태에 따라 흐름 방향이 달라질 수 있었다.&lt;br /&gt;플레이어 입장에서는 프리뷰에서 본 방향과 설치 후 실제 방향이 다르게 느껴질 수 있는 문제가 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방향 추론 로직 분리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 방향 추론 로직은 AKOConveyorBelt 내부에 있었다.&lt;br /&gt;하지만 고스트 프리뷰는 실제 컨베이어 Actor가 아니라 AKOGhostPreview이기 때문에, 컨베이어의 멤버 함수를 그대로 사용하는 구조는 적절하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 방향 추론 로직을 별도의 공용 Resolver로 분리했다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;FKOConveyorFlowResolver::TryResolveStraightReverse(...)
FKOConveyorFlowResolver::TryResolveCornerFlip(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실제 컨베이어와 고스트 프리뷰가 같은 방향 계산 로직을 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;실제 컨베이어
&amp;rarr; 설치된 위치와 회전을 기준으로 Resolver 호출

고스트 프리뷰
&amp;rarr; 설치 예정 위치와 회전을 기준으로 Resolver 호출
&lt;/code&gt;&lt;/pre&gt;
&lt;p 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;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이웃 셀 역할 판정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방향을 자동으로 추론하려면 먼저 주변 셀에 있는 Actor가 어떤 역할인지 판단해야 한다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;+1 : 업스트림
     나에게 아이템을 보내는 쪽

-1 : 다운스트림
     내가 아이템을 보내야 하는 쪽

 0 : 모호함
     방향을 결정할 수 없는 상태
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이웃이 컨베이어라면 해당 벨트가 현재 셀을 향해 출력하는지, 또는 현재 셀에서 입력받는지를 검사한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;if (Belt-&amp;gt;OutputsToCell(MyCell)) return +1;

if (Belt-&amp;gt;InputsFromCell(MyCell)) return -1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이웃이 설비라면 Source/Sink 여부를 기준으로 판단한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;if (bHasSource &amp;amp;&amp;amp; !bHasSink) return +1;

if (bHasSink &amp;amp;&amp;amp; !bHasSource) return -1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력만 가능한 설비는 현재 컨베이어에게 아이템을 공급하는 쪽이므로 &lt;span style=&quot;background-color: #c0d1e7; color: #000000;&quot;&gt;업스트림&lt;/span&gt;으로 본다.&lt;br /&gt;반대로 입력만 가능한 설비는 현재 컨베이어가 아이템을 보내야 하는 쪽이므로 &lt;span style=&quot;background-color: #c0d1e7; color: #000000;&quot;&gt;다운스트림&lt;/span&gt;으로 본다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;직선 컨베이어 방향 계산&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직선 컨베이어는 기본적으로 현재 Forward 방향으로 흐른다.&lt;br /&gt;하지만 주변 연결에 따라 반대로 흘러야 할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;const bool bWantForward = (RoleBack &amp;gt; 0) || (RoleFront &amp;lt; 0);
const bool bWantReverse = (RoleFront &amp;gt; 0) || (RoleBack &amp;lt; 0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤쪽이 업스트림이거나 앞쪽이 다운스트림이면 현재 방향 그대로 흐르는 것이 자연스럽다.&lt;br /&gt;반대로 앞쪽이 업스트림이거나 뒤쪽이 다운스트림이면 역방향으로 흐르는 것이 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동 추론이 성공하면 그 결과를 사용하고, 실패하면 기본 방향을 유지한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;const bool bFinalReverse = bResolved ? bResolvedReverse : false;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 직선 컨베이어의 화살표는 다음처럼 결정된다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;reverse=false &amp;rarr; 0도
reverse=true  &amp;rarr; 180도
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;L자 컨베이어 방향 계산&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;L자 컨베이어는 직선 컨베이어처럼 단순히 180도 반전되는 구조가 아니다.&lt;br /&gt;두 다리 중 어느 쪽이 입구이고 어느 쪽이 출구인지가 바뀐다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;const bool bWantFalse = (RoleB &amp;gt; 0) || (RoleA &amp;lt; 0);
const bool bWantTrue = (RoleA &amp;gt; 0) || (RoleB &amp;lt; 0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flip = false일 때는 Side 쪽에서 들어와 Forward 쪽으로 나간다.&lt;br /&gt;반대로 flip = true일 때는 Forward 쪽에서 들어와 Side 쪽으로 나간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동 추론에 성공하면 그 값을 사용하고, 추론할 수 없다면 기존 수동 flip 값을 사용한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;const bool bFinalFlip = bResolved ? bResolvedFlip : bCornerFlipPlacement;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;L자 컨베이어의 화살표는 출구 방향에 맞춰 다음처럼 결정된다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;flip = false &amp;rarr; 0도
flip = true  &amp;rarr; 90도&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;고스트 프리뷰 화살표 회전 적용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고스트 프리뷰의 화살표 함수는 추가 회전값을 받을 수 있게 수정했다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;FRotator FinalRotation = DirectionArrowRelativeRotation;
FinalRotation.Yaw += AdditionalYaw;

DirectionArrowComponent-&amp;gt;SetRelativeRotation(FinalRotation);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 화살표 회전값에 AdditionalYaw를 더해 최종 방향을 결정한다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;AdditionalYaw = 0도   &amp;rarr; 기본 방향
AdditionalYaw = 90도  &amp;rarr; L자 벨트의 Side 방향
AdditionalYaw = 180도 &amp;rarr; 직선 벨트의 역방향
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프리뷰에서 예상 방향 계산&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고스트 프리뷰에서는 현재 설치 예정 셀과 회전값을 기준으로 방향을 계산한다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;const FIntPoint MyCell = CurrentAnchor;
const FIntPoint Forward = GetPreviewForwardStep();
const FIntPoint Side = GetPreviewSideStep();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 컨베이어 형태에 따라 다른 방식으로 화살표 회전값을 반환한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;if (BeltCDO-&amp;gt;GetShape() == EKOBeltShape::Corner)
{
    return bFinalFlip ? 90.0f : 0.0f;
}

return bFinalReverse ? 180.0f : 0.0f;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직선 컨베이어는 reverse 여부에 따라 0도 또는 180도를 반환하고, L자 컨베이어는 flip 여부에 따라 0도 또는 90도를 반환한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실제 컨베이어와 프리뷰의 계산 기준 통일&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 컨베이어와 고스트 프리뷰가 같은 Resolver를 사용하도록 만들었다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;실제 설치 방향 결정
= FKOConveyorFlowResolver

프리뷰 화살표 방향 결정
= FKOConveyorFlowResolver
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 컨베이어는 설치된 Actor의 위치와 회전을 기준으로 Resolver를 호출한다.&lt;br /&gt;고스트 프리뷰는 설치 예정 셀과 현재 회전값을 기준으로 Resolver를 호출한다.&lt;/p&gt;
&lt;p 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;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 컨베이어 고스트 프리뷰가 단순히 현재 회전 방향만 보여줬기 때문에, 주변 벨트와 이어지는 경우 실제 설치 후 방향이 달라질 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정 후에는 주변 벨트와 설비의 연결 상태를 프리뷰 단계에서 미리 반영되도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 컨베이어와 고스트 프리뷰가 같은 흐름 추론 로직을 사용하게 하여, 프리뷰 화살표가 설치 후 실제 흐름 방향과 일치하도록 개선하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 컨베이어 라인을 이어 붙일 때 설치 방향을 더 직관적으로 확인할 수 있게 되었고, 잘못된 방향으로 설치하는 상황을 줄일 수 있게 하였다.&lt;/p&gt;</description>
      <category>Unreal 최종 팀프로젝트</category>
      <category>Unreal_C++</category>
      <category>팀 프로젝트</category>
      <author>코딩하는미피</author>
      <guid isPermaLink="true">https://moung051.tistory.com/126</guid>
      <comments>https://moung051.tistory.com/126#entry126comment</comments>
      <pubDate>Tue, 23 Jun 2026 20:46:03 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 그리드 표시 | 최종 팀프로젝트</title>
      <link>https://moung051.tistory.com/125</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기존에 디버깅용으로 사용하던 그리드 표시 방식을 제거하고, 실제 게임 플레이에서 사용할 수 있는 &lt;/span&gt;&lt;b&gt;&lt;span&gt;건설 모드 전용 그리드 표시 기능&lt;/span&gt;&lt;/b&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;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;게임용 그리드 Actor&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AKOGridVisual은 UInstancedStaticMeshComponent를 사용해 실제 Mesh 기반의 그리드 선을 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cube Mesh를 사용했는데, 기본 Cube를 그대로 사용하면 선이 아니라 두꺼운 박스처럼 보였다. 그래서 Z Scale을 작게 줄여 바닥 위에 얇은 선처럼 보이도록 조정했다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;const FVector Scale(
    LineThickness / 100.0f,
    (MaxY - MinY) / 100.0f,
    0.005f
);&lt;/code&gt;&lt;/pre&gt;
&lt;p 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;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;건설 모드에서만 그리드 표시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리드는 평상시에는 보이지 않고, 건설과 관련된 모드에서만 보이도록 했다. 건설 시스템은 BuildMenu, Placing, Destroying, None 상태를 가지고 있기 때문에, 이 상태값을 기준으로 그리드 표시 여부를 제어했다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;const bool bShouldShowGrid =
    CurrentMode == EKOGridBuildMode::BuildMenu ||
    CurrentMode == EKOGridBuildMode::Placing ||
    CurrentMode == EKOGridBuildMode::Destroying;

GridVisualActor-&amp;gt;SetGridVisible(bShouldShowGrid);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UKOGridBuildComponent의 모드 변경 시점에 호출되도록 연결했다. 그 결과 건설 메뉴를 열거나 설치/파괴 모드에 들어갔을 때만 그리드가 보이고, 건설 모드를 종료하면 자동으로 숨겨진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설치된 발전기의 전력 범위 표시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;건설 모드에서 이미 설치된 발전기들의 전력 범위를 표시하도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 AKOGridVisual에서 월드에 설치된 건물들을 순회하고, 그중 UKOEnergyProducerComponent가 붙은 건물만 발전기로 판단했다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;for (TActorIterator&amp;lt;AKOBaseBuilding&amp;gt; It(World); It; ++It)
{
    AKOBaseBuilding* Building = *It;
    if (!IsValid(Building))
    {
        continue;
    }

    UKOEnergyProducerComponent* Producer =
        Building-&amp;gt;FindComponentByClass&amp;lt;UKOEnergyProducerComponent&amp;gt;();

    if (!Producer)
    {
        continue;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발전기라고 판단된 건물은 FactoryId를 통해 DataTable Row를 찾고, 해당 Row의 EnergyCoverageRadius 값을 가져온다. 이 값이 실제 전력 범위 반경으로 사용된다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;const FKOFactoryRow* FactoryRow = LoadSub-&amp;gt;FindFactoryRow(FactoryId);
if (!FactoryRow)
{
    continue;
}

const int32 PowerRadius = FactoryRow-&amp;gt;EnergyCoverageRadius;
if (PowerRadius &amp;lt;= 0)
{
    continue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 GridSubsystem에서 해당 건물이 점유하고 있는 그리드 영역을 가져온 뒤, 그 영역을 기준으로 전력 범위를 계산했다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;FIntPoint OccupiedAnchor;
FIntPoint OccupiedSize;

if (!GridSub-&amp;gt;TryGetOccupiedAreaForActor(Building, OccupiedAnchor, OccupiedSize))
{
    continue;
}

AddPowerCoverageArea(OccupiedAnchor, OccupiedSize, PowerRadius);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전력 범위는 발전기가 차지하는 영역을 기준으로 상하좌우 반경만큼 확장해서 계산했다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;const FIntPoint CoverageAnchor(
    Anchor.X - PowerRadius,
    Anchor.Y - PowerRadius
);

const FIntPoint CoverageSize(
    Size.X + PowerRadius * 2,
    Size.Y + PowerRadius * 2
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계산된 영역 안의 셀마다 노란색 Plane 인스턴스를 배치해 전력 범위를 표시했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할 분리&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;UKOGridSubsystem
- 그리드 데이터 관리
- 설치 가능 여부 검사
- 점유 영역 관리

AKOGridVisual
- 건설 모드에서 보이는 그리드 표시
- 설치된 발전기들의 전력 범위 표시

UKOGridBuildComponent
- 건설 모드 상태에 따라 GridVisual 표시 제어
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 평상시에는 그리드가 보이지 않고, 건설 모드에 들어갔을 때만 바닥 그리드와 설치된 발전기들의 전력 범위가 표시된다. 이를 통해 플레이어가 설비를 배치할 때 전력 공급 범위를 더 직관적으로 확인할 수 있게 되었다.&lt;/p&gt;</description>
      <category>Unreal 최종 팀프로젝트</category>
      <category>Unreal_C++</category>
      <category>팀 프로젝트</category>
      <author>코딩하는미피</author>
      <guid isPermaLink="true">https://moung051.tistory.com/125</guid>
      <comments>https://moung051.tistory.com/125#entry125comment</comments>
      <pubDate>Mon, 22 Jun 2026 21:11:21 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] UI 구조 개선 | 최종 팀프로젝트</title>
      <link>https://moung051.tistory.com/124</link>
      <description>&lt;p 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;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;통합 메뉴 구조 설계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 만든 위젯 : WBP_PlayerMenu&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 위젯은 플레이어가 Tab 키를 눌렀을 때 열리는 메뉴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조는 크게 두 부분으로 나누었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상단: 인벤토리, 제작, 스킬, 옵션 버튼&lt;/li&gt;
&lt;li&gt;하단: 선택한 탭에 맞는 실제 UI 내용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하단 영역에는 WidgetSwitcher를 사용했다. WidgetSwitcher는 여러 개의 위젯을 자식으로 가지고 있다가, 현재 선택된 인덱스의 위젯만 보여주는 역할을 한다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;Inventory    = 0
FactoryCraft = 1
SkillTree    = 2
Option       = 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 인벤토리 버튼을 누르면 WidgetSwitcher의 인덱스를 0으로 바꾸고, 제작 버튼을 누르면 1로 바꾸는 방식이다. 이렇게 하면 실제로는 WBP_PlayerMenu 하나만 열린 상태에서 내부 내용만 바뀌게 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;게임 일시정지 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어 메뉴가 열려 있는 동안에는 게임이 진행되지 않도록 일시정지도 적용했다. 메뉴가 활성화될 때 게임을 멈추고, 메뉴가 닫힐 때 다시 풀어주는 방식이다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;SetGamePaused(World, true);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 처리는 NativeOnActivated()에서 수행했다. Common UI 기반 위젯에서는 화면에 올라왔는지보다 &amp;ldquo;활성화되었는지&amp;rdquo;가 더 중요하기 때문에, 단순히 NativeConstruct()에서 처리하는 것보다 NativeOnActivated()를 사용하는 쪽이 자연스럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;닫힐 때는 NativeOnDeactivated()에서 퍼즈를 해제했다. 단, 이미 다른 이유로 게임이 퍼즈되어 있던 상황까지 마음대로 해제하지 않도록, 이 위젯이 직접 퍼즈를 걸었는지 여부를 따로 저장했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;입력 문제 해결&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 중 Tab 키와 ESC 입력이 서로 꼬이는 문제가 있었다. 메뉴를 닫았는데 다시 열리는 현상이 발생했는데, 원인은 IA_TogglePlayerMenu에 Pressed와 Released 트리거가 둘 다 들어가 있었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tab을 누르는 순간 Pressed가 실행되어 메뉴가 열리고 게임이 퍼즈되었다. 이후 Tab을 떼는 Released 입력이 퍼즈 상태에서 밀렸다가, ESC로 메뉴를 닫고 퍼즈가 풀린 뒤 뒤늦게 처리되면서 메뉴가 다시 열리는 문제가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 메뉴 열기 입력은 Pressed만 남기고 Released는 제거했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메뉴 입력 설정은 다음처럼 정리했다.&lt;/p&gt;
&lt;pre class=&quot;haml&quot;&gt;&lt;code&gt;IA_TogglePlayerMenu
- Key: Tab
- Value Type: Digital
- Trigger When Paused: true
- Trigger: Pressed
- Released 제거
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 메뉴가 열린 상태에서 Tab 키를 눌렀을 때 버튼 포커스가 다음 버튼으로 이동하는 문제도 있었다. 이는 UMG의 기본 키보드 내비게이션 때문에 발생한 것이다. 버튼의 Is Focusable을 꺼서 Tab 키가 버튼 선택 이동에 사용되지 않도록 처리했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업을 통해 UI 구조를 개별 창 중심에서 통합 메뉴 중심으로 변경했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 방식은 인벤토리, 제작, 스킬 창을 각각 따로 열고 관리해야 했다. 하지만 이제는 WBP_PlayerMenu 하나만 열고, 내부 WidgetSwitcher를 통해 탭을 전환하는 방식으로 바뀌었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조의 장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메뉴 열림 상태를 하나의 위젯 기준으로 관리할 수 있다.&lt;/li&gt;
&lt;li&gt;탭 전환 로직이 단순해진다.&lt;/li&gt;
&lt;li&gt;ESC 처리와 일시정지 처리를 한 곳에서 관리할 수 있다.&lt;/li&gt;
&lt;li&gt;UI가 추가되어도 새로운 창을 따로 여는 대신 탭만 추가하면 된다.&lt;/li&gt;
&lt;li&gt;입력 충돌 문제를 줄일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 플레이어 메뉴의 구조가 더 명확해졌고, 앞으로 옵션, 지도, 장비 같은 UI를 추가할 때도 같은 방식으로 확장할 수 있는 기반을 만들었다.&lt;/p&gt;</description>
      <category>Unreal 최종 팀프로젝트</category>
      <category>Unreal_C++</category>
      <category>팀 프로젝트</category>
      <author>코딩하는미피</author>
      <guid isPermaLink="true">https://moung051.tistory.com/124</guid>
      <comments>https://moung051.tistory.com/124#entry124comment</comments>
      <pubDate>Fri, 19 Jun 2026 21:02:09 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 개수 지정 제작 기능 | 최종 팀프로젝트</title>
      <link>https://moung051.tistory.com/123</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;제작 개수 제한 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제작 개수는 무한정 늘어날 수 없다.&lt;br /&gt;현재 보유한 재료와 인벤토리 공간을 기준으로 실제 제작 가능한 최대 개수까지만 올라가야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 제작 개수를 변경할 때는 먼저 최대 제작 가능 개수를 계산하고, 그 범위 안에서만 값이 바뀌도록 했다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;ClampedCount = FMath::Clamp(NewCount, MinCraftCount, MaxCraftableCount);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 현재 최대 7개까지 만들 수 있는데 +10 버튼을 눌렀다면, 제작 개수는 11이나 12가 되는 것이 아니라 7에서 멈춘다.&lt;/p&gt;
&lt;pre id=&quot;code_1781511887456&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RefreshCraftCountText();
RefreshDetail();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 제작 개수만 변경될 때는 전체 UI를 새로 만들지 않고, 제작 개수 텍스트와 상세 정보만 갱신했다.&lt;br /&gt;왼쪽 설비 목록까지 다시 만들 필요는 없기 때문이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;제작 가능 여부 기준 수정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 제작 개수만큼 만들 수 있는지를 확인해야 하므로&amp;nbsp;제작 가능 여부를 확인할 때 제작 개수를 함께 넘겼다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;CanCraftFactory(FactoryId, InCraftCount)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 선택한 설비를 InCraftCount개 제작할 수 있는지 확인한다.&lt;br /&gt;내부에서는 제작 비용에 제작 개수를 곱해 필요한 총 재료 수를 계산하고, 인벤토리에 해당 재료가 충분한지 검사한다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;RequiredCount = Cost.Value * InCraftCount;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인벤토리 공간 검사&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제작 개수 기능을 추가하면서 재료만 충분한지 확인하는 것으로는 부족했다.&lt;br /&gt;재료는 충분해도 설비가 인벤토리에 들어갈 공간이 없다면 제작이 실패해야 한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 재료 차감
2. 설비 지급
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제작은 재료를 먼저 차감한 뒤, 설비를 지급한다.&lt;br /&gt;따라서 인벤토리 공간도 현재 상태가 아니라, 재료가 차감된 이후의 상태를 기준으로 판단해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 인벤토리가 가득 차 있어도 제작에 사용되는 재료가 모두 소모되면 그 슬롯이 비게 되고, 그 빈 슬롯에 제작된 설비가 들어갈 수 있다.&lt;br /&gt;그래서 실제 인벤토리를 바로 수정하지 않고, 인벤토리 슬롯을 복사한 뒤 그 복사본에서 재료 차감을 시뮬레이션하는 방식으로 처리했다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;TArray&amp;lt;FKOItemSlot&amp;gt; SimulatedSlots = Slots;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복사된 슬롯에서 필요한 재료를 제거하고, 그 상태에서 결과 설비가 몇 개까지 들어갈 수 있는지 계산했다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;GetAddableCountInSlots(SimulatedSlots, AddKind, AddItemId, AddCount);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실제 추가 함수와 계산 함수 분리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 인벤토리 컴포넌트에는 실제로 아이템을 추가하는 TryAddItem()이 있었다.&lt;br /&gt;하지만, 제작 가능 여부를 검사할 때 TryAddItem()을 직접 사용할 수는 없다. 이 함수는 실제로 인벤토리 상태를 변경하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 역할을 분리했다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;TryAddItem()
&amp;rarr; 실제로 아이템을 인벤토리에 추가한다.

TryRemoveItem()
&amp;rarr; 실제로 아이템을 인벤토리에서 제거한다.

GetAddableCountAfterRemoving()
&amp;rarr; 실제 인벤토리는 바꾸지 않고, 재료 제거 후 결과물이 들어갈 수 있는지만 계산한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제작 가능 여부를 확인하는 단계에서는 계산 전용 함수를 사용하고, 실제 제작 버튼을 눌렀을 때만 TryRemoveItem()과 TryAddItem()을 사용하도록 했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실제 제작 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 제작에서는 현재 제작 개수만큼 재료를 차감하고, 결과 설비도 제작 개수만큼 지급했다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;RequiredCount = Cost.Value * CraftCount;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재료 차감이 끝나면 설비를 지급한다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;TryAddItem(EKOSlotKind::Factory, SelectedFactoryId, CraftCount);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 설비 지급에 실패하면 이미 차감한 재료를 다시 복구한다.&lt;br /&gt;제작 가능 여부를 미리 검사하더라도 실제 제작 과정에서 상태가 바뀔 가능성이 있기 때문에, 복구 로직은 안전장치로 유지했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제작 개수에 따라 필요한 재료량을 다시 계산하고, 제작 가능한 최대 개수를 제한하며, 인벤토리 공간까지 고려하도록 구조를 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인벤토리 공간 검사는 현재 공간만 보는 것이 아니라, 재료가 차감된 이후의 상태를 기준으로 계산해야 했다. 이를 위해 인벤토리 컴포넌트에 슬롯 복사 기반의 시뮬레이션 로직을 추가했고, 실제 인벤토리를 변경하지 않은 상태에서 제작 가능 여부를 판단할 수 있게 만들었다.&lt;/p&gt;</description>
      <category>Unreal 최종 팀프로젝트</category>
      <category>Unreal_C++</category>
      <category>팀 프로젝트</category>
      <author>코딩하는미피</author>
      <guid isPermaLink="true">https://moung051.tistory.com/123</guid>
      <comments>https://moung051.tistory.com/123#entry123comment</comments>
      <pubDate>Mon, 15 Jun 2026 21:04:45 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 설비 제작 UI | 최종 팀프로젝트</title>
      <link>https://moung051.tistory.com/122</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;설비 제작 UI 구조&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설비 제작 UI는 크게 세 가지 위젯으로 나누어 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 전체 제작 창을 담당하는 FactoryCraftWidget이다. 이 위젯은 제작 가능한 설비 목록을 만들고, 선택된 설비의 상세 정보와 필요 재료 목록, 제작 버튼 상태를 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 설비 목록의 한 줄을 담당하는 FactoryCraftEntryWidget이다. 설비 이름과 아이콘을 표시하고, 버튼 클릭 시 어떤 설비가 선택되었는지 상위 위젯에 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째는 제작 비용의 한 줄을 담당하는 FactoryCraftCostEntryWidget이다. 필요한 아이템의 아이콘, 이름, 현재 보유 개수와 필요 개수를 표시한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;제작 가능한 설비 목록 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설비 목록은 RebuildFactoryList()에서 다시 만든다. 먼저 LoadSubsystem을 통해 제작 가능한 설비 ID 목록을 가져오고, 그 ID들을 순회하면서 설비 엔트리 위젯을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설비 목록을 가져올 때는 다음 흐름을 사용했다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;FKOBuildMenuQuery Query;

TArray&amp;lt;FName&amp;gt; FactoryIds;
LoadSub-&amp;gt;GetBuildableFactoryIds(Query, FactoryIds);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Query는 설비 목록을 가져올 때 사용할 조건값이다. 현재는 별도 조건을 넣지 않았기 때문에 기본 조건으로 제작 가능한 설비를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FactoryIds는 결과를 담는 배열이다. GetBuildableFactoryIds()가 조건에 맞는 설비 ID들을 이 배열에 채워 넣는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에는 FactoryIds를 순회하면서 각 설비의 데이터 행을 찾고, 해당 정보를 바탕으로 목록 위젯을 생성한다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;for (const FName FactoryId : FactoryIds)
{
    const FKOFactoryRow* Row = LoadSub-&amp;gt;FindFactoryRow(FactoryId);
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 제작 UI는 설비 데이터를 직접 전부 들고 있는 것이 아니라, 먼저 설비 ID 목록을 받은 뒤 ID를 기준으로 실제 데이터 테이블 행을 조회하는 방식으로 동작한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;선택된 설비 상세 정보 표시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설비 목록에서 특정 엔트리를 클릭하면 해당 설비 ID가 SelectedFactoryId에 저장된다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void UKOFactoryCraftWidget::HandleFactoryEntryClicked(FName InFactoryId)
{
    SelectedFactoryId = InFactoryId;
    RefreshDetail();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 RefreshDetail()을 호출하여 오른쪽 상세 영역을 갱신한다. 상세 영역에는 선택된 설비의 아이콘, 이름, 설명, 현재 보유 개수가 표시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보유 개수는 인벤토리 컴포넌트에서 가져온다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int32 OwnedCount = Inventory ? Inventory-&amp;gt;GetCountOf(SelectedFactoryId) : 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 플레이어가 이미 해당 설비를 몇 개 가지고 있는지 UI에서 바로 확인할 수 있게 했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;제작 비용 목록 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택된 설비의 제작 비용은 RebuildCostList()에서 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설비 데이터의 CraftCosts를 순회하면서 필요한 아이템 태그와 개수를 읽는다. 이때 제작 비용은 아이템 ID가 아니라 GameplayTag로 들어 있기 때문에, 먼저 태그를 실제 아이템 ID로 변환한다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;const FName ItemId = LoadSub-&amp;gt;FindItemIdByTag(Cost.Key);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 인벤토리에서 해당 아이템의 보유 개수를 가져온다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int32 OwnedCount = Inventory-&amp;gt;GetCountOf(ItemId);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아이템 이름과 아이콘을 가져와 FactoryCraftCostEntryWidget에 전달한다.&lt;/p&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;CostEntry-&amp;gt;SetupCost(
    ItemName,
    ItemIcon,
    OwnedCount,
    RequiredCount
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재료 엔트리 위젯에서는 보유 개수 / 필요 개수 형식으로 표시하고, 재료가 부족하면 개수 텍스트를 빨간색으로 보여준다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;const bool bEnough = InOwnedCount &amp;gt;= InRequiredCount;
CountText-&amp;gt;SetColorAndOpacity(
    bEnough
        ? FSlateColor(FLinearColor::Black)
        : FSlateColor(FLinearColor::Red)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p 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;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;제작 가능 여부 검사&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제작 버튼은 현재 선택된 설비를 제작할 수 있을 때만 활성화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제작 가능 여부는 CanCraftFactory()에서 검사한다. 이 함수는 선택된 설비의 제작 비용을 모두 확인하고, 인벤토리에 각 재료가 충분히 있는지 검사한다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;if (!Inventory-&amp;gt;HasEnoughItems(ItemId, Cost.Value))
{
    return false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나라도 부족하면 false를 반환하고, 모든 재료가 충분하면 true를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제작 버튼은 이 결과에 따라 활성화된다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;CraftButton-&amp;gt;SetIsEnabled(CanCraftFactory(SelectedFactoryId));
&lt;/code&gt;&lt;/pre&gt;
&lt;p 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;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실제 제작 처리와 재료 차감&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제작 버튼을 누르면 CraftSelectedFactory()가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 데이터와 인벤토리, 선택된 설비 ID가 유효한지 확인하고, 다시 한 번 제작 가능 여부를 검사한다. 그 후 필요한 재료를 하나씩 차감한다.&lt;/p&gt;
&lt;pre class=&quot;erlang-repl&quot;&gt;&lt;code&gt;if (!Inventory-&amp;gt;TryRemoveItem(ItemId, Cost.Value))
{
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 TryRemoveItem()은 인벤토리에서 해당 아이템을 필요한 개수만큼 제거하려고 시도하는 함수다. 앞에 !가 붙어 있기 때문에, 이 조건문은 재료 차감에 실패했을 때 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재료를 차감하는 도중 실패할 수도 있기 때문에, 이미 차감한 재료는 RemovedItems 배열에 저장해둔다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;RemovedItems.Add(TPair&amp;lt;FName, int32&amp;gt;(ItemId, Cost.Value));
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 중간에 차감 실패가 발생하면 이전에 차감했던 재료를 다시 복구한다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;for (const TPair&amp;lt;FName, int32&amp;gt;&amp;amp; Removed : RemovedItems)
{
    Inventory-&amp;gt;TryAddItem(EKOSlotKind::Item, Removed.Key, Removed.Value);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 제작 처리에서 중요한 안전장치다. 여러 재료를 차감하는 중간에 문제가 생겼을 때 일부 재료만 사라지는 상황을 막을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재료 차감이 모두 성공하면 설비를 인벤토리에 추가한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const int32 Remaining = Inventory-&amp;gt;TryAddItem(
    EKOSlotKind::Factory,
    SelectedFactoryId,
    1
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 설비 지급에 실패하면 이때도 차감했던 재료를 다시 복구한다. 제작 성공 후에는 Refresh()를 호출해 UI를 갱신한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제작 가능한 설비 목록을 데이터 기반으로 생성하고, 선택된 설비의 상세 정보와 제작 비용을 표시하며, 인벤토리 재료를 검사해 제작 가능 여부를 판단하는 구조까지 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에는 플레이어가 제작 개수를 직접 지정하여 제작할 수 있는 기능을 추가할 예정이다.&lt;/p&gt;</description>
      <category>Unreal 최종 팀프로젝트</category>
      <category>Unreal_C++</category>
      <category>팀 프로젝트</category>
      <author>코딩하는미피</author>
      <guid isPermaLink="true">https://moung051.tistory.com/122</guid>
      <comments>https://moung051.tistory.com/122#entry122comment</comments>
      <pubDate>Thu, 11 Jun 2026 21:00:36 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 무기 장착 슬롯 | 최종 팀프로젝트</title>
      <link>https://moung051.tistory.com/121</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;장비 데이터 테이블 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 무기나 장비로 취급할 아이템을 판별하기 위해 FKOEquipmentRow를 추가했다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;USTRUCT(BlueprintType)
struct KARON_API FKOEquipmentRow : public FTableRowBase
{
    GENERATED_BODY()

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = &quot;Equipment&quot;)
    FGameplayTag ItemTag;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서 GAS를 사용하고 있기 때문에, 능력치 처리는 나중에 GameplayEffect나 Ability 쪽에서 담당할 예정이기 때문에&amp;nbsp;현재 단계에서는 장비의 공격력, 방어력, 장착 소켓 같은 정보는 넣지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장비 테이블은 단순히 ItemTag를&amp;nbsp;가진&amp;nbsp;아이템은&amp;nbsp;장비로&amp;nbsp;취급한다.&lt;/p&gt;
&lt;p 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;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Equipment 데이터 로드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장비 테이블을 사용하기 위해 KOLoadSubsystem에&amp;nbsp;Equipment 테이블을 추가했다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;IndexTableRowsByName&amp;lt;FKOEquipmentRow&amp;gt;(
    Settings-&amp;gt;EquipmentTables,
    EquipmentCache,
    TEXT(&quot;Equipment&quot;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 장비 여부를 빠르게 확인하기 위해 EquipmentTagToId 역인덱스를 만들었다.&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;EquipmentTagToId.Add(Row-&amp;gt;ItemTag, Pair.Key);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 아이템의 ItemTag가 DT_Equipment에 등록되어 있는지 확인할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;무기 장비 판별&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI에서 매번 LoadSubsystem을 직접 가져와서 사용하면 코드가 지저분해지기 때문에, 장비 판별 로직은 UKOItemLibrary로 분리했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IsEquipmentItem() : 해당 아이템이 장비 테이블에 등록되어 있는지 확인한다.&lt;/li&gt;
&lt;li&gt;IsWeaponEquipmentItem()&lt;br /&gt;1.&amp;nbsp;DT_Item에&amp;nbsp;존재하는&amp;nbsp;아이템인가? &lt;br /&gt;2.&amp;nbsp;해당&amp;nbsp;ItemTag가&amp;nbsp;DT_Equipment에&amp;nbsp;등록되어&amp;nbsp;있는가? &lt;br /&gt;3. Item.Category.Weapon&amp;nbsp;카테고리를&amp;nbsp;가지고&amp;nbsp;있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 무기 슬롯에는 단순히 장비 테이블에 등록된 아이템이 아니라, Item.Category.Weapon 태그까지 가진 아이템만 들어갈 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;무기 드롭&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NativeOnDrop() 흐름&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;1. 드롭된 Operation이 UKOItemDragDropOperation인지 확인
2. 아이템이 있는 드래그인지 확인
3. Factory 아이템이면 거부
4. 인벤토리에서 시작된 드래그인지 확인
5. 드롭된 ItemId 확인
6. IsWeaponEquipmentItem()으로 무기 장비인지 검사
7. Source-&amp;gt;Extract()로 인벤토리에서 1개 제거
8. 기존 장착 무기가 있으면 인벤토리로 반환
9. EquippedWeaponId에 새 무기 저장
10. 아이콘, 이름, 툴팁 갱신&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 중요한 부분은 기존 무기 반환 처리다.&lt;/p&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;if (!EquippedWeaponId.IsNone())
{
    const int32 Remaining = Inventory-&amp;gt;TryAddItem(EKOSlotKind::Item, EquippedWeaponId, 1);

    if (Remaining &amp;gt; 0)
    {
        ItemDragOperation-&amp;gt;Source-&amp;gt;Restore(DraggedItemId, Extracted);
        return false;
    }

    EquippedWeaponId = NAME_None;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 무기를 장착하기 전에 기존 무기를 인벤토리로 돌려보낸다.&lt;br /&gt;만약 인벤토리가 가득 차서 기존 무기를 반환할 수 없다면, 방금 꺼낸 새 무기도 원래 위치로 복구한다.&lt;/p&gt;
&lt;p 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;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인벤토리 컴포넌트 찾기 방식 정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 InventoryPanelWidget에서 WeaponSlotWidget에 InventoryComponent를 넘겨주는 방식으로 접근했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 기존 InventoryWidget은 이미 자기 내부에서 ResolveInventoryComponentIfNeeded()를 통해 인벤토리 컴포넌트를 직접 찾는 구조였다. 그래서 무기 슬롯도 같은 방식으로 정리했다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;SetInventoryComponent()
= 외부에서 명시적으로 인벤토리 컴포넌트를 주입받는 방식

ResolveInventoryComponentIfNeeded()
= 주입받은 컴포넌트가 없으면 OwningPlayer 또는 Pawn에서 직접 찾는 방식
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 무기 슬롯도 인벤토리 위젯과 동일하게 동작하도록 만들었다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;외부에서 넘겨주면 그 컴포넌트를 사용
없으면 직접 PlayerController / Pawn에서 InventoryComponent 탐색
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장착 해제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UnequipWeapon() 흐름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;장착된&amp;nbsp;무기가&amp;nbsp;있는지&amp;nbsp;확인 &lt;br /&gt;2.&amp;nbsp;인벤토리&amp;nbsp;컴포넌트&amp;nbsp;확인 &lt;br /&gt;3.&amp;nbsp;장착된&amp;nbsp;무기를&amp;nbsp;인벤토리에&amp;nbsp;TryAddItem() &lt;br /&gt;4.&amp;nbsp;성공하면&amp;nbsp;EquippedWeaponId를&amp;nbsp;None으로&amp;nbsp;변경 &lt;br /&gt;5.&amp;nbsp;UI&amp;nbsp;갱신&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;DT_Equipment&amp;nbsp;추가 &lt;br /&gt;-&amp;nbsp;LoadSubsystem에서&amp;nbsp;장비&amp;nbsp;테이블&amp;nbsp;로드 &lt;br /&gt;-&amp;nbsp;ItemLibrary에서&amp;nbsp;장비/무기&amp;nbsp;판별&amp;nbsp;함수&amp;nbsp;추가 &lt;br /&gt;-&amp;nbsp;WBP_InventoryPanel에&amp;nbsp;WeaponSlotWidget&amp;nbsp;추가 &lt;br /&gt;-&amp;nbsp;무기&amp;nbsp;슬롯&amp;nbsp;드롭&amp;nbsp;처리 &lt;br /&gt;-&amp;nbsp;무기&amp;nbsp;장착&amp;nbsp;시&amp;nbsp;인벤토리에서&amp;nbsp;수량&amp;nbsp;차감 &lt;br /&gt;-&amp;nbsp;기존&amp;nbsp;무기&amp;nbsp;교체&amp;nbsp;처리 &lt;br /&gt;-&amp;nbsp;장착&amp;nbsp;해제&amp;nbsp;함수&amp;nbsp;추가 &lt;br /&gt;-&amp;nbsp;무기&amp;nbsp;슬롯&amp;nbsp;아이콘/이름&amp;nbsp;갱신 &lt;br /&gt;-&amp;nbsp;무기&amp;nbsp;슬롯&amp;nbsp;툴팁&amp;nbsp;표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 단계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;장착&amp;nbsp;무기&amp;nbsp;메시를&amp;nbsp;캐릭터&amp;nbsp;소켓에&amp;nbsp;붙이기 &lt;br /&gt;-&amp;nbsp;GAS&amp;nbsp;GrantedSet&amp;nbsp;또는&amp;nbsp;GameplayEffect&amp;nbsp;적용 &lt;br /&gt;-&amp;nbsp;장착&amp;nbsp;상태&amp;nbsp;저장/로드 &lt;br /&gt;-&amp;nbsp;무기&amp;nbsp;슬롯&amp;nbsp;우클릭&amp;nbsp;해제&amp;nbsp;입력&amp;nbsp;정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 인벤토리 시스템 안에 장비 개념을 확장하였고,&lt;br /&gt;무기도 여전히 Item으로 관리하면서, 장비 판별은 별도의 DT_Equipment에서 처리하도록 분리하였다.&lt;/p&gt;</description>
      <category>Unreal 최종 팀프로젝트</category>
      <category>Unreal_C++</category>
      <category>팀 프로젝트</category>
      <author>코딩하는미피</author>
      <guid isPermaLink="true">https://moung051.tistory.com/121</guid>
      <comments>https://moung051.tistory.com/121#entry121comment</comments>
      <pubDate>Wed, 10 Jun 2026 21:25:55 +0900</pubDate>
    </item>
  </channel>
</rss>