<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>와악</title>
    <link>https://brian521.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 1 Aug 2026 07:05:47 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>이시보</managingEditor>
    <item>
      <title>TIL - 101</title>
      <link>https://brian521.tistory.com/104</link>
      <description>&lt;h1&gt;인벤토리(WBP_InventoryPanel / InventoryPanelWidget) 작업&lt;code&gt;&lt;/code&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 장비 슬롯 버튼 툴팁이 마우스에 붙어 안 사라지던 문제&lt;/h2&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;code&gt;NativeTick()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원인&lt;/b&gt;: 툴팁 강제 닫기 안전장치가 그리드만 검사 &amp;rarr; 슬롯 버튼 호버 중 매 틱 &lt;code&gt;CloseToolTip()&lt;/code&gt;이 불려 Slate 상태머신이 꼬임.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;변경&lt;/b&gt;: 조건을 &lt;code&gt;그리드 호버 || 슬롯 버튼 호버&lt;/code&gt; 통합으로 확장. 두 영역 어디에도 호버가 없을 때만 &lt;code&gt;CloseToolTip()&lt;/code&gt; 호출.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Button_SlotHead/Weapon/Chest/Hands_L/Hands_R/Legs/Feet&lt;/code&gt;를 &lt;code&gt;IsWidgetOrDescendantHovered&lt;/code&gt;로 검사(null 안전).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 장비 교체 확인 창이 뜰 때 그리드 툴팁 잔상 제거&lt;/h2&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;code&gt;ShowWeaponSwapConfirm(bShow=true)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원인&lt;/b&gt;: 더블클릭 시 마우스가 그리드 아이템 위에 있어 툴팁이 남음. 매 틱 CloseToolTip은 재소환 싸움을 일으켜 커서를 따라다님(회귀).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;변경&lt;/b&gt;: 창이 뜨는 순간(엣지) 호버 중인 그리드 슬롯의 툴팁을 &lt;code&gt;ClearWidgetTooltipRecursive&lt;/code&gt;로 제거 + &lt;code&gt;CloseToolTip()&lt;/code&gt; + &lt;code&gt;MarkInventoryTooltipsDirty()&lt;/code&gt;.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;창이 닫히고 마우스가 슬롯을 벗어나면 &lt;code&gt;ApplyInventorySlotTooltips&lt;/code&gt;가 툴팁을 복구.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 교체 확인 창을 마우스 커서 옆에 표시&lt;/h2&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;code&gt;PositionConfirmDialogNearCursor(UWidget* DialogWidget)&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커서 절대좌표 &amp;rarr; 부모 Canvas 로컬좌표 변환(+12,+12 오프셋), 화면 밖으로 안 나가게 클램프.&lt;/li&gt;
&lt;li&gt;앵커/정렬을 좌상단(0,0)으로 고정 후 &lt;code&gt;UCanvasPanelSlot::SetPosition&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;적용&lt;/b&gt;: 장비 교체 창(&lt;code&gt;Border_WeaponSwapConfirm&lt;/code&gt;), 퀵슬롯 교체 창(&lt;code&gt;Border_QuickSlotReplaceConfirm&lt;/code&gt;) 모두 커서 옆에 표시.&lt;/li&gt;
&lt;li&gt;include 추가: &lt;code&gt;Components/CanvasPanelSlot.h&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 퀵슬롯 교체 창이 퀵슬롯 휠에 가려지던 z-order 문제&lt;/h2&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;: 인벤토리 패널 z50, 퀵슬롯 휠 z80 &amp;rarr; 패널 안(z50)의 교체 창이 휠(z80) 뒤에 깔림.&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;code&gt;HandleQuickSlotWheelSlotClicked&lt;/code&gt;에서 교체 확인 시 휠을 &lt;code&gt;HitTestInvisible&lt;/code&gt; + &lt;b&gt;z49(패널 아래)&lt;/b&gt;로 재배치(&lt;code&gt;RemoveFromParent&lt;/code&gt; &amp;rarr; &lt;code&gt;AddToViewport(49)&lt;/code&gt;), &lt;code&gt;bQuickSlotWheelLowered = true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;신규 헬퍼 &lt;code&gt;RestoreQuickSlotWheelZOrder()&lt;/code&gt;: 휠을 원래 z80으로 복원.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ConfirmQuickSlotReplace&lt;/code&gt; / &lt;code&gt;CancelQuickSlotReplace&lt;/code&gt;에서 복원 호출(취소 시 휠 다시 &lt;code&gt;Visible&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;멤버 추가&lt;/b&gt;: &lt;code&gt;bool bQuickSlotWheelLowered&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;⚠️ 주의: 휠을 패널 아래로 내리므로 패널 배경이 불투명하면 휠이 가려질 수 있음(필요 시 교체 창을 뷰포트 최상단으로 올리는 방식으로 전환).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 사용 버튼(Button_UseConsumable) &amp;mdash; 소모품일 때만 표시&lt;/h2&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;code&gt;Button_UseConsumable&lt;/code&gt;을 &lt;code&gt;BindWidgetOptional&lt;/code&gt;로 선언(WBP 위젯명 일치 필요).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;위치&lt;/b&gt;: &lt;code&gt;UpdateEquipActionButtons()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;변경&lt;/b&gt;: 선택 아이템이 소모품일 때만 &lt;code&gt;Visible&lt;/code&gt;, 그 외 &lt;code&gt;Collapsed&lt;/code&gt;. 활성화는 &lt;code&gt;CanUseSelectedConsumable()&lt;/code&gt;(보유 수량&amp;gt;0).&lt;/li&gt;
&lt;li&gt;클릭 동작은 기존 WBP 처리 유지(가시성만 C++ 제어).&lt;/li&gt;
&lt;li&gt;⚠️ WBP에 이 버튼 Visibility 바인딩이 있으면 제거해야 충돌 없음.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프-언리얼</category>
      <author>이시보</author>
      <guid isPermaLink="true">https://brian521.tistory.com/104</guid>
      <comments>https://brian521.tistory.com/104#entry104comment</comments>
      <pubDate>Wed, 8 Jul 2026 22:55:48 +0900</pubDate>
    </item>
    <item>
      <title>TIL - 100</title>
      <link>https://brian521.tistory.com/103</link>
      <description>&lt;h1&gt;Manager_Interactor &quot;Accessed None: OwnerController&quot; 크래시&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;OwnerController&lt;/code&gt;는 &lt;b&gt;비복제 + 컴포넌트 BeginPlay에서만 캐싱&lt;/b&gt;됨.&lt;/li&gt;
&lt;li&gt;서버&amp;rarr;클라 RPC &lt;code&gt;Client_Update_PointOfInterests&lt;/code&gt;가 그 BeginPlay보다 &lt;b&gt;먼저 도착&lt;/b&gt;하면 None을 읽어 크래시 &amp;rarr; &lt;b&gt;초기화 순서 레이스&lt;/b&gt;(코옵/조인&amp;middot;레벨 시작 시).&lt;/li&gt;
&lt;li&gt;플러그인 버그 아님. 컴포넌트 위치&amp;middot;설정은 정상.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;수정 (플러그인 미변경)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 PC &lt;code&gt;ARetrievePlayerController&lt;/code&gt;의 &lt;code&gt;PostInitializeComponents&lt;/code&gt;(BeginPlay/RPC보다 먼저 실행)에서 리플렉션으로 &lt;code&gt;OwnerController&lt;/code&gt;를 미리 세팅해 레이스 창 제거.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RetrievePlayerController.h&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;virtual void PostInitializeComponents() override;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RetrievePlayerController.cpp&lt;/b&gt; (익명 namespace 헬퍼 + 오버라이드)&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;void PrimeInteractorOwnerController(APlayerController* PC)
{
    if (!PC) return;
    TInlineComponentArray&amp;lt;UActorComponent*&amp;gt; Components(PC);
    for (UActorComponent* Comp : Components)
    {
        if (!Comp || !Comp-&amp;gt;GetClass()-&amp;gt;GetName().Contains(TEXT(&quot;Manager_Interactor&quot;))) continue;
        if (FObjectProperty* OwnerProp =
            FindFProperty&amp;lt;FObjectProperty&amp;gt;(Comp-&amp;gt;GetClass(), TEXT(&quot;OwnerController&quot;)))
        {
            OwnerProp-&amp;gt;SetObjectPropertyValue_InContainer(Comp, PC);
        }
        break;
    }
}

void ARetrievePlayerController::PostInitializeComponents()
{
    Super::PostInitializeComponents();
    PrimeInteractorOwnerController(this); // OwnerController 선캐싱
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>내일배움캠프-언리얼</category>
      <author>이시보</author>
      <guid isPermaLink="true">https://brian521.tistory.com/103</guid>
      <comments>https://brian521.tistory.com/103#entry103comment</comments>
      <pubDate>Tue, 7 Jul 2026 21:13:49 +0900</pubDate>
    </item>
    <item>
      <title>TIL - 99</title>
      <link>https://brian521.tistory.com/102</link>
      <description>&lt;h1&gt;루멘(Lumen) 이슈 정리&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 쉬핑 패키징에서 루멘이 플레이어를 못 따라옴&lt;/h2&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;: PIE는 정상, 쉬핑 빌드에서 특정 구역에서 거의 못 따라옴 (적 AI도 특정 구역 동일)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원인&lt;/b&gt;: 레벨 RecastNavMesh의 &lt;code&gt;Runtime Generation&lt;/code&gt;이 &lt;b&gt;Static&lt;/b&gt;이었음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트는 &lt;code&gt;bGenerateNavigationOnlyAroundNavigationInvokers=True&lt;/code&gt; (플레이어 인보커 주변만 런타임 네비메시 생성) 사용.&lt;/li&gt;
&lt;li&gt;이 방식은 &lt;b&gt;RuntimeGeneration = Dynamic&lt;/b&gt;이어야 동작. Static이면 런타임 생성이 안 돼 네비메시 구멍 &amp;rarr; &lt;code&gt;MoveToActor&lt;/code&gt; 실패 &amp;rarr; 루멘은 2초마다 텔레포트 복구로만 붙음.&lt;/li&gt;
&lt;li&gt;PIE는 에디터가 네비메시를 항상 갖고 있어 문제가 가려졌던 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수정&lt;/b&gt;: RecastNavMesh &lt;b&gt;Runtime Generation = Static &amp;rarr; Dynamic&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레벨에 배치된 RecastNavMesh 액터가 있으면 그 액터 Details에서 변경 후 레벨 저장&lt;/li&gt;
&lt;li&gt;없으면 Project Settings ▸ Navigation Mesh ▸ Runtime Generation = Dynamic (DefaultEngine.ini에 반영)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검증&lt;/b&gt;: Development 설정으로 패키징 &amp;rarr; 콘솔 &lt;code&gt;show Navigation&lt;/code&gt; &amp;rarr; 죽었던 구역에 네비메시 생성되는지 확인&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주의&lt;/b&gt;: 네비메시는 플레이어 인보커 30m 버블 + NavMeshBoundsVolume 커버 구역에만 생성됨 (bounds 없는 구역은 Dynamic이어도 안 됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 특정 선 너머로 가면 루멘이 사라짐 (에디터 + 쉬핑 공통)&lt;/h2&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;/li&gt;
&lt;li&gt;&lt;b&gt;원인&lt;/b&gt;: 루멘(BP_LumenCharacter)이 P_Level_WP에 &lt;b&gt;직접 배치된 액터&lt;/b&gt;이고 World Partition &lt;b&gt;Spatially Loaded&lt;/b&gt; 상태.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WP는 배치 액터를 edit-time 위치의 셀에 귀속 &amp;rarr; 런타임에 이동해도 셀이 안 바뀜.&lt;/li&gt;
&lt;li&gt;플레이어가 그 원본 셀의 스트리밍 범위를 벗어나면 셀 언로드 = 루멘 디스폰. 돌아오면 재로딩 = 재생성.&lt;/li&gt;
&lt;li&gt;WP 스트리밍은 PIE에서도 돌기 때문에 에디터에서도 재현됨 (쉬핑 전용 아님).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수정&lt;/b&gt;: P_Level_WP에서 배치된 BP_LumenCharacter 선택 &amp;rarr; Details ▸ World Partition ▸ &lt;b&gt;&lt;code&gt;Is Spatially Loaded&lt;/code&gt; 체크 해제&lt;/b&gt; &amp;rarr; 레벨 저장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(대안: 배치 대신 GameMode/PlayerController에서 런타임 스폰 &amp;rarr; 자동 always-loaded)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;같이 확인&lt;/b&gt;: 루멘이 리전별로 켜졌다 꺼지는 Data Layer에 할당돼 있지 않은지 (있으면 always-loaded 레이어로 이동)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프-언리얼</category>
      <author>이시보</author>
      <guid isPermaLink="true">https://brian521.tistory.com/102</guid>
      <comments>https://brian521.tistory.com/102#entry102comment</comments>
      <pubDate>Mon, 6 Jul 2026 23:09:45 +0900</pubDate>
    </item>
    <item>
      <title>TIL - 98</title>
      <link>https://brian521.tistory.com/101</link>
      <description>&lt;h1&gt;BGM 시스템&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황에 따라 BGM을 자동 전환하는 시스템. 세션 상태(메뉴/게임/결과), 전투 여부, 지역(존)에 따라&lt;br /&gt;트랙을 고르고, 두 오디오 슬롯을 크로스페이드로 전환한다. 각 트랙은 개별 볼륨 배수를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우선순위&lt;/b&gt;: 세션 상태(메뉴/결과) &amp;gt; 지역(존) &amp;gt; 전투 &amp;gt; 기본&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구성 요소&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;FRetrieveMusicTrack&lt;/code&gt;&lt;/b&gt;: 사운드(&lt;code&gt;TSoftObjectPtr&amp;lt;USoundBase&amp;gt;&lt;/code&gt;) + 볼륨 배수(0~2). 전역 설정&amp;middot;존 볼륨 공용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;URetrieveMusicSettings&lt;/code&gt;&lt;/b&gt; (&lt;code&gt;UDeveloperSettings&lt;/code&gt;): Project Settings &amp;gt; Retrieve &amp;gt; Retrieve Music.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;MenuBGM&lt;/code&gt; / &lt;code&gt;DefaultBGM&lt;/code&gt; / &lt;code&gt;CombatBGM&lt;/code&gt; / &lt;code&gt;ResultBGM&lt;/code&gt;, &lt;code&gt;CrossfadeDuration&lt;/code&gt;, &lt;code&gt;LoopGapSeconds&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;URetrieveMusicSubsystem&lt;/code&gt;&lt;/b&gt; (&lt;code&gt;UWorldSubsystem&lt;/code&gt;): 재생&amp;middot;전환의 중심. 슬롯 A/B 크로스페이드.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;ARetrieveMusicZoneVolume&lt;/code&gt;&lt;/b&gt; (&lt;code&gt;AActor&lt;/code&gt; + &lt;code&gt;UBoxComponent&lt;/code&gt;): 특정 구역 전용 BGM 볼륨.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상태별 BGM (세션)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메뉴는 별도 레벨이 아니라 &lt;code&gt;ERetrieveSessionState&lt;/code&gt;로 관리됨. &lt;code&gt;Channel.Session.StateChanged&lt;/code&gt; 구독으로 전환 감지.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;세션 상태&lt;/th&gt;
&lt;th&gt;BGM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MainMenu&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MenuBGM&lt;/code&gt; (게임플레이 존/전투 무시)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Result&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResultBGM&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loading&lt;/td&gt;
&lt;td&gt;무음(보류)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InGame&lt;/td&gt;
&lt;td&gt;지역 &amp;gt; 전투 &amp;gt; 기본&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전투 BGM 판정&lt;/h2&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;code&gt;Channel.Enemy.PlayerSpotted&lt;/code&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;공격 발동&amp;middot;무기 장착만으로는 진입하지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유지/해제&lt;/b&gt;: 0.5초 폴링으로 교전 집합에서 &lt;b&gt;파괴됨 / &lt;code&gt;State.Enemy.Dead&lt;/code&gt; / &lt;code&gt;State.Enemy.Chase&lt;/code&gt; 없음&lt;/b&gt;인&lt;br /&gt;몬스터를 제거 &amp;rarr; 집합이 비면(= &lt;b&gt;나를 추적하는 몬스터가 하나도 없으면&lt;/b&gt;) 기본 BGM 복귀.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지역(존) BGM&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ZoneBGM&lt;/code&gt;(평상시) / &lt;code&gt;bUseCombatMusic&lt;/code&gt;(토글) / &lt;code&gt;ZoneCombatBGM&lt;/code&gt;(전투)&lt;/li&gt;
&lt;li&gt;존 안 + &lt;code&gt;bUseCombatMusic&lt;/code&gt; + 전투 중 + 전투곡 지정됨 &amp;rarr; &lt;code&gt;ZoneCombatBGM&lt;/code&gt;, 아니면 &lt;code&gt;ZoneBGM&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사운드 미지정 존 안에서는 무음&lt;/b&gt; (기본 BGM이 새어나오지 않음)&lt;/li&gt;
&lt;li&gt;진입/이탈은 오버랩 이벤트 + 직접 확인(&lt;code&gt;RefreshPlayerOverlap&lt;/code&gt;) 병행&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;볼륨 (3단 구조)&lt;/h2&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;최종 음량 = 트랙 볼륨 배수(에디터) &amp;times; 음악 볼륨 슬라이더(옵션) &amp;times; 마스터 볼륨&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랙 볼륨: &lt;code&gt;FRetrieveMusicTrack.Volume&lt;/code&gt; &amp;mdash; 파일별 음량 편차 보정&lt;/li&gt;
&lt;li&gt;옵션 슬라이더: BGM 슬롯의 &lt;code&gt;SoundClassOverride = MusicSoundClass&lt;/code&gt;로 기존 SoundMix 볼륨 시스템과 자동 연동&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리루프&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;곡이 자연 종료되면 &lt;code&gt;LoopGapSeconds&lt;/code&gt; 뒤에 같은 곡 재생(간격을 둔 반복)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주의: 사운드 에셋의 루프를 꺼야(원샷)&lt;/b&gt; 이 로직이 동작함. 에셋이 무한 루프면 간격 없이 이어짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결한 주요 이슈&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;전투 종료 판정&lt;/b&gt; &amp;mdash; &lt;code&gt;State.Player.Combat&lt;/code&gt; 태그(마지막 활동 후 3.5초) &amp;rarr; &lt;b&gt;추적 몬스터 집합&lt;/b&gt;(추적 적 0이면 해제)으로 교체.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시작 지점 존이 무음 안 되던 문제&lt;/b&gt; &amp;mdash; 메뉴 경유로 시작 시 possess/카메라 전환 과정에서 존 진입 오버랩 이벤트가&lt;br /&gt;누락됨. 해결: 존이 서브시스템에 &lt;b&gt;자가등록&lt;/b&gt;(BeginPlay) + &lt;b&gt;InGame 진입 후 0.3초&amp;times;10회 재평가&lt;/b&gt;(&lt;code&gt;RefreshPlayerOverlap&lt;/code&gt;,&lt;br /&gt;&lt;code&gt;GetOverlappingActors&lt;/code&gt;로 이벤트 비의존 직접 확인).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;볼륨 적용 시 BGM 사라짐&lt;/b&gt; &amp;mdash; BGM 슬롯의 &lt;code&gt;bIsUISound = true&lt;/code&gt;가 SoundMix 볼륨 적용과 충돌 &amp;rarr; 해당 플래그 제거.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;에디터 설정&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Project Settings &amp;rarr; Retrieve &amp;rarr; Retrieve Music&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Menu / Default / Combat / Result BGM&lt;/code&gt;의 &lt;code&gt;Sound&lt;/code&gt; 지정 + &lt;code&gt;Volume&lt;/code&gt; 배수 조정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Crossfade Duration&lt;/code&gt;(기본 1.5초), &lt;code&gt;Loop Gap Seconds&lt;/code&gt;(기본 3초)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지역에 &lt;code&gt;Retrieve Music Zone Volume&lt;/code&gt; 배치&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Box Extent를 구역 전체로, &lt;code&gt;Zone BGM&lt;/code&gt; 지정 (필요 시 &lt;code&gt;Use Combat Music&lt;/code&gt; + &lt;code&gt;Zone Combat BGM&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;World Partition 레벨이면 &lt;code&gt;Is Spatially Loaded&lt;/code&gt; 끄기 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;(연동 확인) &lt;b&gt;Retrieve Settings &amp;rarr; Audio &amp;rarr; Music Sound Class&lt;/b&gt; 지정 시 옵션 음악 볼륨 슬라이더가 BGM에 적용됨&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>내일배움캠프-언리얼</category>
      <author>이시보</author>
      <guid isPermaLink="true">https://brian521.tistory.com/101</guid>
      <comments>https://brian521.tistory.com/101#entry101comment</comments>
      <pubDate>Thu, 2 Jul 2026 21:03:02 +0900</pubDate>
    </item>
    <item>
      <title>TIL - 97</title>
      <link>https://brian521.tistory.com/100</link>
      <description>&lt;h1&gt;BGM 시스템 (기본 / 전투 / 성 지역)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배경음악을 상황에 따라 자동 전환하는 시스템. 우선순위는 &lt;b&gt;성 지역 &amp;gt; 전투 &amp;gt; 기본&lt;/b&gt;이며,&lt;br /&gt;두 개의 오디오 슬롯을 크로스페이드해 부드럽게 전환한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구성 요소&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;URetrieveMusicSettings&lt;/code&gt;&lt;/b&gt; (&lt;code&gt;UDeveloperSettings&lt;/code&gt;): Project Settings에서 기본/전투 BGM 에셋과 크로스페이드 시간 지정.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;URetrieveMusicSubsystem&lt;/code&gt;&lt;/b&gt; (&lt;code&gt;UWorldSubsystem&lt;/code&gt;): 재생/전환/전투 판정의 중심. 슬롯 A/B 크로스페이드.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;ARetrieveMusicZoneVolume&lt;/code&gt;&lt;/b&gt; (&lt;code&gt;AActor&lt;/code&gt; + &lt;code&gt;UBoxComponent&lt;/code&gt;): 성 구역처럼 특정 영역 전용 BGM을 재생하는 볼륨.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동작 규칙&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;성 지역(존)&lt;/b&gt;: 볼륨 안에 있으면 전투 여부와 무관하게 &lt;code&gt;ZoneBGM&lt;/code&gt;이 &lt;b&gt;최우선&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전투 BGM 진입&lt;/b&gt;: &lt;code&gt;Channel.Enemy.PlayerSpotted&lt;/code&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;공격 발동&amp;middot;무기 장착만으로는 진입하지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전투 BGM 유지/종료&lt;/b&gt;: 나를 포착한 몬스터를 교전 집합에 넣고, 0.5초 주기로&lt;br /&gt;&lt;b&gt;파괴됨 / &lt;code&gt;State.Enemy.Dead&lt;/code&gt; / &lt;code&gt;State.Enemy.Chase&lt;/code&gt; 없음&lt;/b&gt;인 몬스터를 걷어낸다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;집합이 비면(= 추적하는 몬스터가 하나도 없으면) 기본 BGM으로 복귀 (최대 0.5초 내).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;볼륨 연동&lt;/b&gt;: &lt;code&gt;Retrieve Settings &amp;gt; Audio &amp;gt; Music Sound Class&lt;/code&gt;가 지정돼 있으면&lt;br /&gt;기존 옵션 메뉴의 음악 볼륨 슬라이더가 BGM에도 그대로 적용됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;상황&lt;/th&gt;
&lt;th&gt;결과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;적이 나를 포착&lt;/td&gt;
&lt;td&gt;전투 BGM 진입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포착한 적이 계속 추적 중&lt;/td&gt;
&lt;td&gt;전투 BGM 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적이 죽음 / 추적 포기(도망&amp;middot;흥미 잃음)&lt;/td&gt;
&lt;td&gt;해당 적을 집합에서 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;추적하는 적이 하나도 안 남음&lt;/td&gt;
&lt;td&gt;기본 BGM 복귀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;성 볼륨 안에 진입&lt;/td&gt;
&lt;td&gt;전투 여부 무시하고 성 BGM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;에디터 설정&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Project Settings &amp;rarr; Retrieve &amp;rarr; Retrieve Music&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Default BGM&lt;/code&gt; / &lt;code&gt;Combat BGM&lt;/code&gt; 에셋 지정 (&lt;code&gt;USoundBase&lt;/code&gt;: Wave/Cue/MetaSound 모두 가능)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Crossfade Duration&lt;/code&gt; (기본 1.5초)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성 구역에 &lt;code&gt;Retrieve Music Zone Volume&lt;/code&gt; 배치&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Box Extent를 성 구역 전체를 덮도록 조정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Zone BGM&lt;/code&gt;에 성 전용 트랙 지정&lt;/li&gt;
&lt;li&gt;World Partition 레벨이면 &lt;b&gt;&lt;code&gt;Is Spatially Loaded&lt;/code&gt; 끄기 권장&lt;/b&gt; (경계에서 언로드로 오버랩 놓침 방지)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;(선택) &lt;b&gt;Retrieve Settings &amp;rarr; Audio &amp;rarr; Music Sound Class&lt;/b&gt; 지정 시 볼륨 슬라이더 연동&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>내일배움캠프-언리얼</category>
      <author>이시보</author>
      <guid isPermaLink="true">https://brian521.tistory.com/100</guid>
      <comments>https://brian521.tistory.com/100#entry100comment</comments>
      <pubDate>Wed, 1 Jul 2026 22:13:06 +0900</pubDate>
    </item>
    <item>
      <title>TIL - 96</title>
      <link>https://brian521.tistory.com/99</link>
      <description>&lt;h1&gt;검방패 사운드 작업 정리&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. 한 줄 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검방패 콤보어택 &lt;b&gt;스윙 사운드&lt;/b&gt;를 랜덤 큐 + 몽타주 노티파이로 구현&amp;middot;검증 완료&lt;br /&gt;히트음/설정볼륨연동/CastSound/소환액터음은 방식 확정 후 보류 또는 진행 예정.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 완료된 작업 (monolith로 적용&amp;middot;저장&amp;middot;검증)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-1. 랜덤 Sound Cue 2개 생성 &amp;mdash; &lt;code&gt;/Game/Retrieve/Audio/SwordShield/&lt;/code&gt;&lt;/h3&gt;
&lt;table style=&quot;height: 105px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 24px;&quot;&gt;
&lt;th style=&quot;height: 24px;&quot;&gt;큐&lt;/th&gt;
&lt;th style=&quot;height: 24px;&quot;&gt;구성&lt;/th&gt;
&lt;th style=&quot;height: 24px;&quot;&gt;변조&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 39px;&quot;&gt;
&lt;td style=&quot;height: 39px;&quot;&gt;&lt;code&gt;SC_SSH_ComboSwing&lt;/code&gt; (스윙)&lt;/td&gt;
&lt;td style=&quot;height: 39px;&quot;&gt;&lt;code&gt;SW_Whoosh_Metal_4-1/2/3/5/7&lt;/code&gt; &amp;rarr; Random&lt;/td&gt;
&lt;td style=&quot;height: 39px;&quot;&gt;Pitch 0.9&lt;del&gt;1.1, Vol 0.85&lt;/del&gt;1.0, Randomize Without Replacement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px;&quot;&gt;&lt;code&gt;SC_SSH_SwordHit&lt;/code&gt; (히트)&lt;/td&gt;
&lt;td style=&quot;height: 42px;&quot;&gt;&lt;code&gt;SW_Sword_Hit_1-1/3/4&lt;/code&gt; + &lt;code&gt;SW_Generic_Hit_1-1/4&lt;/code&gt; &amp;rarr; Random&lt;/td&gt;
&lt;td style=&quot;height: 42px;&quot;&gt;Pitch 0.92&lt;del&gt;1.08, Vol 0.9&lt;/del&gt;1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 wav 위치: &lt;code&gt;Content/External/NaPH_RPG_Fantasy_Sounds_Bunle/Weapons_and_Combat/wav/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;구조: &lt;code&gt;WavePlayer(N) &amp;rarr; Random &amp;rarr; Modulator &amp;rarr; Output&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;큐 안에는 cue(SC_)가 아니라 wav(SW_)를 넣음. cue = 레시피(랜덤/변조), wav = 재료.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-2. 스윙 노티파이 9개 배치 (3 몽타주 &amp;times; 콤보 3타)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Play Sound&lt;/code&gt; 노티파이를 각 콤보 타의 &lt;b&gt;슬래시 VFX(&lt;code&gt;PlayNiagaraEffect&lt;/code&gt;)와 같은 시점&lt;/b&gt;, &lt;code&gt;SlashEffect&lt;/code&gt; 트랙에 배치 &amp;rarr; &lt;code&gt;SC_SSH_ComboSwing&lt;/code&gt; 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;몽타주&lt;/th&gt;
&lt;th&gt;스윙 시점(초)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AM_SSH_WindComboAttack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.352 / 2.141 / 3.645&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AM_SSH_FireComboAttack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.344 / 2.125 / 3.641&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AM_SSH_WaterComboAttack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.810 / 2.166 / 3.977&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검증: &lt;code&gt;find_audio_references(SC_SSH_ComboSwing)&lt;/code&gt; &amp;rarr; 위 3개 몽타주 모두 참조 확인 ✅&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 핵심 아키텍처 (참고)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콤보 사운드 = &lt;b&gt;2개 레이어&lt;/b&gt; (기존 VFX 패턴과 동일):&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스윙(휘두르기)&lt;/b&gt; &amp;rarr; 콤보 몽타주의 &lt;code&gt;Play Sound&lt;/code&gt; AnimNotify (= 슬래시 VFX와 같은 위치)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;히트(타격)&lt;/b&gt; &amp;rarr; &lt;code&gt;GCN_Hit_Impact&lt;/code&gt; GameplayCue (= 히트 VFX와 같은 위치)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콤보 구동: &lt;code&gt;GA_Attack&lt;/code&gt;이 &lt;code&gt;FWeaponComboStep.SectionName&lt;/code&gt;별로 몽타주 섹션을 따로 재생.&lt;br /&gt;&amp;rarr; 노티파이는 반드시 해당 섹션 시간 범위 안에 있어야 그 타에서 울림.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 남은 작업 (TODO)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. 히트음 &amp;mdash; &lt;code&gt;GCN_Hit_Impact&lt;/code&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;code&gt;GCN_Hit_Impact&lt;/code&gt; (&lt;code&gt;/Game/Retrieve/AbilitySystem/GameplayCues/&lt;/code&gt;), 타입 &lt;code&gt;GameplayCueNotify_Static&lt;/code&gt;, tag &lt;code&gt;GameplayCue.Combat.Hit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Burst Sounds 배열이 없는 타입 &amp;rarr; &lt;b&gt;OnExecute 그래프에 &lt;code&gt;Play Sound at Location&lt;/code&gt; 노드 추가&lt;/b&gt; 필요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Sound = &lt;code&gt;SC_SSH_SwordHit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Location = 기존 &lt;code&gt;NS_Hit_Impact&lt;/code&gt;(Spawn System at Location)가 쓰는 위치 핀과 동일하게 연결 (보통 &lt;code&gt;Parameters.Location&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;막힌 이유: &lt;code&gt;GCN_Hit_Impact.uasset&lt;/code&gt;이 &lt;b&gt;읽기전용&lt;/b&gt; &amp;rarr; 해제 후 진행 (&lt;code&gt;attrib -r&lt;/code&gt; 또는 속성창)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. 설정 볼륨(SFX 슬라이더) 연동 &amp;mdash; 미설정 상태&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 &lt;code&gt;ApplyAudio()&lt;/code&gt;는 SoundMix + SoundClass override 방식인데 &lt;b&gt;SoundMix/SoundClass 애셋이 프로젝트에 미할당&lt;/b&gt; &amp;rarr; &lt;b&gt;Master 슬라이더만 작동&lt;/b&gt;, SFX 등 개별 채널은 무효&lt;/li&gt;
&lt;li&gt;연동하려면:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;SoundMix&lt;/code&gt; 1개 + &lt;code&gt;SoundClass&lt;/code&gt; 계층 생성 (Master 루트 &amp;rarr; SFX/Music/Ambience/UI/Voice 자식)&lt;/li&gt;
&lt;li&gt;Project Settings &amp;rarr; Game &amp;rarr; &quot;Retrieve Settings&quot;에서 SettingsSoundMix / 각 채널 SoundClass 할당&lt;/li&gt;
&lt;li&gt;큐들(&lt;code&gt;SC_SSH_ComboSwing&lt;/code&gt;/&lt;code&gt;SC_SSH_SwordHit&lt;/code&gt;/&lt;code&gt;SC_SSH_HeavySwing&lt;/code&gt;)의 &lt;b&gt;Sound Class = SFX&lt;/b&gt;로 지정&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;코드 위치: &lt;code&gt;Source/Retrieve/Settings/RetrieveSettingsSubsystem.cpp&lt;/code&gt; (ApplyAudio), &lt;code&gt;RetrieveSettingsConfig.h&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-3. CastSound &amp;mdash; 미연결(dead field)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;FSkillCombination::CastSound&lt;/code&gt;(RetrieveDataTableTypes.h:857), &lt;code&gt;FWeaponHeavyAttack::CastSound&lt;/code&gt;(:1331) &lt;b&gt;둘 다 코드에서 안 읽힘&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BeginBurstSkill(Row)&lt;/code&gt;이 9개 필드만 복사하고 CastSound는 복사 안 함 &amp;rarr; 데이터 넣어도 무동작&lt;/li&gt;
&lt;li&gt;살리려면 ①C++: &lt;code&gt;BeginBurstSkill&lt;/code&gt;에 아래 추가(재컴파일 필요), 또는 ②시전 몽타주에 AnimNotify
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;if (USoundBase* Cast = Row-&amp;gt;CastSound.LoadSynchronous())
    if (const AActor* O = GetOwner())
        UGameplayStatics::PlaySoundAtLocation(O-&amp;gt;GetWorld(), Cast, O-&amp;gt;GetActorLocation());&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;참고: &lt;code&gt;FBurstHitInstance::HitSound&lt;/code&gt;(per-hit)는 이미 작동 중(&lt;code&gt;PlayerBurstComponent.cpp:400&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-4. 소환 액터(WorldSpawnActor) 사운드 &amp;rarr; &lt;b&gt;액터 BP에&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;code&gt;Owner + 정면*WorldSpawnDistance&lt;/code&gt;(플레이어에서 떨어진 곳)에 스폰됨. 몽타주 노티파이는 캐릭터 위치에서 울려 위치가 틀림. &lt;code&gt;BurstWorldActorBase&lt;/code&gt; 설계도 &quot;비주얼은 BP 자식 SceneRoot 아래 추가&quot;.&lt;/li&gt;
&lt;li&gt;방법(에디터, WorldSpawnActorClass가 가리키는 BP):
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;권장: &lt;b&gt;Audio Component&lt;/b&gt; 추가(SceneRoot 아래), Sound=큐, Auto Activate=true &amp;rarr; 스폰 위치에서 재생&amp;middot;따라다님&amp;middot;파괴 시 자동정지&lt;/li&gt;
&lt;li&gt;또는 일회성: &lt;b&gt;Event BeginPlay &amp;rarr; Play Sound at Location&lt;/b&gt; (Location=GetActorLocation)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;퇴장(가라앉기)음은 &lt;code&gt;BeginSink&lt;/code&gt;가 private C++라 BP 훅 불가 &amp;rarr; 필요시 별도 C++ 노출&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 트러블슈팅&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. &lt;code&gt;BP_FFF_WorldActor&lt;/code&gt; 저장 에러 (RF_Standalone 누락)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에러: &lt;code&gt;... does not have any of the provided object flags (0x10000002)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;의미: BP 오브젝트에 &lt;code&gt;RF_Standalone&lt;/code&gt;(0x2) / &lt;code&gt;RF_HasExternalPackage&lt;/code&gt;(0x10000000) 둘 다 없음 &amp;rarr; 최상위 애셋으로 인식 안 돼 저장 거부. (자동화/복제로 생성된 BP에서 흔함)&lt;/li&gt;
&lt;li&gt;해결 A(작업 유지): 에디터 콘솔에 &lt;code&gt;save.FixupStandaloneFlags 1&lt;/code&gt; 입력 &amp;rarr; 저장(Ctrl+S). 한 번 저장되면 영구 수정. (안 먹으면 &lt;code&gt;-dpcvars=save.FixupStandaloneFlags=1&lt;/code&gt; 인자로 재실행)&lt;/li&gt;
&lt;li&gt;해결 B(확실): 깨진 BP 삭제 &amp;rarr; &lt;code&gt;BurstWorldActorBase&lt;/code&gt; 부모로 Blueprint Class 재생성 &amp;rarr; 컴포넌트 재추가&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 주의/리마인더&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;⚠️ 사운드 번들 &lt;code&gt;Content/External/NaPH_RPG_Fantasy_Sounds_Bunle/&lt;/code&gt;가 &lt;b&gt;git 미추적&lt;/b&gt; &amp;rarr; 새 큐가 이 wav 참조하므로 &lt;b&gt;번들 커밋(또는 팀 공유) 필수&lt;/b&gt;, 안 그러면 팀원 쪽 참조 깨짐&lt;/li&gt;
&lt;li&gt;새 큐는 추적 경로 &lt;code&gt;Content/Retrieve/Audio/SwordShield/&lt;/code&gt;에 저장됨&lt;/li&gt;
&lt;li&gt;Wind 몽타주에 빈 &lt;code&gt;Sound&lt;/code&gt; 트랙 잔재 가능(첫 시도 부산물, 무해)&lt;/li&gt;
&lt;li&gt;스윙음이 슬래시와 동시 재생 &amp;mdash; 더 &quot;치고 나가는&quot; 느낌 원하면 노티파이 1~2프레임 앞당김&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프-언리얼</category>
      <author>이시보</author>
      <guid isPermaLink="true">https://brian521.tistory.com/99</guid>
      <comments>https://brian521.tistory.com/99#entry99comment</comments>
      <pubDate>Tue, 30 Jun 2026 21:37:31 +0900</pubDate>
    </item>
    <item>
      <title>TIL - 95</title>
      <link>https://brian521.tistory.com/98</link>
      <description>&lt;h1&gt;전투 VFX 작업&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 상태이상 VFX (Burn / Cold)&lt;/h2&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;: 머티리얼 overlay 틴트 + Niagara 파티클 (GameplayCue 기반)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;태그&lt;/b&gt;: &lt;code&gt;GameplayCue.Status.Burn&lt;/code&gt; / &lt;code&gt;GameplayCue.Status.Cold&lt;/code&gt; (네이티브, RetrieveGameplayTags)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;머티리얼&lt;/b&gt;: &lt;code&gt;M_StatusOverlay&lt;/code&gt;(부모, Unlit&amp;middot;Additive&amp;middot;Fresnel 발광) + &lt;code&gt;MI_StatusBurn&lt;/code&gt; / &lt;code&gt;MI_StatusFrost&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;notify&lt;/b&gt;: &lt;code&gt;GCN_Status_Burn/Cold&lt;/code&gt; (Looping)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OnActive: 타깃 메시에 &lt;code&gt;SetOverlayMaterial&lt;/code&gt; + NS 스폰&lt;/li&gt;
&lt;li&gt;OnRemove: overlay 해제 + 스폰 컴포넌트 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;몸 전체 커버&lt;/b&gt;: NS에 Skeletal Mesh Location 모듈 + &lt;code&gt;User.Mesh&lt;/code&gt; DI
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU sim은 메시에 Allow CPU Access 필요 &amp;rarr; &lt;b&gt;GPU sim으로 전환해 해결&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;1초 주기 펄스&lt;/b&gt;: EmitterState Loop 1s + SpawnBurst + SpawnRate off&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 타격 임팩트 VFX (Hit Impact)&lt;/h2&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;: 데미지 GE에 burst GameplayCue&lt;/li&gt;
&lt;li&gt;&lt;b&gt;태그&lt;/b&gt;: &lt;code&gt;GameplayCue.Combat.Hit&lt;/code&gt; (네이티브)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;연결&lt;/b&gt;: &lt;code&gt;GE_BasicHitDamage&lt;/code&gt; &amp;rarr; Gameplay Cues 컴포넌트에 태그 추가&lt;/li&gt;
&lt;li&gt;&lt;b&gt;notify&lt;/b&gt;: &lt;code&gt;GCN_Hit_Impact&lt;/code&gt; (burst / GameplayCueNotify_Static)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OnExecute: 타격 지점에 임팩트 NS 스폰&lt;/li&gt;
&lt;li&gt;위치는 &lt;code&gt;Params.Location&lt;/code&gt;이 비어 있음 &amp;rarr; &lt;b&gt;EffectContext의 HitResult&lt;/b&gt; ImpactPoint/Normal 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;발광&lt;/b&gt;: &lt;code&gt;VFX_Color&lt;/code&gt;를 HDR(&amp;gt;1)로 + Bloom 활성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원소별 색&lt;/b&gt;: EffectContext &amp;rarr; Get Instigator Actor &amp;rarr; PlayerState &amp;rarr; &lt;code&gt;GetCurrentElementTag&lt;/code&gt; &amp;rarr; 색 매핑 &amp;rarr; &lt;code&gt;VFX_Color&lt;/code&gt;(활성화 전 세팅)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 함정&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;GameplayCue/태그는 네이티브로 관리&lt;/b&gt; (RetrieveGameplayTags + 리빌드). monolith 런타임 config 태그 핫-추가는 &lt;code&gt;GameplayTags.dll&lt;/code&gt; 크래시 유발.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GameplayCueNotify OnRemove&lt;/b&gt;: 사망 시 &lt;code&gt;MyTarget&lt;/code&gt;&amp;middot;스폰 컴포넌트가 무효화될 수 있음 &amp;rarr; &lt;code&gt;IsValid&lt;/code&gt; 가드 필수.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Niagara DI / User 파라미터(메시&amp;middot;색)는 Activate 전에 세팅&lt;/b&gt; (비활성 스폰 &amp;rarr; set &amp;rarr; Activate). 안 그러면 첫 버스트에 안 먹음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GE로 발동된 cue의 &lt;code&gt;Params.Location&lt;/code&gt;은 자동으로 안 채워짐&lt;/b&gt; &amp;rarr; EffectContext의 HitResult에서 가져올 것.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Static cue에서 &lt;code&gt;GetPlayerState&lt;/code&gt;(인덱스)는 월드 컨텍스트가 없어 실패&lt;/b&gt; &amp;rarr; EffectContext의 Instigator 사용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CPU Niagara 스켈레탈 메시 샘플링은 Allow CPU Access 필요&lt;/b&gt;, GPU sim이면 불필요.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프-언리얼</category>
      <author>이시보</author>
      <guid isPermaLink="true">https://brian521.tistory.com/98</guid>
      <comments>https://brian521.tistory.com/98#entry98comment</comments>
      <pubDate>Mon, 29 Jun 2026 21:46:32 +0900</pubDate>
    </item>
    <item>
      <title>TIL - 94</title>
      <link>https://brian521.tistory.com/97</link>
      <description>&lt;h1&gt;GAS에서 지속 데미지(Burn)가 매 틱 피격 리액션을 유발하는 문제&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;증상&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어가 몬스터에게 Burn(&lt;code&gt;GE_Burn&lt;/code&gt;)을 부여하면, 몬스터가 화상 데미지를 받는 &lt;b&gt;매 틱마다 움찔(Flinch)&lt;/b&gt; 거리는 현상.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Burn은 Periodic(주기) 데미지라 틱마다 &lt;code&gt;PostGameplayEffectExecute&lt;/code&gt; &amp;rarr; &lt;code&gt;BroadcastHitEvent&lt;/code&gt;가 호출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;BroadcastHitEvent&lt;/code&gt;는 공격 GE의 asset tag를 읽어 두 개의 이벤트를 발행한다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;매칭 베이스 태그&lt;/th&gt;
&lt;th&gt;Burn에서의 값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;공격자 이벤트&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GameplayEvent.Attack.HitSuccess&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GameplayEvent.Attack.HitSuccess.Burn&lt;/code&gt; ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;타겟 이벤트&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GameplayEvent.Hit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Burn엔 &lt;code&gt;Hit.*&lt;/code&gt; 태그가 없어 &lt;b&gt;기본값 &lt;code&gt;GameplayEvent.Hit.Normal&lt;/code&gt;로 떨어짐&lt;/b&gt; ⚠️&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 타겟(몬스터)에게 매 틱 &lt;code&gt;GameplayEvent.Hit.Normal&lt;/code&gt;이 전송됨&lt;br /&gt;&amp;rarr; &lt;code&gt;HitReactionComponent::HandleHitEvent&lt;/code&gt; 수신&lt;br /&gt;&amp;rarr; &lt;code&gt;ResolveReactType&lt;/code&gt;에서 &lt;code&gt;HitReact.Type.*&lt;/code&gt; 태그가 없으면 &lt;b&gt;기본값 Flinch 반환&lt;/b&gt;&lt;br /&gt;&amp;rarr; 결국 Burn 틱마다 Flinch 발동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심&lt;/b&gt;: 피격 리액션 억제는 GE 데미지 처리(&lt;code&gt;CombatAttributeSet&lt;/code&gt;) 단계에서 발행되는 &lt;code&gt;GameplayEvent.Hit&lt;/code&gt; 이벤트가 트리거다. 데미지 계산과 리액션 발행이 같은 함수에 묶여 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;BroadcastHitEvent&lt;/code&gt;에서 공격 이벤트가 DoT(Burn)면 &lt;b&gt;타겟 Hit 이벤트만 건너뛴다.&lt;/b&gt; 데미지 / 플로터 / AI 인지는 그대로 유지.&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;EventData.EventTag = AttackerEventTag;
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(AttackerActor, AttackerEventTag, EventData);

// Burn 등 지속 데미지(DoT)는 데미지만 적용하고 피격 반응(움찔/몽타주)은 발생시키지 않는다.
const bool bIsDamageOverTime =
    AttackerEventTag.MatchesTag(RetrieveGameplayTags::GameplayEvent_Attack_HitSuccess_Burn);

if (TargetActor != AttackerActor &amp;amp;&amp;amp; !bIsDamageOverTime)
{
    EventData.EventTag = TargetEventTag;
    UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(TargetActor, TargetEventTag, EventData);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Burn 틱&lt;/th&gt;
&lt;th&gt;일반 공격&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;데미지&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데미지 플로터&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 인지&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;피격 리액션&lt;/td&gt;
&lt;td&gt;❌ (차단)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배운 점 / 확장&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;MatchesTag&lt;/code&gt;를 써서 &lt;code&gt;...Burn.X&lt;/code&gt; 하위 태그가 생겨도 자동 커버.&lt;/li&gt;
&lt;li&gt;데미지 적용 vs 피격 반응을 분리해서 생각해야 한다 &amp;mdash; &quot;데미지를 받되 리액션은 안 함&quot;은 &lt;b&gt;이벤트 발행 단계에서 타겟 이벤트만 막는 것&lt;/b&gt;으로 해결.&lt;/li&gt;
&lt;li&gt;추후 독&amp;middot;감전 등 DoT 추가 시 &lt;code&gt;bIsDamageOverTime&lt;/code&gt; 조건에 &lt;code&gt;|| AttackerEventTag.MatchesTag(...)&lt;/code&gt; 한 줄씩 추가.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프-언리얼</category>
      <author>이시보</author>
      <guid isPermaLink="true">https://brian521.tistory.com/97</guid>
      <comments>https://brian521.tistory.com/97#entry97comment</comments>
      <pubDate>Fri, 26 Jun 2026 21:13:11 +0900</pubDate>
    </item>
    <item>
      <title>TIL - 93</title>
      <link>https://brian521.tistory.com/96</link>
      <description>&lt;h1&gt;전투/UI&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데미지 플로터가 뜨는 조건 (GAS)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데미지 GE가 &lt;code&gt;ExecCalc_BasicHitDamage&lt;/code&gt; &amp;rarr; &lt;code&gt;IncomingDamage&lt;/code&gt; 메타 어트리뷰트를 거쳐야 &lt;code&gt;CombatAttributeSet::BroadcastHitEvent&lt;/code&gt;가 &lt;code&gt;Channel.Combat.DamageDealt&lt;/code&gt;를 발행한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BroadcastHitEvent&lt;/code&gt;는 GE의 &lt;code&gt;GameplayEvent.Attack.HitSuccess.*&lt;/code&gt; 태그를 뽑아 그걸 키로 &lt;code&gt;DT_HitFeedback&lt;/code&gt;을 조회(없으면 &lt;code&gt;.Light&lt;/code&gt; 폴백). 매칭 행이 없으면 &lt;code&gt;AttackFeedbackComponent&lt;/code&gt;가 플로터를 건너뛴다.&lt;/li&gt;
&lt;li&gt;&amp;rArr; &lt;b&gt;새 공격에 플로터를 띄우려면: 데미지 GE에 &lt;code&gt;HitSuccess.*&lt;/code&gt; 태그 + 해당 태그의 &lt;code&gt;DT_HitFeedback&lt;/code&gt; 행 추가.&lt;/b&gt; 태그를 나누면(버스트 &lt;code&gt;.Burst&lt;/code&gt;, 번 &lt;code&gt;.Burn&lt;/code&gt;) 플로터를 따로 연출 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원소모드 = ASC loose tag&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 원소(불/물/바람)는 &lt;code&gt;PlayerState::GetCurrentElementTag()&lt;/code&gt; = ASC의 &lt;code&gt;Element.*&lt;/code&gt; loose 태그.&lt;/li&gt;
&lt;li&gt;버스트/흡수는 게이지 슬롯 조합이 아니라 이 &quot;현재 모드&quot;로 결정. 게이지 슬롯은 원소 구분 없는 충전 칸이고, 색은 현재 모드 하나로 통일.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도트(DoT)는 사망 시 직접 제거해야 함&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주기형 GE(GE_Burn)는 대상이 죽어도 계속 틱 &amp;rarr; 시체에서 플로터가 계속 뜬다.&lt;/li&gt;
&lt;li&gt;사망 처리(&lt;code&gt;HealthComponent::HandleHealthChanged&lt;/code&gt;, 권위 측)에서 &lt;code&gt;ASC-&amp;gt;RemoveActiveEffectsWithGrantedTags({State.Status.Burn})&lt;/code&gt;로 제거하면 멈춤.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UMG: 수동 RenderTransform은 위젯 애니에 덮어써진다&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;SetRenderScale()&lt;/code&gt; 직후 위젯 애니(RiseFade)가 같은 Render Transform(Scale 채널/트랜스폼 전체)을 키프레임하면 수동 스케일이 무시된다.&lt;/li&gt;
&lt;li&gt;&amp;rArr; 애니는 위치+불투명도만 키, Scale은 빼거나 / 스케일은 부모&amp;middot;텍스트로 분리해 곱연산.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프-언리얼</category>
      <author>이시보</author>
      <guid isPermaLink="true">https://brian521.tistory.com/96</guid>
      <comments>https://brian521.tistory.com/96#entry96comment</comments>
      <pubDate>Tue, 23 Jun 2026 22:05:19 +0900</pubDate>
    </item>
    <item>
      <title>TIL - 92</title>
      <link>https://brian521.tistory.com/95</link>
      <description>&lt;h1&gt;다중 Row 드랍 &amp;amp; 인벤토리 직접 지급&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적 사망 시 &lt;b&gt;여러 드랍 Row를 각각 확률 굴림&lt;/b&gt;하여, &lt;b&gt;마지막으로 공격한 플레이어의 인벤토리로 직접 지급&lt;/b&gt;한다. (아이템 액터 스폰 없음)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 변경&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 데이터 구조 &amp;mdash; &lt;code&gt;RetrieveDataTableTypes.h&lt;/code&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;code&gt;FEnemyDropRow&lt;/code&gt;: &lt;code&gt;ItemTag(FGameplayTag)&lt;/code&gt; &amp;rarr; &lt;b&gt;&lt;code&gt;ItemId(FName)&lt;/code&gt; + &lt;code&gt;ItemCategoryTag(FGameplayTag)&lt;/code&gt;&lt;/b&gt; (인벤토리 키와 일치)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FMonsterDataRow&lt;/code&gt;: &lt;code&gt;DropRow(FName)&lt;/code&gt; &amp;rarr; &lt;b&gt;&lt;code&gt;DropRows(TArray&amp;lt;FName&amp;gt;)&lt;/code&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 드랍 처리 &amp;mdash; &lt;code&gt;DropComponent.h/.cpp&lt;/code&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;code&gt;Initialize(UDataTable*, const TArray&amp;lt;FName&amp;gt;&amp;amp;)&lt;/code&gt; &amp;mdash; 배열 수신&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ProcessDrop()&lt;/code&gt;: 서버 권한 확인 &amp;rarr; 마지막 공격자 폰의 &lt;code&gt;InventoryComponent&lt;/code&gt; 확보 &amp;rarr; &lt;code&gt;DropRows&lt;/code&gt; 각 행을 &lt;code&gt;DropChance&lt;/code&gt;로 독립 굴림 &amp;rarr; &lt;code&gt;AddItem(ItemId, ItemCategoryTag, Quantity)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 적용 지점 &amp;mdash; &lt;code&gt;RetrieveEnemyCharacter.cpp&lt;/code&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;code&gt;InitializeComponents()&lt;/code&gt;에서 &lt;code&gt;DropRows.Num() &amp;gt; 0&lt;/code&gt;일 때 배열 전달&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ProcessDrop()&lt;/code&gt; 호출은 BP_EnemyBase(사망 시)에서 단일 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 버그 수정 &amp;mdash; &lt;code&gt;CombatAttributeSet.cpp&lt;/code&gt; (마지막 공격자 null 문제)&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;증상&lt;/b&gt;: 플레이어가 죽여도 &lt;code&gt;LastDamageInstigator&lt;/code&gt;가 null &amp;rarr; 드랍 대상(인벤토리) 못 찾음. &lt;code&gt;MonsterDied.Killer&lt;/code&gt;도 계속 null이던 잠재 버그.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원인&lt;/b&gt;: 데미지가 &lt;code&gt;IncomingDamage&lt;/code&gt; 메타 어트리뷰트 &amp;rarr; &lt;code&gt;SetHealth()&lt;/code&gt;로 적용됨. 이로 인한 Health 변경은 GE 직접 수정이 아니라서 &lt;code&gt;HandleHealthChanged&lt;/code&gt;의 &lt;code&gt;ChangeData.GEModData&lt;/code&gt;가 null &amp;rarr; instigator 세팅 누락. (&lt;code&gt;NotifyDamageContext&lt;/code&gt;는 정의만 있고 호출처 없음)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수정&lt;/b&gt;: &lt;code&gt;PostGameplayEffectExecute&lt;/code&gt;에서 &lt;code&gt;SetHealth()&lt;/code&gt; 직전에 &lt;code&gt;EffectContext&lt;/code&gt;의 instigator/causer를 &lt;code&gt;HealthComponent-&amp;gt;NotifyDamageContext()&lt;/code&gt;로 전달.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;효과&lt;/b&gt;: 근접&amp;middot;원거리&amp;middot;버스트&amp;middot;화상(DoT) 등 모든 데미지 경로에서 마지막 공격자 확정. &lt;code&gt;Killer&lt;/code&gt; 잠재 버그도 함께 해결.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;미적용 / 참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;보스&lt;/b&gt;: &lt;code&gt;RetrieveBossCharacter::HandleDeathStarted&lt;/code&gt;는 상위 &lt;code&gt;RetrieveEnemyCharacter&lt;/code&gt;를 건너뛰므로 이 드랍 경로를 타지 않음. 보스도 동일 드랍이 필요하면 별도 배선 필요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PlayerState fallback&lt;/b&gt;(DropComponent의 폰 해석 보강)은 instigator가 폰으로 확정되어 불필요 &amp;rarr; 미적용.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프-언리얼</category>
      <author>이시보</author>
      <guid isPermaLink="true">https://brian521.tistory.com/95</guid>
      <comments>https://brian521.tistory.com/95#entry95comment</comments>
      <pubDate>Wed, 17 Jun 2026 22:46:50 +0900</pubDate>
    </item>
  </channel>
</rss>