UE5 Gameplay Message Router — 이벤트 브로드캐스트
Gameplay Message Router(UGameplayMessageSubsystem)는 UE5의 GameFeatures 플러그인에 포함된 타입 안전 메시지 브로드캐스트 시스템입니다. 발신자와 수신자가 서로를 알 필요 없이 GameplayTag 채널로 메시지를 주고받아 컴포넌트 간 결합을 줄입니다.
1. 플러그인 활성화
섹션 제목: “1. 플러그인 활성화”Edit > Plugins > GameFeatures → 활성화Edit > Plugins > ModularGameplay → 활성화
.uproject 또는 .Build.cs:PublicDependencyModuleNames.AddRange(new[]{ "GameplayMessageRuntime", // 핵심 모듈 "GameplayTags",});2. 메시지 구조체 정의
섹션 제목: “2. 메시지 구조체 정의”// 채널 태그 정의 (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;};3. 메시지 발신
섹션 제목: “3. 메시지 발신”#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();}4. 메시지 수신 — C++
섹션 제목: “4. 메시지 수신 — C++”#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); }};5. Blueprint 연동
섹션 제목: “5. Blueprint 연동”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);7. 기존 델리게이트와 비교
섹션 제목: “7. 기존 델리게이트와 비교”| 항목 | Delegate/Event | Gameplay Message Router |
|---|---|---|
| 결합도 | 직접 참조 필요 | 태그만 알면 됨 |
| 타입 안전 | 강력 | 구조체 기반 |
| 성능 | 빠름 | 약간 오버헤드 |
| 사용 범위 | 1:1, 1:N | N: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) { /* ... */ }};9. 람다 리스너
섹션 제목: “9. 람다 리스너”// 임시 수신자: 람다로 간단히 등록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로 특정 이벤트만 수신- 매 프레임 발생하는 데이터 동기화는 델리게이트/폴링이 효율적
- 게임 이벤트(사망, 획득, 레벨업)처럼 드물게 발생하는 알림에 최적