<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>생각처럼</title>
    <link>https://think05994.tistory.com/</link>
    <description>생각처럼 개발 공부 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Sun, 28 Jun 2026 14:44:05 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>thinklikethink</managingEditor>
    <image>
      <title>생각처럼</title>
      <url>https://tistory1.daumcdn.net/tistory/8619703/attach/f2ac5df48a2541b98603dd827562cfad</url>
      <link>https://think05994.tistory.com</link>
    </image>
    <item>
      <title>내일배움캠프 언리얼트랙 45일차 - GAS</title>
      <link>https://think05994.tistory.com/59</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Gameplay Ability System, GAS&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;lt; GAS의 개요 &amp;gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;스킬을 중심으로 액터 간의 상호 작용을 설계하는 프레임워크.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 스킬 시스템을 가지고 이를 기반으로 캐릭터들이 상호작용하는 RPG, 액션 어드벤처, 모바 같은 장르의 게임을 만들 때 큰 도움이 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 대규모 RPG 및 네트워크 멀티플레이어 게임 개발에 최적화된 시스템. 높은 효율성과 확장성을 제공하지만 기본적으로 배워야 할 내용이 많아 학습 장벽이 높으며, 올바른 활용을 위해서는 체계적인 이해가 요구된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;lt; 기존의 게임 개발 방식 &amp;gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FSM, Behavior Tree같은 &lt;b&gt;현재 상황에 맞춰 다음 행동을 결정하는 언리얼엔진에서 제공하는 대표적인 의사결정 시스템&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결정에 따른 행동 실행은 있지만 결과를 시스템에 반영하는 피드백 구조가 부족함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결정 시스템으로 명령 내린 행동에 대응하는 캐릭터의 모션은 재생되지만 그 이후의 전체 시스템에 대해서는 개발자가 직접 구형해야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;피드백 루프 시스템을 설계하기 위해서는 정교한 설계 능력과 깊은 개발 경험이 필요함&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;GAS는 코어 게임 플레이 루프 제작을 지원하는 프레임워크&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GAS에서 루프의 구성 요소는 Ability, Attribute, Effect 라는 이름으로 제공&lt;/b&gt;됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;lt; GAS의 장단점 &amp;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;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;모듈화가 잘 되어있다&lt;/b&gt;&lt;/span&gt;.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의존성 최소화.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;각 컴포넌트가 독립적으로 작동할 수 있도록 설계되어있어 프로젝트 규모에 제한되지않고 쉽게 확장이 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;네트워크 멀티플레이어 기능&lt;/b&gt;&lt;/span&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;유연성, 확장성&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡하고 다양한 게임 개발 환경을 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;데이터 기반 설계&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 테이블과 환경 설정을 기반으로 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;&lt;b&gt;높은 학습 난이도&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;구성요소가 많고 복잡함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;&lt;b&gt;잠재적인 오버헤드&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;규모가 작거나 단순한 프로젝트에는 시스템이 너무 복잡&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;lt; GAS의 핵심 구성 요소 &amp;gt;&lt;/h3&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 style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;b&gt;Ability System Component&lt;/b&gt;&lt;br /&gt;&lt;b&gt;(ASC)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Ability System Component&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;b&gt;Gameplay Tag&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Gameplay Tag&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Gameplay Tag Container&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;b&gt;Gameplay Ability&lt;/b&gt;&lt;br /&gt;&lt;b&gt;(GA)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Gameplay Ability&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Ability Task&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Gameplay Event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;b&gt;Attribute Set&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Gameplay Attribute Data&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Attribute Set&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;b&gt;Gameplay Effect&lt;/b&gt;&lt;br /&gt;&lt;b&gt;(GE)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Instance Gameplay Effect&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;Durational Gameplay Effect&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;b&gt;Gameplay Cue&lt;/b&gt;&lt;br /&gt;&lt;b&gt;(GC)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;GameplayCue Static&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;GameplayCue Actor&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;Ability System Component, ASC&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; GAS를 구동하는 핵심 컴포넌트. GAS의 다른 구성요소를 하나씩 쌓아올릴 수 있는 기반.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor에 ASC를 붙이면 해당 액터에서 GAS를 사용 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;Gameplay Tag&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; 언리얼엔진이 기본으로 제공하는 태그보다 강력한 계층 구조를 지원하는 태그 시스템.&lt;/b&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;GAS가 만드는 커다란 시스템을 잘 정리하고 규모를 늘리는 데 유용하게 사용됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;Gameplay Ability, GA&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; Gameplay 설계의 시작을 의미.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐릭터가 행하는 모든 행동(공격, 회피, 캐스팅..)이나 스킬을 GA로 만들 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GA로 포장된 캐릭터의 스킬은 이후 성공/실패/취소 등 여러 결과를 가지게 되는데 이를 발동하는 로직을 Ability Task로 처리함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 스킬을 성공적으로 성공했다면, 이후 Gameplay Event를 통해 다양한 상황 설계 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;Attribute Set&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; 체력, 마나, 임시 데미지 같은 캐릭터가 가진 스탯을 표현하는데 사용.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;Gameplay Effect, GE&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; &lt;b&gt;단순한 데미지를 넘어 복잡한 효과를 제작 가능. 게임시스템 설계의 핵심. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GAS가 제공하는 프레임워크 덕분에 코딩 없이 블루프린트로 기본값을 설정하는 것만으로도 쉽게 구현 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;Gameplay Cue, GC&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; 치장 효과를 효율적으로 처리할 수 있도록 GAS에서 제공하는 독립적인 시스템.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자에게 시각적, 청각적인 피드백 효과를 주는것 : 코스메틱 치장 효과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 시스템과 무관하게 동작함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC에서 게임 클라이언트에 사용되는 시각적, 청각적인 효과를 모두 모아서 관리함.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;lt; GAS 핵심 게임 플레이 루프 &amp;gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;GAS의 핵심 구성 요소들은 독립적인 모듈로 제공되지만, 서로 연결되어서 코어 게임 플레이 루프를 구성함.&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;409&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djkv47/dJMcaiw4pBT/R0zawWEznlSiawxSbyrryk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djkv47/dJMcaiw4pBT/R0zawWEznlSiawxSbyrryk/img.png&quot; data-alt=&quot;GAS 핵심 게임 플레이 루프&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djkv47/dJMcaiw4pBT/R0zawWEznlSiawxSbyrryk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdjkv47%2FdJMcaiw4pBT%2FR0zawWEznlSiawxSbyrryk%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;573&quot; height=&quot;409&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;409&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;GAS 핵심 게임 플레이 루프&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>내일배움캠프</category>
      <author>thinklikethink</author>
      <guid isPermaLink="true">https://think05994.tistory.com/59</guid>
      <comments>https://think05994.tistory.com/59#entry59comment</comments>
      <pubDate>Thu, 25 Jun 2026 20:30:44 +0900</pubDate>
    </item>
    <item>
      <title>내일배움캠프 언리얼트랙 44일차 - 손님NPC 신고게이지</title>
      <link>https://think05994.tistory.com/58</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어제 해결하지 못한 문제를 오늘 이어서 해결해보려합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 1. 시야 내에서 훔치고 있는데 신고하지 않음. (해결)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;현재 상황&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손님NPC가 플레이어를 감지하는가? O&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어가 훔치는 상호작용을 할 때 채널링이 되고 있는가? O&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도둑질로 판정을 하고있는가? O&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심고 흐름 시작 조건에 부합하는가? O&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 전부 통과되면 신고를 진행해야하는데 진행되지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손님NPC의 시야 내에서 훔치는 상호작용을 했을 때 뜨는 경고로그&lt;/p&gt;
&lt;pre id=&quot;code_1782315814746&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LogStateTree: Warning:
Evaluation forced to false: source data cannot be accessed
(e.g. enter conditions trying to access inactive parent state)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 기준으로 NPC의 감지 쪽에는 문제가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State Tree에 문제가 있다는 것.&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;StateTree를 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;399&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJTxkM/dJMcadoTyVr/VvZKmyoezvkkKJ0wLQa91k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJTxkM/dJMcadoTyVr/VvZKmyoezvkkKJ0wLQa91k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJTxkM/dJMcadoTyVr/VvZKmyoezvkkKJ0wLQa91k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJTxkM%2FdJMcadoTyVr%2FVvZKmyoezvkkKJ0wLQa91k%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;549&quot; height=&quot;399&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;399&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어제 Shopping -&amp;gt; Suspicious의 Transition의 Trogger를 On Tick으로 변경했었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;813&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ce4txb/dJMcajo4241/vNBSEdEcxjE3gdRgLIajZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ce4txb/dJMcajo4241/vNBSEdEcxjE3gdRgLIajZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ce4txb/dJMcajo4241/vNBSEdEcxjE3gdRgLIajZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fce4txb%2FdJMcajo4241%2FvNBSEdEcxjE3gdRgLIajZk%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;969&quot; height=&quot;813&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;813&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shopping -&amp;gt; Suspicious의 Condition을 살펴보면 현재&lt;/p&gt;
&lt;pre id=&quot;code_1782316326325&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TargetActor is Valid

&amp;amp;&amp;amp;

HasSeenTarget == true

&amp;amp;&amp;amp;

IsTargetStealing == true&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Shopping상태에서 Suspicious로 가는 조건을 전부 바꿔주겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1782316406093&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CanStartReportFlow() == true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;CanStartReportFlow()는 신고를 시작할 수 있는지 판단하는 함수이다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;이전에 설정한 조건(TargetActor 유효, bHasSeenTarget, bIsTargetStealing)이 모두 참일 경우 true를 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5gZeU/dJMcadvDEEG/i7lbKk3Kvg8ADGWphLtjv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5gZeU/dJMcadvDEEG/i7lbKk3Kvg8ADGWphLtjv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5gZeU/dJMcadvDEEG/i7lbKk3Kvg8ADGWphLtjv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5gZeU%2FdJMcadvDEEG%2Fi7lbKk3Kvg8ADGWphLtjv1%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;543&quot; height=&quot;388&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;388&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;p data-ke-size=&quot;size16&quot;&gt;음...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Suspicious 상태까지 가는지를 확인하기 위해 Suspicious의 Task에 Debug Text를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1782317005965&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LogFTNPC: NPC AI: Picked shopping target BP_FTShoppingPoint_C_1 at X=-1060.000 Y=1190.000 Z=0.000
LogFTNPC: NPC AI: Picked shopping target BP_FTShoppingPoint_C_1 at X=-1060.000 Y=1190.000 Z=0.000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인해본 결과, Suspicious에 추가했던 Debug Text가 출력되긴 한다, 그런데, 훔치지 않고있는데도 뜬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그와 비교하면서 보니 NPC가 쇼핑 타겟을 고를때마다 디버그 텍스트가 같이 깜빡이며 계속 출력되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shopping_Pick에서 바로 Suspicious로 넘어갔다가 다시 쇼핑 루프로 돌아가는 중일 가능성이 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shopping_Pick -&amp;gt; Suspicious가 문제인 듯 하다.&lt;/p&gt;
&lt;pre id=&quot;code_1782317115623&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Shopping_Pick &amp;rarr; Suspicious 전환 삭제&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;1645&quot; data-origin-height=&quot;1029&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AvgbH/dJMcag62vdB/nQz3B48RNh5ZZGTzM8kfV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AvgbH/dJMcag62vdB/nQz3B48RNh5ZZGTzM8kfV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AvgbH/dJMcag62vdB/nQz3B48RNh5ZZGTzM8kfV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAvgbH%2FdJMcag62vdB%2FnQz3B48RNh5ZZGTzM8kfV1%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;1645&quot; height=&quot;1029&quot; data-origin-width=&quot;1645&quot; data-origin-height=&quot;1029&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;또한, Reporting으로 넘어가지 않는 이유는 Suspicious 가 2초 동안 유지되지 못하고 중간에 끊기고 있는 듯 하다.&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;이렇게 고치니 시야 범위 내에서 훔치는 상호작용을 할 때 Debug Text가 뜬다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=Z4WjaYEmXwA&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bUp5JN/dJMb8XkpwW3/FgoO77KdVOtfTWhGc7j5d0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/3N27T/dJMb8XSfT7l/gzL0ked1uMTKgvzBAUBpT0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/dFgFlN/dJMb8T99LUi/ZtW5KHcDBfNpfSPuzNst0k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;Suspicious디버그텍스트확인&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Z4WjaYEmXwA&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 아직 신고를 하지는 않고있다.&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;Reporting쪽을 살펴본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1649&quot; data-origin-height=&quot;1027&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTQyEE/dJMcadblty2/InMbRJl69dFKidqhH19RK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTQyEE/dJMcadblty2/InMbRJl69dFKidqhH19RK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTQyEE/dJMcadblty2/InMbRJl69dFKidqhH19RK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTQyEE%2FdJMcadblty2%2FInMbRJl69dFKidqhH19RK1%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;1649&quot; height=&quot;1027&quot; data-origin-width=&quot;1649&quot; data-origin-height=&quot;1027&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흠... 왜 안되는건지 고민고민...하다가 Task가 눈에 들어왔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;135&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CmGnV/dJMcahdQ95R/sDkCepC19gBta6ZEqspTx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CmGnV/dJMcahdQ95R/sDkCepC19gBta6ZEqspTx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CmGnV/dJMcahdQ95R/sDkCepC19gBta6ZEqspTx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCmGnV%2FdJMcahdQ95R%2FsDkCepC19gBta6ZEqspTx1%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;677&quot; height=&quot;135&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;135&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헉 NPCAIController = STT Enter Reporting.NPCAIController로 설정되어있다?!?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허허... 이거때문이었군. 그래서 source data cannot be accessed라는 경고가 떳던거였다.&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;다른 Task의 변수 값을 참조하는거니 경고가 뜰 수 밖에..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;NPCAIController &lt;/span&gt;&lt;span&gt;= AIController&lt;/span&gt; 으로 당장 고치자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=7HAVmXANOeQ&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bolXa0/dJMb9dHyaqs/1ek14xUxMqVvayckEf4If0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/cft7gL/dJMb9cBRWPb/kFYdKxwxQnZsZHO7QUK8P0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/jbC0T/dJMb9fZFgkB/5mB0l6dFNTzDPCZhrxDIn1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;신고게이지차오름&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/7HAVmXANOeQ&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신고게이지가 잘 오르는걸 확인했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>내일배움캠프</category>
      <author>thinklikethink</author>
      <guid isPermaLink="true">https://think05994.tistory.com/58</guid>
      <comments>https://think05994.tistory.com/58#entry58comment</comments>
      <pubDate>Wed, 24 Jun 2026 21:51:31 +0900</pubDate>
    </item>
    <item>
      <title>내일배움캠프 언리얼트랙 43일차 - 손님NPC AI Controller, State Tree(2)</title>
      <link>https://think05994.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;계속 고치려고 하다보니 너무 꼬여서 손님NPC를 처음부터 다시 만들기로 했습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제1. 회전 회오리~~(해결)&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;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=qovY5g2P9d4&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/g0cE5/dJMb86PbMrx/OfYHw1uaJ0hMTPjRKvscfk/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360,https://scrap.kakaocdn.net/dn/deC8OZ/dJMb82MNb1V/1XuK0Kons7BfNcUlLxTIHK/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360,https://scrap.kakaocdn.net/dn/bmhyJn/dJMb88Ge35D/b3wFQIPyXL9lP04RJAxeUk/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;20260623문제1회전회오리~&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/qovY5g2P9d4&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;해결 과정&amp;gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782191773828&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;원인 : 

STT_PickRandomWanderTarget
&amp;rarr; PickRandomWanderTarget 호출
&amp;rarr; Finish Task Success
&amp;rarr; State Completed
&amp;rarr; Shopping으로 즉시 재진입
&amp;rarr; 다시 PickRandomWanderTarget 호출&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손님 NPC의 목적지가 계속 바뀌는데 Move To가 제대로 이동하기 전에 다음 포인트로 계속 덮어씌워지는 상태인 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;503&quot; data-origin-height=&quot;259&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xqQ64/dJMcabLvwba/7NMheGjE3xxZGrmJsEkJOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xqQ64/dJMcabLvwba/7NMheGjE3xxZGrmJsEkJOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xqQ64/dJMcabLvwba/7NMheGjE3xxZGrmJsEkJOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxqQ64%2FdJMcabLvwba%2F7NMheGjE3xxZGrmJsEkJOK%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;503&quot; height=&quot;259&quot; data-origin-width=&quot;503&quot; data-origin-height=&quot;259&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 Shopping 하나였던걸 3개로 나눠보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(해결영상)&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=rwf3nEEbSUY&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cMF2VM/dJMb9hDa6rV/bfszbQ8pkYRKHzklwP9X40/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/lEEw4/dJMb8XSfTOc/sU7AYzhiVmJq24xf2KM5ak/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bHjDYo/dJMb8U83IFI/MdbOkPodVbjLPiLIBhfGgk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;회전회오리해결&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/rwf3nEEbSUY&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제야 잘 움직인다! 해결~~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;문제2. 시야 내에서 훔치고 있는데 신고하지 않음.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손님NPC가 플레이어를 감지하긴 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 시야 내에서 물건을 훔치는 상호작용을 하고 있는데도 신고 게이지가 차오르지 않음. Suspicious상태가 되지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[NPC] Enter Suspicious 로그가 뜨지않음...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;구현하고자 했던 것&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어가 손님NPC의 시야 내에서 물건을 훔치면 Suspicious됨. 의심게이지가 다 차면 신고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coNsAt/dJMcadPW8m9/HnrFQKLSEIIJKp9McxTgT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coNsAt/dJMcadPW8m9/HnrFQKLSEIIJKp9McxTgT0/img.png&quot; data-alt=&quot;현재 손님NPC의 State Tree&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coNsAt/dJMcadPW8m9/HnrFQKLSEIIJKp9McxTgT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoNsAt%2FdJMcadPW8m9%2FHnrFQKLSEIIJKp9McxTgT0%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;523&quot; height=&quot;398&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;현재 손님NPC의 State Tree&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, Shopping -&amp;gt; Suspicious의 Trigger를 On Tick 으로 변경하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;55&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IrFc6/dJMcaiw2odM/9ufCCKJ0pmkeJeN1zafMOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IrFc6/dJMcaiw2odM/9ufCCKJ0pmkeJeN1zafMOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IrFc6/dJMcaiw2odM/9ufCCKJ0pmkeJeN1zafMOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIrFc6%2FdJMcaiw2odM%2F9ufCCKJ0pmkeJeN1zafMOK%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;456&quot; height=&quot;55&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;55&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 변경하고 플레이 해보니...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 경고가 왕창 뜬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT에게 물어본다.&lt;/p&gt;
&lt;pre id=&quot;code_1782211727762&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LogStateTree: Warning:
Evaluation forced to false: source data cannot be accessed
(e.g. enter conditions trying to access inactive parent state)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StateTree 바인딩/조건 위치 문제라고 한다. 의미는&lt;/p&gt;
&lt;pre id=&quot;code_1782211753336&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;StateTree 조건에서 어떤 데이터를 읽으려고 했는데,
그 데이터가 현재 활성 상태에서 접근 불가능해서
조건 평가를 강제로 false 처리했다&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추정 원인...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shopping -&amp;gt; Suspicious 조건에서 TargetActor, bHasSeenTarget, bIsTargetStealing을 읽을 때, 그 값이 &lt;b&gt;상태 내부 Task의 Instance Data&lt;/b&gt;나 &lt;b&gt;비활성 상태의 데이터&lt;/b&gt;로 잡혀 있을 가능성..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 내일 해결해보려합니다.&lt;/p&gt;</description>
      <category>내일배움캠프</category>
      <author>thinklikethink</author>
      <guid isPermaLink="true">https://think05994.tistory.com/57</guid>
      <comments>https://think05994.tistory.com/57#entry57comment</comments>
      <pubDate>Tue, 23 Jun 2026 12:32:48 +0900</pubDate>
    </item>
    <item>
      <title>내일배움캠프 언리얼트랙 42일차 - 손님NPC AI Controller, State Tree</title>
      <link>https://think05994.tistory.com/56</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 1. 손님 NPC 태그 액터로 이동하지 않는 문제.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;구현하고자 했던 목표&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쇼핑 타겟으로 액터 태그를 가진 액터를 목표로 이동. 어느정도 머물렀다가 다음 타겟으로 이동.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;문제 분석&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 쇼핑 타겟 선택까지는 잘 작동함. 로그도 찍힘.&lt;/p&gt;
&lt;pre id=&quot;code_1782123563694&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LogFTNPC: NPC AI: Picked shopping target BP_ShoppingTestActor_C_1 at X=1060.000 Y=-280.000 Z=69.500&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, PickRandomShoppingTarget() 호출 문제는 아님.&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 id=&quot;code_1782123675752&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PathFollowingComponent = inactive
NPCStateTreeAIComponent = inactive&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;GPT에게 물어보니 State Tree가 타겟 선택 Task까지 실행한 후 끝났거나 Move To Task가 실행되지 않고 있다고 힘.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; State Tree의 Task에 문제가 있다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqDQq1/dJMcagTvRkf/KrTYUqkthjOoA8bTWJy3k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqDQq1/dJMcagTvRkf/KrTYUqkthjOoA8bTWJy3k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqDQq1/dJMcagTvRkf/KrTYUqkthjOoA8bTWJy3k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqDQq1%2FdJMcagTvRkf%2FKrTYUqkthjOoA8bTWJy3k1%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;939&quot; height=&quot;440&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shopping 상태 안에 고작 이거 하나 넣어놨는데... 음.&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;아무래도 Shopping 상태가 끝났을 때 다시 Shopping이 실행되지 않는 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;랜덤&amp;nbsp;위치&amp;nbsp;선택&amp;nbsp;&amp;rarr;&amp;nbsp;이동&amp;nbsp;&amp;rarr;&amp;nbsp;대기&amp;nbsp;&amp;rarr;&amp;nbsp;랜덤&amp;nbsp;위치&amp;nbsp;선택&amp;nbsp;&amp;rarr;&amp;nbsp;이동&amp;nbsp;&amp;rarr;&amp;nbsp;대기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 구현하려 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;해결 과정&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782124111884&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Root
  Shopping
    Task 1: PickRandomShoppingTarget
    Task 2: Move To
      Target Location = AIController.ShoppingTargetLocation
      Acceptable Radius = 50~100
    Task 3: Wait
      Duration = 1~2
    Transition:
      On State Completed &amp;rarr; Shopping&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task 1: PickRandomShoppingTarget은 언리얼 기본 제공 Task가 아니라 직접 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StateTree Task 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-22 193143.png&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QuYJs/dJMcahSlX9u/KPtlwxTXT5Q4K043PDRJN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QuYJs/dJMcahSlX9u/KPtlwxTXT5Q4K043PDRJN0/img.png&quot; data-alt=&quot;StateTreeTaskBlueprintBase&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QuYJs/dJMcahSlX9u/KPtlwxTXT5Q4K043PDRJN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQuYJs%2FdJMcahSlX9u%2FKPtlwxTXT5Q4K043PDRJN0%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;615&quot; height=&quot;672&quot; data-filename=&quot;스크린샷 2026-06-22 193143.png&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;StateTreeTaskBlueprintBase&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-22 204045.png&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDAGGA/dJMcabdwj0f/YEER9ecl8UPaEuKtSNqRJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDAGGA/dJMcabdwj0f/YEER9ecl8UPaEuKtSNqRJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDAGGA/dJMcabdwj0f/YEER9ecl8UPaEuKtSNqRJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDAGGA%2FdJMcabdwj0f%2FYEER9ecl8UPaEuKtSNqRJk%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;1748&quot; height=&quot;363&quot; data-filename=&quot;스크린샷 2026-06-22 204045.png&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&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;Shopping의 Task에 추가해보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5PPRd/dJMcaay0COS/P0x8zCjNaNbC2k4XcWwUpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5PPRd/dJMcaay0COS/P0x8zCjNaNbC2k4XcWwUpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5PPRd/dJMcaay0COS/P0x8zCjNaNbC2k4XcWwUpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5PPRd%2FdJMcaay0COS%2FP0x8zCjNaNbC2k4XcWwUpk%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;734&quot; height=&quot;278&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1782129876661&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;쇼핑 위치 선택
-&amp;gt; 이동
-&amp;gt; 대기
-&amp;gt; 쇼핑 위치 선택
-&amp;gt; 이동
-&amp;gt; 대기
-&amp;gt; ...&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;p data-ke-size=&quot;size16&quot;&gt;이번엔 STT쪽 문제 같아서 Print string 노드를 추가해서 어디서 안되고 있는지 확인해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 성공했을&amp;nbsp; 경우의 뒤에 Print String 추가.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-22 205139.png&quot; data-origin-width=&quot;1397&quot; data-origin-height=&quot;751&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SxBH8/dJMcaaeHTLc/h6hC1NKXdK1IS6WQIyuNe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SxBH8/dJMcaaeHTLc/h6hC1NKXdK1IS6WQIyuNe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SxBH8/dJMcaaeHTLc/h6hC1NKXdK1IS6WQIyuNe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSxBH8%2FdJMcaaeHTLc%2Fh6hC1NKXdK1IS6WQIyuNe0%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;1397&quot; height=&quot;751&quot; data-filename=&quot;스크린샷 2026-06-22 205139.png&quot; data-origin-width=&quot;1397&quot; data-origin-height=&quot;751&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력이 안뜸. &lt;b&gt;즉, 성공하지 않았음.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디버거를 보면...&lt;/p&gt;
&lt;pre id=&quot;code_1782130146432&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Current State:
[Root]
[Shopping]

Tasks:
[STT Pick Random Shopping Target]
[Move To]
[Delay Task]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나오는데 이 말은 즉슨, Shopping 상태에는 들어갔지만, STT 내부의 Blueprint 이벤트가 실행되지 않았거나 Print가 화면에 안 찍히는 상태. Print는 잘 찍힌다고 가정하고 이벤트가 실행되지 않았을 가능성 존재.&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;(2) 이벤트가 실행은 되고있는지 확인하기 위해 이벤트노드 바로 뒤에 Print String 추가.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-22 205445.png&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnwUCz/dJMcahSlYJv/OgwfP6NaWaHAZ8keuebDHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnwUCz/dJMcahSlYJv/OgwfP6NaWaHAZ8keuebDHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnwUCz/dJMcahSlYJv/OgwfP6NaWaHAZ8keuebDHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnwUCz%2FdJMcahSlYJv%2FOgwfP6NaWaHAZ8keuebDHk%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;593&quot; height=&quot;306&quot; data-filename=&quot;스크린샷 2026-06-22 205445.png&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Print가 잘 되는 모습. 그렇다면, 이벤트는 실행되고 있다는 뜻인데..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;ShoppingTargetLocation 값은 설정됐지만 &lt;/span&gt;&lt;span&gt;Move To가 그 위치로 이동하지 못하고 바로 실패/종료됨&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) Get Actor Location 뒤에 Print String을 추가해 실제 좌표가 나오는지 확인.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 나오지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 원인은 &lt;b&gt;Get Actor Location까지 흐름이 도달하지 못한다는 뜻!&lt;/b&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;(4) Get All Actors Of Class 뒤에 Print String을 추가해 쇼핑 타겟으로 액터 태그를 가진 액터를 찾고는 있는지 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-22 205806.png&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccG6j1/dJMcaicLQ11/SlQKCG3bbZXntLuPSkZNrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccG6j1/dJMcaicLQ11/SlQKCG3bbZXntLuPSkZNrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccG6j1/dJMcaicLQ11/SlQKCG3bbZXntLuPSkZNrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccG6j1%2FdJMcaicLQ11%2FSlQKCG3bbZXntLuPSkZNrK%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;602&quot; height=&quot;439&quot; data-filename=&quot;스크린샷 2026-06-22 205806.png&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 나옴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쇼핑 타겟은 정상적으로 찾고 있음. 허허...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(5) Branch True 이후 흐름을 확인하기 위해 Branch의 False에 Print String 추가. 그리고 뒤쪽에 있는 Cast Fail 에도 Print String 추가.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-22 210053.png&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfcadA/dJMcaiDMOHA/9eWfKjJZTLxQdotK4QBfA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfcadA/dJMcaiDMOHA/9eWfKjJZTLxQdotK4QBfA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfcadA/dJMcaiDMOHA/9eWfKjJZTLxQdotK4QBfA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfcadA%2FdJMcaiDMOHA%2F9eWfKjJZTLxQdotK4QBfA0%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;1388&quot; height=&quot;694&quot; data-filename=&quot;스크린샷 2026-06-22 210053.png&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&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;b&gt;Cast 실패&lt;/b&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 지금 Get AIController가 반환한 값이 BP_FTNPCController가 아니라는 뜻.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 Get AIController의 &lt;b&gt;Controlled Actor 입력이 비어 있거나 잘못 들어간 경우&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QwfWn/dJMcabYZ7KJ/eueFcbF1IX2EeAKlowHNmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QwfWn/dJMcabYZ7KJ/eueFcbF1IX2EeAKlowHNmK/img.png&quot; data-alt=&quot;Controlled Actor가 비어있는 탓일까~ 뚠뚠&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QwfWn/dJMcabYZ7KJ/eueFcbF1IX2EeAKlowHNmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQwfWn%2FdJMcabYZ7KJ%2FeueFcbF1IX2EeAKlowHNmK%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;278&quot; height=&quot;98&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Controlled Actor가 비어있는 탓일까~ 뚠뚠&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StateTree Task 블루프린트에서 Cast 대상이 잘못된 듯 하다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드를 찾는건 좀 귀찮으니 C++코드로 바꿔버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVOgGB/dJMcabLuYMt/kFxTyX0IzrxJL8WOP18210/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVOgGB/dJMcabLuYMt/kFxTyX0IzrxJL8WOP18210/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVOgGB/dJMcabLuYMt/kFxTyX0IzrxJL8WOP18210/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVOgGB%2FdJMcabLuYMt%2FkFxTyX0IzrxJL8WOP18210%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;521&quot; height=&quot;243&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;243&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;750&quot; data-origin-height=&quot;909&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsolwi/dJMcajo3m0R/YKVeXwcuE0qA9kFWHvebrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsolwi/dJMcajo3m0R/YKVeXwcuE0qA9kFWHvebrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsolwi/dJMcajo3m0R/YKVeXwcuE0qA9kFWHvebrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbsolwi%2FdJMcajo3m0R%2FYKVeXwcuE0qA9kFWHvebrK%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;750&quot; height=&quot;909&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;909&quot;/&gt;&lt;/span&gt;&lt;/figure&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;FT Pick Random Shopping Target에서 Move To까지 못 가고 상태가 다시 들어가는 상태같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PickRandomShoppingTarget()가 계속 반복 호출되고있다.&lt;/p&gt;
&lt;pre id=&quot;code_1782138788136&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (NPCAIController-&amp;gt;bHasShoppingTarget)
{
	return EStateTreeRunStatus::Running;
}

return NPCAIController-&amp;gt;PickRandomShoppingTarget()
	? EStateTreeRunStatus::Running
	: EStateTreeRunStatus::Failed;&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;한 번 타겟을 고르면 bHasShoppingTarget == true가 되니까 같은 상태에 들어가더라도 계속 새 타겟을 찾지 않을거다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;995&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RvicC/dJMcai4Mb8O/4KgrGOKYs2T7YivvQWBsX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RvicC/dJMcai4Mb8O/4KgrGOKYs2T7YivvQWBsX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RvicC/dJMcai4Mb8O/4KgrGOKYs2T7YivvQWBsX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRvicC%2FdJMcai4Mb8O%2F4KgrGOKYs2T7YivvQWBsX1%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;1385&quot; height=&quot;995&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;995&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 로그가 쭈르륵 뜨지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;... 이 문제는 내일 다시 해결해봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>내일배움캠프</category>
      <author>thinklikethink</author>
      <guid isPermaLink="true">https://think05994.tistory.com/56</guid>
      <comments>https://think05994.tistory.com/56#entry56comment</comments>
      <pubDate>Mon, 22 Jun 2026 11:07:34 +0900</pubDate>
    </item>
    <item>
      <title>내일배움캠프 언리얼트랙 41일차 - 보안요원 State Tree</title>
      <link>https://think05994.tistory.com/55</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Pr2S/dJMcabxO9yE/bGKiEtrmbVpWh3cx4RrzY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Pr2S/dJMcabxO9yE/bGKiEtrmbVpWh3cx4RrzY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Pr2S/dJMcabxO9yE/bGKiEtrmbVpWh3cx4RrzY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Pr2S%2FdJMcabxO9yE%2FbGKiEtrmbVpWh3cx4RrzY0%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;958&quot; height=&quot;306&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;630&quot; data-origin-height=&quot;213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4z9R3/dJMb99084kl/MyUoTbINkILmSORIGG4k51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4z9R3/dJMb99084kl/MyUoTbINkILmSORIGG4k51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4z9R3/dJMb99084kl/MyUoTbINkILmSORIGG4k51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4z9R3%2FdJMb99084kl%2FMyUoTbINkILmSORIGG4k51%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;630&quot; height=&quot;213&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;213&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StateTree AI Compponent 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;283&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DVBzw/dJMcadPUCbY/HqNCVmcYPrklpCEFp6QbH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DVBzw/dJMcadPUCbY/HqNCVmcYPrklpCEFp6QbH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DVBzw/dJMcadPUCbY/HqNCVmcYPrklpCEFp6QbH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDVBzw%2FdJMcadPUCbY%2FHqNCVmcYPrklpCEFp6QbH0%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;555&quot; height=&quot;283&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;283&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;420&quot; data-origin-height=&quot;247&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rF1PO/dJMcadiaJvE/8ZEwywFrOaYBuWPjB2l32K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rF1PO/dJMcadiaJvE/8ZEwywFrOaYBuWPjB2l32K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rF1PO/dJMcadiaJvE/8ZEwywFrOaYBuWPjB2l32K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrF1PO%2FdJMcadiaJvE%2F8ZEwywFrOaYBuWPjB2l32K%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;420&quot; height=&quot;247&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;247&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트러블슈팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비상~하향평준화되어버림;; 주말동안 수리 예정..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 1. 감지 범위 밖으로 벗어나 있는 상태인데도 계속 추격함. (해결)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 구현 목표는 감지범위(노랑, 초록, 시야각)을 벗어나면 마지막으로 목격했던 곳으로 보안요원이 이동하는것입니다. 계속 플레이어를 추격하면 안됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 공격범위 내에 들어갔다 나오면 추격이 아예 멈춤. 감지범위를 들락날락거려도 가만히있음. (해결)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 구현 목표는 공격 범위 안에 있으면 공격을 당하고 그 범위를 벗어나는 즉시 다시 플레이어 추격을 진행.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 플레이어가 감지범위 내에 있는 상황에서 ft.Security.TestCall를 했을 때 플레이어를 추격하는게 아닌 신고된 위치로 먼저 이동함. 이동 중 만약 플레이어가 감지범위를 벗어나려하면 그때 플레이어를 추격함. (해결)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 구현 목표는 ft.Security.TestCall를 했을 때 플레이어가 감지범위 안에 잇으면 바로 플레이어 추격, 감지 범위 밖에 있다면 신고된 위치로 이동.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781885145114&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bHasSeenTarget을 Perception 이벤트에만 의존하지 않게 변경
매 Tick마다 실제 거리, 시야각, LoseSightRadius, Line of Sight 기준으로 bHasSeenTarget 갱신
ft.Security.TestCall 직후에도 즉시 현재 보이는지 계산
State Tree에서 쓰기 쉽게 bIsTargetInAttackRange, TargetDistance 추가&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781885586392&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FTSecurityAIController.h에 추가

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = &quot;FT|Security&quot;)
bool bIsTargetInAttackRange = false;

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = &quot;FT|Security&quot;)
float TargetDistance = 0.0f;


void UpdateTargetState();
bool IsTargetCurrentlyVisible() const;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781885752486&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FTSecurityAIController.cpp에 추가

void AFTSecurityAIController::UpdateTargetState()
{
	const APawn* ControlledPawn = GetPawn();
	if (!ControlledPawn || !TargetActor)
	{
		TargetDistance = 0.0f;
		bHasSeenTarget = false;
		bIsTargetInAttackRange = false;
		return;
	}

	TargetDistance = FVector::Dist(ControlledPawn-&amp;gt;GetActorLocation(), TargetActor-&amp;gt;GetActorLocation());
	bIsTargetInAttackRange = TargetDistance &amp;lt;= AttackRange;
	bHasSeenTarget = IsTargetCurrentlyVisible();
}

bool AFTSecurityAIController::IsTargetCurrentlyVisible() const
{
	const APawn* ControlledPawn = GetPawn();
	if (!ControlledPawn || !TargetActor || !SightConfig)
	{
		return false;
	}

	const FVector ToTarget = TargetActor-&amp;gt;GetActorLocation() - ControlledPawn-&amp;gt;GetActorLocation();
	if (ToTarget.SizeSquared() &amp;gt; FMath::Square(SightConfig-&amp;gt;LoseSightRadius))
	{
		return false;
	}

	const FVector Forward = ControlledPawn-&amp;gt;GetActorForwardVector();
	const FVector DirectionToTarget = ToTarget.GetSafeNormal();
	const float Dot = FVector::DotProduct(Forward, DirectionToTarget);
	const float AngleDegrees = FMath::RadiansToDegrees(FMath::Acos(FMath::Clamp(Dot, -1.0f, 1.0f)));
	if (AngleDegrees &amp;gt; SightConfig-&amp;gt;PeripheralVisionAngleDegrees)
	{
		return false;
	}

	return LineOfSightTo(TargetActor);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781885422571&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;기존 State Tree

Idle -&amp;gt; Chase
조건 :
AIController.bSecurityCalled == True
&amp;amp;&amp;amp;
AIController.bHasSeenTarget == True
&amp;amp;&amp;amp;
AIController.TargetActor is Valid

Idle -&amp;gt; Investigate
조건 :
AIController.bSecurityCalled == True
&amp;amp;&amp;amp;
AIController.bHasSeenTarget == False
&amp;amp;&amp;amp;
AIController.TargetActor is Valid

Investigate -&amp;gt; Chase
조건 : 
AIController.bHasSeenTarget == True
&amp;amp;&amp;amp;
AIController.TargetActor is Valid

Chase -&amp;gt; Attack
조건 : 
Distance from GetActorLocation(Actor) to GetActorLocation(AIController.TargetActor) &amp;lt;= AIController.AttackRange
&amp;amp;&amp;amp;
AIController.TargetActor is Valid

Chase -&amp;gt; Investigate
조건 :
AIController.bHasSeenTarget == False

Attack -&amp;gt; Chase
조건 :
Distance from GetActorLocation(Actor) to GetActorLocation(AIController.TargetActor) &amp;gt; AIController.AttackRange
&amp;amp;&amp;amp;
AIController.bHasSeenTarget == True
&amp;amp;&amp;amp;
AIController.TargetActor is Valid

Attack -&amp;gt; Investigate
조건 :
AIController.bHasSeenTarget == False&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781885235336&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;State Tree 조건 변경

Idle -&amp;gt; Chase
조건 :
AIController.bSecurityCalled == True
&amp;amp;&amp;amp;
AIController.bHasSeenTarget == True
&amp;amp;&amp;amp;
AIController.TargetActor is Valid

Idle -&amp;gt; Investigate
조건 :
AIController.bSecurityCalled == True
&amp;amp;&amp;amp;
AIController.bHasSeenTarget == False
&amp;amp;&amp;amp;
AIController.TargetActor is Valid

Investigate -&amp;gt; Chase
조건 : 
AIController.bHasSeenTarget == True
&amp;amp;&amp;amp;
AIController.TargetActor is Valid

Chase -&amp;gt; Attack
조건 : 
Distance from GetActorLocation(Actor) to GetActorLocation(AIController.TargetActor) &amp;lt;= AIController.AttackRange
&amp;amp;&amp;amp;
AIController.TargetActor is Valid

Chase -&amp;gt; Investigate
조건 :
AIController.bHasSeenTarget == False

Attack -&amp;gt; Chase
조건 :
Distance from GetActorLocation(Actor) to GetActorLocation(AIController.TargetActor) &amp;gt; AIController.AttackRange
&amp;amp;&amp;amp;
AIController.bHasSeenTarget == True
&amp;amp;&amp;amp;
AIController.TargetActor is Valid

Attack -&amp;gt; Investigate
조건 :
AIController.bHasSeenTarget == False&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;139&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkTrQP/dJMcaiDLhTr/AcOx8PCsrWvSqKu4l7EuD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkTrQP/dJMcaiDLhTr/AcOx8PCsrWvSqKu4l7EuD1/img.png&quot; data-alt=&quot;트리거 On Tick으로 변경&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkTrQP/dJMcaiDLhTr/AcOx8PCsrWvSqKu4l7EuD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkTrQP%2FdJMcaiDLhTr%2FAcOx8PCsrWvSqKu4l7EuD1%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;139&quot; height=&quot;138&quot; data-origin-width=&quot;139&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;트리거 On Tick으로 변경&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;변경 후 생긴 문제 1 (해결)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;보안요원이 움직이지 않음. 감지는 잘 되고있는 것으로 확인됨.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(문제 영상) 이면서 이전 문제들의 해결 영상. 감지는 잘 됨.&lt;/b&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=ALPMgcHAGWw&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bTA6c8/dJMb8RR1prq/VQRKx0r7Yo2fXsh8ktLWU0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/hDIe2/dJMb84X8c5w/oJzOgtCkYu4M3DxM38tHq1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/c2ZiOW/dJMb8PGFFNK/7TXavRm4G5ElZ1uZcJ6ZYK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;감지수정 및 보안요원 이동안함 문제&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/ALPMgcHAGWw&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;373&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/loMBN/dJMcaiDLihT/szqiKF3jHP5FOPb7VJOGfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/loMBN/dJMcaiDLihT/szqiKF3jHP5FOPb7VJOGfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/loMBN/dJMcaiDLihT/szqiKF3jHP5FOPb7VJOGfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FloMBN%2FdJMcaiDLihT%2FszqiKF3jHP5FOPb7VJOGfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;373&quot; height=&quot;364&quot; data-origin-width=&quot;373&quot; data-origin-height=&quot;364&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 id=&quot;code_1781890681844&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;수정 전

TargetDistance = FVector::Dist(ControlledPawn-&amp;gt;GetActorLocation(), TargetActor-&amp;gt;GetActorLocation());
bIsTargetInAttackRange = bHasSeenTarget &amp;amp;&amp;amp; TargetDistance &amp;lt;= AttackRange;
bHasSeenTarget = IsTargetCurrentlyVisible();&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781890702549&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;수정 후

TargetDistance = FVector::Dist(ControlledPawn-&amp;gt;GetActorLocation(), TargetActor-&amp;gt;GetActorLocation());
bHasSeenTarget = IsTargetCurrentlyVisible();
bIsTargetInAttackRange = bHasSeenTarget &amp;amp;&amp;amp; TargetDistance &amp;lt;= AttackRange;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bIsTargetInAttackRange가 이전 프레임의 bHasSeenTarget을 보고 계산되던 순서를 수정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(해결 영상 이 영상은 왜 링크밖에 안뜨지;;)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://youtu.be/1_ETtdWHgzc&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/1_ETtdWHgzc&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=1_ETtdWHgzc&quot; data-video-width=&quot;0&quot; data-video-height=&quot;0&quot; data-video-origin-width=&quot;0&quot; data-video-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;보안요원이동문제해결&quot; data-video-thumbnail=&quot;&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/1_ETtdWHgzc&quot; width=&quot;0&quot; height=&quot;0&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=1_ETtdWHgzc&quot; data-video-width=&quot;0&quot; data-video-height=&quot;0&quot; data-video-origin-width=&quot;0&quot; data-video-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;보안요원이동문제해결&quot; data-video-thumbnail=&quot;&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/1_ETtdWHgzc&quot; width=&quot;0&quot; height=&quot;0&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 2 &lt;/b&gt;&lt;b&gt;(해결)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;보안요원의 공격범위(빨간 스피어)에서 Idle 상태가 되고 공격하지 않음.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(문제 영상)&lt;/b&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=FlTDI0Ne_VI&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cHbJzo/dJMb9jgGKLO/kdJDidNpPWnmJP3lRD9GVK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/cakohF/dJMb8RR1psR/rHHLwgI7fJw6ci2KTP3pt1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/wFoCH/dJMb9g5kpBV/Jyd1KuGti0Q3cGHCMStJH0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;AttactRadius와 같아지면 Idle 상태가 되는 문제점&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/FlTDI0Ne_VI&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;871&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JnTpx/dJMcai4KZlh/lOSSyAzBWGEurkSXaZeokk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JnTpx/dJMcai4KZlh/lOSSyAzBWGEurkSXaZeokk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JnTpx/dJMcai4KZlh/lOSSyAzBWGEurkSXaZeokk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJnTpx%2FdJMcai4KZlh%2FlOSSyAzBWGEurkSXaZeokk%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;1478&quot; height=&quot;871&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;871&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Idel -&amp;gt; Chase 추가하여 해결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건:&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;bSecurityCalled == true
&amp;amp;&amp;amp; bHasSeenTarget == true
&amp;amp;&amp;amp; bIsTargetInAttackRange == false
&amp;amp;&amp;amp; TargetActor is Valid&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(해결 영상)&lt;/b&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=_NyTHCP-IqQ&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cj5PlF/dJMb9lMkQ4y/1uTKrmdu1Uzl5bYzgVrYqk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/ekpcMO/dJMb9aKOsZ4/kd4IEH1CFtWd5sUPHUNtyk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/cqR5kQ/dJMb85W2OIa/bcUt75WKHlOfQ5hsUpTAK1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;AttackRange에서 Idle상태 처리되던 문제 수정&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/_NyTHCP-IqQ&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>내일배움캠프</category>
      <author>thinklikethink</author>
      <guid isPermaLink="true">https://think05994.tistory.com/55</guid>
      <comments>https://think05994.tistory.com/55#entry55comment</comments>
      <pubDate>Fri, 19 Jun 2026 12:18:07 +0900</pubDate>
    </item>
    <item>
      <title>내일배움캠프 언리얼트랙 40일차 - 보안요원 AI Controller</title>
      <link>https://think05994.tistory.com/54</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MoveToLocation&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1781751492799&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	const FVector StartLocation = ControllPawn-&amp;gt;GetActorLocation();
	const FVector TargetLocation = StartLocation + FVector(500.0f, 0.0f, 0.0f);
	
	MoveToLocation(TargetLocation);
	UE_LOG(LogTemp, Warning, TEXT(&quot;Security AI: MoveToLocation Test Started&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=1YH8oge2lrY&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/clshxJ/dJMb88Gee0N/k09sxG02jt5FkhpsmbpUmk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/dfT0fM/dJMb83Ssdfb/Mj6uKK5LXuYGKK1AMoAYR0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bd9V6P/dJMb887hZxI/J3tgXwSgKb06imPsxdup6K/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;MoveToLocation 테스트&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/1YH8oge2lrY&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MoveToActor&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1781751290570&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(this,0);
	if (!PlayerPawn)
	{
		UE_LOG(LogTemp, Warning, TEXT(&quot;Security AI: PlayerPawn is null&quot;));
		return;
	}
	MoveToActor(PlayerPawn);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=oRmb7y4hWyg&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/luhbh/dJMb9g5kaBr/YbBDE3pECYCez86xBulPp0/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360,https://scrap.kakaocdn.net/dn/dTHj8s/dJMb9dHxmLh/1K1oCsSkKH6HpEMf8En9k0/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360,https://scrap.kakaocdn.net/dn/UsBPU/dJMb9jOv310/8MrhcUQJKGajJ2DWs89OK0/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;MoveToActor 테스트&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/oRmb7y4hWyg&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AI Perception, AI Sense Config&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1781791353290&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;헤더 추가
#include &quot;Perception/AIPerceptionComponent.h&quot;
#include &quot;Perception/AISenseConfig_Sight.h&quot;
#include &quot;Perception/AIPerceptionTypes.h&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781791512953&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;객체 생성

SecurityPerceptionComponent = CreateDefaultSubobject&amp;lt;UAIPerceptionComponent&amp;gt;(TEXT(&quot;SecurityPerceptionComponent&quot;));
SightConfig = CreateDefaultSubobject&amp;lt;UAISenseConfig_Sight&amp;gt;(TEXT(&quot;SightConfig&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UAIPerceptionComponent는 AI의 감지 기능을 담당하는 컴포넌트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UAISenseConfiig_Sight는 시야 범위, 시야각, 감지 유지 거리 등의 설정을 담당하는 객체&lt;/p&gt;
&lt;pre id=&quot;code_1781791732086&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SightConfig-&amp;gt;SightRadius = 1500.0f;
SightConfig-&amp;gt;LoseSightRadius = 1800.0f;
SightConfig-&amp;gt;PeripheralVisionAngleDegrees = 80.0f;
SightConfig-&amp;gt;SetMaxAge(3.0f);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SightRadius&lt;/b&gt; = 처음 대상을 감지하는 거리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LoseSightRadius&lt;/b&gt; = 이미 감지한 대상을 놓치는 거리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PeripheralVisionAngleDegrees&lt;/b&gt; = 시야각&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SetMaxAge&lt;/b&gt; = 감지 정보를 기억하는 시간&lt;/p&gt;
&lt;pre id=&quot;code_1781791747383&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SightConfig-&amp;gt;DetectionByAffiliation.bDetectEnemies = true;
SightConfig-&amp;gt;DetectionByAffiliation.bDetectFriendlies = true;
SightConfig-&amp;gt;DetectionByAffiliation.bDetectNeutrals = true;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DetectionByAffiliation&lt;/b&gt; = 적/아군/중립 감지 여부&lt;/p&gt;
&lt;pre id=&quot;code_1781791755051&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SecurityPerceptionComponent-&amp;gt;ConfigureSense(*SightConfig);
SecurityPerceptionComponent-&amp;gt;SetDominantSense(SightConfig-&amp;gt;GetSenseImplementation());
SetPerceptionComponent(*SecurityPerceptionComponent);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ConfigureSense()를 통해 시야 설정을 PerceptionComponent에 적용 가능&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781791763343&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SecurityPerceptionComponent-&amp;gt;OnTargetPerceptionUpdated.AddDynamic(
	this,
	&amp;amp;AFTSecurityAIController::OnTargetPerceptionUpdated
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OnTargetPerceptionUpdated&lt;/b&gt; = 감지 상태가 바뀔 때 호출됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 플레이어를 발견하거나 놓쳤을 때 실행됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Broadcast&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1781794100307&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;헤더 추가
#include &quot;GameFramework/GameplayMessageSubsystem.h&quot;
#include &quot;ProjectFT/Message/FTGameplayTags.h&quot;
#include &quot;ProjectFT/Struct/FTNPCReportPayloadStruct.h&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781794108658&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FFTNPCReportPayloadStruct Payload;
Payload.TargetActor = PlayerPawn;
Payload.ReportLocation = PlayerPawn-&amp;gt;GetActorLocation();
Payload.ReportAmount = 100.0f;
Payload.ReportProgress = 1.0f;

UGameplayMessageSubsystem::Get(World).BroadcastMessage(
	TAG_FT_Event_SecurityCalled,
	Payload
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BroadCastMessage()&amp;nbsp;&lt;/b&gt;= 특정 GameplayTag 채널로 메시지를 보내는 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에선 콘솔 명령어 ft.Security.TestCall을 실행하면 &lt;b&gt;Event.Security.Called&lt;/b&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;FTNPCReportPayloadStruct에 다음 정보를 담아 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TargetActor &lt;/b&gt;= 신고된 대상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ReportLocation &lt;/b&gt;= 신고 위치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ReportAmount &lt;/b&gt;= 신고 수치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ReportProgress &lt;/b&gt;= 신고 진행도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Listener&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1781797995446&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SecurityCalledListenerHandle = MessageSubsystem.RegisterListener(
	TAG_FT_Event_SecurityCalled, 
	this, 
	&amp;amp;ThisClass::OnSecurityCalled
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RegisterListener()&lt;/b&gt; = 특정 메시지를 듣기 위해 등록하는 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는 TAG_FT_Event_SecurityCalled 메시지가 발행되면 OnSecurityCalled() 함수가 자동으로 호출됨.&lt;/p&gt;
&lt;pre id=&quot;code_1781798005788&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void AFTSecurityAIController::OnSecurityCalled(
	FGameplayTag Channel,
	const FFTNPCReportPayloadStruct&amp;amp; Payload
)
{
	// 신고 메시지 처리
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781798012276&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (SecurityCalledListenerHandle.IsValid())
{
	UGameplayMessageSubsystem::Get(this).UnregisterListener(SecurityCalledListenerHandle);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UnregisterListener()&amp;nbsp;&lt;/b&gt;= AIController가 사라질 때 &lt;b&gt;등록된 리스너를 해제&lt;/b&gt;하는 코드.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해제하지 않으면 이미 사라진 객체가 메시지를 받으려고 해서 문제가 생길 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1781798337828&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BroadcastMessage()
&amp;rarr; &amp;ldquo;신고 발생!&amp;rdquo; 메시지를 보냄

RegisterListener()
&amp;rarr; &amp;ldquo;신고 발생!&amp;rdquo; 메시지를 기다림

OnSecurityCalled()
&amp;rarr; 메시지를 받았을 때 실제 행동 결정

AI Perception
&amp;rarr; 이동 중 플레이어를 직접 감지하면 추격 시작&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=nWYGwRMTO3M&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bdIWII/dJMb9jOv32o/CD12aAeK9KTIQfq4OjVS70/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/338N1/dJMb9frOyu9/uqrmgHrca9K77rjqzjF9X1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bzyfU5/dJMb9hDajLp/dJqJvStq6KCu0iQbiAr0lK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;AIPerceptionAISenseConfig 테스트&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/nWYGwRMTO3M&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;트러블 슈팅&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 보안요원의 감지 범위 밖에서 호출됐는데도 플레이어를 따라옴&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 원래 구현하고자 했던 방향&lt;/b&gt; : 감지 범위 밖에서 호출되면 호출 된 위치로만 이동. 감지 범위 내에서 호출됐을 때 플레이어를 감지하면 플레이어를 추격.&lt;/p&gt;
&lt;pre id=&quot;code_1781778986915&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;신호 받음
&amp;darr;
TargetActor 유효성 확인
&amp;darr;
보안요원 기준으로 TargetActor까지 거리 계산
&amp;darr;
LoseSightRadius 안쪽이면
    &amp;rarr; SetTargetActor()
    &amp;rarr; StartChase()
&amp;darr;
LoseSightRadius 바깥이면
    &amp;rarr; SetTargetActor()는 해두되
    &amp;rarr; MoveToLocation(Payload.ReportLocation)
    &amp;rarr; 추격은 하지 않음
&amp;darr;
이동 중 Perception이 TargetActor를 감지하면
    &amp;rarr; OnTargetPerceptionUpdated()
    &amp;rarr; StartChase()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&lt;b&gt; 원인 : &lt;/b&gt;현재 MoveToActor(TargetActor)는 범위 밖이어도 Actor 자체를 계속 추적하기 때문에, 플레이어가 움직이면 그대로 따라감.&lt;/p&gt;
&lt;pre id=&quot;code_1781777539304&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void AFTSecurityAIController::OnSecurityCalled(FGameplayTag Channel, const FFTNPCReportPayloadStruct&amp;amp; Payload)
{
	// 보안 호출 메시지에 추적 대상이 없으면 처리 불가
	if (!Payload.TargetActor)
	{
		UE_LOG(LogTemp, Warning, TEXT(&quot;Security AI: Security called but TargetActor is null&quot;));
		return;
	}

	// 메시지로 전달받은 플레이어를 현재 추적 대상으로 설정
	SetTargetActor(Payload.TargetActor);

	// 추격 시작
	StartChase();

	UE_LOG(LogTemp, Log, TEXT(&quot;Security AI: Security called, chasing %s&quot;), *Payload.TargetActor-&amp;gt;GetName());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt; 해결 &amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;TargetActor가 LoseSightRadius 안에 있음
&amp;rarr; StartChase()
&amp;rarr; MoveToActor(TargetActor)

TargetActor가 LoseSightRadius 밖에 있음
&amp;rarr; MoveToLocation(Payload.ReportLocation)
&amp;rarr; 호출 당시 위치로 이동
&amp;rarr; 실제 추격은 Perception으로 다시 감지됐을 때 시작&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781777320998&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void AFTSecurityAIController::OnSecurityCalled(FGameplayTag Channel, const FFTNPCReportPayloadStruct&amp;amp; Payload)
{
	// 신고 메시지에 추적 대상이 없으면 처리할 수 없으므로 종료
	if (!Payload.TargetActor)
	{
		UE_LOG(LogTemp, Warning, TEXT(&quot;Security AI: TargetActor is null&quot;));
		return;
	}

	// AI가 조종 중인 Pawn 또는 시야 설정이 없으면
	// 거리 판단이 불가능하므로 종료
	const APawn* ControlledPawn = GetPawn();
	if (!ControlledPawn || !SightConfig)
	{
		UE_LOG(LogTemp, Warning, TEXT(&quot;Security AI: ControlledPawn or SightConfig is null&quot;));
		return;
	}

	// 신고된 플레이어를 현재 추적 대상으로 저장
	// (즉시 추격 여부는 아래 거리 검사 후 결정)
	SetTargetActor(Payload.TargetActor);

	// 보안요원과 플레이어 사이의 현재 거리 계산
	const float DistanceToTarget = FVector::Dist(
		ControlledPawn-&amp;gt;GetActorLocation(),
		Payload.TargetActor-&amp;gt;GetActorLocation()
	);

	// 플레이어가 감지 유지 범위(LoseSightRadius) 안에 있다면
	// 즉시 추격 시작
	if (DistanceToTarget &amp;lt;= SightConfig-&amp;gt;LoseSightRadius)
	{
		StartChase();

		UE_LOG(LogTemp, Log,
			TEXT(&quot;Security AI: Target in range, chasing %s&quot;),
			*Payload.TargetActor-&amp;gt;GetName());

		return;
	}

	// 플레이어가 감지 범위 밖에 있다면
	// 신고가 들어온 위치를 조사하러 이동
	const FVector InvestigateLocation =
		Payload.ReportLocation.IsNearlyZero()
		? Payload.TargetActor-&amp;gt;GetActorLocation() // 신고 위치가 없으면 현재 위치 사용
		: Payload.ReportLocation;                 // 신고 위치가 있으면 해당 위치 사용

	// 조사 위치로 이동
	const EPathFollowingRequestResult::Type MoveResult =
		MoveToLocation(InvestigateLocation, 150.0f);

	UE_LOG(LogTemp, Log,
		TEXT(&quot;Security AI: Investigating location %s, result %d&quot;),
		*InvestigateLocation.ToString(),
		static_cast&amp;lt;int32&amp;gt;(MoveResult));
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781779678871&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TargetActor 없음 &amp;rarr; 종료
Pawn / SightConfig 없음 &amp;rarr; 종료
TargetActor 저장
거리 계산
LoseSightRadius 안 &amp;rarr; StartChase()
LoseSightRadius 밖 &amp;rarr; ReportLocation으로 이동&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 플레이어와 접촉 후 추격 멈춤&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어를 추격하다가 접촉하면 보안요원의 감지 범위 내에 있어도 플레이어 추격을 멈춤. 감지 범위를 벗어나려 하면 그제서야 다시 추적함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 원래 구현하고자 했던 방향 : &lt;/b&gt;추격하다가 플레이어가 공격 범위 안에 들어오면 공격(공격구현은 아직). 감지 범위 내에 있으면 계속 추적.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 원인 : &lt;/b&gt;MoveToActor()가 한 번 &amp;ldquo;도착 완료&amp;rdquo;로 끝나버림, MoveToActor(TargetActor)AI Perception의 OnTargetPerceptionUpdated는 계속 보이는 중에는 매 프레임 호출되고 있지 않음.&lt;/p&gt;
&lt;pre id=&quot;code_1781780846774&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;현재 흐름
Security.Called
&amp;rarr; MoveToActor(TargetActor)
&amp;rarr; 보안요원이 플레이어에게 접근
&amp;rarr; 플레이어와 캡슐 충돌/겹침 또는 근접
&amp;rarr; AI가 &amp;ldquo;목표에 도착했다&amp;rdquo;고 판단
&amp;rarr; 이동 요청 종료&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 플레이어가 게속 시야안에 있으면 새 이벤트가 없음. -&amp;gt; MoveToActor()를 다시 호출할 일이 없어서 추격을 멈춤.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데! 플레이어가 보안요원의 시야각을 벗어나려고 하면 Perception 상태가 변함 -&amp;gt; OnTargetPerceptionUpdated()가 호출되어 그제서야 MoveToActor()를 실행함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;애가 일머리가 읎어...&lt;/s&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781781178641&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;문제점 정리
부딪힘
&amp;rarr; MoveToActor 완료 처리
&amp;rarr; Perception은 계속 보이는 상태라 새 이벤트 없음
&amp;rarr; 이동 명령이 다시 안 들어감
&amp;rarr; 멈춤
&amp;rarr; 시야를 벗어나려 하면 Perception 이벤트 발생
&amp;rarr; 다시 MoveToActor 호출
&amp;rarr; 다시 따라옴&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT에게 물어보니 Tick에서 매 프레임 호출하는 것으로 구현을 하면 가능하다는데.. 말고도 StateTree로도 이 문제를 해결할 수 있다고 한다. 내일 StateTree로 이 문제를 해결해 볼 예정이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;a href=&quot;https://youtu.be/vvaRqGFa4yE?si=XKrfLPJ5Hk0f1kwY&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;오늘의 영상&lt;/b&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/vvaRqGFa4yE?si=CaE1INz-pxfuAWAr&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/vvaRqGFa4yE?si=CaE1INz-pxfuAWAr&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=vvaRqGFa4yE&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/ddPba6/dJMb9jgGKWi/Laz37WgpfnVlQysDJFQIik/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bOos0h/dJMb9kmlYYR/p6F5VzkBzDcuBZqNLBT4r0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/WC5Mm/dJMb9llguyN/k7m7dZ7eqZ6Ad9KbULMytK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;2026-06-18 작업. 보안요원의 Player 추격 시스템 구현&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/vvaRqGFa4yE&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>내일배움캠프</category>
      <author>thinklikethink</author>
      <guid isPermaLink="true">https://think05994.tistory.com/54</guid>
      <comments>https://think05994.tistory.com/54#entry54comment</comments>
      <pubDate>Thu, 18 Jun 2026 11:55:34 +0900</pubDate>
    </item>
    <item>
      <title>내일배움캠프 언리얼트랙 38일차 - 심화반 2강</title>
      <link>https://think05994.tistory.com/52</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;4대 핵심 클래스&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.5659%;&quot;&gt;&lt;b&gt;클래스 명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25.6588%;&quot;&gt;&lt;b&gt;비유&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 55.7752%;&quot;&gt;&lt;b&gt;핵심 특징&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.5659%;&quot;&gt;&lt;b&gt;AGameMode&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25.6588%;&quot;&gt;Rule Maker (심판)&lt;/td&gt;
&lt;td style=&quot;width: 55.7752%;&quot;&gt;규칙을 정하고 승패를 판정. &lt;b&gt;서버에만 존재&lt;/b&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.5659%;&quot;&gt;&lt;b&gt;AGameState&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25.6588%;&quot;&gt;Scoreboard (전광판)&lt;/td&gt;
&lt;td style=&quot;width: 55.7752%;&quot;&gt;현재 점수, 남은 시간 등 &lt;b&gt;모두가 봐야 하는 정보를 공유&lt;/b&gt;함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.5659%;&quot;&gt;&lt;b&gt;APlayerState&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25.6588%;&quot;&gt;개인 수첩&lt;/td&gt;
&lt;td style=&quot;width: 55.7752%;&quot;&gt;개별 이름, 득점 등 &lt;b&gt;개인 데이터를 기록&lt;/b&gt;함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.5659%;&quot;&gt;&lt;b&gt;UGameInstance&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25.6588%;&quot;&gt;총관리자&lt;/td&gt;
&lt;td style=&quot;width: 55.7752%;&quot;&gt;레벨이 끝나고 다음 레벨로 넘어가도 절대 사라지지 않음.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;AGameMode&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;키워드 : 규칙, 서버 전용, 오직 명령만(내게 명령하지마라. 명령은 내가 한다.)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;게임의 룰 관리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;승리하려면 몇점을 내야하는지, 제한 시간은 얼마나 되는지 등 규칙을 가짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Spawn 결정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어에게 어떤 Pawn을 줄지, PlayerStart를 어디로 할지를 결정.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;게임 흐름 제어&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스테이지 시작, 관전모드 전환, 게임오버 등 굵직한 판정을 내림.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Server Only. 서버에만 산다!&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티플레이 게임을 만들 때 가장 많이 하는 실수가 클라이언트에서 GameMode를 찾으려고 하는것.&lt;/li&gt;
&lt;li&gt;AGameMode는 오직 서버(Host) 메모리에만 존재한다. 즉, A라는 유저의 컴퓨터(클라이언트)에는 GameMode가 아예 존재하지 않음!&lt;/li&gt;
&lt;li&gt;클라이언트에 GameMode가 있으면 게임의 규칙을 바꿔버릴 수 있기 때문에 보안을 위해 서버에만 둔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Base vs 일반&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AGameModeBase (가벼운 기본형)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;싱글 플레이 게임이나 오픈월드RPG처럼 &quot;시작과 끝이 단순한 게임&quot;에 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AGameMode (매치 시스템이 있는 무거운 버전)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FPS처럼 먕확한 단계(State Machine)가 필요한 게임에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;핵심 구현 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결정만 하고 AGameState에 넘기기!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AGameMode는 결정하는 고귀한 아이. 동네방네 소문내는 입이 가벼운 아이가 아님!&lt;/p&gt;
&lt;pre id=&quot;code_1781675190409&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void AMyGameMode::StartMatch()
{
	bMatchInProgress = true;
    
    // 1. GameMode가 매치 시작이라고 결정
    if(AMyGameState* GS = GetGameState&amp;lt;AMyGameState&amp;gt;())
    {
    	// 2. 그 사실을 GameState에 넘겨서 모두가 볼 수 있게 함
        GS-&amp;gt;NotifyMatchStarted(MatchDuration);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 GameMode가 남은 시간 변수를 직접 가지고 있으면, 클라이언트들은 GameMode를 볼 수 없으니 화면에 남은 시간이 뜨지 않는 대참사가 발생함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉, GameMode는 명령만 내리고, 변수는 모두가 볼 수 있는 AGameState에 적어두는것이 정석이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;AGameState&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;키워드 : 네트워크 복제, 읽기 전용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GameMode 가 내린 결정이나 게임의 현재 상황을 담아서 모든 유저가 볼 수 있도록 동기화.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트와&amp;nbsp; UI가 읽기만 해야한다. 데이터를 직접 수정하는 결정은 GameMode의 역할이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;APlayerState&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;키워드 : 개인 데이터의 영속성(Persistence), 모두에게 복제(Replication), 한 레벨에 유지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;역할 / 특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 캐릭터가 죽으면 그 캐릭터는 월드에서 파괴되거나 사라진다. 만약 캐릭터에 점수/킬카운트를 저장했다면 캐릭터가 죽어서 사라질 때 내 점수와 킬카운트값도 공중분해 영영 빠이빠이 되어버린다. 흑흑. 리스폰 했더니 내 화려한 전적이 0이 되어버림..&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;따라서 캐릭터(Pawn)은 언제든 갈아치우는 일회용 몸뚱이로 쓰고 &lt;b&gt;절대 사라지면 안되는 내 데이터(이름, 점수, 팀, 핑...)는 PlayerState라는 개인 수첩에 보관&lt;/b&gt;하는 것이다. 이렇게 하면 캐릭터가 죽어도 수첩의 내용은 그대로이니 완전안심!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;APlayerController는 나와 서버만 볼 수 있다. 즉, 다른 사람의 화면이나 컴퓨터에는 보이지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;APlayerState는 서버가 모든 클라이언트에세 복제(Replication)을 해준다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; 점수판을 확인할때 다른 사람들의 점수와 이름도 다 보여야하니까! PlayerController는 나한테 안보이지만 PlayerState는 모두에게 공유되기 때문에 점수판UI를 만들땐 항상 PlayerState를 뒤져서 데이터를 가져온다!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;접근 방법&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1781677138436&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ❌ 절대 금지 (안티 패턴): 전광판 배열에서 그냥 0번 인덱스 꺼내기
// 싱글 게임에선 돌아갈지 몰라도, 멀티게임에선 누가 0번일지 장담할 수 없습니다!
AMyPlayerState* BadPS = GetWorld()-&amp;gt;GetGameState()-&amp;gt;PlayerArray[0];

//  올바른 방법 1: 내 컨트롤러에게 &quot;내 수첩 줘&quot;라고 하기 (내 UI 갱신할 때)
AMyPlayerState* MyPS = MyController-&amp;gt;GetPlayerState&amp;lt;AMyPlayerState&amp;gt;();

//  올바른 방법 2: 클라이언트가 점수를 얻고 싶을 때 (핵 방지용 RPC 요청)
// &quot;내 수첩아, 나 50점 먹었으니까 서버에 적용해줘!&quot;
MyPlayerState-&amp;gt;Server_AddScore(50);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GameInstance&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;키워드 : 불사신, 레벨 전환 이사 전문&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;전체 누적 데이터 관리&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어의 총 누적 골드, 여태까지 클리어 한 스테이지 번호 등 게임 전체를 관통하는 데이터를 들고있기에 가장 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;레벨 전환의 총지휘관&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레벨을 열고 닫는 권환을 실행하기 가장 좋은 위치이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;수명&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게임 프로그램을 킬 때 태어나서, 게임을 완전히 종료할 때 죽는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;불사신&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로비 레벨, 마을 레벨 심지어 메인 메뉴 화면으로 돌아가더라도 &lt;b&gt;GameInstance만큼은 메모리에서 절대 사라지지 않고 단 하나만 계속 유지&lt;/b&gt;된다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;APlayerController&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내가 이 캐릭터의 영혼이다 중생들아&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 키보드를 누르고 마우스를 움직이는 모든 행동을 게임 속 세계로 전달하는&amp;nbsp; 컨트롤러 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;하는 일&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;입력 처리 (Input Binding)&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어의 입력을 받아 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;육체 빙의 (Possess / UnPossess)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;월드에 배치된 캐릭터(APawn)에 영혼을 불어넣는 역할을 한다. 캐릭터가 죽으면 빠져나오고 부활하면 새 캐릭터에 다시 빙의한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;카메라 및 UI 관리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PlayerCameraManager를 관리하고, 화면에 마우스 커서를 띄우거나 마우스 클릭 기반의 HUD/UMG 위젯 UI를 생성하고 조작하는 주체가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;서버와 본인 컴퓨터에만 존재&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멀티플레이에서 컨트롤러의 메모리 구조는 대단히 독특함!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;서버 (HOST)&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 점속자의 PlayerController를 전부 가지고 관리한다.&lt;/li&gt;
&lt;li&gt;누가 무슨 행동을 하느지 알아야하니까!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;내 컴퓨터 (로컬 클라이언트)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오직 내 것 하나만 존재한다. 다른 사람의 PlayerController는 내 컴퓨터 메모리에 아예 생성되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다른 사람 컴퓨터&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내 PlayerController가 존재하지 않는다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;AIController&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 조종하는 캐릭터가 아닌, 컴퓨터가 조종하는 몬스터나 NPC의 영혼역할을 맡는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력이나 UI를 처리하는 기능이 쏙 빠져있는 대신, Behavior Tree나 주변을 감지하는 UAIPerceptionComponent를 장착해서 스스로 Pawn을 조종하게 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;APawn vs ACharacter&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;움직이는 육체들의 등급&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨트롤러(영혼)가 빙의할 수 있는 모든 물리적인 몸뚱이는 APawn에서 출발한다. 언리얼은 이를 크게 두 가지로 나누어 제공한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;APawn&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인간이 아닌 형태의 탈것이나 물체를 만들 때 주로 상속 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 기본적인 이동 컴포넌트(MovementComponent)만 가지고 있어서, 하늘을 날거나 구르는 물리 로직을 개발자가 커스텀하기에 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ACharacter&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람처럼 두 발로 걷고, 달리고, 점프하는 캐릭터를 만들 때 쓴다. RPG나 FPS게임의 주인공들은 99% 이 녀석을 상속받는다.&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;b&gt;CharacterMovementComponent&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;걷기, 달리기, 점프, 수영, 비행 등 인간형 이동 로직이 이미 완벽하게 구현되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CapsuleComponent&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부딪히는 물리 충돌 체형&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SkeletalMeshComponent&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애니메이션을 입힐 수 있는 뼈대 메쉬&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;특수&amp;nbsp; Pawn&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ADefaultPawn&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자유 비행 카메라용. 에디터/디버깅에서 자주 쓰인다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ASpectatorPawn&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매치 관전 모드용. 충돌 없이 자유 이동 가능.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Framework 로딩 순서 (LifeCycle)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;1단계: 가장 먼저 태어나는 맏형 (GameInstance)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게임이 켜지자마자 UGameInstance::Init()이 실행된다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;14,1,0&quot;&gt;주의:&lt;/b&gt; 이때는 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;14,1,0&quot;&gt;아직 월드(World)도 없고, 다른 4대장들도 아예 안 태어난 상태&lt;/b&gt;. 여기서 GetWorld()나 GetPlayerController()를 호출하면 무조건 튕긴다!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;2단계: 레벨이 열리며 태어나는 동생들 (응애 응애)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;맵이 로드되면 GameMode =&amp;gt; GameState =&amp;gt; PlayerController =&amp;gt; PlayerState =&amp;gt; Pawn 순서로 순식간에 스폰됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;3단계: PostLogin()과 StartPlay()&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;많은 초보자가 BeginPlay()에서 모든 걸 해결하려고 한다. 하지만 BeginPlay는 애들의 호출 순서가 뒤죽박죽이라 위험!&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,0&quot;&gt;가장 안전한 타이밍:&lt;/b&gt; 심판(GameMode)의 &lt;b data-index-in-node=&quot;26&quot; data-path-to-node=&quot;18,1,0&quot;&gt;PostLogin()&lt;/b&gt; 함수. 이 함수가 실행되는 시점에는 플레이어의 영혼(PlayerController)과 수첩(PlayerState)이 완벽하게 세팅되어 연결된 상태이므로, 멀티플레이어 초기화 코드를 짜기에 가장 안전의 정석이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>내일배움캠프</category>
      <author>thinklikethink</author>
      <guid isPermaLink="true">https://think05994.tistory.com/52</guid>
      <comments>https://think05994.tistory.com/52#entry52comment</comments>
      <pubDate>Tue, 16 Jun 2026 23:26:55 +0900</pubDate>
    </item>
    <item>
      <title>내일배움캠프 언리얼트랙 35일차 - TA 1강</title>
      <link>https://think05994.tistory.com/49</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;즐거운 과제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;선택한&amp;nbsp;게임과&amp;nbsp;액션 &lt;br /&gt;- 오버워치, 라인하르트 돌진 액션&lt;br /&gt;&lt;br /&gt;2.&amp;nbsp;시작&amp;nbsp;이벤트 &lt;br /&gt;이&amp;nbsp;액션은&amp;nbsp;어떤&amp;nbsp;입력&amp;nbsp;또는&amp;nbsp;이벤트에서&amp;nbsp;시작되는가? &lt;br /&gt;- 플레이어가 Shift 키 입력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 입력 시스템이 Charge 스킬 실행 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스킬 사용 가능 여부 확인 (쿨타임, 상태 이상 여부)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 조건 충족 시 돌진 State 진입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;3.&amp;nbsp;필요한&amp;nbsp;리소스 &lt;br /&gt;- Mesh: 라인하르트 캐릭터 메시, 로켓 해머 메시, 충돌 판정용 콜리전&lt;br /&gt;- Animation: Charge Start, Charge Loop, Impact, Charge End&lt;br /&gt;- VFX: 발 밑 먼지 효과, 돌진 불꽃 효과, 충돌 스파크 효과, 벽 충돌 이펙트&lt;br /&gt;- Material: 갑옷 발광 효과, 돌진 추진기 발광 머티리얼, 피격 플래시 머티리얼&lt;br /&gt;- Sound: 돌진 시작 음향, 추진기 엔진 소리, 적 명중 효과음, 벽 충돌 효과음&lt;br /&gt;- Camera: FOV 확대, 카메라 쉐이크, 속도감 연출&lt;br /&gt;- UI: 스킬 아이콘, 쿨타임 표시, 적 명중 표시&lt;br /&gt;&lt;br /&gt;4.&amp;nbsp;연결&amp;nbsp;흐름 &lt;br /&gt;1) 플레이어가 Shift 입력&lt;br /&gt;2) 스킬 사용 가능 여부 검사&lt;br /&gt;3)&amp;nbsp; Charge 애니메이션 재생 + 이동 속도 증가(돌진 가속)&lt;br /&gt;4) 전방으로 돌진하며 충돌 판정 검사&lt;br /&gt;5) 적 또는 벽과 충돌 시 피해 적용/애니메이션 전환/스킬 종료 및 쿨타임 시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;5.&amp;nbsp;역할&amp;nbsp;분리 &lt;br /&gt;- 개발자 담당: 입력 처리/상태머신구현/충돌판정/피해계산/쿨타임/네트워크동기화&lt;br /&gt;- 아티스트 담당: 캐릭터 애니메이션/충돌VFX/추진기효과/사운드리소스 제작&lt;br /&gt;- TA 관점에서 확인할 부분: 애니메이션과 이동 속도 싱크/VFX발생 위치 정확성/충돌 타이밍과 이펙트 타이밍 일치/카메라연출성능최적화/네트워크 환경에서 이펙트 동기화&lt;br /&gt;&lt;br /&gt;6.&amp;nbsp;정리 &lt;br /&gt;이&amp;nbsp;액션에서&amp;nbsp;가장&amp;nbsp;중요하다고&amp;nbsp;생각한&amp;nbsp;연결&amp;nbsp;지점은&amp;nbsp;무엇인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌 판정 &amp;lt;= =&amp;gt; 애니메이션 &amp;lt;= =&amp;gt; 피해적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 =&amp;gt; 상태전환 =&amp;gt; 애니메이션 =&amp;gt; 이동 =&amp;gt; 충돌 =&amp;gt; 피해 =&amp;gt; VFX/SFX =&amp;gt; 카메라 연출&lt;/p&gt;</description>
      <category>내일배움캠프</category>
      <author>thinklikethink</author>
      <guid isPermaLink="true">https://think05994.tistory.com/49</guid>
      <comments>https://think05994.tistory.com/49#entry49comment</comments>
      <pubDate>Thu, 11 Jun 2026 16:41:29 +0900</pubDate>
    </item>
    <item>
      <title>내일배움캠프 언리얼트랙 34일차 - 과제04</title>
      <link>https://think05994.tistory.com/48</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;과제 04 필수구현기능&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. C++ Pawn 클래스 및 컴포넌트 구성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Pawn 클래스 생성&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;110&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXQs0L/dJMb99Uef2L/9w5lYKdkkVDJRCSk3enGK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXQs0L/dJMb99Uef2L/9w5lYKdkkVDJRCSk3enGK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXQs0L/dJMb99Uef2L/9w5lYKdkkVDJRCSk3enGK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXQs0L%2FdJMb99Uef2L%2F9w5lYKdkkVDJRCSk3enGK1%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;110&quot; height=&quot;175&quot; data-origin-width=&quot;110&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;컴포넌트 추가
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;CapsuleComponent (또는 Box/Sphere 중 택 1)&lt;/li&gt;
&lt;li&gt;SkeletalMeshComponent&lt;/li&gt;
&lt;li&gt;SpringArmComponent&lt;/li&gt;
&lt;li&gt;CameraComponent&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1781162370836&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category= &quot;Component&quot;)
	UCapsuleComponent* CapsuleComp;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category= &quot;Component&quot;)
	USkeletalMeshComponent* SkeletalMeshComp;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category= &quot;Camera&quot;)
	USpringArmComponent* SpringArmComp;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category= &quot;Camera&quot;)
	UCameraComponent* CameraComp;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;DefaultPawn 설정 : GameMode 클래스에서 DefaultPawnClass를 본인이 만든 Pawn으로 지정.&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;109&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSuEPH/dJMcafUuio7/kKw1XvcK7viWZUdBXOV8nK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSuEPH/dJMcafUuio7/kKw1XvcK7viWZUdBXOV8nK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSuEPH/dJMcafUuio7/kKw1XvcK7viWZUdBXOV8nK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSuEPH%2FdJMcafUuio7%2FkKw1XvcK7viWZUdBXOV8nK%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;109&quot; height=&quot;172&quot; data-origin-width=&quot;109&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781162499230&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	DefaultPawnClass = AMyCharacter::StaticClass();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Physics 설정 : 루트 충돌체 및 Mesh의 Simulate Physics를 false로 설정. (물리 대신 코드로 직접 제어)&lt;/li&gt;
&lt;li&gt;계층 구조 설정 : 충돌 컴포넌트를 RootComponent로 설정하고, 나머지 컴포넌트들을 부착.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1781162431091&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	CapsuleComp = CreateDefaultSubobject&amp;lt;UCapsuleComponent&amp;gt;(TEXT(&quot;CapsuleComp&quot;));
	SetRootComponent(CapsuleComp);
	CapsuleComp-&amp;gt;SetSimulatePhysics(false);
	
	SkeletalMeshComp = CreateDefaultSubobject&amp;lt;USkeletalMeshComponent&amp;gt;(TEXT(&quot;SkeletalMeshComp&quot;));
	SkeletalMeshComp-&amp;gt;SetupAttachment(RootComponent);
	SkeletalMeshComp-&amp;gt;SetSimulatePhysics(false);
	
	SpringArmComp = CreateDefaultSubobject&amp;lt;USpringArmComponent&amp;gt;(TEXT(&quot;SpringArmComp&quot;));
	SpringArmComp-&amp;gt; SetupAttachment(RootComponent);
	SpringArmComp-&amp;gt; TargetArmLength = 300.0f;
	SpringArmComp -&amp;gt; bUsePawnControlRotation = false;
	
	CameraComp = CreateDefaultSubobject&amp;lt;UCameraComponent&amp;gt;(TEXT(&quot;CameraComp&quot;));
	CameraComp-&amp;gt;SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
	CameraComp -&amp;gt; bUsePawnControlRotation = false;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Enhanced Input 매핑 &amp;amp; 바인딩&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;입력 액션(IA) 생성 : 두 가지 액션을 생성. (타입 : Vector2D)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;IA_Move (WASD 이동용)&lt;/li&gt;
&lt;li&gt;IA_Look (마우스 회전용)&lt;/li&gt;
&lt;/ul&gt;
&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;320&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqLBgB/dJMcadI0X8r/ljOBgYiMUSatnyHjginvk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqLBgB/dJMcadI0X8r/ljOBgYiMUSatnyHjginvk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqLBgB/dJMcadI0X8r/ljOBgYiMUSatnyHjginvk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqLBgB%2FdJMcadI0X8r%2FljOBgYiMUSatnyHjginvk1%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;320&quot; height=&quot;174&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;IMC 매핑 : 생성한 액션들을 Input Mapping Context에 등록하고 키를 할당.&lt;/li&gt;
&lt;li&gt;액션 바인딩 : SetupPlayerInputComponent()에서 입력 처리 함수와 액션들을 바인딩.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1781162673504&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	if (UEnhancedInputComponent* EnhancedInput = Cast&amp;lt;UEnhancedInputComponent&amp;gt;(PlayerInputComponent))
	{
		if (AMyPlayerController* PlayerController = Cast&amp;lt;AMyPlayerController&amp;gt;(GetController()))
		{
			if (PlayerController -&amp;gt; MoveAction)
			{
				EnhancedInput-&amp;gt;BindAction(
					PlayerController -&amp;gt; MoveAction,
					ETriggerEvent::Triggered,
					this,
					&amp;amp;AMyCharacter::Move
					);
			}
			
			if (PlayerController -&amp;gt; LookAction)
			{
				EnhancedInput-&amp;gt;BindAction(
					PlayerController -&amp;gt; LookAction,
					ETriggerEvent::Triggered,
					this,
					&amp;amp;AMyCharacter::Look
					);
			}
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 이동 및 회전 로직 구현&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;프레임 독립성 : DeltaTime을 사용하여 프레임 속도와 관계없이 일정한 속도로 움직이도록 구현.&lt;/li&gt;
&lt;li&gt;이동 구현 : AddActorLocalOffset()등을 활용해 WASD 입력에 따라 Pawn이 움직이도록 작성.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이동 방향은 Pawn의 Forward/Right 벡터를 기준으로 결정.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1781162706137&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void AMyCharacter::Move(const FInputActionValue&amp;amp; value)
{
	if (!Controller) return;

	const FVector2D MoveInput = value.Get&amp;lt;FVector2D&amp;gt;();
	const float DeltaTime = GetWorld()-&amp;gt;GetDeltaSeconds();

	const FVector Forward = GetActorForwardVector();
	const FVector Right = GetActorRightVector();
	
	FVector DeltaLocation = FVector::ZeroVector;
	DeltaLocation.X = MoveInput.X * MovementSpeed * DeltaTime;
	DeltaLocation.Y = MoveInput.Y * MovementSpeed * DeltaTime;
	DeltaLocation.Z = 0.0f;

	AddActorLocalOffset(DeltaLocation, true);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;회전 구현 : AddActorLocalRotation() 등을 활용해 마우스 입력에 따라 회전하도록 작성.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;마우스 입력값으로 Yaw와 Pitch를 직접 계산하여 구현&lt;/li&gt;
&lt;li&gt;&lt;span data-token-index=&quot;0&quot;&gt;주의 &lt;/span&gt;: AddControllerYawInput()&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;4&quot;&gt;AddControllerPitchInput() &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;등 엔진 기본 제공 함수는 사용하지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1781162728927&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void AMyCharacter::Look(const FInputActionValue&amp;amp; value)
{
	if (!Controller) return;

	const FVector2D LookInput = value.Get&amp;lt;FVector2D&amp;gt;();
	const float DeltaTime = GetWorld()-&amp;gt;GetDeltaSeconds();

	// 좌우 회전: 캐릭터 몸 전체 회전
	const float DeltaYaw = LookInput.X * RotationSpeed * DeltaTime;
	AddActorLocalRotation(FRotator(0.0f, DeltaYaw, 0.0f));

	// 상하 회전: SpringArm만 회전
	const float DeltaPitch = LookInput.Y * RotationSpeed * DeltaTime * -1;

	FRotator SpringArmRotation = SpringArmComp-&amp;gt;GetRelativeRotation();
	SpringArmRotation.Pitch += DeltaPitch;

	SpringArmRotation.Pitch = FMath::Clamp(SpringArmRotation.Pitch, -60.0f, 30.0f);
	SpringArmRotation.Yaw = 0.0f;
	SpringArmRotation.Roll = 0.0f;

	SpringArmComp-&amp;gt;SetRelativeRotation(SpringArmRotation);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;제한 사항 : 평면 상에서의 이동과 회전만 처리하며, 중력이나 낙하 효과는 고려하지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=X7RH_SKcr1w&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/zBekt/dJMb9jgFAZq/0BJ5bIFfdjemf0YOL2KL71/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;04 vod&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/X7RH_SKcr1w&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>내일배움캠프</category>
      <author>thinklikethink</author>
      <guid isPermaLink="true">https://think05994.tistory.com/48</guid>
      <comments>https://think05994.tistory.com/48#entry48comment</comments>
      <pubDate>Wed, 10 Jun 2026 16:34:00 +0900</pubDate>
    </item>
    <item>
      <title>내일배움캠프 언리얼트랙 33일차</title>
      <link>https://think05994.tistory.com/47</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;팀프로젝트 준비&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NPC 성향 맹인 주인공이 파악하는 단서&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 136px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; NPC 성향 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 맹인 주인공이 파악하는 단서 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;보호형&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;몬스터 쪽으로 뛰어가는 발소리, &amp;ldquo;뒤로 가!&amp;rdquo; 같은 대사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;안내형&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;길 설명, 위험 구역 경고&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;겁쟁이&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;떨리는 목소리, 멀어지는 발소리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;이기적&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;문 잠그는 소리, 숨죽이는 소리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;배신형&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;일부러 틀린 길 안내, 몬스터를 부르는 소리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;약탈형&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;가방 뒤지는 소리, 아이템 무게 변화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;1329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HdeMH/dJMcaiXRY77/DnO6jNszPwk0j8amA53pik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HdeMH/dJMcaiXRY77/DnO6jNszPwk0j8amA53pik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HdeMH/dJMcaiXRY77/DnO6jNszPwk0j8amA53pik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHdeMH%2FdJMcaiXRY77%2FDnO6jNszPwk0j8amA53pik%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;752&quot; height=&quot;1329&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;1329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;AIController&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; NPC 조종.&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;ex)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저기로 이동해/플레이어를 따라가/문을 닫아/도망쳐/몬스터를 막아&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Behavior Tree&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; AI의 행동 순서표.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;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;ex) 겁쟁이 NPC인 경우.&lt;br /&gt;몬스터를 봤는가?&lt;br /&gt;&amp;rarr; 봤다&lt;br /&gt;&amp;rarr; 도망갈 장소가 있는가?&lt;br /&gt;&amp;rarr; 있다&lt;br /&gt;&amp;rarr; 도망간다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;ex)&lt;span&gt; &lt;/span&gt;&lt;/span&gt;보호형 NPC인 경우.&lt;br /&gt;플레이어가 위험한가?&lt;br /&gt;&amp;rarr; 위험하다&lt;br /&gt;&amp;rarr; 몬스터에게 접근한다&lt;br /&gt;&amp;rarr; 시간을 번다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Blackboard&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; AI가 참고하는 메모장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Behavior Tree가 판단하기 위해서는 정보가 필요함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Behavior Tree는 Blackboard 값을 보고 판단함.&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;ex)&lt;br /&gt;PlayerActor = 현재 플레이어&lt;br /&gt;MonsterActor = 근처 몬스터&lt;br /&gt;CurrentState = Escape&lt;br /&gt;NPCType = Coward&lt;br /&gt;TargetLocation = 숨을 장소 위치&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Finite Statr Machine, FSM&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 상태기계.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 햔재 상태를 정해놓고 상태에 따라 다르게 행동하는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; NPC가 지금 무슨 모드인지를 관리하는 구조.&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;ex) NPC 상태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Idle&lt;br /&gt;Talk&lt;br /&gt;Follow&lt;br /&gt;RunAway&lt;br /&gt;Hide&lt;br /&gt;CloseDoor&lt;br /&gt;Report&lt;br /&gt;Loot&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;ex) 겁쟁이 NPC&lt;br /&gt;Idle 상태&lt;br /&gt;&amp;darr; 몬스터 발견&lt;br /&gt;RunAway 상태&lt;br /&gt;&amp;darr; 숨을 곳 도착&lt;br /&gt;Hide 상태&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;ex) 배신형 NPC&lt;br /&gt;Idle 상태&lt;br /&gt;&amp;darr; 플레이어가 도둑질하는 걸 봄&lt;br /&gt;Report 상태&lt;br /&gt;&amp;darr; 몬스터에게 위치 전달&lt;br /&gt;RunAway 상태&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;NPC Base Class&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 모든 NPC가 공통으로 쓰는 기본 설계도.&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;NPC 종류마다 클래스를 따로 만드는 상속구조보다는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NPC클래스는 하나만 만들고, 성향 데이터로 행동을 바꾸는 &lt;b&gt;데이터 기반&lt;/b&gt;으로 채택.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Environment Query System, EQS&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; AI용 위치 추천기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; AI가 환경을 평가하여 최적의 위치를 선택하는 시스템.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어가 NPC 근처 접근&lt;br /&gt;&amp;darr;&lt;br /&gt;ANPCBase가 상호작용 감지&lt;br /&gt;&amp;darr;&lt;br /&gt;NPCPersonalityData 확인&lt;br /&gt;&amp;darr;&lt;br /&gt;현재 상황 판단&lt;br /&gt;몬스터 있음? / 도난 목격? / 플레이어 위험?&lt;br /&gt;&amp;darr;&lt;br /&gt;FSM 상태 변경&lt;br /&gt;&amp;darr;&lt;br /&gt;Behavior Tree가 행동 실행&lt;br /&gt;&amp;darr;&lt;br /&gt;NPC 행동&lt;br /&gt;도망 / 숨기 / 문 닫기 / 신고 / 약탈 / 안내 / 보호&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ANPCBase&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NPCPersonalityData&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ENPCState&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NPC_AIController&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blackboard&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Behavior Tree&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;/ul&gt;</description>
      <category>내일배움캠프</category>
      <author>thinklikethink</author>
      <guid isPermaLink="true">https://think05994.tistory.com/47</guid>
      <comments>https://think05994.tistory.com/47#entry47comment</comments>
      <pubDate>Wed, 10 Jun 2026 01:54:30 +0900</pubDate>
    </item>
  </channel>
</rss>