<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>껍데기방</title>
    <link>https://husk321.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 17 Aug 2026 15:46:48 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>허스크</managingEditor>
    <image>
      <title>껍데기방</title>
      <url>https://tistory1.daumcdn.net/tistory/2910645/attach/196a06fcde784e5f928a6e5e6716ae69</url>
      <link>https://husk321.tistory.com</link>
    </image>
    <item>
      <title>[Unreal] UMassEntityEditorSubsystem::Tick 이 Unreal Insight 에서 병목을 발생시킬 때</title>
      <link>https://husk321.tistory.com/482</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mass Entity 등의 매스 관련 플러그인을 사용하는게 아니라면 5.6 인가부터 에디터의 Outliner 용으로 디폴트가 되어 발생하는 문제&lt;/li&gt;
&lt;li&gt;Module Unload MassEntityEditor 같은 명령어로 모듈 언로드가 가능하지만 Outliner 용으로 사용중이고 적법한 방법이 아님&lt;/li&gt;
&lt;li&gt;에디터에서 Viewport나 PIE 등으로 플레이 하는게 아닌,&amp;nbsp;&lt;b&gt;Standalone Game&lt;/b&gt;으로 실행하여야 UMassEntityEditorSubsystem::Tick 없이 Unreal Insight 측정이 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;참고 링크&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1784010281376&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;UMassEntityEditorSubsystem is now enabled by default&quot; data-og-description=&quot;Steps to Reproduce Start any project (eg. Thirdperson Template) and profile with stat_namedEvents. In unreal insights you will always see UMassEditorSubsystem:Tick with at least 200 microseconds on the CPU [Image Removed] [Attachment Removed]&quot; data-og-host=&quot;forums.unrealengine.com&quot; data-og-source-url=&quot;https://forums.unrealengine.com/t/umassentityeditorsubsystem-is-now-enabled-by-default/2692038/2&quot; data-og-url=&quot;https://forums.unrealengine.com/t/umassentityeditorsubsystem-is-now-enabled-by-default/2692038/2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/TXqKW/dJMb8XSib49/TZ9P2OXrBYKkKFyfWDxN2K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/c7bRHA/dJMb887k3kv/dDhcEIU4sqfY3gOExKxcwK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://forums.unrealengine.com/t/umassentityeditorsubsystem-is-now-enabled-by-default/2692038/2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://forums.unrealengine.com/t/umassentityeditorsubsystem-is-now-enabled-by-default/2692038/2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/TXqKW/dJMb8XSib49/TZ9P2OXrBYKkKFyfWDxN2K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/c7bRHA/dJMb887k3kv/dDhcEIU4sqfY3gOExKxcwK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;UMassEntityEditorSubsystem is now enabled by default&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Steps to Reproduce Start any project (eg. Thirdperson Template) and profile with stat_namedEvents. In unreal insights you will always see UMassEditorSubsystem:Tick with at least 200 microseconds on the CPU [Image Removed] [Attachment Removed]&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;forums.unrealengine.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>쾌락없는 책임 (공부)/Unreal</category>
      <author>허스크</author>
      <guid isPermaLink="true">https://husk321.tistory.com/482</guid>
      <comments>https://husk321.tistory.com/482#entry482comment</comments>
      <pubDate>Tue, 14 Jul 2026 15:24:42 +0900</pubDate>
    </item>
    <item>
      <title>[AI] Gemini CLI 로 1인 게임 개발 시간 단축하기</title>
      <link>https://husk321.tistory.com/481</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 들의 성능이 폭발적으로 향상된 후 기술 블로그를 가꾸는데 관심이 적어졌습니다. AI에게 물어보기만 해도 원하는 답변을 꽤나 쉽게 찾을 수 있기 때문에 등한시하게 되는 경향이 생기게 되네요.&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;이 글에서는 최근 AI 를 통해 여러 게임 개발을 하는 시도를 하고 있는데 관련해 제가 어떤 식으로 1인 개발에서 시간 단축을 하는지 요약해 보려고 합니다. 대부분 아시는 내용일 테지만 모르시는 분들을 위한 작은 노하우 공유라고 생각하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기획 초안 짜기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 AI 와 함께 일 하기 위해서는 '자세한 요구 사한'이 필요로 합니다. 최근 AI들이 개떡같이 말해도 찰떡같이 알아듣기는 하는데 요청사항이 자세할수록 코드가 양질로 나오는 건 부정할 수 없습니다.&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;b&gt;옵시디언&lt;/b&gt;을 통해 정리하고 있습니다. 옵시디언은 여러 장점이 있지만&amp;nbsp;&lt;b&gt;로컬에 기획 문서가 저장되기에 Gemini CLI 를 사용할 수 있다&lt;/b&gt;는 장점이 있어 이쪽으로 옮기게 되었습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1775556626581&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Obsidian - Sharpen your thinking&quot; data-og-description=&quot;The free and flexible app for your private thoughts.&quot; data-og-host=&quot;obsidian.md&quot; data-og-source-url=&quot;https://obsidian.md/&quot; data-og-url=&quot;https://obsidian.md/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bOmxBw/dJMb9jgx2Be/E3TdbtSjxdKHCJ1ONc8Dm1/img.png?width=1200&amp;amp;height=688&amp;amp;face=0_0_1200_688,https://scrap.kakaocdn.net/dn/csj2x2/dJMb9hC15c3/T13fcAckq31Wkn4CuXdU4K/img.png?width=1200&amp;amp;height=688&amp;amp;face=0_0_1200_688,https://scrap.kakaocdn.net/dn/bUwNLL/dJMb8SXyx6T/Uka1t3oz5S96ZARThgcKLK/img.png?width=2899&amp;amp;height=1757&amp;amp;face=0_0_2899_1757&quot;&gt;&lt;a href=&quot;https://obsidian.md/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://obsidian.md/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bOmxBw/dJMb9jgx2Be/E3TdbtSjxdKHCJ1ONc8Dm1/img.png?width=1200&amp;amp;height=688&amp;amp;face=0_0_1200_688,https://scrap.kakaocdn.net/dn/csj2x2/dJMb9hC15c3/T13fcAckq31Wkn4CuXdU4K/img.png?width=1200&amp;amp;height=688&amp;amp;face=0_0_1200_688,https://scrap.kakaocdn.net/dn/bUwNLL/dJMb8SXyx6T/Uka1t3oz5S96ZARThgcKLK/img.png?width=2899&amp;amp;height=1757&amp;amp;face=0_0_2899_1757');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Obsidian - Sharpen your thinking&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The free and flexible app for your private thoughts.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;obsidian.md&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 후 커뮤니티 플러그인에서&amp;nbsp;&lt;b&gt;Google Drive Sync&lt;/b&gt; 를 활성화하면 구글 드라이브를 통해 여러 곳에서 Sync를 맞출 수 있습니다. 해당 방법은 드라이브에서 권한을 일부 줘야 하는 거라 불안할 수 있으니 다른 방안으로 해도 괜찮지만 저는 IOS 환셩(아이패드)에서도 되었으면 해서 해당 플러그인을 활용했습니다.&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;이후 해야 하는 일은 기획 문서가 있는 곳에서 CMD 를 켜 gemini를 호출한 뒤 아래와 같이 질문해 주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;지금 우리 게임에 추가되어야 하는 어빌리티가 있어. NPC 에게 플레이어가 선물을 해 호감도를 올리는 어빌리티야.&lt;br /&gt;이 어빌리티에서는 다음과 같은 것들을 고려해야 해.&lt;br /&gt;- 유저는 기존 상호작용을 통해 NPC 에게 선물 능력을 활성화할 수 있음&lt;br /&gt;- 유저의 인벤토리 내 아이템만 선물 가능&lt;br /&gt;- 유저가 선물한 아이템에 따라 호감도의 증감이 있어야 함.&lt;br /&gt;&amp;nbsp; &amp;nbsp;- 이는 특정 분류의 아이템일수도, 특정 종류의 아이템일 수도 있음.&lt;br /&gt;위 요구사항 목록을 바탕으로 기획 문서를 작성하고 개발자들이 고려해야 할 체크리스트를 만들어줘.&lt;/blockquote&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;1057&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AsrVk/dJMcahxd4N6/W5puvgXOQk7tFVZfjExTPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AsrVk/dJMcahxd4N6/W5puvgXOQk7tFVZfjExTPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AsrVk/dJMcahxd4N6/W5puvgXOQk7tFVZfjExTPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAsrVk%2FdJMcahxd4N6%2FW5puvgXOQk7tFVZfjExTPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;871&quot; height=&quot;1057&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;1057&quot;/&gt;&lt;/span&gt;&lt;/figure&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;그러면 이렇게 막 자세하게 문서를 작성해 줍니다. 대신 해당 기획용 CLI는&amp;nbsp;&lt;b&gt;개발 내용에 대해서 모르는 기획용 AI&lt;/b&gt;기 때문에 1인 개발이라면 조금 수정해야 할 부분들이 있을 겁니다. 예를 들어 없는 클래스라던가 등... 그런 것들을 제거해 주어야 추후 개발용 AI에게 이를 넣었을 때 만족스러운 코드를 뽑아내게 됩니다.&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개발 AI 에게 기획 문서 먹이기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 경우 사실 기획 자체가 간단합니다. 제 프로젝트에서는&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선물하기 UI 제작 (MVVM 플러그인을 활용 중이라 Widget과 ViewModel 제작)&lt;/li&gt;
&lt;li&gt;선물하기 어빌리티 제작&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개 정도의 일만 하면 되니깐요. 최종적으로 기획 문서를 먹이고 첫 개발 삽을 뜰 때는 이렇게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;@&quot;~~&quot; 경로에 우리가 새로 제작해야 하는 기능에 대한 문서가 있어. 이를 참고해서 필요한 어빌리티 클래스들을 만들고 피로도 등의 부분들을 고려해서 기능을 제작해 줘.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;423&quot; data-origin-height=&quot;807&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r6BL9/dJMcagry9iP/RkL6VKFEovQi8zc8TfbbX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r6BL9/dJMcagry9iP/RkL6VKFEovQi8zc8TfbbX0/img.png&quot; data-alt=&quot;해당하는 경로는 이렇게 얻으면 편합니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r6BL9/dJMcagry9iP/RkL6VKFEovQi8zc8TfbbX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr6BL9%2FdJMcagry9iP%2FRkL6VKFEovQi8zc8TfbbX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;423&quot; height=&quot;807&quot; data-origin-width=&quot;423&quot; data-origin-height=&quot;807&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;해당하는 경로는 이렇게 얻으면 편합니다&lt;/figcaption&gt;
&lt;/figure&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;실제로 나오는 클래스도 3개 정도였습니다. 다만 이 과정에서&amp;nbsp;&lt;b&gt;1인 개발로 AI를 적극 활용하기 위해서는 해당 프로젝트 코드에 대한 이해가 높아야 합니다.&lt;/b&gt; 즉,&amp;nbsp;&lt;u&gt;AI 가 만들어낸 코드도 본인이 이해해야 합니다.&lt;/u&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;저 같은 경우에도 위 기획 문서를 그대로 먹였을 때 80% 는 만족을 하지만 20% 정도의 수정사항이 있어 처리를 해줬습니다. 중간중간 수정 과정도 AI에게 맡길 수 있으니 코드 칠 시간에 AI에게 먹이기만 해도 좋습니다.&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Gemini CLI 를 조금 더 빠르게 쓸 수 있게 하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 발전으로 소스코드의 품질은 많이 좋아졌지만 처음 ~~ 해줘 를 했을 때 프로젝트의 소스코드를 읽는데 시간이 많이 걸렸습니다. UI 하나 수정하고 싶은데 처음 터미널 키고 10분을 기다렸나...&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;blockquote data-ke-style=&quot;style3&quot;&gt;우리 프로젝트에서는 큰 문제가 있어. 신입 개발자가 오거나 다른 AI 가 작업할 때 우리 프로젝트의 소스코드가 어떤 식으로 동작하는지 구조를 빠르게 파악하지 못해 신규 기능 개발이나 수정, 유지보수에 시간이 걸린다는 점이야.&lt;br /&gt;여기서 너에게 아래 요구사항에 맞춰 우리 소스코드를 분석해달라는 요청을 할게. &lt;br /&gt;- 각 디렉토리별 소스코드들이 어떤 역할을 하는지에 대해서 분석 (이에 대해서 문서화는 필요 없고 하위 요구사항을 처리하기 위함) &lt;br /&gt;- 분석이 완료된 후 기능별로 어떤 디렉터리와 이중 가장 참고할만한 masterpiece 소스코드/클래스는 무엇인지 기록하기&lt;br /&gt;&amp;nbsp; &amp;nbsp;- 예를 들어 UI 를 보기 위해서는 Widgets 하위 폴더들을 봐야 하며 ViewModel 등을 고려해 제작해야 한다... &lt;br /&gt;- 해당 프로젝트에서 어떤 구조를 택하고 있는지, 그래서 신규 기능 개발을 할 때 어떤 식으로 고려해야 하는지 &lt;br /&gt;&amp;nbsp; &amp;nbsp;- MVVM 패턴 등에 대한 언급으로 신규 코드를 작성할 때 해당 패턴에서 벗어나지 않고 제작 가능하게 &lt;br /&gt;- 대체적으로 나타나는 코딩 컨벤션은 어떤 것들이 있고 이를 잘 참고할 수 있는 소스코드는 무엇인지&lt;br /&gt;&amp;nbsp; &amp;nbsp;- 한 사람이 쓴 코드처럼 할 수 있는 규칙성을 파악해&lt;br /&gt;- 해당 문서는 평서문으로 작성해 (이모지 없음)&lt;br /&gt;- ~~자 이상의 문서로 아주 자세히 적어줘.&lt;br /&gt;&lt;br /&gt;문서화 분석이 완료된 내용들을~~ 경로에 마크다운 문서로 제작해서 내가 볼 수 있게 해 줘.&lt;/blockquote&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;&lt;b&gt;지도를 만드는 과정&lt;/b&gt;을 거쳤습니다. 예를 들어 UI 코드를 추가한다 했을 때 소스코드를 다 보지 말고 특정 소스코드만 보게 해서 IO 시간을 줄이자는 생각이었죠.&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;몇 번 해 보았을 때 3.1 pro 던 flash 던 ~~ 자 이상의 자세한 문서로 적어줘라는 요청이 있어야 좀 더 잘 적어주는 느낌이었습니다. 그 외에는 50-80자인데 모든 경우의 수를 고려하지 않고 요약한 느낌이 나 문서의 결과물이 좋지 못했습니다.&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;아무튼 해당 문서 이후로 기존 프로젝트 코드의 기조대로 나오는 듯하고 시간은... 정확한 수치는 없지만 아직까지는 이전처럼 막 10분 기다리는 일은 발생하지 않아 만족하고 있습니다.&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Perforce mcp&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 집에서 버전관리로 퍼포스를 사용 중이기에 관련한 mcp 도 받아두었습니다. 공식은 아니고 엄연히 서드파티지만 버전관리를 위해 두면 좋습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1775825999826&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - perforce/p4mcp-server: [Community Supported] Perforce P4 MCP Server is a Model Context Protocol (MCP) server that integ&quot; data-og-description=&quot;[Community Supported] Perforce P4 MCP Server is a Model Context Protocol (MCP) server that integrates with the Perforce P4 version control system. - perforce/p4mcp-server&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/perforce/p4mcp-server?tab=readme-ov-file&quot; data-og-url=&quot;https://github.com/perforce/p4mcp-server&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dI1tFk/dJMb8869T7O/XXjAJc3HKtUXMdsE7WqA0k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/RdE19/dJMb87f7yk2/XX2CfynkKacXQKqNLSUuNk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/perforce/p4mcp-server?tab=readme-ov-file&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/perforce/p4mcp-server?tab=readme-ov-file&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dI1tFk/dJMb8869T7O/XXjAJc3HKtUXMdsE7WqA0k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/RdE19/dJMb87f7yk2/XX2CfynkKacXQKqNLSUuNk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - perforce/p4mcp-server: [Community Supported] Perforce P4 MCP Server is a Model Context Protocol (MCP) server that integ&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[Community Supported] Perforce P4 MCP Server is a Model Context Protocol (MCP) server that integrates with the Perforce P4 version control system. - perforce/p4mcp-server&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 해당 MCP를 넣지 않더라도&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- ✅ 만약 파일을 수정해야 하는데 **읽기 전용(Read-only)**이거나 **권한 오류**가 발생한다면, `p4 edit -c default &amp;lt;파일경로&amp;gt;` 명령어를 통해 퍼포스 체크아웃을 먼저 수행한 뒤 수정을 진행하세요.&lt;br /&gt;- 새 파일을 만들었다면 `p4 add -d -c default` 명령어를 통해 퍼포스에 추가해 주세요&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 문구를 GEMINI.md 에 넣으면 버전관리를 해줍니다. 물론 간혹 빼먹는 경우가 있기에... 출력 로그나 권한 등을 잘 살펴봐야 합니다.&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기타&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 개발에 있어 AI 활용법들은 아직 많이 공유되지 않고 언리얼 MCP 도 아직은... 써볼 만한 급의 기능들은 많이 없어 추가 기재하지는 않았습니다. 다만 그 외 개발하면서 AI를 통해 할 만한 유용한 것들을 이야기해 봅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발 후 AI에게 코드 리뷰 요청
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단 이 때는 성능이 어떻던 다른 AI에게 리뷰를 부탁하는게 좋습니다. 동일한 AI 에게 리뷰를 부탁하면 모델 버전에 관계없이 큰 수확이 없었습니다.&lt;/li&gt;
&lt;li&gt;개발은 Gemini에게 하고 리뷰는 Claude에게 하는 등의 방식이 좋습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;개발 후 테스트 해야 할 항목들을 AI에게 부탁&lt;br /&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>쾌락없는 책임 (공부)/짜잘쓰</category>
      <author>허스크</author>
      <guid isPermaLink="true">https://husk321.tistory.com/481</guid>
      <comments>https://husk321.tistory.com/481#entry481comment</comments>
      <pubDate>Sun, 12 Apr 2026 22:14:09 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal] Mover 플러그인 사용 시 Move To 가 동작하지 않는 경우</title>
      <link>https://husk321.tistory.com/480</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;b&gt;개요&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;CharacterMovementComponent 에서 Mover 플러그인으로 변경한 후 플레이어가 직접 조작하는 Pawn 은 잘 동작하지만 AI 용 Pawn 이 AI Move To 작업을 진행하지 않는 문제가 발생했었습니다. NavMoverComponent 도 추가하였는데 움직이지 않아 어떤 문제인지 파악이 힘들었던 이슈였습니다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;수정&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bapxhS/dJMcaakbX30/sVlytNrKimg43YwrX7Bim1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bapxhS/dJMcaakbX30/sVlytNrKimg43YwrX7Bim1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bapxhS/dJMcaakbX30/sVlytNrKimg43YwrX7Bim1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbapxhS%2FdJMcaakbX30%2FsVlytNrKimg43YwrX7Bim1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;667&quot; height=&quot;150&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;CapsuleComponent-&amp;gt;SetCanEverAffectNavigation(false);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;위 BP 화면이나 코드처럼 해당 옵션을 False 처리하면 됩니다.&lt;br&gt;&lt;br&gt;이런 이슈가 나오게 된 건 기존 Character 는 CapsuleComponent에 기본값으로 설정해두지만 이번에 Mover 로 적용하면서 Pawn 에 없는 CapsuleComponent 를 직접 만들다보니 해당 옵션을 고려하지 않아 발생한 문제였습니다. 해당 옵션이 켜 있으면 본인 위치는 네비게이션 이동이 안되는 위치로 인식되어 움직일 수 없었던 것이죠.&lt;/p&gt;</description>
      <category>쾌락없는 책임 (공부)/Unreal</category>
      <author>허스크</author>
      <guid isPermaLink="true">https://husk321.tistory.com/480</guid>
      <comments>https://husk321.tistory.com/480#entry480comment</comments>
      <pubDate>Fri, 13 Mar 2026 12:46:39 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal] UMG View Model 바인딩 후 컴파일이 되지 않는 오류</title>
      <link>https://husk321.tistory.com/479</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;There is no field to bind to. The binding must be a OneTime binding.&lt;/blockquote&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;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k0uUc/dJMcafeviRS/pdLkjh61cBHIuHgZKFfDKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k0uUc/dJMcafeviRS/pdLkjh61cBHIuHgZKFfDKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k0uUc/dJMcafeviRS/pdLkjh61cBHIuHgZKFfDKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk0uUc%2FdJMcafeviRS%2FpdLkjh61cBHIuHgZKFfDKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;949&quot; height=&quot;145&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;145&quot;/&gt;&lt;/span&gt;&lt;/figure&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;이 경우 View Model 에서 저런 알림 표시가 없을 가능성이 농후합니다. 이 경우 다시 컴파일 하고 에디터를 다시 켜는 경우 오류가 사라져 있을 수 있으니 재실행을 추천드립니다.&lt;/p&gt;</description>
      <category>쾌락없는 책임 (공부)/Unreal</category>
      <author>허스크</author>
      <guid isPermaLink="true">https://husk321.tistory.com/479</guid>
      <comments>https://husk321.tistory.com/479#entry479comment</comments>
      <pubDate>Fri, 13 Feb 2026 20:58:30 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal] Instanced Static Mesh 사용기</title>
      <link>https://husk321.tistory.com/478</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;공식 문서&lt;/b&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1770174380552&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;언리얼 엔진의 인스턴스드 스태틱 메시 컴포넌트  | 언리얼 엔진 5.7 문서 | Epic Developer Community&quot; data-og-description=&quot;언리얼 엔진에서 인스턴스드 스태틱 메시 컴포넌트를 사용한 인스턴싱을 통해 프로젝트를 최적화하는 방법을 살펴봅니다.&quot; data-og-host=&quot;dev.epicgames.com&quot; data-og-source-url=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/instanced-static-mesh-component-in-unreal-engine&quot; data-og-url=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/instanced-static-mesh-component-in-unreal-engine&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cgrIkU/dJMb9aKAnT8/QI3OBnpRdgmUxKFrHEHJZk/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/qtoQg/dJMb89x8P42/Yjhs6rkV2bw5UeKmekC7zK/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/FKLuA/dJMb84XT2Wa/TGVSJIkiGcF04fy1VMnoO0/img.png?width=1920&amp;amp;height=335&amp;amp;face=0_0_1920_335&quot;&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/instanced-static-mesh-component-in-unreal-engine&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/instanced-static-mesh-component-in-unreal-engine&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cgrIkU/dJMb9aKAnT8/QI3OBnpRdgmUxKFrHEHJZk/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/qtoQg/dJMb89x8P42/Yjhs6rkV2bw5UeKmekC7zK/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/FKLuA/dJMb84XT2Wa/TGVSJIkiGcF04fy1VMnoO0/img.png?width=1920&amp;amp;height=335&amp;amp;face=0_0_1920_335');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;언리얼 엔진의 인스턴스드 스태틱 메시 컴포넌트 | 언리얼 엔진 5.7 문서 | Epic Developer Community&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;언리얼 엔진에서 인스턴스드 스태틱 메시 컴포넌트를 사용한 인스턴싱을 통해 프로젝트를 최적화하는 방법을 살펴봅니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.epicgames.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자에게는 필요한 역량일까 싶지만 게임 개발자에게는 필요한 역량이라 생각해 현재 개발 중인 게임에 적용해 두기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Instanced Static Mesh 란 같은 메쉬/메테리얼을 공유하는 레벨 내 요소들의 드로우 콜을 줄여주는 기능입니다. 원래라면 100개가 있으면 같은 메쉬라도 드로우콜을 100번 보내지만 해당 기능으로 수집한 메쉬들의 경우에는 드로우콜을 1번만 보내게 된다는 이야기죠.&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;보통 대부분의 엔진들은 가지고 있는 기능으로 보이고 유니티에서도 있었던 기능으로 알고 있으니 3D 게임 제작 중이라면 최적화를 위해 적용할 가치가 있다고 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실제 개인 프로젝트에 적용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;현재 제 개인 프로젝트에서는 레벨 디자인을 하면서 Foliage를 제외하고 마구잡이로 액터들을 배치해 두었습니다. 같은 메쉬가 최소 10개부터 400개까지 되는 경우가 많았기에 해당 메쉬들에 대해서 ISM (Instanced Static Mesh)를 적용해 보려고 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;553&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BVUop/dJMcafFs6ll/cdGXhkeJlJe4mxprwlDoCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BVUop/dJMcafFs6ll/cdGXhkeJlJe4mxprwlDoCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BVUop/dJMcafFs6ll/cdGXhkeJlJe4mxprwlDoCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBVUop%2FdJMcafFs6ll%2FcdGXhkeJlJe4mxprwlDoCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1020&quot; height=&quot;553&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;553&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;172&quot; data-origin-height=&quot;30&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaBgPQ/dJMcadAO0n2/PjPgfI0c1fqjXBeuiSPr1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaBgPQ/dJMcadAO0n2/PjPgfI0c1fqjXBeuiSPr1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaBgPQ/dJMcadAO0n2/PjPgfI0c1fqjXBeuiSPr1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaBgPQ%2FdJMcadAO0n2%2FPjPgfI0c1fqjXBeuiSPr1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;172&quot; height=&quot;30&quot; data-origin-width=&quot;172&quot; data-origin-height=&quot;30&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;80&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rzDeC/dJMcagEoUey/YQkwxpiFc6PkZvfBeMc7s0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rzDeC/dJMcagEoUey/YQkwxpiFc6PkZvfBeMc7s0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rzDeC/dJMcagEoUey/YQkwxpiFc6PkZvfBeMc7s0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrzDeC%2FdJMcagEoUey%2FYQkwxpiFc6PkZvfBeMc7s0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;80&quot; height=&quot;48&quot; data-origin-width=&quot;80&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;u&gt;&lt;b&gt;총 액터 수는 8520개, 프레임은 57&lt;/b&gt;&lt;/u&gt;정도 나오고 있었습니다. 맨 위 RenderViewFamily의 경우에도 10ms를 넘는 경우가 빈번해 프레임 방어가 힘들어진 상태였습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;551&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAEzwn/dJMcabQARSP/207UbwnVsTPuKvOwzNiSG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAEzwn/dJMcabQARSP/207UbwnVsTPuKvOwzNiSG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAEzwn/dJMcabQARSP/207UbwnVsTPuKvOwzNiSG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAEzwn%2FdJMcabQARSP%2F207UbwnVsTPuKvOwzNiSG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;551&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;551&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;164&quot; data-origin-height=&quot;33&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bavjoh/dJMb996kq6V/1SDQ3kbNLSZQ6YVlCnM0x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bavjoh/dJMb996kq6V/1SDQ3kbNLSZQ6YVlCnM0x0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bavjoh/dJMb996kq6V/1SDQ3kbNLSZQ6YVlCnM0x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbavjoh%2FdJMb996kq6V%2F1SDQ3kbNLSZQ6YVlCnM0x0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;164&quot; height=&quot;33&quot; data-origin-width=&quot;164&quot; data-origin-height=&quot;33&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;88&quot; data-origin-height=&quot;55&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vP0q4/dJMcaaKVul6/TnK7FQtrDUfDkBhlP7obG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vP0q4/dJMcaaKVul6/TnK7FQtrDUfDkBhlP7obG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vP0q4/dJMcaaKVul6/TnK7FQtrDUfDkBhlP7obG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvP0q4%2FdJMcaaKVul6%2FTnK7FQtrDUfDkBhlP7obG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;88&quot; height=&quot;55&quot; data-origin-width=&quot;88&quot; data-origin-height=&quot;55&quot;/&gt;&lt;/span&gt;&lt;/figure&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;최적화 후에는 액터 수를 1/8 수준인 1705개로 줄였습니다. 최적화 도중도중 여러 프랍들을 설치했지만 그를 무시해도 액터 수는 드라마틱하게 줄었다고 볼 수 있습니다. RenderViewFamily도 평균이 1ms로 줄여져 많이 줄게 되었습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임은 다시 보니 라이팅이 일부 삭제된게 클 것으로 보여 ISM 만으로는 줄였다고 보기 어려울 것 같습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주의점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 아는 사실일 수 있지만 ISM 은 메테리얼도 모든 메쉬들이 '공유' 하게 됩니다. 때문에 UV 좌표를 통해 뭔가를 하는, 텍스쳐 패턴에 변형을 주는 메테리얼들의 경우 모든 위치나 방향 등에서 ISM의 메쉬들이 동일해지기에 사용에 있어 주의가 필요해 보입니다.&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;추가로 ISM 으로 된 메쉬들은 1개의 액터로 병합 후 다시 분해가 안되니 레벨을 백업해 두고 작업하는게 편하며(전/후 성능 비교에도 좋음) 가급적 레벨 디자인이 완료된 후 마무리 단계에서 진행하는게 생산성에 좋을 것으로 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드로우콜을 줄이기 위한 여러 기법들이 있겠지만 ISM 은 간단하고 그 성과가 바로 눈에 보인다는 점에서 필수적인 사용이라 봅니다. 개발자가 해야 하는 업무냐?라고 한다면 아니겠지만 이런 기법들을 알아두는 건 분명 도움이 될 것이고 특히 1인 개발이면 분야에 대한 경계는 없으니 알아두면 더더욱 좋을 거라 봅니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;</description>
      <category>쾌락없는 책임 (공부)/Unreal</category>
      <author>허스크</author>
      <guid isPermaLink="true">https://husk321.tistory.com/478</guid>
      <comments>https://husk321.tistory.com/478#entry478comment</comments>
      <pubDate>Wed, 4 Feb 2026 12:22:51 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal] Unreal Engine5 Unreal Lightmass executable is outdated 오류</title>
      <link>https://husk321.tistory.com/477</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lighting needs to be rebuild 오류를 수정하려다 발생한 로그로 전체 로그는 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;LogStaticLightingSystem: Warning: Failed to build lighting!!! Unreal Lightmass executable is outdated. Recompile UnrealLightmass project with Development configuration in Visual Studio.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 소스코드로 엔진을 빌드했는데 엔진의 소스코드 경로로 가&amp;nbsp; Engine\Build\BatchFiles 에서 아래 명령어를 쳐 lightmass 를 다시 빌드 하였습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Build.bat UnrealLightmass Win64 Development -verbose&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소 에디터를 DebugGame Editor 로 빌드하는데 이건 Development 로 해도 되는가 했는데 괜찮습니다. 아무래도 UnrealLightmass 는 Development 로 사용하는가 보더군요.&lt;/p&gt;</description>
      <category>쾌락없는 책임 (공부)/Unreal</category>
      <author>허스크</author>
      <guid isPermaLink="true">https://husk321.tistory.com/477</guid>
      <comments>https://husk321.tistory.com/477#entry477comment</comments>
      <pubDate>Sun, 18 Jan 2026 15:05:55 +0900</pubDate>
    </item>
    <item>
      <title>[짜잘쓰] Unreal UMG Tweening 제작 중</title>
      <link>https://husk321.tistory.com/476</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;attach4.gif&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CWjyZ/btsQtTWKSNs/1HdlJJtRsNgAHDItKxyXtk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CWjyZ/btsQtTWKSNs/1HdlJJtRsNgAHDItKxyXtk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CWjyZ/btsQtTWKSNs/1HdlJJtRsNgAHDItKxyXtk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/CWjyZ/btsQtTWKSNs/1HdlJJtRsNgAHDItKxyXtk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;544&quot; height=&quot;427&quot; data-filename=&quot;attach4.gif&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;/figure&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;유니티에서는 DoTween 이라는 유/무료 라이브러리가 있었는데 언리얼에는 없을까 했는데 없더라구요. 누군가 만들어둔 UMG Tweening 오픈소스는 있는데 cpp 로만 가능하고 BP 는 하나도 지원하지 않는다는 단점이 있었습니다. cpp 로는 잘 동작할 것 같이 생겼더라구요.&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;일단 지금 UI Tweening 용으로 제작 중에 있는데 BP 로도 생산성이 나올 수 있게 제작하는게 목표입니다. 개인적인 생각으로는 Tweening 효과는 가볍고 빠르게 테스트 가능해야 하는데 C++로만 동작 가능하면 가치가 떨어질 것 같아서...ㅋㅋㅋ&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>쾌락없는 책임 (공부)/짜잘쓰</category>
      <author>허스크</author>
      <guid isPermaLink="true">https://husk321.tistory.com/476</guid>
      <comments>https://husk321.tistory.com/476#entry476comment</comments>
      <pubDate>Wed, 10 Sep 2025 20:59:16 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal] Game Ability System, GAS를 써본 후기</title>
      <link>https://husk321.tistory.com/475</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;모듈화에 힘을 쓰는 에픽&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 들어 언리얼 개발자에게 있어서는 Game Ability System, 통칭 GAS의 이야기가 많이 오가고 있습니다. 이 기본 시스템을 통해 많은 것들을 확장성 좋게 만드냐, 아니면 너무 과하게 구현되어 있어 최적화의 여지가 필요하다 등 저도 이러저러한 이야기들을 많이 들었습니다.&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;이야기를 듣기만 했을 때는 확실히 에픽의 기조가 '모듈화'로 기울었다는 생각이 듭니다. 회사에서 구세대 엔진인 언리얼 3을 해서 더 그렇게 느끼는지는 모르겠으나 컴포넌트화부터 해서 플러그인들까지 점점 모듈화에 힘쓰는 거 같습니다. 좋게 이야기되면 모듈화고 나쁘게 이야기하면 파편화가 될 수 있는데 실제로는 어떠할지 직접 경험한 것들을 이야기해 보려고 합니다.&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;에픽의 샘플들을 보았을 때 - Lyra, Titan&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 에픽 게임즈의 언리얼 엔진 샘플들을 보면 대부분의 경우 GAS 를 활용해 프로젝트를 제작하였습니다. 특히 Lyra(이하 라이라)에서 많은걸 볼 수 있어 다들 참고하는 것 같습니다.&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;일단 라이라 에서는 기본 총기 동작이라던가 대시 등에 대해서 어빌리티로 구현을 하였는데 각종 태그들을 함께 활용하면서 게임에서 사용할 때의 장점을 알려주었습니다. 재장전 중에 태그를 추가해 발사 등의 로직이 들어갈 수 없게 하는 등의 것들을 볼 수 있었습니다. 멀티플레이에서도 사용 가능하다는 걸 보여준 프로젝트로 GAS를 보기 위해서는 알아가야 하는 프로젝트라 생각됩니다. Titan(이하 타이탄)&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;그런데 '슈팅 게임용이냐?' 라고 하면 GAS는 슈팅 게임용은 아닌 것 같습니다. 정확히는 총기 기능에 대해서는 GAS를 쓰지 않는 게 맞았을 것 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;'총 발사가종료되었냐'&lt;/b&gt;를 알아야 하는데 라이라의 GAS로는 알기 어렵습니다.
&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;총기 발사 종료 시의 작업을 언제 진행할지 알 수 없어(총열 이펙트, 반동 돌아오기 등) FPS, TPS에서는 개량이 필요해 보입니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데미지용&amp;nbsp;GameplayEffect에서 무기의 공격력을 가져오지 않는 상태라 무기별 버프/디버프등이 섞이는 경우 GameplayEffect의 코드가 난잡해질 우려가 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;타이탄 프로젝트에서 &lt;b&gt;UI 버튼 누르는 동작까지 어빌리티&lt;/b&gt;로 만들었는데 '이렇게까지 해야 하나' 는 생각이 듭니다.&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;사실 첫 이유가 가장 크지 않을까 해서 슈팅 게임에서는, 적어도 총기에서는 GAS를 사용하지 않는 것이 더 좋아 보입니다. 특히 라이라 샘플의 경우 무기도 파츠하나 없는 단일 무기에 장착/해제 시 SpawnActor/Destroy를 하는 괴랄한 구조를 탄생시켰기에... 성능에 민감하거나 총기 로직이 복잡하다면 라이라는 참고 정도만 하는 게 좋아 보입니다.&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사이드 프로젝트(생존 RPG)에서의 GAS&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 슈팅게임을 벗어나면 GAS 는 이야기가 달라질 정도로 유용하게 사용됩니다.&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;보통 이런 게임에서 지닌 무기에 따라 행위가 달라지고 이에 따른 Input 이 달라지게 됩니다. 복잡한 아이템은 요구되는 Input 이 많아질 수도 있죠. 원래대로라면 Input에 따른 키 바인딩을 추가하기 위해 여러 함수들이 제작되고 바인딩되었을 텐데 어빌리티를 활용하면 지닌 무기에 따라 어빌리티를 동적으로 주고/거두고 하는 방식으로 간소화할 수 있습니다. 쓸데없는 Input에 대한 예외처리를 하지 않고, 필요한 Input 만 처리하는 방식이 가능한 거죠.&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;또 생존 게임에서는 행동들이 많이 요구됩니다. 낚시나 앉기, 쉬기, 아이템 상호작용 등 여러 일들이 많은데 이것들을 전부 Character에 넣기에는 Character 코드가 난잡해질 것이고, 컴포넌트화를 하자니 컴포넌트가 너무 많아져 매한가지고. 이런 경우 어빌리티를 활용하면 각 '행동'에 대해서 클래스들이 따로 만들어지기에 생성, 유지보수가 한결 간편해진다는 느낌이 들었습니다. Tag를 통해 트리거하는 시스템을 만들어두면 UI나 이벤트, 상호작용 등에서도 잘 사용하기도 해 이런 RPG 게임에서는 어빌리티로 관리하는 게 확실히 편했습니다.&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;또한 AI Behavior Tree를 만드는 데 있어서 ASC 기반의 AI Pawn 이 만들어지면 BT Task 가 가벼워집니다. 공격을 한다고 할 때 해당하는 어빌리티를 AI에게 쥐어준 뒤 Tag 이벤트를 트리거 해 공격을 활성화할 수 있거든요. 이전 BT에서 공격을 하기 위해서는 AI에서 공격 인터페이스를 제작한 뒤 BT Task를 통해 해당 인터페이스를 부르는 등 여러 작업이 추가되었었습니다. 그런데 어빌리티를 기반해 행동들을 정의하면 BT Task 작업도 상대적으로 적다는 게 체감됩니다.&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;다만 여전히 단점은 존재합니다. GAS에서 따라오는 Attribute 시스템이 있습니다. 보통 체력같이 능력치를 나타내는 데 사용하는 시스템인데 Gameplay Effect와 연계하면 편하게 아이템 효과, 대미지 등이 제작 가능합니다. 다만 해당하는 Attribute를 리플리케이트 할 때&amp;nbsp;&lt;b&gt;Listen-Server&lt;/b&gt;(생존게임이라 해당 서버 형태도 고려 중입니다)라면 RepNotify를 주는 게 좀 귀찮아집니다. 잘못 코드를 작성하면 Listten-Server에서 노티파이의 무한루프에 빠져서 추적하기도 어려웠던 기억이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1756782602414&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Listen-Server 에서 Authority(ListenServer) 가 있는 상태에서 Notify 를 호출하면 무한루프에 빠집니다
// 이를 위해 OnRep 함수를 부를 때 Notify를 호출할지의 조건을 체크합니다
// 매크로로 한 이유는 코드가 너무 길어져서...

#define REPLICATE_IF_NOT_AUTHORITY(Attribute, OldValue) \
if (GetOwningActor() &amp;amp;&amp;amp; !GetOwningActor()-&amp;gt;HasAuthority()) \
{ \
	GAMEPLAYATTRIBUTE_REPNOTIFY(UPlayerAttributeSet, Attribute, OldValue); \
}

void UPlayerAttributeSet::OnRep_Stamina(const FGameplayAttributeData&amp;amp; OldStamina)
{
	REPLICATE_IF_NOT_AUTHORITY(Stamina, OldStamina);
    if (GetStamina() &amp;lt;= 0.0f)
    {
        SetSprinting(false);
    }
    StaminaAttributeChangedEvent.Broadcast(OldStamina.GetCurrentValue(), GetStamina());
    OnStaminaChangedEvent.Broadcast(GetMaxStamina(), GetStamina());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 제 프로젝트에서는 Listen-Server인 경우에 Notify를 주기 위해서 이런 예외처리를 계속해야 하는 문제도 있었습니다. DS에서는 이 정도로 고려하지는 않지만 Listen-Server를 제작하시는 분들에게는 음... Attribute를 자체적으로 하는 게 더 나을 수도 있겠다는 생각이 들게 됩니다.&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 그래도 몇몇 사용처에서 GAS를 사용해 본 이야기를 해 보았습니다. 장르에 따라서 해당 기능을 넣을까 말까에 대한 고민을 하시는 분들에게 조금이나마 도움이 되었으면 합니다. 각자 직접 사용해 보는 후기가 제일 체감이 되겠지만 시간이 없으니 이런 후기를 찾아보시는 분들에게 도움이 되었으면 합니다.&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;</description>
      <category>쾌락없는 책임 (공부)/Unreal</category>
      <author>허스크</author>
      <guid isPermaLink="true">https://husk321.tistory.com/475</guid>
      <comments>https://husk321.tistory.com/475#entry475comment</comments>
      <pubDate>Tue, 2 Sep 2025 12:12:10 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal] Urneal MVVM plugin, UMG ViewModel 사용 및 후기</title>
      <link>https://husk321.tistory.com/474</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVVM 패턴 자체는 게임뿐 아니라 다른 곳에서도 많이 사용하는 패턴입니다. 때문에 여러 곳에서도 자료를 찾아볼 수 있을 정도라 이 글에서 해당 패턴에 대해서 설명하는 글을 기재하지는 않겠습니다. 이번에 소개할 건 에픽의 자체적인 MVVM 패턴용 플러그인인 &quot;UMG ViewModel&quot;입니다.&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;기존 개발자 입장에서도 프레임워크를 제작해 MVVM 패턴을 충분히 개발할 수 있지만 해당 플러그인은&amp;nbsp;&lt;b&gt;블루프린트 지원을 많이 준비&lt;/b&gt;해 두었습니다. 이로 인해 가능하다면 개발자는 View Model 만을 바라보면서 개발이 가능하고 UI 디자이너는 만들어진 ViewModel로 바인딩이 가능하게 되었습니다. 물론 디자이너에게 ViewModel 관련된 지식이 요구되지만 개발자 입장에서는 일부 업무를 전가 가능해 협업에 있어 타이밍을 맞추지 않아도 View Model을 우선 개발해도 된다는 장점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기본 사용법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 독스에서는 cpp 코드로 작업하는 부분들에 대해 설명이 부족하기에 이번 블로그 글에서는 해당 부분들에 대해서 다루려고 합니다.&lt;/p&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;b&gt;Module 추가&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1756537993988&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PublicDependencyModuleNames.AddRange(new string[]
{
    //...
    &quot;ModelViewViewModel&quot;,
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 기본적으로 Module에 이렇게 추가를 해 두어야 합니다. 이후 'UMVVMViewModelBase'가 기본 View Model 클래스이므로 이를 상속받아 사용할 수 있습니다.&lt;/p&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;b&gt;ViewModel 용 매크로들을 통해 이벤트를 발생할 수 있도록 소스코드 작성&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1756539375339&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/** After a field value changed. Broadcast the event. */
#define UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(MemberName) \
	BroadcastFieldValueChanged(ThisClass::FFieldNotificationClassDescriptor::MemberName)

/** If the property value changed then set the new value and notify. */
#define UE_MVVM_SET_PROPERTY_VALUE(MemberName, NewValue) \
	SetPropertyValue(MemberName, NewValue, ThisClass::FFieldNotificationClassDescriptor::MemberName)

/** Use this version to set property values that can't be captured as a function arguments (i.e. bitfields). */
#define UE_MVVM_SET_PROPERTY_VALUE_INLINE(MemberName, NewValue) \
	[this, InNewValue = (NewValue)]() { if (MemberName == InNewValue) { return false; } MemberName = InNewValue; BroadcastFieldValueChanged(ThisClass::FFieldNotificationClassDescriptor::MemberName); return true; }()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UMVVMViewModelBase 클래스를 상속받으면 위와 같은 매크로들을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 46.2791%;&quot;&gt;&lt;b&gt;매크로&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 53.7209%;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 46.2791%;&quot;&gt;&lt;b&gt;UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 53.7209%;&quot;&gt;ViewModel 의 Field 가 변경되었다는 이벤트를 발생합니다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 46.2791%;&quot;&gt;&lt;b&gt;UE_MVVM_SET_PROPERTY_VALUE&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 53.7209%;&quot;&gt;ViewModel 의 Field 의 Setter 함수입니다. 만일 값이 달라진 경우 이벤트를 발생합니다 &lt;br /&gt;(UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED 호출과 동일)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 46.2791%;&quot;&gt;&lt;b&gt;UE_MVVM_SET_PROPERTY_VALUE_INLINE&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 53.7209%;&quot;&gt;위 매크로의 인라인 버전입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1756540329782&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UPROPERTY(BlueprintReadWrite, FieldNotify, Setter, Getter, meta = (AllowPrivateAccess = true))
float PlayerInventoryWeight;

void SetPlayerInventoryWeight(const float NewWeight)
{
    UE_MVVM_SET_PROPERTY_VALUE(PlayerInventoryWeight, NewWeight);
}
float GetPlayerInventoryWeight() const { return PlayerInventoryWeight; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로는 FieldNotify, Setter, Getter 속성을 추가한 뒤 Set {필드 이름}, Get {필드 이름}을 위와 같이 선언하면 완료됩니다. 이를 통해 Blueprint에서도 볼 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1756540180621&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UPROPERTY(BlueprintReadWrite, FieldNotify, Getter, meta = (AllowPrivateAccess = true))
FItemEncodedInfo HoveredItemInfo;

// Getter 함수로 인해 Get{필드 이름}() const 함수를 선언해야 합니다.
FItemEncodedInfo GetHoveredItemInfo() const { return HoveredItemInfo; }

// cpp 에서 이런 방식으로 Broadcast 를 호출할 수 있습니다
void UItemListViewModel::HandleItemHover(const FItemEncodedInfo&amp;amp; ItemInfo, bool bIsPlayerSide)
{
	HoveredItemInfo = ItemInfo;
	UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(HoveredItemInfo);
}

// Getter 만 선언하게 되면 GetbIsContextMenuVisible 이라는 이상한 이름을 만들어야 합니다
UPROPERTY(BlueprintReadWrite, FieldNotify, Getter = &quot;GetIsContextMenuVisible&quot;, meta = (AllowPrivateAccess = true))
bool bIsContextMenuVisible;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외 Setter를 두기 애매한 경우 위와 같은 방법으로 해당 아이템이 변경되는 위치에서 BROADCAST 매크로를 호출해 주면 됩니다. 또한 Getter, Setter는 함수 이름을 직접 지정할 수도 있으니 함수 이름을 마음대로 지정하여도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Blueprint에서 사용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 소스코드를 통해 View Model을 제작하였다면 UMG의 View Model 을 설정하여야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P8lZf/btsQeCOsp4x/WpAzaU1E6BOsSeamzam9p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P8lZf/btsQeCOsp4x/WpAzaU1E6BOsSeamzam9p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P8lZf/btsQeCOsp4x/WpAzaU1E6BOsSeamzam9p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP8lZf%2FbtsQeCOsp4x%2FWpAzaU1E6BOsSeamzam9p0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;253&quot; height=&quot;355&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;/figure&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;해당하는 UMG 의 Blueprint에서&amp;nbsp;&lt;b&gt;View Bindings, Viewmodels를&lt;/b&gt; 위 사진의 메뉴에서 활성화하여야 설정이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE0kso/btsQeDfzv33/7Lg1OcdiCfzcmYuLvcn8l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE0kso/btsQeDfzv33/7Lg1OcdiCfzcmYuLvcn8l1/img.png&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;528&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.9382%; margin-right: 10px;&quot; data-widthpercent=&quot;51.54&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE0kso/btsQeDfzv33/7Lg1OcdiCfzcmYuLvcn8l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE0kso%2FbtsQeDfzv33%2F7Lg1OcdiCfzcmYuLvcn8l1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvSqMq/btsQdXFZE0T/fpHmFvk7ETD8uCmQBpRoCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvSqMq/btsQdXFZE0T/fpHmFvk7ETD8uCmQBpRoCk/img.png&quot; data-origin-width=&quot;373&quot; data-origin-height=&quot;340&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.899%;&quot; data-widthpercent=&quot;48.46&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvSqMq/btsQdXFZE0T/fpHmFvk7ETD8uCmQBpRoCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvSqMq%2FbtsQdXFZE0T%2FfpHmFvk7ETD8uCmQBpRoCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;373&quot; height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;이후 Viewmodels 창에서&amp;nbsp;&lt;b&gt;[+Viewmodel]&lt;/b&gt; 메뉴를 누르면 UMG에 View Model 추가가 가능합니다. 이는 여러 개도 가능하니 구조를 짜실 때 해당 부분을 참고하여도 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzdg9w/btsQgdUOGjg/XrUuhAv569IK1G2ztq5qs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzdg9w/btsQgdUOGjg/XrUuhAv569IK1G2ztq5qs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzdg9w/btsQgdUOGjg/XrUuhAv569IK1G2ztq5qs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzdg9w%2FbtsQgdUOGjg%2FXrUuhAv569IK1G2ztq5qs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;901&quot; height=&quot;343&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;b&gt;View Bindings에서&lt;/b&gt; 해당하는 위젯 요소들, 함수에 대해서 바인딩을 추가할 수 있습니다. 여기서 볼 건 중간의 화살표와 Auto로 되어 있는 드롭박스이며 이는 아래 표에서 설명하도록 하겠습니다.&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 35.1163%;&quot;&gt;&lt;b&gt;바인딩 방향&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 64.8837%;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 35.1163%;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;위젯에 한 번(One Time to W&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;idget)&lt;/span&gt; &lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 64.8837%;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 바인딩이 뷰모델에서 위젯에 한 번만 적용됩니다. 이로 인해 선택된 위젯 프로퍼티가 업데이트됩니다. &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 35.1163%;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 위젯으로 단방향(One Way to Widget) &lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 64.8837%;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 바인딩은 뷰 모델에서 위젯으로만 적용됩니다. 뷰 모델에서 관련 변수를 업데이트할 때마다 위젯에 변수가 변경되었음을 알리고 선택된 위젯 프로퍼티를 업데이트합니다. 또는 함수를 선택한 경우 해당 함수를 호출하면 선택된 위젯 프로퍼티가 업데이트됩니다. &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 35.1163%;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 뷰 모델로 단방향(One Way to Viewmodel) &lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 64.8837%;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 바인딩은 위젯에서 뷰 모델로만 적용됩니다. 위젯에서 사용자 또는 코드가 선택된 프로퍼티를 변경할 때마다 해당 변경사항을 뷰 모델 프로퍼티에 적용합니다. 일반적인 예시로는 사용자가 편집한 텍스트 필드 또는 그래픽 옵션이 있습니다. &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 35.1163%;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 양방향 &lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 64.8837%;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 바인딩이 두 방향 모두에 적용됩니다. &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;118&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DEeLy/btsQfIOdPjb/GXUntK8kDMghGunJCA9Fu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DEeLy/btsQfIOdPjb/GXUntK8kDMghGunJCA9Fu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DEeLy/btsQfIOdPjb/GXUntK8kDMghGunJCA9Fu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDEeLy%2FbtsQfIOdPjb%2FGXUntK8kDMghGunJCA9Fu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;118&quot; height=&quot;132&quot; data-origin-width=&quot;118&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ Trigger Option ]&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.186%;&quot;&gt;&lt;b&gt;옵션&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 65.814%;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.186%;&quot;&gt;&lt;b&gt;Immediate&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 65.814%;&quot;&gt;변수가 변경되는 즉시 이벤트를 활성화 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.186%;&quot;&gt;&lt;b&gt;Delayed&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 65.814%;&quot;&gt;변수가 변경된 후 해당 프레임의 마지막에서 이벤트를 활성화 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.186%;&quot;&gt;&lt;b&gt;Tick&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 65.814%;&quot;&gt;프레임의 마지막 시점에 Tick 단위로 항상 이벤트를 활성화 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.186%;&quot;&gt;&lt;b&gt;Auto &lt;/b&gt;&lt;br /&gt;&lt;b&gt;(DelayedWhenSharedElseImmediate)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 65.814%;&quot;&gt;여러곳에서 트리거 된 순간 Delayed, 그 외의 경우 Immediate 처럼 동작합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;View Model을 할당하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후의 문제는 UMG에서 열심히 바인딩 한 View Model 은 어디서 오느냐입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W0QZM/btsQdcwTxeW/sSBQkmd50KpLbH8SwPm4o1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W0QZM/btsQdcwTxeW/sSBQkmd50KpLbH8SwPm4o1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W0QZM/btsQdcwTxeW/sSBQkmd50KpLbH8SwPm4o1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW0QZM%2FbtsQdcwTxeW%2FsSBQkmd50KpLbH8SwPm4o1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;904&quot; height=&quot;424&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&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;Viewmodels에서 설정한 ViewModel을 클릭하면 Detail 창에서 이를 확인할 수 있으며 여기서&amp;nbsp;&lt;b&gt;Creation Type에서&lt;/b&gt; 설정이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.4651%;&quot;&gt;&lt;b&gt;Creation Type&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.5349%;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.4651%;&quot;&gt;&lt;b&gt;Manual&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.5349%;&quot;&gt;시작 시 View Model 은 null 로 비어 있으며 이후 할당을 해야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.4651%;&quot;&gt;&lt;b&gt;Create Instance&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.5349%;&quot;&gt;인스턴스를 자동으로 생성합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.4651%;&quot;&gt;&lt;b&gt;Global Viewmodel&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.5349%;&quot;&gt;UMVVMGameSubsystem 을 활용해 찾아냅니다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.4651%;&quot;&gt;&lt;b&gt;Property Path&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.5349%;&quot;&gt;위젯 초기화 시 함수를 실행해 뷰 모델을 찾습니다. 애니메이션 BP 에서와 유사하게 마침표로 구분된 경로를 활용합니다. (GetPlayerController.Vechicle.ViewModel)&lt;br /&gt;본인일 경우 Self 는 따로 지정하지 않습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.4651%;&quot;&gt;&lt;b&gt;Resolver&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.5349%;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Viewmodel을 제공하는 기능을 가진 객체&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우 기존의 Subsystem 은 후술할 이유로 사용하지 않고 있습니다. 때문에 Manual을 통해 BP에서 설정을 해주고 있습니다. 그 외 Property Path의 경우 View Model을 할당하는게 초기화 시점이라 기본적으로 View Model 을 View(위젯)에서 할당한 게 아니라면, 또는 초기화 이후 시점에 ViewModel을 생성한다면 고려하지 않는 게 좋아 보입니다.&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;Create Instance는 해당 방법을 했을 때 C++에서 가져오기 위해서는 BP 함수를 거쳐야 한다는 문제 등이 있어 거의 사용하고 있지 않습니다. 저의 개인적인 견해입니다.&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;프로젝트의 업무 진행 절차에 따라 Creation Type을 지정하면 좋을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;View Binding에 내 함수를 보여주고 싶어요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 View Binding 창에서 기본으로 정의된 함수가 아닌 내가 cpp에서 만든 위젯 함수를 보여주고 싶다면 아래와 같은 지정자를 추가하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1756537547578&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, BlueprintCosmetic)
void HandleToolTipWidgetChanged(const FItemEncodedInfo&amp;amp; ItemInfo);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzdg9w/btsQgdUOGjg/XrUuhAv569IK1G2ztq5qs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzdg9w/btsQgdUOGjg/XrUuhAv569IK1G2ztq5qs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzdg9w/btsQgdUOGjg/XrUuhAv569IK1G2ztq5qs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzdg9w%2FbtsQgdUOGjg%2FXrUuhAv569IK1G2ztq5qs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;901&quot; height=&quot;343&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&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;이후 View Binding에서 위젯&amp;nbsp; 자체를 Add Widget 한 뒤 함수를 검색해 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;자체 제작한 Subsystem&lt;/b&gt;&lt;/h3&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;&lt;b&gt;어디서든 View Model을 제작하고 언제 어디서든 View Model 을 View(위젯) 이 할당&lt;/b&gt;받을 수 있는 걸 원했습니다. 때문에 기존 서브시스템의 경우 사용할 수가 없었고 이를 위해 아래 서브시스템을 추가해 사용하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1756379365885&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// All copyrights for this code are owned by Aster.

#pragma once

#include &quot;CoreMinimal.h&quot;
#include &quot;Subsystems/GameInstanceSubsystem.h&quot;
#include &quot;PAViewModelSubsystem.generated.h&quot;

class UPABaseViewModel;

/** ViewModel이 준비되었을 때 브로드캐스트될 멀티캐스트 델리게이트입니다. */
DECLARE_MULTICAST_DELEGATE_OneParam(FOnViewModelReady, UPABaseViewModel* /*ViewModel*/);

/** ViewModel을 식별하기 위한 복합 키 구조체입니다. */
USTRUCT()
struct FViewModelKey
{
	GENERATED_BODY()

	UPROPERTY()
	FName Name;

	UPROPERTY()
	TSubclassOf&amp;lt;UPABaseViewModel&amp;gt; Class;

	bool operator==(const FViewModelKey&amp;amp; Other) const;
	bool IsValid() const;

	friend uint32 GetTypeHash(const FViewModelKey&amp;amp; Key)
	{
		return HashCombine(GetTypeHash(Key.Name), GetTypeHash(Key.Class));
	}
};

template&amp;lt;typename T&amp;gt;
concept CViewModel = TIsDerivedFrom&amp;lt;T, UPABaseViewModel&amp;gt;::IsDerived;

/**
 * 프로젝트의 ViewModel을 중앙에서 관리하고, 등록을 구독하는 기능을 제공하는 독립적인 서브시스템입니다.
 */
UCLASS()
class PROJECTPA_API UPAViewModelSubsystem : public UGameInstanceSubsystem
{
	GENERATED_BODY()

public:
	virtual void Deinitialize() override;

	/** 지정된 키로 ViewModel을 등록하고, 이 키를 기다리던 모든 콜백을 실행(브로드캐스트)합니다. */
	void AddViewModel(const FViewModelKey&amp;amp; ViewModelKey, UPABaseViewModel* ViewModel);

	/** 특정 ViewModel이 준비되면 호출될 콜백을 등록(구독)합니다. */
	void BindCallbackOnViewModelAdded(const FViewModelKey&amp;amp; ViewModelKey, const FOnViewModelReady::FDelegate&amp;amp; Callback);
	
	template&amp;lt;CViewModel TViewModel&amp;gt;
	TViewModel* GetViewModel(const FViewModelKey&amp;amp; ViewModelKey)
	{
		return (ViewModelCollection.Contains(ViewModelKey)) ? ViewModelCollection[ViewModelKey] : nullptr;
	}

private:
	UPROPERTY(Transient)
	TMap&amp;lt;FViewModelKey, TObjectPtr&amp;lt;UPABaseViewModel&amp;gt;&amp;gt; ViewModelCollection;

	TMap&amp;lt;FViewModelKey, FOnViewModelReady&amp;gt; PendingCallbacks;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&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;언제 어디서든 View Model을 만들고 등록할 수 있기에 해당 View Model 이 생성되면 Callback 을 받아야 합니다&lt;/li&gt;
&lt;li&gt;이를 구현하기 위해 View Model 을 구분할 2가지 요소 '클래스 타입, FName)'을 준비해 구분할 수 있는 요소를 제작
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FName 이 추가로 지정된 이유는 같은 타입의 View Model 이 여러 개 존재할 가능성도 있어서입니다.&lt;/li&gt;
&lt;li&gt;프로젝트에서 사용 중인 View Model 은 기본적으로 DefaultName을 가지게 해 1개만 있을 수 있는 View Model의 등록을 편하게 하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;View Model 이 언제 생성될지 알 수 없으므로 View에서 이 알림 받기 위해 Callback 들을 준비해 둡니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만일 이미 View Model 이 있다면 해당 Callback을 저장하지 않고 바로 호출해 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1756379393853&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void UPAViewModelSubsystem::BindCallbackOnViewModelAdded(const FViewModelKey&amp;amp; ViewModelKey,
	const FOnViewModelReady::FDelegate&amp;amp; Callback)
{
	if (ViewModelKey.IsValid() == false || Callback.IsBound() == false)
	{
		return;
	}

	if (TObjectPtr&amp;lt;UPABaseViewModel&amp;gt;* FoundViewModel = ViewModelCollection.Find(ViewModelKey))
	{
		Callback.Execute(*FoundViewModel);
	}
	else
	{
		PendingCallbacks.FindOrAdd(ViewModelKey).Add(Callback);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 함수가 핵심으로 View 역할을 하는 각 위젯들에서 해당 함수를 호출해 View Model을 받을 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마주했던 오류 사항들&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직까지는 해당 플러그인이 베타 버전이라 기존 UMG 시스템을 리팩토링 하면서 겪은 오류 사항들을 기입해 봅니다.&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;b&gt;Property Path 가 잘 동작하지 않을 때&lt;/b&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;b&gt;The field for source '[위젯의 요소 이름]' exists but is not accessible at runtime.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위젯 요소가 &lt;b&gt;BlueprintReadWrite&lt;/b&gt; 가 없어서 생기는 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 관련 프로퍼티가 없거나 BlueprintReadOnly 인 경우에도 동일한 오류가 발생하므로 해당하는 요소는 꼭 BlueprintReadWtire로 지정해야 합니다.&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;b&gt;View Model을 Widget BP에서 설정했는데 다시 시작하면 해당 정보가 보이지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 새로운 WBP를 제작한 뒤 View Model을 할당합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그래도 사라진다면 DefaultEngine.ini의 [CoreRedirects] 부분들을 제거해 보세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 왜인지는 모르나 &lt;b&gt;ConstructorHelpers를&lt;/b&gt; 통해 해당 BP를 로드하면 바인딩이 사라지는 문제가 있습니다&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사용 후기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 자체적으로 MVVM 프레임 워크를 만들 필요가 없이 공식의 플러그인들을 쓸 수 있는 건 큰 장점입니다. 또한 가장 큰 장점으로는&amp;nbsp;&lt;b&gt;블루프린트 바인딩 시 GUI를 통해 바인딩&lt;/b&gt;이 가능합니다. 이 덕분에 UI 디자이너와 병행적인 업무 진행이 가능하며 더 이상 UI 코드는 UI 적인 부분만 신경을 쓰면 됩니다. 기능적이나 이벤트 바인딩 등은 View Model에서 진행하니깐요.&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;다만 위에서 기술한 여러 오류들이 있으며 View Model들이 UObject 들이라 모든 위젯에 대해서 (특히 ListView 안의 각 요소들이 걱정됩니다) View Model을 제작하게 되면 해당 객체들의 성능과 생명주기 관리가 걱정요소로 떠오르기도 합니다. 또한 디자이너들에게도 Binding 작업이 전가될 수 있어 프로젝트 내부 정책에 따라 개발자가 바인딩을 진행하게 될 가능성이 큽니다. 이 경우에는 더 가볍게 직접 MVVM 패턴을 적용해도 될 정도입니다. 개발자만 사용하는데 플러그인을 쓰기에는 블루프린트 지원용 함수가 많아 과한 느낌이 있거든요.&lt;/p&gt;</description>
      <category>쾌락없는 책임 (공부)/Unreal</category>
      <author>허스크</author>
      <guid isPermaLink="true">https://husk321.tistory.com/474</guid>
      <comments>https://husk321.tistory.com/474#entry474comment</comments>
      <pubDate>Sun, 31 Aug 2025 10:35:20 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal] Unreal Engine 에서 RAII 패턴을 통해 자동으로 함수에서 EndAbility 하기</title>
      <link>https://husk321.tistory.com/473</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트 내에서 아이템 종류에 따라 다른 어빌리티들을 활성화해주는&amp;nbsp;&lt;b&gt;라우터 어빌리티&lt;/b&gt;가 필요하게 되었습니다. 해당 라우터 어빌리티의 역할은 ActivateAbility 에서 상황에 따라 어빌리티를 활성화 한 뒤 EndAbility를 해주면 됩니다.&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;다만 코드에서 Early Return 을 통해 여러 조건이 맞지 않으면 return을 미리 하도록 작성했는데 해당 코드에서 EndAbility를 계속 써야 하는 공수와 언제 내가 이걸 빼먹었을지 모른다는 공포가.... 있었습니다. 때문에 간단하게 RAII 패턴의 구조체를 만들어 ActivateAbility 함수(스코프)를 벗어나면 EndAbility를 자동으로 호출하도록 하였습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;코드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 제작한 RAII 패턴의 구조체입니다. 생성자에서 어빌리티와 Cancel 을 호출할지의 옵션만 받도록 제작하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1755607153598&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// All copyrights for this code are owned by Aster.

#pragma once

#include &quot;CoreMinimal.h&quot;
#include &quot;ProjectPA/Abilities/PAGameplayAbility.h&quot;

#define SCOPE_END_ABILITY() const FScopedAbilityEnd ANONYMOUS_VARIABLE(ScopedEnd)(this);

/**
 * 아래 구조체는 RAII 패턴으로 만들어져 해당하는 스코프를 벗어나면 바로 EndAbility 를 호출하게 됩니다.
 * 어빌리티에서만 사용 가능하며 특수한 경우가 아니라면 사용하지 말도록 합니다.
 */
struct FScopedAbilityEnd
{
	explicit FScopedAbilityEnd(UPAGameplayAbility* InAbility, bool bForceCancel = false)
		: Ability(InAbility)
		, bShouldCancel(bForceCancel)
	{
	}

	~FScopedAbilityEnd()
	{
		if (Ability &amp;amp;&amp;amp; Ability-&amp;gt;IsActive())
		{
			if (bShouldCancel)
			{
				Ability-&amp;gt;K2_CancelAbility();
			}
			else
			{
				Ability-&amp;gt;SafeEndAbility();
			}
		}
	}

	FScopedAbilityEnd(const FScopedAbilityEnd&amp;amp;) = delete;
	FScopedAbilityEnd&amp;amp; operator=(const FScopedAbilityEnd&amp;amp;) = delete;

private:
	UPAGameplayAbility* Ability = nullptr;
	bool bShouldCancel = false;
};&lt;/code&gt;&lt;/pre&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;&lt;b&gt;EndAbility 와 K2_EndAbility 함수는 protected로 선언되어 있어 외부에서 어빌리티를 종료할 수 있는 함수를 제작해야&lt;/b&gt; 합니다. 외부에서 부를 때의 위험성이 있을 수 있어 해당 함수를 위 구조체에서만 만들 수 있도록 하던가 등의 조치가 있으면 더 좋을 거 같기는 합니다만, 일단 저렇게 제작하였습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755607210428&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void UUseEquippingItemAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
	const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
	const FGameplayEventData* TriggerEventData)
{
	// Auto-Call EndAbility
	SCOPE_END_ABILITY();
	
	if (HasAuthority(&amp;amp;ActivationInfo) == false)
	{
		return;
	}

	const AActor* OwnerActor = ActorInfo-&amp;gt;AvatarActor.Get();
	UEquipmentComponent* EquipmentComponent = OwnerActor ? OwnerActor-&amp;gt;FindComponentByClass&amp;lt;UEquipmentComponent&amp;gt;() : nullptr;
    
	if (IsValid(EquipmentComponent) == false)
	{
		PALog_E(TEXT(&quot;EquipmentComponent is nullptr&quot;));
		return;
	}
    
	const FItemEncodedInfo&amp;amp; EquippingSlot = EquipmentComponent-&amp;gt;GetEquippingItem();
	if (EquippingSlot.IsValid() == false)
	{
		PALog_E(TEXT(&quot;EquippingSlot is not valid&quot;));
		return;
	}
	
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);

	if (FItemDataTable* ItemDataTable = EquipmentComponent-&amp;gt;GetEquippingItemData())
	{
		EItemType ItemType = ItemDataTable-&amp;gt;ItemType;
		if (ItemTypeAbilityTags.Contains(ItemType))
		{
			if (UAbilitySystemComponent* ASC = ActorInfo-&amp;gt;AbilitySystemComponent.Get())
			{
				ASC-&amp;gt;TryActivateAbilityByClass(ItemTypeAbilityTags[ItemType]);
			}
		}	
	}
}&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 해당 매크로를 활용하면 바로 어빌리티가 종료되어버리니 관련된 사이드 이펙트들을 주의해야 합니다. 저의 경우 다른 곳에서도 활용하려 했으나 EndAbility 가 활성화 되면 CurrentEventData 가 날아가버려 관련된 정보를 활용할 수 없다는 문제가 있었습니다. 이런 것들에 주의한다면 Early Return으로 코드를 짰을 때 EndAbility를 반복해서, 호출하지 않는 경우에 대한 문제를 고민하지 않아도 됩니다.&lt;/p&gt;</description>
      <category>쾌락없는 책임 (공부)/Unreal</category>
      <author>허스크</author>
      <guid isPermaLink="true">https://husk321.tistory.com/473</guid>
      <comments>https://husk321.tistory.com/473#entry473comment</comments>
      <pubDate>Wed, 20 Aug 2025 12:20:20 +0900</pubDate>
    </item>
  </channel>
</rss>