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) <- 위젯의 중심을 기준점에 맞춤

+ Recent posts