この記事はK3 Advent Calendar 2025の17日目です。
ゲーム制作ではイベントが発生済みかどうかを管理したい場面が多々あります。
・このアイテムは既に入手済み?
・このNPCと会話済み?
・このステージはクリア済み? などなど。
規模の小さいゲームであればイベント管理クラスを作って、単に各イベント毎にフラグ用の変数を作って管理すれば良いだけなんですが、少し規模が大きくなるとフラグの数がとんでもないことになります。
SubsystemとGameplayTagを活用することで、通常のフラグを使うよりはるかにわかりやすくイベントの発生状態を管理できます!
イベント管理用Subsystemの作成
まずは発生したイベントを管理するためのSaveGameとSubsystemを作成します。
SaveGameの方は、名前をEventSaveGameとしておきます。
Subsystemの方ですが、レベル単位でイベントの発生状態を管理したいならWorldSubsystem、ゲーム全体でイベントの発生状態を管理したいならGameInstanceSubsystemを継承してください。
ここでは、名前はEventManagerSubsystemとしておきます。
では、以下のようなコードを書いていきます。
#pragma once
#include "CoreMinimal.h"
#include "GameplayTags.h"
#include "GameFramework/SaveGame.h"
#include "EventSaveGame.generated.h"
UCLASS()
class MYPROJECT_API UEventSaveGame : public USaveGame
{
GENERATED_BODY()
public:
//発生済みのイベントタグを格納するコンテナ
UPROPERTY(BlueprintReadOnly)
FGameplayTagContainer ExcutedEventTags;
};
※EventSaveGame.cppはデフォルトから変更が無いため割愛
#pragma once
#include "CoreMinimal.h"
#include "GameplayTags.h"
#include "EventSaveGame.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "EventManagerSubsystem.generated.h"
UCLASS()
class MYPROJECT_API UEventManagerSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
virtual void Initialize(FSubsystemCollectionBase& Collection);
public:
//発生済みイベントタグの情報を保存するSaveGame
UPROPERTY(BlueprintReadOnly)
UEventSaveGame* EventSaveGame;
//イベントが発生済みか確認する関数
UFUNCTION(BlueprintCallable)
bool CheckEventExcuted(FGameplayTag EventTag);
//発生済みイベントを登録する関数
UFUNCTION(BlueprintCallable)
void RegisterExcutedEventTag(FGameplayTag NewEventTag);
//発生済みイベントを取り除く関数
UFUNCTION(BlueprintCallable)
void RemoveExcutedEventTag(FGameplayTag EventTag);
};
#include "EventManagerSubsystem.h"
#include "Kismet/GameplayStatics.h"
void UEventManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
if (UGameplayStatics::DoesSaveGameExist("EventData", 0))
{
EventSaveGame = Cast<UEventSaveGame>(UGameplayStatics::LoadGameFromSlot("EventData", 0));
}
else
{
EventSaveGame = Cast<UEventSaveGame>(UGameplayStatics::CreateSaveGameObject(UEventSaveGame::StaticClass()));
}
}
bool UEventManagerSubsystem::CheckEventExcuted(FGameplayTag EventTag)
{
return EventSaveGame->ExcutedEventTags.HasTag(EventTag);
}
void UEventManagerSubsystem::RegisterExcutedEventTag(FGameplayTag NewEventTag)
{
EventSaveGame->ExcutedEventTags.AddTag(NewEventTag);
UGameplayStatics::SaveGameToSlot(EventSaveGame, "EventData", 0);
}
void UEventManagerSubsystem::RemoveExcutedEventTag(FGameplayTag EventTag)
{
EventSaveGame->ExcutedEventTags.RemoveTag(EventTag);
UGameplayStatics::SaveGameToSlot(EventSaveGame, "EventData", 0);
}
コードについては見たままです。
発生済みのイベントと紐づいたGameplayTagを、EventSavegameのGameplayTagContainerに格納することで、イベントが発生済みかどうかを簡単に確認可能なようにしています。
使い方
一例として、1度拾ったら2度と出現しないアイテムの実装を行います。
まず、Actorを継承したBP_Itemを作り、変数としてGameplayTag型のPickupEventTagを用意します。InstancedEditableにしておきましょう。

次に、適当な当たり判定を用意してそこにプレイヤーが当たったらPickupを行うようにBPを組みます。

Pickupイベント内で、アイテムを拾った際の処理を記述した後、EventManagerSubsystemにPickupEventTagを追加するようにします。最後にDestoryActorでアイテムを消滅させます。

そして、拾った次回以降のプレイでアイテムが出現しないように、Beginplayで、イベントが発生済みかどうかを確認して、発生済みなら即座にDestoryActorでアイテムを消滅させるようにします。

では、実際にアイテムを配置して拾ってみましょう。
レベルにBP_Itemを3つ置き、PickupEventTagをそれぞれEvent.ItemPickup.Test.1、Event.ItemPickup.Test.2、Event.ItemPickup.Test.3として実行します。
まずEvent.Item.Test.1を設定したものだけ拾って、実行を終了します。もう一度実行するとそれだけが消えているのがわかると思います。セーブデータを消去しない限り何度実行しなおしても2度と出現しません。
この挙動自体は普通にフラグを大量作成することでも実現可能ですが、このやり方をすることでアイテムやイベントが増えてもイベント管理クラスのコードを変更してフラグを増やす必要が無いです。さらに、GameplayTagは階層的に管理することができるため、フラグの乱立に比べてどの場面でどのようなイベントがあるのかが非常にわかりやすいです。

みなさんもGameplayTagとSubsystemを活用して、快適なイベント発生管理ライフを送りましょう!
