<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>editor66484 님의 블로그</title>
    <link>https://editor66484.tistory.com/</link>
    <description>editor66484 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 24 Sep 2026 03:29:50 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>editor66484</managingEditor>
    <item>
      <title>유닛 이동 불량 문제 해결: 부착 무기의 Collision 설정</title>
      <link>https://editor66484.tistory.com/39</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;문제 &lt;/span&gt;&lt;span&gt;상황&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;병영에서 &lt;/span&gt;&lt;span&gt;생산된 &lt;/span&gt;&lt;span&gt;유닛에게 &lt;/span&gt;&lt;span&gt;집결지 &lt;/span&gt;&lt;span&gt;또는 &lt;/span&gt;&lt;span&gt;특정 &lt;/span&gt;&lt;span&gt;위치로 &lt;/span&gt;&lt;span&gt;이동 &lt;/span&gt;&lt;span&gt;명령을 &lt;/span&gt;&lt;span&gt;내렸을 &lt;/span&gt;&lt;span&gt;때, &lt;/span&gt;&lt;span&gt;다음과 &lt;/span&gt;&lt;span&gt;같은 &lt;/span&gt;&lt;span&gt;문제가 &lt;/span&gt;&lt;span&gt;발생했다.&lt;/span&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;span&gt;목적지를 &lt;/span&gt;&lt;span&gt;향해 &lt;/span&gt;&lt;span&gt;자연스럽게 &lt;/span&gt;&lt;span&gt;이동하지 &lt;/span&gt;&lt;span&gt;못함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이동 &lt;/span&gt;&lt;span&gt;도중 &lt;/span&gt;&lt;span&gt;다른 &lt;/span&gt;&lt;span&gt;유닛 &lt;/span&gt;&lt;span&gt;주변에서 &lt;/span&gt;&lt;span&gt;끼는 &lt;/span&gt;&lt;span&gt;현상 &lt;/span&gt;&lt;span&gt;발생&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;방향을 &lt;/span&gt;&lt;span&gt;반복해서 &lt;/span&gt;&lt;span&gt;변경하며 &lt;/span&gt;&lt;span&gt;비비는 &lt;/span&gt;&lt;span&gt;듯한 &lt;/span&gt;&lt;span&gt;움직임 &lt;/span&gt;&lt;span&gt;발생&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;목적지 &lt;/span&gt;&lt;span&gt;근처까지 &lt;/span&gt;&lt;span&gt;정상적으로 &lt;/span&gt;&lt;span&gt;이동하지 &lt;/span&gt;&lt;span&gt;못함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;동일한 &lt;/span&gt;&lt;span&gt;이동 &lt;/span&gt;&lt;span&gt;코드를 &lt;/span&gt;&lt;span&gt;사용하는 &lt;/span&gt;&lt;span&gt;다른 &lt;/span&gt;&lt;span&gt;유닛은 &lt;/span&gt;&lt;span&gt;정상적으로 &lt;/span&gt;&lt;span&gt;움직임&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처음에는 &lt;/span&gt;&lt;span&gt;AI &lt;/span&gt;&lt;span&gt;이동이나 &lt;/span&gt;&lt;span&gt;NavMesh &lt;/span&gt;&lt;span&gt;문제처럼 &lt;/span&gt;&lt;span&gt;보였지만, &lt;/span&gt;&lt;span&gt;실제 &lt;/span&gt;&lt;span&gt;원인은 &lt;/span&gt;&lt;span&gt;유닛에 &lt;/span&gt;&lt;span&gt;부착된 &lt;/span&gt;&lt;span&gt;무기의 &lt;/span&gt;&lt;span&gt;충돌 &lt;/span&gt;&lt;span&gt;설정이었다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;원인 &lt;/span&gt;&lt;span&gt;분석 &lt;/span&gt;&lt;span&gt;과정&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;1. &lt;/span&gt;&lt;span&gt;이동 &lt;/span&gt;&lt;span&gt;명령 &lt;/span&gt;&lt;span&gt;전달 &lt;/span&gt;&lt;span&gt;여부 &lt;/span&gt;&lt;span&gt;확인&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;녹화 &lt;/span&gt;&lt;span&gt;영상을 &lt;/span&gt;&lt;span&gt;통해 &lt;/span&gt;&lt;span&gt;유닛의 &lt;/span&gt;&lt;span&gt;움직임을 &lt;/span&gt;&lt;span&gt;확인했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;유닛이 &lt;/span&gt;&lt;span&gt;완전히 &lt;/span&gt;&lt;span&gt;정지한 &lt;/span&gt;&lt;span&gt;것은 &lt;/span&gt;&lt;span&gt;아니었으며 &lt;/span&gt;&lt;span&gt;다음 &lt;/span&gt;&lt;span&gt;동작은 &lt;/span&gt;&lt;span&gt;정상적으로 &lt;/span&gt;&lt;span&gt;실행되고 &lt;/span&gt;&lt;span&gt;있었다.&lt;/span&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;span&gt;이동 &lt;/span&gt;&lt;span&gt;명령이 &lt;/span&gt;&lt;span&gt;전달됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이동 &lt;/span&gt;&lt;span&gt;목적지 &lt;/span&gt;&lt;span&gt;표시가 &lt;/span&gt;&lt;span&gt;생성됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;유닛의 &lt;/span&gt;&lt;span&gt;월드 &lt;/span&gt;&lt;span&gt;위치가 &lt;/span&gt;&lt;span&gt;변경됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 &lt;/span&gt;&lt;span&gt;이동 &lt;/span&gt;&lt;span&gt;명령이나 &lt;/span&gt;MoveTo&lt;span&gt;가 &lt;/span&gt;&lt;span&gt;전혀 &lt;/span&gt;&lt;span&gt;실행되지 &lt;/span&gt;&lt;span&gt;않는 &lt;/span&gt;&lt;span&gt;문제는 &lt;/span&gt;&lt;span&gt;아니라고 &lt;/span&gt;&lt;span&gt;판단했다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;2. &lt;/span&gt;&lt;span&gt;의심했던 &lt;/span&gt;&lt;span&gt;항목&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;초기에는 &lt;/span&gt;&lt;span&gt;다음 &lt;/span&gt;&lt;span&gt;항목들을 &lt;/span&gt;&lt;span&gt;원인으로 &lt;/span&gt;&lt;span&gt;의심했다.&lt;/span&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;span&gt;NavMesh&lt;/span&gt;&lt;span&gt;가 &lt;/span&gt;&lt;span&gt;목적지까지 &lt;/span&gt;&lt;span&gt;연결되지 &lt;/span&gt;&lt;span&gt;않은 &lt;/span&gt;&lt;span&gt;문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Capsule &lt;/span&gt;&lt;span&gt;Component &lt;/span&gt;&lt;span&gt;크기가 &lt;/span&gt;&lt;span&gt;지나치게 &lt;/span&gt;&lt;span&gt;큰 &lt;/span&gt;&lt;span&gt;문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Detour &lt;/span&gt;&lt;span&gt;Crowd &lt;/span&gt;&lt;span&gt;회피 &lt;/span&gt;&lt;span&gt;처리 &lt;/span&gt;&lt;span&gt;문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이동 &lt;/span&gt;&lt;span&gt;Blend &lt;/span&gt;&lt;span&gt;Space &lt;/span&gt;&lt;span&gt;또는 &lt;/span&gt;&lt;span&gt;Root &lt;/span&gt;&lt;span&gt;Motion &lt;/span&gt;&lt;span&gt;문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;집결지가 &lt;/span&gt;&lt;span&gt;NavMesh &lt;/span&gt;&lt;span&gt;바깥에 &lt;/span&gt;&lt;span&gt;배치된 &lt;/span&gt;&lt;span&gt;문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;여러 &lt;/span&gt;&lt;span&gt;유닛이 &lt;/span&gt;&lt;span&gt;동일한 &lt;/span&gt;&lt;span&gt;목적지로 &lt;/span&gt;&lt;span&gt;이동하며 &lt;/span&gt;&lt;span&gt;발생한 &lt;/span&gt;&lt;span&gt;충돌&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 &lt;/span&gt;&lt;span&gt;같은 &lt;/span&gt;&lt;span&gt;환경에서 &lt;/span&gt;&lt;span&gt;다른 &lt;/span&gt;&lt;span&gt;유닛은 &lt;/span&gt;&lt;span&gt;정상적으로 &lt;/span&gt;&lt;span&gt;이동했기 &lt;/span&gt;&lt;span&gt;때문에, &lt;/span&gt;&lt;span&gt;문제가 &lt;/span&gt;&lt;span&gt;발생하는 &lt;/span&gt;&lt;span&gt;유닛 &lt;/span&gt;&lt;span&gt;BP&lt;/span&gt;&lt;span&gt;의 &lt;/span&gt;&lt;span&gt;컴포넌트 &lt;/span&gt;&lt;span&gt;설정을 &lt;/span&gt;&lt;span&gt;비교했다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;실제 &lt;/span&gt;&lt;span&gt;원인&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;문제 &lt;/span&gt;&lt;span&gt;유닛의 &lt;/span&gt;Weapon&lt;span&gt; &lt;/span&gt;&lt;span&gt;컴포넌트에 &lt;/span&gt;&lt;span&gt;다음 &lt;/span&gt;&lt;span&gt;충돌 &lt;/span&gt;&lt;span&gt;설정이 &lt;/span&gt;&lt;span&gt;적용되어 &lt;/span&gt;&lt;span&gt;있었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Collision Presets       = BlockAllDynamic
Collision Enabled       = Query and Physics
Generate Overlap Events = 활성화&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bowB0A/dJMcagf0ML8/qZMYiylcLi8hz3FNkylQM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bowB0A/dJMcagf0ML8/qZMYiylcLi8hz3FNkylQM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bowB0A/dJMcagf0ML8/qZMYiylcLi8hz3FNkylQM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbowB0A%2FdJMcagf0ML8%2FqZMYiylcLi8hz3FNkylQM1%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;1920&quot; height=&quot;1032&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;유닛 이동 불량 문제 해결: 부착 무기의 Collision 설정&lt;/span&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;문제 상황&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;병영에서 생산된 유닛에게 집결지 또는 특정 위치로 이동 명령을 내렸을 때, 다음과 같은 문제가 발생했다.&lt;/span&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;span&gt;목적지를 향해 자연스럽게 이동하지 못함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이동 도중 다른 유닛 주변에서 끼는 현상 발생&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;방향을 반복해서 변경하며 비비는 듯한 움직임 발생&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;목적지 근처까지 정상적으로 이동하지 못함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;동일한 이동 코드를 사용하는 다른 유닛은 정상적으로 움직임&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처음에는 AI 이동이나 NavMesh 문제처럼 보였지만, 실제 원인은 유닛에 부착된 무기의 충돌 설정이었다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;원인 분석 과정&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;1. 이동 명령 전달 여부 확인&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;녹화 영상을 통해 유닛의 움직임을 확인했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;유닛이 완전히 정지한 것은 아니었으며 다음 동작은 정상적으로 실행되고 있었다.&lt;/span&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;span&gt;이동 명령이 전달됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이동 목적지 표시가 생성됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;유닛의 월드 위치가 변경됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 이동 명령이나 &lt;/span&gt;MoveTo&lt;span&gt;가 전혀 실행되지 않는 문제는 아니라고 판단했다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;2. 의심했던 항목&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;초기에는 다음 항목들을 원인으로 의심했다.&lt;/span&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;span&gt;NavMesh가 목적지까지 연결되지 않은 문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Capsule Component 크기가 지나치게 큰 문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Detour Crowd 회피 처리 문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이동 Blend Space 또는 Root Motion 문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;집결지가 NavMesh 바깥에 배치된 문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;여러 유닛이 동일한 목적지로 이동하며 발생한 충돌&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 같은 환경에서 다른 유닛은 정상적으로 이동했기 때문에, 문제가 발생하는 유닛 BP의 컴포넌트 설정을 비교했다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;실제 원인&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;문제 유닛의 &lt;/span&gt;Weapon&lt;span&gt; 컴포넌트에 다음 충돌 설정이 적용되어 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Collision Presets       = BlockAllDynamic
Collision Enabled       = Query and Physics
Generate Overlap Events = 활성화&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;무기는 캐릭터의 손에 부착된 시각적 컴포넌트였지만, &lt;/span&gt;BlockAllDynamic&lt;span&gt;이 적용되어 다른 유닛 및 오브젝트와 실제로 충돌하고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;특히 무기 메시가 캐릭터의 Capsule 바깥으로 길게 돌출되어 있었기 때문에 다음 현상이 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;유닛 이동
&amp;rarr; 무기가 다른 유닛과 충돌
&amp;rarr; 이동 방향 변경
&amp;rarr; Crowd Avoidance가 새로운 경로 계산
&amp;rarr; 무기가 다시 충돌
&amp;rarr; 끼거나 비비는 것처럼 보임&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, 캐릭터의 Capsule뿐만 아니라 부착된 무기까지 이동 충돌에 관여하고 있었던 것이 원인이었다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;해결 방법&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;문제가 발생한 유닛 BP를 열고 &lt;/span&gt;Weapon&lt;span&gt; 컴포넌트의 설정을 변경했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Components
└─ Weapon
   └─ Details
      └─ Collision&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;변경 전:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Collision Presets       = BlockAllDynamic
Generate Overlap Events = 활성화
Can Character Step Up On = Yes&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;변경 후:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Collision Presets       = NoCollision
Generate Overlap Events = 비활성화
Can Character Step Up On = No&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Physics 항목도 다음과 같이 설정했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Simulate Physics = 비활성화
Enable Gravity   = 비활성화&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;정상적인 충돌 구성&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;캐릭터 유닛은 이동 충돌을 &lt;/span&gt;Capsule Component&lt;span&gt; 하나에서 담당하도록 구성하는 것이 안전하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Capsule Component
└─ 이동 및 월드 충돌 담당

Character Mesh
└─ NoCollision

Weapon
└─ NoCollision

장식용 Static/Skeletal Mesh
└─ NoCollision&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Capsule Component&lt;span&gt;까지 &lt;/span&gt;NoCollision&lt;span&gt;으로 변경하면 안 된다. Capsule은 캐릭터 이동과 월드 충돌 판정에 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;결과&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Weapon&lt;span&gt;의 &lt;/span&gt;Collision Presets&lt;span&gt;를 &lt;/span&gt;NoCollision&lt;span&gt;으로 변경한 후 다음 결과를 확인했다.&lt;/span&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;span&gt;유닛이 목적지를 향해 정상적으로 이동함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;다른 유닛 주변에서 끼는 현상이 사라짐&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;방향을 반복해서 바꾸는 현상이 사라짐&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;집결지 이동이 정상적으로 완료됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;별도의 AI 이동 코드 수정 없이 문제가 해결됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;트러블슈팅을 통해 알게 된 점&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;유닛 이동이 이상하다고 해서 반드시 NavMesh나 AI 코드 문제인 것은 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;캐릭터에 부착된 다음 컴포넌트들도 이동을 방해할 수 있다.&lt;/span&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;span&gt;무기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;방패&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;장식품&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;투구&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;가방&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이펙트용 Static Mesh&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;충돌이 활성화된 자식 컴포넌트&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;특히 메시가 Capsule 바깥으로 돌출되어 있다면 &lt;/span&gt;BlockAllDynamic&lt;span&gt; 설정이 이동에 큰 영향을 줄 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;신규 유닛 점검 항목&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;새로운 유닛 BP를 만들거나 무기를 부착한 후에는 다음 항목을 확인한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;[ ] Capsule Component만 이동 충돌을 담당하는가?
[ ] Character Mesh가 NoCollision인가?
[ ] Weapon이 NoCollision인가?
[ ] Weapon의 Generate Overlap Events가 꺼져 있는가?
[ ] Weapon의 Simulate Physics가 꺼져 있는가?
[ ] Weapon의 Enable Gravity가 꺼져 있는가?
[ ] 이동 Blend Space가 실제 속도를 입력받는가?
[ ] Root Motion이 의도하지 않게 활성화되어 있지 않은가?
[ ] 목적지가 NavMesh 영역 안에 있는가?&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;결론&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 이동 불량 문제는 AI 이동 코드가 아니라, 유닛에 부착된 무기 메시의 &lt;/span&gt;BlockAllDynamic&lt;span&gt; 충돌 설정 때문에 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;시각적 표현만 담당하는 무기와 장식 컴포넌트에는 원칙적으로 &lt;/span&gt;NoCollision&lt;span&gt;을 적용하고, 캐릭터 이동 충돌은 &lt;/span&gt;Capsule Component&lt;span&gt;에서 일관되게 처리해야 한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>StrikForge 프로젝트/트러블슈팅</category>
      <author>editor66484</author>
      <guid isPermaLink="true">https://editor66484.tistory.com/39</guid>
      <comments>https://editor66484.tistory.com/39#entry39comment</comments>
      <pubDate>Wed, 23 Sep 2026 21:53:04 +0900</pubDate>
    </item>
    <item>
      <title>유닛 이동 불량 원인 분석과 생산&amp;middot;튜토리얼 스폰 구조 정리</title>
      <link>https://editor66484.tistory.com/38</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;1. 유닛 이동 이상 현상 분석&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;병영에서 생산된 유닛이 목표 지점으로 자연스럽게 이동하지 못하고, 주변에서 끼거나 비비는 듯한 현상이 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처음에는 다음 원인들을 의심했다.&lt;/span&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;span&gt;NavMesh 경로 문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;유닛 Capsule 크기 문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Crowd Avoidance 문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이동 애니메이션 또는 Root Motion 문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;집결지 위치 문제&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;녹화 영상을 확인한 결과, 이동 명령 자체는 정상적으로 전달되고 있었으며 유닛의 위치도 실제로 변경되고 있었다. 따라서 경로 탐색보다는 개별 컴포넌트의 충돌 설정을 우선 확인했다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;2. 이동 문제의 실제 원인 발견&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;문제 유닛의 무기 &lt;/span&gt;Static Mesh Component&lt;span&gt;에 다음 설정이 적용되어 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Collision Presets       = BlockAllDynamic
Generate Overlap Events = 활성화&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;무기 메시가 캐릭터의 Capsule 바깥으로 길게 돌출되어 있어, 다른 유닛이나 월드 오브젝트와 충돌하면서 이동을 방해하고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음과 같이 변경했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Weapon
├─ Collision Presets       = NoCollision
├─ Generate Overlap Events = 비활성화
├─ Simulate Physics        = 비활성화
└─ Enable Gravity          = 비활성화&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;변경 후 유닛 이동이 정상적으로 돌아오는 것을 확인했다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;알게 된 점&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;캐릭터의 이동 충돌은 기본적으로 &lt;/span&gt;Capsule Component&lt;span&gt;가 담당해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Capsule Component &amp;rarr; 이동 충돌 담당
Character Mesh    &amp;rarr; NoCollision
Weapon Mesh       &amp;rarr; NoCollision&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;캐릭터에 부착된 무기나 장식용 메시가 &lt;/span&gt;BlockAllDynamic&lt;span&gt;으로 설정되어 있으면 다음 문제가 발생할 수 있다.&lt;/span&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;span&gt;다른 유닛과 서로 걸림&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이동 중 방향을 반복해서 변경함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;목적지 주변에서 비비는 현상&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;좁은 위치를 통과하지 못함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Crowd Avoidance가 비정상적으로 보임&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 새로운 무기 메시를 부착할 때는 가장 먼저 충돌 설정을 확인해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;튜토리얼 적 스폰 구조 확인&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;튜토리얼 진행에 맞춰 적을 생성하려면 맵의 &lt;/span&gt;UnitSpawner&lt;span&gt;와 튜토리얼의 &lt;/span&gt;Gameplay Tag&lt;span&gt;를 연결해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;DT_SFStageIntro 목표 완료
        &amp;darr;
Gameplay Tag 신호 발생
        &amp;darr;
UnitSpawner의 Activation Objective Tag와 비교
        &amp;darr;
Spawner 활성화
        &amp;darr;
Initial Delay 이후 적 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;튜토리얼 진행을 기다리는 스포너는 다음과 같이 설정한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Start Active             = 비활성화
Activation Objective Tag = 튜토리얼에서 발생하는 목표 태그&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Start Active&lt;span&gt;가 활성화되어 있으면 튜토리얼 목표와 관계없이 게임 시작 시 바로 적을 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;생산 건물 데이터 구조 확인&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;생산 건물에서 표시되는 유닛 목록은 건물 BP에 직접 들어 있는 것이 아니라 건물 Definition의 &lt;/span&gt;Tiers &amp;rarr; Spawnable Units&lt;span&gt;에서 결정된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여우 진영 아카데미 데이터 위치:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;Content/Core/Primary/Buildings/Definitions/Animal/DA_AnimalRanged&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;유닛 생성 흐름:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;DA_AnimalRanged
&amp;rarr; 티어별 Spawnable Units
&amp;rarr; DT_Units의 Row Name 검색
&amp;rarr; 해당 행의 Unit Class 로드
&amp;rarr; 실제 유닛 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 생산 버튼은 보이지만 엉뚱한 유닛이 생성된다면 &lt;/span&gt;DT_Units &amp;rarr; 해당 행 &amp;rarr; Unit Class&lt;span&gt; 연결을 확인해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;오늘 해결한 문제&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;병영 생산 유닛이 주변에서 끼며 비정상적으로 움직이던 문제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;무기 메시의 충돌이 유닛 이동에 영향을 주는 원인 확인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;튜토리얼 목표와 &lt;/span&gt;UnitSpawner&lt;span&gt; 활성화 연결 구조 확인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;생산 건물과 &lt;/span&gt;DT_Units&lt;span&gt; 사이의 유닛 생성 구조 확인&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;다음 작업&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;새 유닛 BP를 만들 때 무기 및 장식 메시의 충돌 설정 점검&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;아카데미 건물이 실제 건설 목록에 표시되는지 확인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;아카데미 티어별 생산 목록 확인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;1티어 유닛부터 순서대로 기능 테스트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;각 유닛 생산 후 집결지 이동이 정상인지 재검증&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>StrikForge 프로젝트/작업일지</category>
      <author>editor66484</author>
      <guid isPermaLink="true">https://editor66484.tistory.com/38</guid>
      <comments>https://editor66484.tistory.com/38#entry38comment</comments>
      <pubDate>Wed, 23 Sep 2026 21:50:30 +0900</pubDate>
    </item>
    <item>
      <title>Unit 적 RowName설정 오류 및 공격 설정 변경</title>
      <link>https://editor66484.tistory.com/37</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 적 유닛 체력이 1로 시작하는 문제&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격 Anim Notify의 피해량을 검증하려고 했지만 적 유닛이 한 번의 공격에 바로 사망했다. 처음에는 적 유닛의 체력이 실제로 1로 설정된 것으로 의심했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적 유닛 스포너에는 다음 Row Name이 설정되어 있었다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Infantry&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 DT_Units를 확인한 결과 Infantry라는 Row Name은 존재하지 않았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1030&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwxDUH/dJMcahTdDvu/JsFrP3fcN45qBDKg8DFyqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwxDUH/dJMcahTdDvu/JsFrP3fcN45qBDKg8DFyqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwxDUH/dJMcahTdDvu/JsFrP3fcN45qBDKg8DFyqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwxDUH%2FdJMcahTdDvu%2FJsFrP3fcN45qBDKg8DFyqk%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;1920&quot; height=&quot;1030&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1030&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;유닛 스탯 초기화 구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;UnitSpawner의 Unit Row Name
&amp;rarr; DT_Units에서 동일한 행 검색
&amp;rarr; FUnitStatData 로드
&amp;rarr; HealthComponent 초기화&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Infantry 행을 찾지 못하면서 다음 초기화가 실행되지 않았다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;HealthComponent-&amp;gt;InitializeHealth(InRow.MaxHealth);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 적 유닛에는 HealthComponent의 초기 기본값이 남았다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Current Health = 1
Max Health = 100&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 방향&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스포너의 Unit Row Name을 실제 데이터 테이블에 존재하는 행으로 변경해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 데이터 연결은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Brawler   &amp;rarr; BP_TestUnit
Berserker &amp;rarr; BP_TestUnit2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 생성되는 적 유닛 클래스에 맞춰 Brawler 또는 Berserker를 사용한다. 별도의 적 스탯이 필요하면 추후 Infantry 행을 새로 추가하는 방법도 사용할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배운 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 테이블 기반 초기화에 실패하면 객체의 기본값이 남기 때문에, 단순히 &amp;ldquo;체력이 잘못 설정됐다&amp;rdquo;고 판단하기보다 다음 항목을 먼저 확인해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스포너의 Row Name&lt;/li&gt;
&lt;li&gt;데이터 테이블에 해당 행이 존재하는지&lt;/li&gt;
&lt;li&gt;Row Name의 철자와 대소문자&lt;/li&gt;
&lt;li&gt;실행 로그의 데이터 조회 실패 경고&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 공격 애니메이션 한 번에 피해가 여러 번 들어가는 문제&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격 애니메이션은 한 번 재생됐지만 실제로는 피해가 2~3회 적용되어 적이 예상보다 빠르게 사망했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 여우 유닛은 한 공격 애니메이션에 두 번의 타격 동작이 있어서 일반 공격과 동일하게 처리하기 어려웠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 구조의 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 공격 처리는 애니메이션의 타격 프레임과 관계없이 PerformAttack() 실행 시점에 즉시 피해를 적용했다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;공격 조건 만족
&amp;rarr; PerformAttack 실행
&amp;rarr; 즉시 피해 적용
&amp;rarr; 공격 애니메이션 재생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서는 공격 Tick, 쿨다운, 몽타주 재호출 등의 상황에 따라 애니메이션과 피해 횟수가 어긋날 수 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 피해 계산을 Anim Notify가 실행되는 순간으로 이동했다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;PerformAttack
&amp;rarr; 공격 대상 임시 저장
&amp;rarr; 공격 몽타주 재생
&amp;rarr; 타격 프레임에서 BP_AN_AttackHit 실행
&amp;rarr; HandleAttackHit 호출
&amp;rarr; 실제 피해 적용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BP_AN_AttackHit에서는 Notify를 발생시킨 Skeletal Mesh의 소유자를 찾아 UnitBase로 변환한 후 공격 피해 함수를 호출한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b624Sh/dJMcagfNNUp/69gNktSeob69V7lZKEa470/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b624Sh/dJMcagfNNUp/69gNktSeob69V7lZKEa470/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b624Sh/dJMcagfNNUp/69gNktSeob69V7lZKEa470/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb624Sh%2FdJMcagfNNUp%2F69gNktSeob69V7lZKEa470%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;1919&quot; height=&quot;1080&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1080&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;h3 data-ke-size=&quot;size23&quot;&gt;DamageRatio 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 Notify 블루프린트를 일반 공격과 2연타 공격에서 공통으로 사용하기 위해 DamageRatio 변수를 추가했다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Received Notify
&amp;rarr; Get Owner
&amp;rarr; Cast To UnitBase
&amp;rarr; HandleAttackHit(DamageRatio)
&amp;rarr; Return True&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격 방식별 설정은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;공격 종류Notify 개수각 DamageRatio총 피해
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;휴먼 일반 공격&lt;/td&gt;
&lt;td&gt;1개&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;공격력의 100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;여우 2연타&lt;/td&gt;
&lt;td&gt;2개&lt;/td&gt;
&lt;td&gt;각각 0.5&lt;/td&gt;
&lt;td&gt;공격력의 50% + 50%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 공격력이 10인 여우 유닛은 한 공격 주기에서 5 + 5의 피해를 적용한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가 안전장치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Notify가 실수로 중복 배치되어도 피해가 무한히 적용되지 않도록 유닛별 AttackHitCount를 사용했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;휴먼: AttackHitCount = 1&lt;/li&gt;
&lt;li&gt;여우: AttackHitCount = 2&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배운 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Anim Notify의 개수는 애니메이션의 실제 타격 횟수와 일치시켜야 한다. 또한 다단 공격에서는 각 타격 피해의 합이 데이터 테이블의 최종 공격력과 일치하는지 확인해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 공격 중 후퇴 명령이 적용되지 않는 문제&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닛이 적을 공격하는 도중 뒤쪽을 클릭해도 공격을 중단하지 않고 계속 같은 적을 공격했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 우클릭 이동도 현재 구조에서는 AttackMove로 처리된다. 공격을 취소해도 바로 다음 Tick에서 가까운 적을 다시 탐색하면서 같은 적과 다시 교전했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 명령을 Move로 바꾸면 후퇴는 가능하지만, 기존 요구사항인 다음 기능이 사라지는 문제가 있었다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;이동 중 적 발견
&amp;rarr; 자동으로 이동 중단
&amp;rarr; 적 공격&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AttackMove는 유지하면서 새로운 이동 명령 직후에만 짧은 재탐색 유예시간을 적용했다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;공격 중 뒤쪽 클릭
&amp;rarr; 기존 공격 대상 해제
&amp;rarr; 공격 몽타주 중단
&amp;rarr; 남아 있는 Notify 대상 제거
&amp;rarr; 0.3초 동안 이동 우선
&amp;rarr; 유예시간 종료
&amp;rarr; 적 자동 탐색 재개&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 CommandAttackMoveGraceRemaining을 추가하고 Tick에서 감소시켰다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;if (CommandAttackMoveGraceRemaining &amp;gt; 0.f)
{
    CommandAttackMoveGraceRemaining =
        FMath::Max(
            0.f,
            CommandAttackMoveGraceRemaining - DeltaSeconds
        );
}&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;if (bHasCommandedDestination &amp;amp;&amp;amp;
    (!bCommandIsAttackMove ||
     CommandAttackMoveGraceRemaining &amp;gt; 0.f))&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배운 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 문제처럼 보여도 실제 원인은 상태 머신이 다음 Tick에서 명령을 덮어쓰는 것일 수 있다. 기존 자동 전투 기능을 제거하지 않고 사용자의 새 명령에 우선권을 주려면 짧은 상태 전환 유예시간을 사용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트러블 슈팅 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 확인한 문제들은 서로 연결되어 있었다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;잘못된 Row Name
&amp;rarr; 체력 초기화 실패
&amp;rarr; 적 유닛이 즉시 사망
&amp;rarr; 공격 Notify 피해량 검증 불가능

즉시 피해 방식
&amp;rarr; 애니메이션과 타격 횟수 불일치
&amp;rarr; Notify 기반 피해 처리로 변경

AttackMove의 즉각적인 재탐색
&amp;rarr; 후퇴 명령 무시
&amp;rarr; 0.3초 이동 우선시간 적용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 작업에서는 BP_AN_DeathPose를 사망 몽타주의 완전히 누운 프레임에 배치하고, HandleDeathPose()와 연결하여 다시 일어나는 사망 애니메이션 문제를 처리할 예정이다.&lt;/p&gt;</description>
      <category>StrikForge 프로젝트/트러블슈팅</category>
      <author>editor66484</author>
      <guid isPermaLink="true">https://editor66484.tistory.com/37</guid>
      <comments>https://editor66484.tistory.com/37#entry37comment</comments>
      <pubDate>Tue, 8 Sep 2026 23:15:43 +0900</pubDate>
    </item>
    <item>
      <title>유닛 공격 판정 및 애니메이션 개선</title>
      <link>https://editor66484.tistory.com/36</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;오늘 작업한 내용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 유닛 선택 정보 오류를 확인하고, 공격 피해가 애니메이션과 정확히 일치하도록 Anim Notify 기반 공격 시스템을 구성했다. 추가로 공격 중 이동 명령 처리와 사망 애니메이션 개선 작업을 시작했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 유닛 선택 정보가 출력되지 않는 문제 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BP_TestUnit의 GetSelectableInfo에 내용이 없는 재정의 함수가 생성되어 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 빈 재정의가 부모 C++ 함수인 AUnitBase::GetSelectableInfo_Implementation()의 실행을 막으면서 다음 정보가 기본값으로 반환되고 있었다.&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;공격력&lt;/li&gt;
&lt;li&gt;이동속도&lt;/li&gt;
&lt;li&gt;유닛 타입&lt;/li&gt;
&lt;li&gt;초상화 애니메이션&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 재정의를 제거하여 부모 C++의 선택 정보 처리 함수를 사용하도록 정리했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 공격 피해를 Anim Notify 방식으로 변경&lt;/h2&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;nginx&quot;&gt;&lt;code&gt;PerformAttack
&amp;rarr; 공격 대상 저장
&amp;rarr; 공격 몽타주 재생
&amp;rarr; 타격 프레임에서 Anim Notify 실행
&amp;rarr; HandleAttackHit 호출
&amp;rarr; 실제 피해 적용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PerformAttack() 내부의 기존 즉시 피해 계산을 분리하고, HandleAttackHit()와 ApplyAttackDamage()를 통해 Notify 시점에 피해가 적용되도록 구성했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 공격용 Anim Notify 제작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BP_AN_AttackHit을 생성하고 Received Notify에서 공격 중인 UnitBase를 찾아 HandleAttackHit()을 호출하도록 연결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 구조는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4VLv1/dJMcajcuyhv/p4tofsApBML3K3YxUJrWP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4VLv1/dJMcajcuyhv/p4tofsApBML3K3YxUJrWP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4VLv1/dJMcajcuyhv/p4tofsApBML3K3YxUJrWP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4VLv1%2FdJMcajcuyhv%2Fp4tofsApBML3K3YxUJrWP1%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;978&quot; height=&quot;248&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Received Notify
&amp;rarr; Mesh Comp
&amp;rarr; Get Owner
&amp;rarr; Cast To UnitBase
&amp;rarr; Handle Attack Hit
&amp;rarr; Return True&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DamageRatio 변수를 Handle Attack Hit의 입력으로 연결하여 몽타주마다 피해 비율을 다르게 설정할 수 있도록 만들었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 일반 공격과 2연타 공격 구분&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;휴먼 공격은 한 번 타격하고, 여우 공격은 한 번의 공격 애니메이션에서 두 번 타격한다. 같은 BP_AN_AttackHit을 사용하되 Notify 개수와 DamageRatio 값으로 공격 방식을 구분했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;휴먼 일반 공격&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Notify 개수: 1개&lt;/li&gt;
&lt;li&gt;Damage Ratio: 1.0&lt;/li&gt;
&lt;li&gt;Attack Hit Count: 1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PUpUD/dJMcaafFNQS/tS6Ix7aCCuaeknMvRPhNC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PUpUD/dJMcaafFNQS/tS6Ix7aCCuaeknMvRPhNC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PUpUD/dJMcaafFNQS/tS6Ix7aCCuaeknMvRPhNC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPUpUD%2FdJMcaafFNQS%2FtS6Ix7aCCuaeknMvRPhNC0%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;1919&quot; height=&quot;1080&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;여우 2연타 공격&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Notify 개수: 2개&lt;/li&gt;
&lt;li&gt;첫 번째 Damage Ratio: 0.5&lt;/li&gt;
&lt;li&gt;두 번째 Damage Ratio: 0.5&lt;/li&gt;
&lt;li&gt;Attack Hit Count: 2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MDctD/dJMcaben9Tf/3gnpwlaMQA2OTP5ForQ11k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MDctD/dJMcaben9Tf/3gnpwlaMQA2OTP5ForQ11k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MDctD/dJMcaben9Tf/3gnpwlaMQA2OTP5ForQ11k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMDctD%2FdJMcaben9Tf%2F3gnpwlaMQA2OTP5ForQ11k%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;1919&quot; height=&quot;1080&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1080&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;1919&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vww8c/dJMcahlBSFv/Kea8SQMVG2gIdzhUbFvv3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vww8c/dJMcahlBSFv/Kea8SQMVG2gIdzhUbFvv3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vww8c/dJMcahlBSFv/Kea8SQMVG2gIdzhUbFvv3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVww8c%2FdJMcahlBSFv%2FKea8SQMVG2gIdzhUbFvv3k%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;1919&quot; height=&quot;1080&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 데이터 테이블 공격력이 10이면 여우는 다음처럼 피해를 적용한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1타: 10 &amp;times; 0.5 = 5
2타: 10 &amp;times; 0.5 = 5
총 피해: 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 2연타 애니메이션을 유지하면서도 한 공격 주기의 총 피해가 과도하게 증가하지 않도록 했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 적 유닛 체력이 1로 시작하는 문제 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적 스포너에 설정된 Row Name은 Infantry였지만, 현재 DT_Units에는 Infantry 행이 존재하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 테이블 조회에 실패하면 HealthComponent가 정상적으로 초기화되지 않아 기본값이 남는다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Current Health = 1
Max Health = 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 실제로 체력이 1로 설계된 것이 아니라, 데이터 테이블 행 이름 불일치로 초기화가 실패한 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 연결 가능한 주요 행은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Brawler   &amp;rarr; BP_TestUnit
Berserker &amp;rarr; BP_TestUnit2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적 스포너의 Unit Row Name을 실제 유닛 클래스와 일치하는 데이터 행으로 변경해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 공격 중 이동 명령 반응 개선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격 도중 뒤쪽을 클릭해도 유닛이 바로 이동하지 않는 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 일반 우클릭이 AttackMove로 처리되기 때문에 공격을 취소한 직후 같은 적을 다시 탐색하는 것이 원인이었다. 기존의 &amp;ldquo;이동 중 적을 발견하면 자동 공격&amp;rdquo; 기능을 유지하기 위해 일반 Move로 변경하지 않고 짧은 재탐색 유예시간을 추가했다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;공격 중 이동 명령
&amp;rarr; 현재 공격 대상 해제
&amp;rarr; 공격 몽타주 중단
&amp;rarr; PendingAttackTarget 초기화
&amp;rarr; 0.3초간 목적지 이동 우선
&amp;rarr; 유예시간 종료 후 적 자동 탐색 재개&lt;/code&gt;&lt;/pre&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;CommandAttackMoveGraceRemaining&lt;/li&gt;
&lt;li&gt;공격 이동 명령 시 0.3초 설정&lt;/li&gt;
&lt;li&gt;Tick에서 유예시간 감소&lt;/li&gt;
&lt;li&gt;유예시간 동안 목적지 이동 우선&lt;/li&gt;
&lt;li&gt;취소된 공격의 늦은 Notify 피해 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 사망 모션 개선 작업 시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사망 애니메이션에 쓰러진 뒤 다시 일어나는 구간이 포함되어 있어, 완전히 누운 순간 애니메이션을 정지하기 위한 BP_AN_DeathPose를 생성했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mARLB/dJMcaf2dPED/rJF3jLL1YiQPqIRNnLmMHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mARLB/dJMcaf2dPED/rJF3jLL1YiQPqIRNnLmMHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mARLB/dJMcaf2dPED/rJF3jLL1YiQPqIRNnLmMHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmARLB%2FdJMcaf2dPED%2FrJF3jLL1YiQPqIRNnLmMHK%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;1919&quot; height=&quot;1080&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1080&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;현재 생성된 Notify:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BP_AN_AttackHit: 실제 타격 시점에 피해 적용&lt;/li&gt;
&lt;li&gt;BP_AN_DeathPose: 완전히 누운 사망 자세에서 애니메이션 정지 예정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사망 Notify는 에셋 생성까지만 완료했으며 C++ 연결과 사망 몽타주 배치는 다음 작업으로 남겨두었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 작업&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;UnitBase에 HandleDeathPose() 추가&lt;/li&gt;
&lt;li&gt;BP_AN_DeathPose의 Received Notify 연결&lt;/li&gt;
&lt;li&gt;휴먼과 여우 사망 몽타주에서 완전히 누운 프레임에 Notify 배치&lt;/li&gt;
&lt;li&gt;사망 자세가 고정되고 다시 일어나지 않는지 확인&lt;/li&gt;
&lt;li&gt;공격 Notify 피해량과 2연타 횟수 실전 검증&lt;/li&gt;
&lt;li&gt;적 스포너의 Infantry Row Name 수정 및 체력 초기화 확인&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>StrikForge 프로젝트/작업일지</category>
      <author>editor66484</author>
      <guid isPermaLink="true">https://editor66484.tistory.com/36</guid>
      <comments>https://editor66484.tistory.com/36#entry36comment</comments>
      <pubDate>Tue, 8 Sep 2026 23:11:29 +0900</pubDate>
    </item>
    <item>
      <title>UE5 RTS 유닛 자동 전투 개선 및 주변 아군 타겟 공유 설계</title>
      <link>https://editor66484.tistory.com/35</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 RTS 유닛 전투 시스템을 전체적으로 점검하고, 플레이어 유닛의 자동 공격이 자연스럽게 이어지도록 개선 방향을 정리했다.&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;플레이어 및 적 AI의 자동 전투 구조 확인&lt;/li&gt;
&lt;li&gt;뒤쪽 유닛이 전투에 참여하지 않는 원인 분석&lt;/li&gt;
&lt;li&gt;일반 이동 중 적 발견 시 자동 교전하도록 개선&lt;/li&gt;
&lt;li&gt;한 유닛이 발견한 적을 주변 아군에게 공유하는 기능 설계&lt;/li&gt;
&lt;li&gt;유닛이 한 대상에게 몰릴 때 발생할 수 있는 문제 검토&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 현재 유닛 전투 구조 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 팀 구분은 TeamIndex를 사용한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;진영TeamIndex
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;플레이어&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적 AI&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양쪽 유닛 모두 공격 자체는 자동으로 수행한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;플레이어 유닛&lt;/h3&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;일반 이동과 공격 이동을 구분할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;적 AI 유닛&lt;/h3&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;적을 발견하면 자동으로 이동하고 공격한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 뒤쪽 유닛이 공격하지 않는 문제&lt;/h2&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뒤쪽 유닛이 AggroRadius 밖에 있음&lt;/li&gt;
&lt;li&gt;각 유닛이 서로 독립적으로 적을 탐색함&lt;/li&gt;
&lt;li&gt;앞쪽 유닛의 적 발견 정보가 뒤쪽 유닛에게 전달되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 앞 유닛이 교전을 시작하더라도 뒤 유닛은 자신의 탐색 범위 안에 적이 들어오기 전까지 전투에 참여하지 않는 구조였다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 일반 이동을 공격 이동으로 처리&lt;/h2&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;reasonml&quot;&gt;&lt;code&gt;SFInGamePlayerController.cpp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 코드:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;case ESFUnitCommandMode::Move:
    UnitCommand = ESFUnitCommand::Move;
    break;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경 코드:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;case ESFUnitCommandMode::Move:
    UnitCommand = ESFUnitCommand::AttackMove;
    break;&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;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;강제 공격: 플레이어가 지정한 대상만 추적&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 주변 아군에게 적 공유 기능 설계&lt;/h2&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;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;```mermaid
flowchart LR
    A[&quot;유닛이 적 발견&quot;] --&amp;gt; B[&quot;주변 아군 검색&quot;]
    B --&amp;gt; C[&quot;TeamIndex 비교&quot;]
    C --&amp;gt; D[&quot;같은 팀의 유닛만 선택&quot;]
    D --&amp;gt; E[&quot;발견한 적 전달&quot;]
    E --&amp;gt; F[&quot;아군의 CurrentTarget 설정&quot;]
    F --&amp;gt; G[&quot;경로 재탐색&quot;]
    G --&amp;gt; H[&quot;전투 합류&quot;]
```&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 기능은 전 맵의 모든 아군에게 적을 전달하는 방식이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AllyAssistRadius 안에 있는 같은 팀의 유닛만 지원 요청을 받도록 설계했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. UnitBase에 추가한 선언&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UnitBase.h에 적 공유를 담당할 함수 두 개를 선언했다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;/** 발견한 적을 일정 거리 안의 아군에게grade 전달한다 */
void AlertNearbyAllies(AActor* InTarget);

/** 주변 아군의 지원 요청으로 전달된 적을 받아들인다 */
void ReceiveAssistTarget(AActor* InTarget);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 함수의 역할은 다음과 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AlertNearbyAllies&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신이 발견한 적을 주변 아군에게 전달한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ReceiveAssistTarget&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 아군에게 전달받은 적이 유효한지 확인하고 자신의 공격 대상으로 받아들인다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 아군 지원 범위 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UnitBase.h의 전투 설정 영역에 아군 지원 범위를 추가했다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;/** 주변 아군이 발견한 적에게 합류하는 거리 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = &quot;Combat&quot;,
    meta = (AllowPrivateAccess = true, ClampMin = &quot;0.0&quot;))
float AllyAssistRadius = 1500.f;&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;float AggroRadius = 1050.f;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 범위의 역할은 서로 다르다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;설정역할
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AggroRadius&lt;/td&gt;
&lt;td&gt;유닛이 직접 적을 발견하는 범위&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AllyAssistRadius&lt;/td&gt;
&lt;td&gt;아군이 발견한 적 정보를 전달받는 범위&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 테스트에서는 전투 참여 범위가 너무 넓어지지 않도록 AllyAssistRadius를 약 1200.f부터 확인하는 것이 좋다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;float AllyAssistRadius = 1200.f;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤쪽 유닛의 반응이 부족하면 1300 &amp;rarr; 1500 순서로 증가시킬 예정이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. SFCombatSubsystem 활용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에는 이미 공간 그리드를 이용하여 주변 아군을 찾는 함수가 구현되어 있었다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void GetNearbyAlliesForSeparation(
    const AActor* InSelf,
    int32 InTeamIndex,
    float InRadius,
    TArray&amp;lt;AActor*&amp;gt;&amp;amp; OutActors
) const;&lt;/code&gt;&lt;/pre&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;전달받은 TeamIndex와 같은 유닛&lt;/li&gt;
&lt;li&gt;CombatUnit 유형&lt;/li&gt;
&lt;li&gt;지정한 반경 안에 있는 유닛&lt;/li&gt;
&lt;li&gt;자기 자신이 아닌 유닛&lt;/li&gt;
&lt;li&gt;현재 유효한 액터&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 월드에 존재하는 모든 유닛을 직접 반복하지 않고, 기존 전투 서브시스템의 공간 검색 기능을 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 함수 이름에 ForSeparation이 포함되어 있기 때문에 추후 구조를 정리할 때는 범용 주변 아군 조회 함수를 따로 만드는 것도 고려할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void GetNearbyAlliedUnits(
    const AActor* InSelf,
    int32 InTeamIndex,
    float InRadius,
    TArray&amp;lt;AActor*&amp;gt;&amp;amp; OutActors
) const;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 주변 아군에게 적 전달&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AlertNearbyAllies()에서는 전투 서브시스템을 통해 주변 아군을 찾고, 각 아군에게 적을 전달하도록 설계했다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;void AUnitBase::AlertNearbyAllies(AActor* InTarget)
{
    if (!IsTargetUsable(InTarget))
    {
        return;
    }

    USFCombatSubsystem* Combat = USFCombatSubsystem::Get(this);
    if (!IsValid(Combat))
    {
        return;
    }

    TArray&amp;lt;AActor*&amp;gt; NearbyAllies;

    Combat-&amp;gt;GetNearbyAlliesForSeparation(
        this,
        GetTeamIndex(),
        AllyAssistRadius,
        NearbyAllies
    );

    for (AActor* AllyActor : NearbyAllies)
    {
        AUnitBase* AllyUnit = Cast&amp;lt;AUnitBase&amp;gt;(AllyActor);
        if (!IsValid(AllyUnit))
        {
            continue;
        }

        AllyUnit-&amp;gt;ReceiveAssistTarget(InTarget);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유받은 유닛이 다시 주변에 전달하지 않도록 구성하여, 전 맵으로 지원 요청이 연쇄적으로 퍼지는 현상을 방지했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 공유된 적을 공격 대상으로 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReceiveAssistTarget()에서는 지원 요청을 무조건 받아들이지 않고 현재 유닛 상태를 검사해야 한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;void AUnitBase::ReceiveAssistTarget(AActor* InTarget)
{
    if (CurrentCombatState == EUnitCombatState::Dead)
    {
        return;
    }

    if (!IsTargetUsable(InTarget))
    {
        return;
    }

    if (IsValid(ForcedTarget))
    {
        return;
    }

    if (bHoldGround)
    {
        return;
    }

    if (IsTargetUsable(CurrentTarget))
    {
        return;
    }

    CurrentTarget = InTarget;
    RetargetTimer = RetargetInterval;

    bSightBlockedReported = false;
    bRangeUnreachableReported = false;
    bNoTargetReported = false;
    bAdvanceTargetReported = false;

    if (AUnitAIController* AI = GetUnitAI())
    {
        AI-&amp;gt;ForceRepath();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 우선순위를 유지하도록 했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;플레이어가 직접 지정한 ForcedTarget&lt;/li&gt;
&lt;li&gt;현재 교전 중인 CurrentTarget&lt;/li&gt;
&lt;li&gt;주변 아군에게 전달받은 지원 대상&lt;/li&gt;
&lt;li&gt;일반 자동 탐색 대상&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 공격 시 끊김 가능성 검토&lt;/h2&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;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;좁은 공간에서 분리 조향과 공격 이동이 충돌함&lt;/li&gt;
&lt;li&gt;공유 범위가 너무 넓으면 먼 유닛까지 전투에 끌려감&lt;/li&gt;
&lt;li&gt;경로 재탐색이 반복되면 순간적으로 이동이 끊김&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 방지하기 위해 다음 조건을 적용했다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;if (IsTargetUsable(CurrentTarget))
{
    return;
}&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;또한 ForceRepath()는 공유된 대상을 처음 받아들였을 때 한 번만 실행하고, StopMovement()는 호출하지 않는다. 이로 인해 공격 도중 이동이 반복적으로 끊기는 현상을 줄일 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오늘의 작업 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 실제 전투 문제를 다음 두 단계로 나누어 개선 방향을 잡았다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1차 수정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어 유닛이 이동 중 적을 발견하면 자동으로 교전할 수 있도록 일반 이동 명령을 공격 이동으로 연결했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2차 수정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞쪽 유닛이 발견한 적을 일정 범위 안의 아군에게 공유하여, 탐색 범위 밖에 있던 뒤쪽 유닛도 전투에 합류할 수 있도록 구조를 설계했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 UnitBase.h에는 관련 함수 선언과 AllyAssistRadius 설정이 추가된 상태다. 다음 작업에서는 UnitBase.cpp의 함수 구현과 실제 호출 부분을 완성한 뒤 에디터에서 전투 테스트를 진행할 예정이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 테스트 항목&lt;/h2&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;뒤쪽 유닛이 공유받은 적에게 이동하는지 확인&lt;/li&gt;
&lt;li&gt;TeamIndex가 다른 유닛에게 정보가 전달되지 않는지 확인&lt;/li&gt;
&lt;li&gt;공격 중인 유닛의 타겟이 불필요하게 변경되지 않는지 확인&lt;/li&gt;
&lt;li&gt;유닛이 제자리에서 떨거나 반복적으로 멈추는지 확인&lt;/li&gt;
&lt;li&gt;적 제거 후 다음 타겟을 정상적으로 탐색하는지 확인&lt;/li&gt;
&lt;li&gt;AllyAssistRadius를 1200, 1300, 1500 순서로 비교&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;느낀 점&lt;/h2&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>StrikForge 프로젝트/작업일지</category>
      <author>editor66484</author>
      <guid isPermaLink="true">https://editor66484.tistory.com/35</guid>
      <comments>https://editor66484.tistory.com/35#entry35comment</comments>
      <pubDate>Wed, 2 Sep 2026 23:01:05 +0900</pubDate>
    </item>
    <item>
      <title>선택한 유닛의 3D 초상화가 표시되지 않는 문제</title>
      <link>https://editor66484.tistory.com/34</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지휘관이나 유닛을 선택하면 체력과 이동속도 같은 정보는 정상적으로 갱신됐지만, 왼쪽의 초상화 영역에는 선택한 캐릭터가 나타나지 않았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20260831_003550_2.png&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;773&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eCmxol/dJMcadb6iUx/CBlg9olBYKc99kZZ5bDuK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eCmxol/dJMcadb6iUx/CBlg9olBYKc99kZZ5bDuK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eCmxol/dJMcadb6iUx/CBlg9olBYKc99kZZ5bDuK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeCmxol%2FdJMcadb6iUx%2FCBlg9olBYKc99kZZ5bDuK0%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;1293&quot; height=&quot;773&quot; data-filename=&quot;20260831_003550_2.png&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;773&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;선택 정보가 InGameUI까지 전달됨&lt;/li&gt;
&lt;li&gt;선택 정보 패널의 표시 및 텍스트 갱신은 정상 작동함&lt;/li&gt;
&lt;li&gt;문제 범위는 Scene Capture &amp;rarr; Render Target &amp;rarr; UI Material 출력 과정으로 좁혀짐&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scene Capture Component 2D가 실제 게임 월드를 기준으로 촬영하면서, 초상화용 캐릭터뿐만 아니라 하늘과 지형, 주변 오브젝트까지 함께 렌더링하고 있었다.&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;h3 data-ke-size=&quot;size23&quot;&gt;해결 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BP_SFUnitPortraitActor의 이벤트 그래프에서 BeginPlay 시점에 Show Only Component를 호출하도록 구성했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20260831_003550_1.png&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0X8bG/dJMcaa7IJSc/lfRNnXO4MqugvcVSU0Z6e0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0X8bG/dJMcaa7IJSc/lfRNnXO4MqugvcVSU0Z6e0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0X8bG/dJMcaa7IJSc/lfRNnXO4MqugvcVSU0Z6e0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0X8bG%2FdJMcaa7IJSc%2FlfRNnXO4MqugvcVSU0Z6e0%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;566&quot; height=&quot;336&quot; data-filename=&quot;20260831_003550_1.png&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드 연결은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;Event BeginPlay
    &amp;rarr; Show Only Component

Target
    &amp;rarr; Portrait Capture

In Component
    &amp;rarr; Portrait Mesh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Portrait Capture의 Primitive Render Mode를 Use ShowOnly List 방식으로 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정으로 Scene Capture Component 2D가 전체 게임 월드를 촬영하지 않고, 초상화 액터가 가진 Portrait Mesh만 Render Target에 출력하도록 제한했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;처리 흐름&lt;/h3&gt;
&lt;div class=&quot;mermaid&quot;&gt;
flowchart LR
    A[&quot;게임 시작&quot;] --&gt; B[&quot;BP_SFUnitPortraitActor BeginPlay&quot;]
    B --&gt; C[&quot;Show Only Component 호출&quot;]
    C --&gt; D[&quot;Portrait Mesh를 ShowOnly 목록에 등록&quot;]
    D --&gt; E[&quot;Scene Capture가 Portrait Mesh만 촬영&quot;]
    E --&gt; F[&quot;Render Target에 캐릭터만 출력&quot;]
    F --&gt; G[&quot;UI Material을 통해 초상화 표시&quot;]
&lt;/div&gt;

&lt;script type=&quot;module&quot;&gt;
  import mermaid from &quot;https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs&quot;;

  mermaid.initialize({
    startOnLoad: true,
    theme: &quot;default&quot;,
    securityLevel: &quot;loose&quot;
  });
&lt;/script&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가로 확인한 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 해결 과정에서 UI의 기존 Set Brush From Texture가 Render Target 머티리얼 출력을 다시 덮어쓰는 현상도 확인했다.&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;Commander, Unit
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Set Brush From Material&lt;/li&gt;
&lt;li&gt;Render Target 기반 3D 초상화 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;건물, 광물 등
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 Set Brush From Texture&lt;/li&gt;
&lt;li&gt;데이터 테이블 기반 2D 이미지 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 사용하면 기존 UI 데이터 테이블을 제거하지 않고도 2D 이미지와 3D 초상화를 함께 사용할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Show Only Component 적용 후에는 다음과 같은 효과를 얻을 수 있었다.&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;선택한 지휘관과 유닛의 Skeletal Mesh만 출력&lt;/li&gt;
&lt;li&gt;기존 건물 및 광물의 2D 이미지 출력 방식 유지&lt;/li&gt;
&lt;li&gt;하나의 선택 정보 패널에서 2D와 3D 초상화를 대상 종류에 따라 구분하여 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;고찰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Render Target의 알파 값을 조절하는 것은 촬영된 결과의 투명도를 처리하는 작업이다. 하지만 촬영 단계에서 불필요한 월드 오브젝트가 포함된 경우에는 머티리얼 처리만으로 문제를 해결하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이번 작업에서는 다음 순서가 중요했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Scene Capture가 촬영할 대상을 Show Only Component로 제한한다.&lt;/li&gt;
&lt;li&gt;Render Target의 알파 채널을 UI 머티리얼에서 처리한다.&lt;/li&gt;
&lt;li&gt;선택 대상의 종류에 따라 3D 머티리얼과 2D 텍스처 출력을 분기한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 배경 투명화와 촬영 대상 제한은 서로 다른 역할이며 두 설정을 함께 적용해야 안정적인 3D 초상화를 구현할 수 있었다.&lt;/p&gt;</description>
      <author>editor66484</author>
      <guid isPermaLink="true">https://editor66484.tistory.com/34</guid>
      <comments>https://editor66484.tistory.com/34#entry34comment</comments>
      <pubDate>Mon, 31 Aug 2026 00:38:08 +0900</pubDate>
    </item>
    <item>
      <title>RTS 선택 UI에 3D 초상화 연동</title>
      <link>https://editor66484.tistory.com/33</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;오늘의 목표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 InGameUI의 선택 정보 패널에 Render Target 기반 3D 초상화를 연결하고, 선택 대상에 따라 2D 이미지와 3D 초상화를 구분해 출력하는 작업을 진행했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지휘관&amp;middot;전투 유닛: 실시간 3D 초상화&lt;/li&gt;
&lt;li&gt;건물&amp;middot;광물: 기존 데이터테이블의 2D 이미지&lt;/li&gt;
&lt;li&gt;선택 해제: 선택 정보 패널 숨김&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 기존 2D 초상화 출력 구조 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 WBP_SFSelectedInfoPanel에서는 데이터테이블에서 가져온 PortraitImage를 SelectedPortraitImage에 적용하고 있었다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;FSFSelectableInfo.PortraitImage
&amp;rarr; Set Brush From Texture
&amp;rarr; SelectedPortraitImage&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20260830_222648_1.png&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXMODK/dJMb991ZiAF/5DH8qvKdnjXkO0IOO2g8oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXMODK/dJMb991ZiAF/5DH8qvKdnjXkO0IOO2g8oK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXMODK/dJMb991ZiAF/5DH8qvKdnjXkO0IOO2g8oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXMODK%2FdJMb991ZiAF%2F5DH8qvKdnjXkO0IOO2g8oK%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;577&quot; height=&quot;318&quot; data-filename=&quot;20260830_222648_1.png&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조는 건물이나 광물처럼 정적인 2D 이미지를 표시하는 데 적합하다. 하지만 지휘관과 전투 유닛에는 Render Target Material을 사용해야 하므로 선택 대상에 따른 분기가 필요했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 2D 이미지와 3D 초상화 출력 분기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SelectableType을 기준으로 지휘관과 유닛을 확인하도록 구성했다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;SelectableType == Commander
SelectableType == Unit
&amp;rarr; OR
&amp;rarr; Branch&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분기 결과:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;True
&amp;rarr; Set Brush From Material
&amp;rarr; M_SFUnitPortraitUI

False
&amp;rarr; Set Brush From Texture
&amp;rarr; 데이터테이블의 PortraitImage&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o0eCS/dJMcaazQfsq/KYsEOQPDsm0C5oxws8kr1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o0eCS/dJMcaazQfsq/KYsEOQPDsm0C5oxws8kr1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o0eCS/dJMcaazQfsq/KYsEOQPDsm0C5oxws8kr1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo0eCS%2FdJMcaazQfsq%2FKYsEOQPDsm0C5oxws8kr1k%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;1450&quot; height=&quot;543&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 통해 기존 건물&amp;middot;광물 이미지 기능을 제거하지 않고, 지휘관과 유닛에만 3D 초상화를 적용할 수 있게 되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최종 분기 구조&lt;/h3&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;Commander 또는 Unit
&amp;rarr; Render Target Material

MainBuilding
NormalBuilding
ProductionBuilding
ResourceBuilding
&amp;rarr; 기존 PortraitImage&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. HUD와 Portrait Actor 연결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SFInGameHUDWidget에서 실제 선택된 캐릭터의 Skeletal Mesh를 가져와 공용 SFUnitPortraitActor에 전달하도록 연결했다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;유닛 선택
&amp;rarr; SelectedActor 확인
&amp;rarr; ACharacter Cast
&amp;rarr; SkeletalMeshComponent 조회
&amp;rarr; Skeletal Mesh Asset 획득
&amp;rarr; ApplyPortrait 호출&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;UnitPortraitActor-&amp;gt;ApplyPortrait(
    SelectedSkeletalMesh,
    nullptr
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 선택한 유닛의 Mesh를 교체하는 기능까지 연결되어 있으며, Animation은 아직 nullptr로 전달하고 있다.&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;선택 해제&lt;/li&gt;
&lt;li&gt;선택 대상 파괴&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Render Target 배경 투명화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scene Capture 결과에 인게임 배경이 함께 촬영되는 문제가 있었다. Render Target의 Alpha 채널과 UI Material을 이용해 배경을 투명하게 처리했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Render Target&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;Clear Color
R = 0
G = 0
B = 0
A = 0&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Scene Capture&lt;/h3&gt;
&lt;pre class=&quot;dns&quot;&gt;&lt;code&gt;Capture Source
&amp;rarr; SceneColor (HDR) in RGB, Inv Opacity in A&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UI Material&lt;/h3&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;Render Target RGB
&amp;rarr; Final Color

Render Target Alpha
&amp;rarr; OneMinus
&amp;rarr; Opacity&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 Alpha 방향이 반대로 적용되어 캐릭터가 사라지고 배경만 출력되는 문제가 있었다. OneMinus를 적용하여 캐릭터 영역은 표시하고 배경 영역은 투명하게 처리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 인게임 맵에서는 하늘, 나무, 지형 등이 불투명 오브젝트로 함께 촬영될 수 있기 때문에 PortraitMesh만 촬영하도록 제한하는 과정도 필요했다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Primitive Render Mode
&amp;rarr; Use ShowOnly List

Show Only Component
&amp;rarr; PortraitMesh&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 초상화 조명 변경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Rect Light Component를 Spot Light Component로 교체했다.&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;SFUnitPortraitActor.h
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URectLightComponent 제거&lt;/li&gt;
&lt;li&gt;USpotLightComponent 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SFUnitPortraitActor.cpp
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RectLightComponent.h 제거&lt;/li&gt;
&lt;li&gt;SpotLightComponent.h 추가&lt;/li&gt;
&lt;li&gt;PortraitSpotLight 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조명의 위치와 밝기는 코드에 고정하지 않고 BP_SFUnitPortraitActor에서 직접 조정할 수 있도록 유지했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 실제 지휘관 선택 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지휘관을 선택했을 때 실제 선택 정보 패널의 초상화 영역에 지휘관 Skeletal Mesh가 출력되는 것을 확인했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20260830_222648_3.png&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;757&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GrlYF/dJMcaazQfs4/ArttVOPCnUH4hlk0XKWko0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GrlYF/dJMcaazQfs4/ArttVOPCnUH4hlk0XKWko0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GrlYF/dJMcaazQfs4/ArttVOPCnUH4hlk0XKWko0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGrlYF%2FdJMcaazQfs4%2FArttVOPCnUH4hlk0XKWko0%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;1289&quot; height=&quot;757&quot; data-filename=&quot;20260830_222648_3.png&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;757&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인된 처리 흐름:&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;지휘관 클릭
&amp;rarr; SelectableType = Commander
&amp;rarr; Set Brush From Material
&amp;rarr; 지휘관 Mesh를 Portrait Actor에 적용
&amp;rarr; Render Target 출력
&amp;rarr; 실제 InGameUI에 표시&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 실제 전투 유닛 선택 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전투 유닛을 선택했을 때 같은 PortraitActor와 Render Target을 재사용하면서 선택한 유닛의 Mesh로 교체되는 것을 확인했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20260830_222648_4.png&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ljntw/dJMcahseb9x/kzKKAJLgf0WNgNekYMh641/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ljntw/dJMcahseb9x/kzKKAJLgf0WNgNekYMh641/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ljntw/dJMcahseb9x/kzKKAJLgf0WNgNekYMh641/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fljntw%2FdJMcahseb9x%2FkzKKAJLgf0WNgNekYMh641%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;1292&quot; height=&quot;768&quot; data-filename=&quot;20260830_222648_4.png&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닛마다 별도의 카메라나 Render Target을 생성하지 않고, 공용 Portrait Actor 하나를 재사용했다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;지휘관 선택
&amp;rarr; 지휘관 Mesh 출력

전투 유닛 선택
&amp;rarr; 같은 PortraitMesh를 전투 유닛 Mesh로 교체&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RTS처럼 다수의 유닛이 생성되는 환경에서 Scene Capture를 유닛마다 생성하지 않아도 된다는 장점이 있다.&lt;/p&gt;</description>
      <category>StrikForge 프로젝트/작업일지</category>
      <author>editor66484</author>
      <guid isPermaLink="true">https://editor66484.tistory.com/33</guid>
      <comments>https://editor66484.tistory.com/33#entry33comment</comments>
      <pubDate>Sun, 30 Aug 2026 22:29:49 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal Engine 5] Scene Capture와 Render Target을 활용한 3D 포트레이트 구현</title>
      <link>https://editor66484.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;RTS 게임에서 유닛을 선택했을 때 미니맵 왼쪽에 해당 유닛의 3D 초상화를 표시하기 위한 포트레이트 시스템을 작업했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 구현하려는 흐름은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div class=&quot;mermaid&quot;&gt;
flowchart LR
    A[&quot;유닛 선택&quot;] --&gt; B[&quot;포트레이트 데이터 조회&quot;]
    B --&gt; C[&quot;Skeletal Mesh 및 Animation 적용&quot;]
    C --&gt; D[&quot;Scene Capture Component 2D 촬영&quot;]
    D --&gt; E[&quot;Render Target 출력&quot;]
    E --&gt; F[&quot;InGameUI 포트레이트 표시&quot;]
&lt;/div&gt;

&lt;script type=&quot;module&quot;&gt;
  import mermaid from &quot;https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs&quot;;

  mermaid.initialize({
    startOnLoad: true,
    theme: &quot;default&quot;,
    securityLevel: &quot;loose&quot;
  });
&lt;/script&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업은 크게 다음 두 단계로 나누어 진행했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Blueprint를 이용한 Render Target 사전 테스트&lt;/li&gt;
&lt;li&gt;재사용 가능한 C++ 포트레이트 액터 작성&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Blueprint 기반 사전 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++ 구현에 들어가기 전, Scene Capture Component 2D와 Render Target으로 실제 3D 유닛을 촬영할 수 있는지 Blueprint를 이용해 먼저 검증했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트에 사용한 구성은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;BP_SFUnitPortraitTest
├─ DefaultSceneRoot
├─ PortraitMesh
├─ PortraitCapture
└─ PortraitLight&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PortraitMesh: 초상화로 출력할 Skeletal Mesh&lt;/li&gt;
&lt;li&gt;PortraitCapture: 3D 모델을 촬영할 Scene Capture&lt;/li&gt;
&lt;li&gt;PortraitLight: 캐릭터의 얼굴과 상반신을 비추는 조명&lt;/li&gt;
&lt;li&gt;RT_SFUnitPortraitTest: 촬영 결과를 저장하는 Render Target&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Render Target 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scene Capture Component 2D의 촬영 결과를 저장하기 위해 별도의 Render Target 에셋을 생성했다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20260824_214832_1.png&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;217&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvxkXy/dJMcajwHMRe/OdixguyeWYq9kC8SKzQQN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvxkXy/dJMcajwHMRe/OdixguyeWYq9kC8SKzQQN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvxkXy/dJMcajwHMRe/OdixguyeWYq9kC8SKzQQN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvxkXy%2FdJMcajwHMRe%2FOdixguyeWYq9kC8SKzQQN0%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;402&quot; height=&quot;283&quot; data-filename=&quot;20260824_214832_1.png&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;217&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에셋에 캐릭터 모습이 표시된 것을 통해 다음 연결이 정상적으로 동작한다는 것을 확인했다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;PortraitMesh
&amp;rarr; SceneCaptureComponent2D
&amp;rarr; RT_SFUnitPortraitTest&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3D 캐릭터 출력 결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라 위치와 회전, FOV를 조절하여 캐릭터의 얼굴과 상반신이 화면 중앙에 나오도록 설정했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20260824_214832_2.png&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CbZOv/dJMcaaftaoR/mFzodyQUDmv7PMgEN56zck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CbZOv/dJMcaaftaoR/mFzodyQUDmv7PMgEN56zck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CbZOv/dJMcaaftaoR/mFzodyQUDmv7PMgEN56zck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCbZOv%2FdJMcaaftaoR%2FmFzodyQUDmv7PMgEN56zck%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;635&quot; height=&quot;638&quot; data-filename=&quot;20260824_214832_2.png&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Render Target에서 다음 사항을 확인할 수 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Skeletal Mesh가 정상적으로 촬영됨&lt;/li&gt;
&lt;li&gt;카메라 구도 조절 가능&lt;/li&gt;
&lt;li&gt;게임 실행 중 Idle Animation 재생 가능&lt;/li&gt;
&lt;li&gt;Render Target이 실시간으로 갱신됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 에디터 화면에서 애니메이션이 움직이지 않아 설정 문제라고 생각했다. 하지만 현재 구성은 PIE 또는 실제 게임 실행 중에 Skeletal Mesh 애니메이션 Tick이 시작되는 구조였다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;에디터 상태
&amp;rarr; 정지된 마지막 프레임이 보일 수 있음

게임 실행
&amp;rarr; Animation Tick
&amp;rarr; Scene Capture 실시간 촬영
&amp;rarr; 움직이는 Render Target 출력&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 공통 C++ 포트레이트 액터 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blueprint 테스트에서 기능이 정상적으로 동작하는 것을 확인한 후, 유닛마다 포트레이트 Blueprint를 따로 만들지 않도록 공통 C++ 액터를 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성한 클래스는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;ASFUnitPortraitActor&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 파일:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Source/StrikeForge/InGame/HUD/Portrait/
├─ SFUnitPortraitActor.h
└─ SFUnitPortraitActor.cpp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 C++ 코드는 &lt;b&gt;작성 완료 상태이며, C++ 기반 Blueprint 자식을 통한 에디터 테스트는 아직 진행 전 단계&lt;/b&gt;다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. C++ 컴포넌트 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASFUnitPortraitActor는 다음 컴포넌트로 구성했다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;ASFUnitPortraitActor
├─ SceneRoot
├─ PortraitMesh
├─ PortraitCapture
└─ PortraitLight&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SceneRoot&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트레이트를 구성하는 모든 컴포넌트의 기준점이다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;SceneRoot =
    CreateDefaultSubobject&amp;lt;USceneComponent&amp;gt;(
        TEXT(&quot;SceneRoot&quot;)
    );

SetRootComponent(SceneRoot);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PortraitMesh&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택된 유닛의 Skeletal Mesh를 표시한다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;PortraitMesh =
    CreateDefaultSubobject&amp;lt;USkeletalMeshComponent&amp;gt;(
        TEXT(&quot;PortraitMesh&quot;)
    );

PortraitMesh-&amp;gt;SetupAttachment(SceneRoot);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PortraitCapture&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3D 캐릭터를 촬영해 Render Target으로 전달한다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;PortraitCapture =
    CreateDefaultSubobject&amp;lt;USceneCaptureComponent2D&amp;gt;(
        TEXT(&quot;PortraitCapture&quot;)
    );

PortraitCapture-&amp;gt;SetupAttachment(SceneRoot);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PortraitLight&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Point Light보다 넓은 면에서 빛을 비출 수 있도록 Rect Light Component를 사용했다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;PortraitLight =
    CreateDefaultSubobject&amp;lt;URectLightComponent&amp;gt;(
        TEXT(&quot;PortraitLight&quot;)
    );

PortraitLight-&amp;gt;SetupAttachment(SceneRoot);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rect Light를 선택한 이유는 얼굴 일부분만 강하게 밝아지는 현상을 줄이고, 캐릭터 전체에 비교적 부드럽게 빛을 전달하기 위해서다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 선택 유닛의 Mesh와 Animation 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택된 유닛에 따라 포트레이트 모델을 교체할 수 있도록 ApplyPortrait() 함수를 구성했다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;bool ASFUnitPortraitActor::ApplyPortrait(
    USkeletalMesh* NewPortraitMesh,
    UAnimationAsset* NewPortraitAnimation
)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Skeletal Mesh 교체&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;PortraitMesh-&amp;gt;SetSkeletalMesh(
    NewPortraitMesh
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 PortraitMesh 하나를 사용하고 선택된 유닛에 따라 메시만 변경한다.&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;Scene Capture&lt;/li&gt;
&lt;li&gt;Render Target&lt;/li&gt;
&lt;li&gt;포트레이트 액터&lt;/li&gt;
&lt;li&gt;UI Material&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닛마다 준비할 데이터는 다음 두 가지다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Skeletal Mesh
Idle Animation&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단일 Animation Asset 재생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트레이트는 실제 전투 상태를 재현할 필요가 없기 때문에, 복잡한 Animation Blueprint 대신 Idle Animation을 직접 반복 재생하도록 구성했다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;PortraitMesh-&amp;gt;SetAnimationMode(
    EAnimationMode::AnimationSingleNode
);

PortraitMesh-&amp;gt;SetAnimation(
    NewPortraitAnimation
);

PortraitMesh-&amp;gt;Play(true);&lt;/code&gt;&lt;/pre&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;AI Controller&lt;/li&gt;
&lt;li&gt;Character Movement&lt;/li&gt;
&lt;li&gt;이동 속도&lt;/li&gt;
&lt;li&gt;전투 상태&lt;/li&gt;
&lt;li&gt;공격 대상&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 화면에 보이지 않아도 애니메이션 갱신&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트레이트 전용 메시가 일반 게임 카메라에 보이지 않으면 엔진 최적화에 의해 애니메이션 Pose 갱신이 멈출 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 방지하기 위해 다음 설정을 적용했다.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;PortraitMesh-&amp;gt;VisibilityBasedAnimTickOption =
    EVisibilityBasedAnimTickOption::
    AlwaysTickPoseAndRefreshBones;&lt;/code&gt;&lt;/pre&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;Animation Tick&lt;/li&gt;
&lt;li&gt;Pose 업데이트&lt;/li&gt;
&lt;li&gt;Bone Transform 갱신&lt;/li&gt;
&lt;li&gt;Scene Capture에 변경된 자세 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scene Capture 전용 캐릭터에서 애니메이션이 정지하는 문제를 방지하기 위한 중요한 설정이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Render Target 연결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서 특정 Render Target 경로를 하드코딩하지 않고, Blueprint 자식 클래스에서 Render Target을 지정할 수 있도록 구성했다.&lt;/p&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;UPROPERTY(
    EditDefaultsOnly,
    BlueprintReadOnly,
    Category = &quot;SF|Portrait&quot;
)
TObjectPtr&amp;lt;UTextureRenderTarget2D&amp;gt;
    PortraitRenderTarget = nullptr;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 시작 시 지정된 Render Target을 Scene Capture에 연결한다.&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;PortraitCapture-&amp;gt;TextureTarget =
    PortraitRenderTarget;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 사용하면 C++ 코드를 수정하지 않고도 Blueprint에서 다른 Render Target을 교체할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 캡처 활성화와 비활성화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Render Target을 매 프레임 갱신하면 추가적인 렌더링 비용이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 포트레이트가 필요할 때만 Scene Capture를 활성화할 수 있도록 함수를 분리했다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;void ASFUnitPortraitActor::
    SetPortraitCaptureEnabled(bool bEnabled)
{
    PortraitCapture-&amp;gt;bCaptureEveryFrame =
        bEnabled;

    PortraitCapture-&amp;gt;bCaptureOnMovement =
        bEnabled;

    if (bEnabled)
    {
        PortraitCapture-&amp;gt;CaptureScene();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상되는 사용 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;유닛 선택
&amp;rarr; 포트레이트 Mesh와 Animation 적용
&amp;rarr; Scene Capture 활성화

선택 해제
&amp;rarr; 애니메이션 정지
&amp;rarr; PortraitMesh 숨김
&amp;rarr; Scene Capture 비활성화&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RTS 게임에서는 화면에 많은 유닛과 건물이 존재하므로, 포트레이트 렌더링도 필요할 때만 동작하도록 관리하는 것이 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 최신 InGameUI 구조 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신 병합 소스를 확인한 결과, InGameUI에는 다음 기능이 추가된 상태였다.&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;다중 선택 중 대표 유닛 변경&lt;/li&gt;
&lt;li&gt;건설 모드 전환&lt;/li&gt;
&lt;li&gt;이동 목적지 표시&lt;/li&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;포트레이트는 기존 선택 시스템을 새로 만드는 것이 아니라 현재 선택 흐름에 연결할 예정이다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;SFInGameHUDWidget
&amp;rarr; 선택 정보 확정
&amp;rarr; SFUnitPortraitActor::ApplyPortrait()
&amp;rarr; Render Target 갱신
&amp;rarr; 미니맵 왼쪽 포트레이트 UI 출력&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 다중 선택 중 대표 유닛이 변경될 때도 포트레이트가 함께 변경되어야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;현재 진행 상태&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;완료&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Blueprint 기반 포트레이트 테스트 액터 구성&lt;/li&gt;
&lt;li&gt;Scene Capture와 Render Target 연결&lt;/li&gt;
&lt;li&gt;3D 캐릭터 얼굴 및 상반신 출력&lt;/li&gt;
&lt;li&gt;Idle Animation 반복 재생 확인&lt;/li&gt;
&lt;li&gt;C++ ASFUnitPortraitActor 작성&lt;/li&gt;
&lt;li&gt;Mesh 교체 함수 작성&lt;/li&gt;
&lt;li&gt;Animation Asset 적용 함수 작성&lt;/li&gt;
&lt;li&gt;Capture 활성화&amp;middot;비활성화 함수 작성&lt;/li&gt;
&lt;li&gt;Rect Light Component 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 전&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C++ 기반 BP_SFUnitPortraitActor 생성&lt;/li&gt;
&lt;li&gt;C++ 컴포넌트 에디터 출력 확인&lt;/li&gt;
&lt;li&gt;Render Target 재연결&lt;/li&gt;
&lt;li&gt;Rect Light 실제 밝기 조절&lt;/li&gt;
&lt;li&gt;카메라 위치와 FOV 조절&lt;/li&gt;
&lt;li&gt;ApplyPortrait() 실행 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이후 진행 예정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Render Target 배경 투명화&lt;/li&gt;
&lt;li&gt;Scene Capture의 캐릭터 전용 Show Only 설정&lt;/li&gt;
&lt;li&gt;UI Material 제작&lt;/li&gt;
&lt;li&gt;유닛별 Mesh&amp;middot;Animation 데이터 등록&lt;/li&gt;
&lt;li&gt;InGameUI 선택 시스템 연결&lt;/li&gt;
&lt;li&gt;미니맵 왼쪽 포트레이트 배치&lt;/li&gt;
&lt;li&gt;선택 해제와 유닛 사망 처리&lt;/li&gt;
&lt;li&gt;다중 선택 대표 유닛 변경 처리&lt;/li&gt;
&lt;li&gt;Scene Capture 성능 테스트&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>StrikForge 프로젝트/작업일지</category>
      <author>editor66484</author>
      <guid isPermaLink="true">https://editor66484.tistory.com/32</guid>
      <comments>https://editor66484.tistory.com/32#entry32comment</comments>
      <pubDate>Wed, 26 Aug 2026 00:14:37 +0900</pubDate>
    </item>
    <item>
      <title>Scene Capture Component와 3D 애니메이션 적용</title>
      <link>https://editor66484.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;미니맵 하단 UI에 선택한 유닛의 모습을 표시하기 위해 처음에는 일반적인 2D 이미지를 사용하는 방법을 고려했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 유닛의 실제 외형과 애니메이션을 보여주려면 정적인 이미지보다 3D 모델을 직접 UI에 출력하는 방식이 더 적합하다고 판단했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 발생한 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UMG의 Image 위젯은 3D 액터나 Skeletal Mesh를 직접 출력할 수 없다.&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;선택한 유닛의 3D 모델 출력&lt;/li&gt;
&lt;li&gt;유닛의 Idle 애니메이션 재생&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;처음에는 월드에 배치된 실제 유닛을 직접 촬영하는 방법을 생각했지만, 다음과 같은 문제가 있었다.&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;다른 유닛이나 이펙트가 화면에 포함될 수 있음&lt;/li&gt;
&lt;li&gt;인게임 애니메이션 상태와 UI 프리뷰 상태가 서로 영향을 받을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 해결 방향: 프리뷰 전용 액터 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 게임 유닛을 직접 촬영하는 대신, UI 출력을 담당하는 프리뷰 전용 액터를 구성하는 방향으로 변경했다.&lt;/p&gt;
&lt;pre class=&quot;vhdl&quot;&gt;&lt;code&gt;SFUnitPreviewActor
├─ Scene Component
├─ Skeletal Mesh Component
└─ Scene Capture Component 2D&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Skeletal Mesh Component: 선택한 유닛의 3D 모델 표시&lt;/li&gt;
&lt;li&gt;Scene Capture Component 2D: 프리뷰 모델 촬영&lt;/li&gt;
&lt;li&gt;Texture Render Target 2D: 촬영 결과 저장&lt;/li&gt;
&lt;li&gt;UMG Image: Render Target 결과 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 처리 과정은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div class=&quot;mermaid&quot;&gt;
flowchart LR
    A[&quot;유닛 선택&quot;] --&gt; B[&quot;프리뷰 메시 변경&quot;]
    B --&gt; C[&quot;Idle 애니메이션 적용&quot;]
    C --&gt; D[&quot;Scene Capture Component 2D 촬영&quot;]
    D --&gt; E[&quot;Render Target 저장&quot;]
    E --&gt; F[&quot;미니맵 하단 UMG Image 출력&quot;]
&lt;/div&gt;

&lt;script type=&quot;module&quot;&gt;
  import mermaid from &quot;https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs&quot;;

  mermaid.initialize({
    startOnLoad: true,
    theme: &quot;default&quot;,
    securityLevel: &quot;loose&quot;
  });
&lt;/script&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Scene Capture 범위 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scene Capture Component 2D를 단순하게 사용하면 프리뷰 모델뿐만 아니라 주변 배경과 다른 액터도 함께 촬영될 수 있다.&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;Primitive Render Mode를 필요한 액터만 표시하도록 설정&lt;/li&gt;
&lt;li&gt;프리뷰 액터를 Show Only Actors에 등록&lt;/li&gt;
&lt;li&gt;프리뷰용 배경과 조명을 별도로 구성&lt;/li&gt;
&lt;li&gt;카메라와 모델의 위치를 고정해 일정한 구도 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 사용하면 실제 게임 맵의 상태와 관계없이 동일한 유닛 프리뷰 화면을 출력할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 애니메이션이 재생되지 않는 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리뷰의 Skeletal Mesh에 인게임용 Animation Blueprint를 그대로 적용하는 방법도 고려했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 인게임 Animation Blueprint는 다음 값을 참조할 가능성이 있다.&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;AI Controller&lt;/li&gt;
&lt;li&gt;Character Movement Component&lt;/li&gt;
&lt;li&gt;공격 상태&lt;/li&gt;
&lt;li&gt;현재 타깃&lt;/li&gt;
&lt;li&gt;지상 이동 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리뷰 전용 액터에는 이러한 정보가 없기 때문에 Animation Blueprint의 조건을 충족하지 못하고 기본 포즈로 정지할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 해결 방향: 단일 애니메이션 재생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 프리뷰에서는 실제 전투 상태를 전부 재현할 필요가 없기 때문에, Idle 애니메이션을 직접 반복 재생하는 방식을 우선 적용하는 것이 적합하다고 판단했다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;PreviewMesh-&amp;gt;SetAnimationMode(
    EAnimationMode::AnimationSingleNode
);

PreviewMesh-&amp;gt;PlayAnimation(
    PreviewIdleAnimation,
    true
);&lt;/code&gt;&lt;/pre&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;AI Controller가 필요하지 않음&lt;/li&gt;
&lt;li&gt;이동 속도나 전투 상태가 필요하지 않음&lt;/li&gt;
&lt;li&gt;Animation Blueprint의 조건에 영향을 받지 않음&lt;/li&gt;
&lt;li&gt;UI 프리뷰에 필요한 애니메이션만 간단하게 재생 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 공격, 대기, 선택 등의 여러 애니메이션이 필요해지면 프리뷰 전용 Animation Blueprint를 별도로 구성할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 성능 문제에 대한 고민&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Capture Every Frame을 활성화하면 애니메이션이 자연스럽게 표시되지만, Scene Capture가 매 프레임 렌더링되기 때문에 추가적인 렌더링 비용이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 RTS 게임은 다수의 유닛과 건물을 동시에 처리하므로 Scene Capture를 항상 활성화하면 성능에 영향을 줄 수 있다.&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;유닛 프리뷰 UI가 보일 때만 Scene Capture 활성화&lt;/li&gt;
&lt;li&gt;유닛 선택이 해제되면 캡처 비활성화&lt;/li&gt;
&lt;li&gt;Render Target 해상도를 UI 크기에 맞게 제한&lt;/li&gt;
&lt;li&gt;프리뷰 모델과 필요한 배경만 렌더링&lt;/li&gt;
&lt;li&gt;정지 화면만 필요하면 선택 변경 시 한 번만 캡처&lt;/li&gt;
&lt;li&gt;애니메이션 출력이 필요할 때만 Capture Every Frame 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과 및 배운 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 트러블슈팅을 통해 UMG에서 3D 모델을 직접 출력하는 것이 아니라 다음과 같은 렌더링 과정이 필요하다는 것을 확인했다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;3D 모델
&amp;rarr; Scene Capture Component 2D
&amp;rarr; Texture Render Target 2D
&amp;rarr; UI Material
&amp;rarr; UMG Image&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 인게임 Animation Blueprint를 프리뷰에 그대로 재사용하면 의존성 문제로 애니메이션이 동작하지 않을 수 있다는 점을 확인했다.&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;필요한 모델만 Scene Capture로 촬영&lt;/li&gt;
&lt;li&gt;Idle Animation Sequence 직접 반복 재생&lt;/li&gt;
&lt;li&gt;UI가 표시될 때만 Scene Capture 활성화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 실제 인게임 로직과 UI 프리뷰를 분리하면서도 선택된 유닛의 3D 외형과 애니메이션을 안정적으로 표현할 수 있는 구조를 마련했다.&lt;/p&gt;</description>
      <category>StrikForge 프로젝트/트러블슈팅</category>
      <author>editor66484</author>
      <guid isPermaLink="true">https://editor66484.tistory.com/31</guid>
      <comments>https://editor66484.tistory.com/31#entry31comment</comments>
      <pubDate>Mon, 24 Aug 2026 00:12:48 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal Engine 5] TeamIndex 기반 자원 관리와 3D 유닛 프리뷰 구현</title>
      <link>https://editor66484.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 RTS 프로젝트의 팀별 자원 관리 구조를 정리하고, Render Target과 Scene Capture Component를 활용해 UI에 3D 유닛 모델을 표시하는 작업을 진행했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. TeamIndex 기반 상태 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 자원 데이터가 하나의 공용 값으로 관리되면 플레이어와 AI의 자원이 섞일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 AI가 광물을 획득했는데 플레이어의 광물까지 증가하는 문제가 발생할 수 있다. 이를 방지하기 위해 GameState에서 TeamIndex를 기준으로 각 팀의 데이터를 분리하도록 구조를 설계했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TeamIndex 0: 플레이어&lt;/li&gt;
&lt;li&gt;TeamIndex 1: 적 AI&lt;/li&gt;
&lt;li&gt;필요하면 이후 다른 팀도 추가 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 구조는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div class=&quot;mermaid&quot;&gt;
flowchart LR
    A[&quot;자원 획득&quot;] --&gt; B[&quot;TeamIndex 확인&quot;]
    B --&gt; C[&quot;GameState&quot;]
    C --&gt; D{&quot;TeamIndex&quot;}
    D --&gt;|&quot;0&quot;| E[&quot;플레이어 자원&quot;]
    D --&gt;|&quot;1&quot;| F[&quot;적 AI 자원&quot;]
&lt;/div&gt;

&lt;script type=&quot;module&quot;&gt;
  import mermaid from &quot;https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs&quot;;

  mermaid.initialize({
    startOnLoad: true,
    theme: &quot;default&quot;,
    securityLevel: &quot;loose&quot;
  });
&lt;/script&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 TeamIndex가 종족을 나타내는 값이 아니라 &lt;b&gt;해당 액터가 어느 팀에 소속되어 있는지 구분하는 값&lt;/b&gt;이라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종족까지 구분해야 한다면 추후 RaceType 같은 별도의 열거형을 사용하는 것이 적절하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 팀별 자원 구조체 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 팀의 자원을 하나로 묶어 관리할 수 있도록 FSFTeamResourceState 구조체를 구성했다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;USTRUCT(BlueprintType)
struct FSFTeamResourceState
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = &quot;SF|Resource&quot;)
    int32 Mineral = 0;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = &quot;SF|Resource&quot;)
    int32 Ether = 0;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = &quot;SF|Population&quot;)
    int32 CurrentPopulation = 0;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = &quot;SF|Population&quot;)
    int32 MaximumPopulation = 10;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조체에 포함한 데이터는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;변수역할
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mineral&lt;/td&gt;
&lt;td&gt;일반적인 건설 및 유닛 생산에 사용하는 광물&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ether&lt;/td&gt;
&lt;td&gt;특정 건물이나 고급 기능에 사용하는 희귀 자원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CurrentPopulation&lt;/td&gt;
&lt;td&gt;현재 생산된 유닛이 사용하는 인구수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MaximumPopulation&lt;/td&gt;
&lt;td&gt;현재 생산할 수 있는 최대 인구수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SF 접두어와 Pascal Case 규칙을 적용해 프로젝트의 명명 규칙도 유지했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. GameState에서 팀별 자원 저장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글플레이 RTS에서도 플레이어와 AI의 자원 상태는 인게임 도중 계속 변경된다. 따라서 게임 진행 상태를 관리하는 GameState에 팀별 자원 구조체를 저장하도록 구성했다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = &quot;SF|Resource&quot;)
TMap&amp;lt;int32, FSFTeamResourceState&amp;gt; TeamResources;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TMap의 구성은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;TeamIndex &amp;rarr; 해당 팀의 자원 정보&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 다음과 같이 저장된다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;0 &amp;rarr; 플레이어의 광물, 에테르, 인구수
1 &amp;rarr; 적 AI의 광물, 에테르, 인구수&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 FSFTeamResourceState* TeamResource =
    TeamResources.Find(TeamIndex);

if (TeamResource == nullptr)
{
    return 0;
}

return TeamResource-&amp;gt;Mineral;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 사용하면 자원을 획득하거나 소비할 때 반드시 TeamIndex를 함께 전달할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;AddMineral(TeamIndex, Amount);
TrySpendResource(TeamIndex, MineralCost, EtherCost);
AddPopulation(TeamIndex, PopulationCost);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI는 플레이어 팀인 TeamIndex 0의 값만 가져와 출력하면 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Render Target을 활용한 3D 유닛 프리뷰&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 미니맵 하단 UI에 선택된 유닛의 3D 모델을 보여주는 기능을 구성했다.&lt;/p&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;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;```mermaid
flowchart LR
    A[&quot;3D 유닛 모델&quot;] --&amp;gt; B[&quot;Scene Capture Component 2D&quot;]
    B --&amp;gt; C[&quot;Texture Render Target 2D&quot;]
    C --&amp;gt; D[&quot;UI Material&quot;]
    D --&amp;gt; E[&quot;UMG Image&quot;]
    E --&amp;gt; F[&quot;미니맵 하단 유닛 프리뷰&quot;]
```&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 정적인 초상화와 다르게 실제 3D 모델과 애니메이션을 UI 안에서 보여줄 수 있다는 장점이 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Scene Capture Component 사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3D 모델을 촬영하기 위해 프리뷰 전용 액터에 다음 컴포넌트를 구성했다.&lt;/p&gt;
&lt;pre class=&quot;vhdl&quot;&gt;&lt;code&gt;SFUnitPreviewActor
├─ Scene Component
├─ Skeletal Mesh Component
└─ Scene Capture Component 2D&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scene Capture Component 2D는 게임 월드를 별도의 카메라 시점으로 촬영하고, 결과를 Texture Render Target 2D에 전달한다.&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;Texture Target: 생성한 Render Target 지정&lt;/li&gt;
&lt;li&gt;Capture Source: Final Color 사용&lt;/li&gt;
&lt;li&gt;Projection Type: Perspective&lt;/li&gt;
&lt;li&gt;Primitive Render Mode: 필요한 액터만 렌더링&lt;/li&gt;
&lt;li&gt;Capture Every Frame: 애니메이션을 표시할 경우 활성화&lt;/li&gt;
&lt;li&gt;Capture On Movement: 움직일 때만 갱신할 경우 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리뷰에 필요한 유닛만 출력하려면 Show Only Actors를 활용할 수 있다. 이렇게 하면 실제 게임 맵의 배경이나 불필요한 오브젝트가 Render Target에 함께 나타나는 것을 방지할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Render Target을 UI에 연결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scene Capture Component 2D에서 촬영한 화면을 UMG에 표시하기 위해 다음 순서로 연결했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Texture Render Target 2D 생성&lt;/li&gt;
&lt;li&gt;Render Target을 사용하는 UI Material 생성&lt;/li&gt;
&lt;li&gt;Material Domain을 User Interface로 설정&lt;/li&gt;
&lt;li&gt;Render Target을 Texture Sample에 연결&lt;/li&gt;
&lt;li&gt;UI 위젯의 Image Brush에 해당 Material 적용&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;SceneCapture2D
    &amp;darr;
Texture Render Target 2D
    &amp;darr;
UI Material
    &amp;darr;
UMG Image&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 미니맵 하단의 Image 위젯을 통해 선택된 유닛의 3D 모델을 표시할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 3D 애니메이션 적용 방법 조사&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3D 유닛 프리뷰가 정지된 모델로만 보이지 않도록 Skeletal Mesh Component에 애니메이션을 적용하는 방법도 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용 방식은 크게 두 가지가 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Animation Blueprint 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 인게임 유닛과 동일한 애니메이션 블루프린트를 적용하는 방식이다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;PreviewMesh-&amp;gt;SetAnimInstanceClass(AnimationBlueprintClass);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 인게임 애니메이션 블루프린트가 이동 속도나 AI Controller 상태를 요구한다면 프리뷰 액터에서는 정상적으로 동작하지 않을 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단일 Animation Sequence 재생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리뷰용 Idle 애니메이션 하나만 반복 재생하는 방식이다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;PreviewMesh-&amp;gt;SetAnimationMode(
    EAnimationMode::AnimationSingleNode
);

PreviewMesh-&amp;gt;PlayAnimation(
    PreviewIdleAnimation,
    true
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닛 프리뷰에서는 복잡한 인게임 상태가 필요하지 않기 때문에, 우선은 Idle Animation Sequence를 반복 재생하는 방식이 더 단순하고 안정적이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작업하면서 확인한 점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;팀 데이터와 종족 데이터는 분리해야 한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TeamIndex는 플레이어와 적 AI를 구분하기 위한 값이다. 비스트, 인간 등의 종족 정보를 함께 처리하려면 별도의 RaceType이 필요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UI가 자원 값을 직접 변경하면 안 된다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI는 GameState에서 자원 값을 조회하고 화면에 표시하는 역할만 담당해야 한다. 실제 자원 증감은 채집 건물, 생산 시스템 또는 게임 로직을 통해 처리해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Scene Capture는 성능을 고려해야 한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Capture Every Frame을 사용하면 매 프레임 Render Target을 갱신한다. 유닛 프리뷰가 항상 표시될 필요가 없다면 다음과 같이 최적화할 수 있다.&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;결과창이나 HUD가 열려 있을 때만 활성화&lt;/li&gt;
&lt;li&gt;Render Target 해상도를 필요 이상으로 크게 설정하지 않기&lt;/li&gt;
&lt;li&gt;프리뷰용 조명과 메시만 렌더링하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RTS 게임에서는 화면에 많은 유닛이 존재하므로, 프리뷰 UI까지 불필요하게 매 프레임 갱신하지 않도록 주의해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작업 결과&lt;/h2&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;GameState에서 TeamIndex별 데이터 분리&lt;/li&gt;
&lt;li&gt;광물, 에테르, 현재 인구수, 최대 인구수를 하나의 구조체로 관리&lt;/li&gt;
&lt;li&gt;플레이어와 AI의 자원이 서로 섞이지 않는 구조 마련&lt;/li&gt;
&lt;li&gt;Render Target을 이용한 3D 유닛 프리뷰 흐름 구성&lt;/li&gt;
&lt;li&gt;Scene Capture Component 2D와 UMG Image 연결 방식 확인&lt;/li&gt;
&lt;li&gt;프리뷰 유닛에 Idle 애니메이션을 적용하는 방법 조사&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 작업에서는 실제 자원 채집 시스템이 TeamIndex를 전달하도록 연결하고, 선택된 유닛이 변경될 때 프리뷰 메시와 애니메이션도 함께 변경되도록 확장할 예정이다.&lt;/p&gt;</description>
      <category>StrikForge 프로젝트/작업일지</category>
      <author>editor66484</author>
      <guid isPermaLink="true">https://editor66484.tistory.com/30</guid>
      <comments>https://editor66484.tistory.com/30#entry30comment</comments>
      <pubDate>Mon, 24 Aug 2026 00:11:17 +0900</pubDate>
    </item>
  </channel>
</rss>