C++은 데이터와 신호를 노출하고 WBP는 그걸 읽어서 그리기만 함
델리게이트
WBP가 매 프레임 GetThrowItemCount()를 확인(폴링)하는 것보다 바뀔 때만 알림받는 게 효율적
아이템 개수는 자주 안 바뀜 (주울 때, 쓸 때만)
C++ (인벤토리 컴포넌트) WBP
개수 바뀜 방송 -> 구독 중 -> 숫자 다시 그림
아이템 획득 방송 -> 구독 중 -> 알림 팝업
가득 참 방송 -> 구독 중 -> 경고 문구
OnInventoryChanged 하나로는 부족함
- 개수가 늘었는지 줄었는지 구분 안 됨 (획득인지 사용인지)
- 어떤 종류를 얻었는지 모름
알림 팝업은 "투명화를 획득했습니다"처럼 구체적 정보가 필요 -> 별도 신호로 종류를 같이 전달
| OnInventoryChanged | 개수 변경 | 없음 | 슬롯 숫자 갱신 |
| OnItemAcquired | 획득 성공 | 아이템 종류 | "○○ 획득!" 알림 |
| OnInventoryFull | 한도 초과 | 아이템 종류 | "N개까지만" 경고 |
알림을 Client_ RPC로 감싼 이유
ReceiveDrawnItem은 서버에서 실행됨
거기서 바로 OnItemAcquired.Broadcast()하면 서버에서만 방송
-> Client_NotifyItemAcquired RPC로 주운 클라이언트에게 신호를 보내고, 거기서 방송
FDItemInventoryComponent.h - 델리게이트 선언
// 클래스 선언 위에
// 인벤토리 개수가 바뀔 때 (획득/사용 둘 다) - UI 슬롯 숫자 갱신용
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnInventoryChanged);
// 아이템을 획득했을 때 - "투사체 획득!" 알림용 (어떤 종류인지 전달)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnItemAcquired, EFDItemEffect, ItemType);
// 한도 초과로 획득 실패했을 때 - "최대 3개까지만" 경고용
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnInventoryFull, EFDItemEffect, ItemType);
public:
// ===== UI 연동용 이벤트 =====
// WBP에서 이 이벤트들을 바인딩해서 화면을 갱신함
UPROPERTY(BlueprintAssignable, Category = "Item|UI")
FOnInventoryChanged OnInventoryChanged;
UPROPERTY(BlueprintAssignable, Category = "Item|UI")
FOnItemAcquired OnItemAcquired;
UPROPERTY(BlueprintAssignable, Category = "Item|UI")
FOnInventoryFull OnInventoryFull;
// 아이템 획득 알림 (서버 → 주운 클라이언트에게만)
UFUNCTION(Client, Reliable)
void Client_NotifyItemAcquired(EFDItemEffect Which);
// UI가 읽어갈 개수 조회 (BlueprintPure - WBP에서 바인딩)
UFUNCTION(BlueprintPure, Category = "Item")
int32 GetInvisibilityCount() const { return InvisibilityCount; }
UFUNCTION(BlueprintPure, Category = "Item")
int32 GetThrowItemCount() const { return ThrowItemCount; }
protected:
// UI 위젯 클래스 (에디터에서 할당)
UPROPERTY(EditDefaultsOnly, Category = "Item|UI")
TSubclassOf<class UUserWidget> InventoryWidgetClass;
UPROPERTY(EditDefaultsOnly, Category = "Item|UI")
TSubclassOf<class UUserWidget> NotifyWidgetClass;
virtual void BeginPlay() override;
private:
UPROPERTY()
class UUserWidget* InventoryWidget;
UPROPERTY()
class UUserWidget* NotifyWidget;
FDItemInventoryComponent.cpp
#include "Blueprint/UserWidget.h"
void UFDItemInventoryComponent::BeginPlay()
{
Super::BeginPlay();
// 로컬 플레이어 화면에만 UI 생성
AFDHiderCharacter* Hider = Cast<AFDHiderCharacter>(GetOwner());
if (!Hider || !Hider->IsLocallyControlled()) return;
APlayerController* PC = Cast<APlayerController>(Hider->GetController());
if (!PC) return;
// 인벤토리 UI - 계속 떠있음
if (InventoryWidgetClass)
{
InventoryWidget = CreateWidget<UUserWidget>(PC, InventoryWidgetClass);
if (InventoryWidget)
{
InventoryWidget->AddToViewport();
}
}
// 알림 UI - 계속 떠있되 평소엔 숨김 (WBP 안에서 제어)
if (NotifyWidgetClass)
{
NotifyWidget = CreateWidget<UUserWidget>(PC, NotifyWidgetClass);
if (NotifyWidget)
{
NotifyWidget->AddToViewport();
}
}
}
void UFDItemInventoryComponent::OnRep_Inventory()
{
// 개수가 바뀌었다고 방송
OnInventoryChanged.Broadcast();
}
void UFDItemInventoryComponent::Client_NotifyItemAcquired_Implementation(EFDItemEffect Which)
{
// 획득 알림 방송
OnItemAcquired.Broadcast(Which);
}
void UFDItemInventoryComponent::Client_NotifyInventoryFull_Implementation(EFDItemEffect Which)
{
// 한도 초과 방송
OnInventoryFull.Broadcast(Which);
}
ReceiveDrawnItem - 획득 알림 호출 추가
switch (Data->Effect)
{
case EFDItemEffect::Invisibility:
if (InvisibilityCount >= MaxInvisibility)
{
Client_NotifyInventoryFull(EFDItemEffect::Invisibility);
return;
}
++InvisibilityCount;
OnRep_Inventory(); // 서버 수동 호출
Client_NotifyItemAcquired(EFDItemEffect::Invisibility); // 획득 알림
break;
case EFDItemEffect::TaggerStun:
if (ThrowItemCount >= MaxThrowItem)
{
Client_NotifyInventoryFull(EFDItemEffect::TaggerStun);
return;
}
++ThrowItemCount;
OnRep_Inventory();
Client_NotifyItemAcquired(EFDItemEffect::TaggerStun);
break;
default:
break;
}
WBP_ItemInventory

Anchor: 좌하단 (화면 구석에 상시 표시)
개수 표시 - Text 바인딩
Designer에서 InvisibilityCountText 선택 -> Details의 Text 속성 옆 Bind -> Create Binding

WBP_ItemNotify
ShowNotify 커스텀 이벤트
입력: Message (Text), bIsWarning (Boolean)


Event Construct - 델리게이트 바인딩
OnAcquired (입력: ItemType)
OnFull (입력: ItemType)

블루프린트에 할당
BP_FDHiderCharacter -> ItemInventoryComp
- Inventory Widget Class = WBP_ItemInventory
- Notify Widget Class = WBP_ItemNotify

알게된 것
Select 노드가 돌려주는 건 Linear Color
Set Color and Opacity가 받는 건 Slate Color
다른 구조체라 직접 연결 불가
-> Make Slate Color 노드를 중간에 끼움
Slate Color = UI 전용 색상
C++/WBP 경계선
C++이 하는 일:
- 데이터 저장 및 복제 (InvisibilityCount, ThrowItemCount)
- 읽기 통로 제공 (GetInvisibilityCount() - BlueprintPure)
- 사건 방송 (델리게이트)
- 위젯 생성 및 화면 부착
WBP가 하는 일:
- 레이아웃, 아이콘, 색상
- 데이터를 읽어서 그리기
- 방송 구독
앵커는 화면 크기가 바뀌어도 이 위젯이 어디에 붙어있을지를 정하는 기준점
앵커를 좌하단으로 잡으면 화면이 커지든 작아지든 항상 좌하단 기준으로 위치가 계산됨
- Anchor = 화면의 어느 지점을 기준으로
- Alignment = 위젯의 어느 부분을 그 지점에 맞출지
예를 들어 크로스헤어를 화면 정중앙에 두려면:
- Anchor = 정중앙
- Alignment = (0.5, 0.5) <- 위젯의 중심을 기준점에 맞춤
'프로젝트 > Funny Or Die (멀티플레이 술래잡기 게임)' 카테고리의 다른 글
| 타이머 UI 시스템 구축 (0) | 2026.07.17 |
|---|---|
| [트러블슈팅] EOS 세션 연동 (0) | 2026.07.15 |
| 조준 표시 개선 (디버그 구체 -> 데칼 + 크로스헤어) (0) | 2026.07.13 |
| 소리 아이템 구현 (0) | 2026.07.13 |
| 투사체 명중 시 술래 스턴 (0) | 2026.07.13 |
