콘텐츠로 이동

UE5 Gameplay Message Router — 이벤트 브로드캐스트

Gameplay Message Router(UGameplayMessageSubsystem)는 UE5의 GameFeatures 플러그인에 포함된 타입 안전 메시지 브로드캐스트 시스템입니다. 발신자와 수신자가 서로를 알 필요 없이 GameplayTag 채널로 메시지를 주고받아 컴포넌트 간 결합을 줄입니다.


Edit > Plugins > GameFeatures → 활성화
Edit > Plugins > ModularGameplay → 활성화
.uproject 또는 .Build.cs:
Build.cs
PublicDependencyModuleNames.AddRange(new[]
{
"GameplayMessageRuntime", // 핵심 모듈
"GameplayTags",
});

Game.Message.Player.Died
// 채널 태그 정의 (GameplayTags.ini 또는 코드)
// Game.Message.Item.Picked
USTRUCT(BlueprintType)
struct FPlayerDiedMessage
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite)
APlayerState* PlayerState = nullptr;
UPROPERTY(BlueprintReadWrite)
FVector DeathLocation;
UPROPERTY(BlueprintReadWrite)
AActor* Killer = nullptr;
UPROPERTY(BlueprintReadWrite)
int32 RemainingLives = 0;
};
USTRUCT(BlueprintType)
struct FItemPickedMessage
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite)
FGameplayTag ItemTag;
UPROPERTY(BlueprintReadWrite)
int32 Quantity = 1;
UPROPERTY(BlueprintReadWrite)
AActor* Picker = nullptr;
};

#include "GameFramework/GameplayMessageSubsystem.h"
// 플레이어 사망 메시지 발송
void AMyCharacter::Die(AActor* Killer)
{
// ... 사망 처리 로직 ...
// 메시지 브로드캐스트
UGameplayMessageSubsystem& MsgSys =
UGameplayMessageSubsystem::Get(GetWorld());
FPlayerDiedMessage Msg;
Msg.PlayerState = GetPlayerState();
Msg.DeathLocation = GetActorLocation();
Msg.Killer = Killer;
Msg.RemainingLives = LivesComponent->GetLives();
MsgSys.BroadcastMessage(
TAG_GameMessage_Player_Died, // FGameplayTag
Msg);
}
// 아이템 획득 메시지
void APickup::OnPickedUp(AActor* Picker)
{
UGameplayMessageSubsystem& MsgSys =
UGameplayMessageSubsystem::Get(GetWorld());
FItemPickedMessage Msg;
Msg.ItemTag = ItemTag;
Msg.Quantity = StackSize;
Msg.Picker = Picker;
MsgSys.BroadcastMessage(TAG_GameMessage_Item_Picked, Msg);
Destroy();
}

#include "GameFramework/GameplayMessageSubsystem.h"
UCLASS()
class AGameHUD : public AHUD
{
GENERATED_BODY()
private:
FGameplayMessageListenerHandle _deathHandle;
FGameplayMessageListenerHandle _itemHandle;
public:
virtual void BeginPlay() override
{
Super::BeginPlay();
RegisterListeners();
}
virtual void EndPlay(const EEndPlayReason::Type Reason) override
{
// 반드시 해제 (메모리 누수 방지)
UGameplayMessageSubsystem& MsgSys =
UGameplayMessageSubsystem::Get(GetWorld());
MsgSys.UnregisterListener(_deathHandle);
MsgSys.UnregisterListener(_itemHandle);
Super::EndPlay(Reason);
}
private:
void RegisterListeners()
{
UGameplayMessageSubsystem& MsgSys =
UGameplayMessageSubsystem::Get(GetWorld());
// 플레이어 사망 수신
_deathHandle = MsgSys.RegisterListener<FPlayerDiedMessage>(
TAG_GameMessage_Player_Died,
this,
&AGameHUD::OnPlayerDied);
// 아이템 획득 수신
_itemHandle = MsgSys.RegisterListener<FItemPickedMessage>(
TAG_GameMessage_Item_Picked,
this,
&AGameHUD::OnItemPicked);
}
void OnPlayerDied(FGameplayTag Channel,
const FPlayerDiedMessage& Msg)
{
// HUD에 사망 알림 표시
ShowDeathNotification(Msg.PlayerState, Msg.Killer);
UpdateLivesDisplay(Msg.RemainingLives);
}
void OnItemPicked(FGameplayTag Channel,
const FItemPickedMessage& Msg)
{
ShowItemPickupFeedback(Msg.ItemTag, Msg.Quantity);
}
};

BP에서 발신:
- GameplayMessageSubsystem > Broadcast Message 노드
- 채널: GameplayTag 선택
- 메시지: 구조체 핀
BP에서 수신:
- GameplayMessageSubsystem > Register Listener 노드
- On Message 이벤트 핀 연결
- Unregister Listener를 EndPlay에서 호출

6. 메시지 필터링 — 채널 매칭 모드

섹션 제목: “6. 메시지 필터링 — 채널 매칭 모드”
// 정확한 태그 매칭 (기본)
MsgSys.RegisterListener<FMsg>(
TAG_Game_Message_Player_Died,
this, &AClass::Handler,
EGameplayMessageMatch::ExactMatch);
// 부분 매칭: TAG_Game_Message_Player 채널로
// TAG_Game_Message_Player_Died 도 수신
MsgSys.RegisterListener<FMsg>(
TAG_Game_Message_Player,
this, &AClass::Handler,
EGameplayMessageMatch::PartialMatch);
// 예: 모든 Game.Message 수신
MsgSys.RegisterListener<FGenericMessage>(
TAG_Game_Message,
this, &AClass::OnAnyMessage,
EGameplayMessageMatch::PartialMatch);

항목Delegate/EventGameplay Message Router
결합도직접 참조 필요태그만 알면 됨
타입 안전강력구조체 기반
성능빠름약간 오버헤드
사용 범위1:1, 1:NN:N 브로드캐스트
BP 지원제한적완전 지원

8. UObject에서 자동 해제 — RegisterListenerHandleWrapped

섹션 제목: “8. UObject에서 자동 해제 — RegisterListenerHandleWrapped”

핸들을 수동으로 UnregisterListener하는 것을 잊는 실수를 방지하려면 FGameplayMessageListenerHandle의 RAII 래퍼를 활용합니다.

// WeakPtr 기반 자동 해제 패턴
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
// TArray에 핸들 보관 — EndPlay 없이도 AActor 소멸 시 자동 해제됨
TArray<FGameplayMessageListenerHandle> MessageHandles;
public:
virtual void BeginPlay() override
{
Super::BeginPlay();
UGameplayMessageSubsystem& MsgSys =
UGameplayMessageSubsystem::Get(GetWorld());
// RegisterListener가 반환하는 핸들을 보관
MessageHandles.Add(
MsgSys.RegisterListener<FPlayerDiedMessage>(
TAG_GameMessage_Player_Died,
this, &AMyActor::OnPlayerDied));
MessageHandles.Add(
MsgSys.RegisterListener<FItemPickedMessage>(
TAG_GameMessage_Item_Picked,
this, &AMyActor::OnItemPicked));
}
virtual void EndPlay(const EEndPlayReason::Type Reason) override
{
UGameplayMessageSubsystem* MsgSys =
UGameplayMessageSubsystem::GetPtr(GetWorld());
if (MsgSys)
{
for (auto& Handle : MessageHandles)
MsgSys->UnregisterListener(Handle);
}
Super::EndPlay(Reason);
}
void OnPlayerDied(FGameplayTag, const FPlayerDiedMessage& Msg) { /* ... */ }
void OnItemPicked(FGameplayTag, const FItemPickedMessage& Msg) { /* ... */ }
};

// 임시 수신자: 람다로 간단히 등록
FGameplayMessageListenerHandle Handle =
MsgSys.RegisterListener<FPlayerDiedMessage>(
TAG_GameMessage_Player_Died,
[this](FGameplayTag Channel, const FPlayerDiedMessage& Msg)
{
UE_LOG(LogTemp, Log, TEXT("Player died at %s"),
*Msg.DeathLocation.ToString());
});
// 한 번만 수신 후 해제 패턴
FGameplayMessageListenerHandle* HandlePtr = new FGameplayMessageListenerHandle();
*HandlePtr = MsgSys.RegisterListener<FPlayerDiedMessage>(
TAG_GameMessage_Player_Died,
[this, HandlePtr, &MsgSys](FGameplayTag, const FPlayerDiedMessage& Msg)
{
OnFirstPlayerDeath(Msg);
MsgSys.UnregisterListener(*HandlePtr);
delete HandlePtr;
});

Gameplay Message Router는 UI ↔ 게임플레이, 시스템 ↔ 시스템 간 통신에 이상적입니다. FGameplayMessageListenerHandle을 반드시 EndPlay에서 해제하고, 채널 태그는 Game.Message.Domain.Event 형식의 계층 구조로 설계하세요. 성능이 중요한 매 틱 데이터 동기화는 델리게이트를 사용하고, 드물게 발생하는 게임 이벤트 알림에 Message Router를 활용하세요.

설계 지침:

  • 채널 태그: Game.Message.{Domain}.{Event} 계층 구조로 설계
  • PartialMatch로 도메인 전체 수신, ExactMatch로 특정 이벤트만 수신
  • 매 프레임 발생하는 데이터 동기화는 델리게이트/폴링이 효율적
  • 게임 이벤트(사망, 획득, 레벨업)처럼 드물게 발생하는 알림에 최적