アイテムを拾ったら、所持数を増やし、画面に知らせ、効果音も鳴らしたい。すべてをアイテムのBlueprintへ書くと、反応を追加するたびに、アイテムの取得処理も編集することになります。
Event Dispatcher を使うと、アイテムは「拾われた」と知らせ、それを受けた各担当が反応を決められます。ベルを鳴らす人と、その音を聴いて動く人を分けるイメージです。
他のエンジンでいうと: Godot の signal 、Unity の
UnityEventや C# のeventに当たります。知らせる側が、受け取る側を知らなくてよい点も同じです。
今回は「ベルを作る・登録する・鳴らす」の順に組み、取得数と表示を更新します。さらに表示側だけ受信を止めて、通知を送る処理を変えずに、受け取り方を変えられる ことを確かめましょう。
この記事でわかること
- Dispatcherの作成、受信登録のBind、通知を送るCall
- 通知にアイテム名と個数を載せる方法
- 1回の取得で、個数管理と表示を別々に動かす実践
- Unbindで受信を止め、再びBindする方法
Event Dispatcher:出来事と反応を分ける
Dispatcherは、出来事を受け取るイベントの登録先です。アイテムに OnItemPickedUp というDispatcherを作り、取得したときに呼ぶと、登録しておいたイベントが動きます。
| 操作 | 意味 | ベルに例えると |
|---|---|---|
| Dispatcherを作る | 通知の名前と、渡す値を決める | ベルを用意する |
| Bind | この通知で動かすイベントを登録する | どのベルを聴くか決める |
| Call | 登録されたイベントへ通知する | ベルを鳴らす |
| Unbind | 指定したイベントの登録を外す | そのベルを聴くのをやめる |

アイテム側に、表示や音の具体的な処理を並べなくてよくなるのが利点です。新しい反応を同じ通知へ登録すれば、取得処理を直さずに追加できます。
ただし、つながりがなくなるわけではありません。受信登録には 発信元の個体を指す参照 が必要です。また、受け手は通知の名前と、渡される値の型を合わせます。こうして、相手の内部の作りに頼る部分を減らす設計を、疎結合と呼びます(→ 通信3方式の使い分け)。
実践:拾ったアイテムの数と表示を更新する
1個のアイテムに触れると、Gemを3個取得したことを通知します。それを個数管理と表示の2つのActorが受け取り、次の結果を出します。
| 受け取る担当 | 起こること |
|---|---|
| BP_InventoryManager | TotalCountが0から3になり、Print Stringで確認できる |
| BP_PickupNotice | ワールド内の文字がWaiting...からGem x3へ変わる |
今回は、所持品一覧や専用HUDを作る前に、通知に載せた値が別々の担当へ届くかを試します。

① 拾えるアイテムを用意する

Third PersonのBlueprintプロジェクトを使います。Variantがある版はNoneにし、テンプレートの歩けるレベルを開きます。
Actorを親に BP_Item を作ります。ComponentsへSphere Collisionを追加し、DefaultSceneRootの上へドラッグしてRootにします。その子へStatic Meshを追加し、エンジン標準のSphereを選びます。
| 部品 | 設定 |
|---|---|
| Sphere Collision | Sphere Radius:60、Collision Enabled:Query Only |
| Sphere Collisionの応答 | PawnだけOverlap、ほかはIgnore |
| Generate Overlap Events | オン |
| Static Mesh | Sphere、相対位置0、ScaleはXYZすべて0.4 |
| MeshのCollision Presets | NoCollision |
| MeshのSimulate Physics | オフ |
Sphere CollisionのCollision PresetsをCustomにすると、Collision Enabledと各チャンネルへの応答を設定できます。
標準Sphereが見つから なければ、アセット選択欄でShow Engine Contentを有効にし、EngineのBasicShapesから選びます。
Overlap は、相手を押し返す代わりに、領域の重なりを検出する仕組みです。プレイヤー側のCapsule ComponentもGenerate Overlap Eventsをオンにします。今回は球の見た目より少し広い範囲へ触れると拾えます。
Variablesへ次の2つを作り、Compileしてから既定値を入力します。
| 変数 | 型 | 既定値 |
|---|---|---|
| ItemID | Name | Gem |
| Quantity | Integer | 3 |
ItemIDはアイテムを見分ける名前、Quantityは今回拾う個数です。Compile・保存し、レベルへBP_Itemを1個置きます。ActorのScaleは1、中心は床の表面から60cm上を目安にし、プレイヤー開始位置から500cmほど離します。開始時から重ならないようにしてください。
② ベルを作る:通知に載せる値を決める
BP_ItemのMy Blueprint → Event Dispatchersの「+」から、OnItemPickedUp を作ります。選択したDispatcherのDetails → Inputsへ、次の2つを追加します。
| 入力名 | 型 | 受け手へ伝えること |
|---|---|---|
| ItemID | Name | 何を拾ったか |
| Quantity | Integer | 何個拾ったか |
この Inputsは、通知を送るときに一緒に渡す値 です。先ほどの変数がアイテム自身の設定、DispatcherのInputsが通知に載せる項目、という違いがあり ます。
今回の受け手が必要なのは名前と個数なので、それだけを渡します。拾ったActorは後で消えますが、通知で受け取った値を使えば、消えたアイテムへ問い合わせ直す必要はありません。
③ 個数管理のActorを作り、受信登録する
Actorを親に BP_InventoryManager を作ります。見た目の部品は要りません。次の変数を追加します。
| 変数 | 型 | 設定 |
|---|---|---|
| TargetItem | BP_ItemのObject Reference | Instance Editableをオン |
| TotalCount | Integer | 既定値0 |
Object Reference は、相手の個体を指す値です。Instance Editableをオンにすると、レベルへ置いたManagerのDetailsから相手を指定できます。
Event GraphでBeginPlayから、白い実行ピンのある Is Valid へつなぎ、Input ObjectにGet TargetItemを入れます。Is Validは、その相手が存在して使えるかを確認します。
Get TargetItemの青いピンから Bind Event to OnItemPickedUp を作り、Is Validの成功側をBindの白い入力へつなぎます。BindのTargetはTargetItemです。Is Not Valid側は何もしません。
Bindの 赤いEventピン からドラッグし、「Add Custom Event for Dispatcher」で HandleItemPickup を作ります。Dispatcherに合わせてItemIDとQuantityの入力を持つイベントが作られ、赤い線でBindへつながります。
これで「このアイテムの通知が来たら、HandleItemPickupを呼ぶ」という登録になります。白い線は登録を実行する順番、赤い線は登録するイベントを示しています。

図はIs Validの成功側から先を示しています。左上の「Is Validの成功から」は接続元の注記です。
④ 通知を受けたら、個数を足す
HandleItemPickupの白い出力からSet TotalCountへつなぎます。設定する値は、Integerの加算で Get TotalCount + イベントのQuantity にします。

ここで使うのは、先ほどBindへつないだHandleItemPickupです。別のイベントを作り直す必要はありません。
Setの後ろへPrint Stringをつなぎ、Durationを10秒にします。文字列はAppendで、Aを TotalCount: 、Bを更新後のTotalCountのString変換にします。
これで3個という通知を受けたら、0に3を足して「TotalCount: 3」と表示できます。ここでは取得した総数だけを扱い、アイテム別の一覧はインベントリの記事へ進めます。
Compile・保存してManagerをレベルへ1個置き、DetailsのTarget Itemへ、配置済みのBP_Itemを指定します。参照の変数を作っただけでは相手は入りません。割り当てまで済ませます。
⑤ 表示担当も、同じ通知を受け取る
Actorを親に BP_PickupNotice を作り、Text Render Componentを追加して名前を NoticeText にします。Text Renderは、ワールド内に文字を表示する部品です。
Textを Waiting...、World Sizeを40、Text Render Colorを濃い青にします。Compile・保存してレベルへ置き、床から150cmほどの高さで、Play開始位置から読める向きに回転させます。文字の裏側を見ている場合は、Actorの向きを調整してください。
BP_PickupNoticeにも、BP_ItemのObject Reference型で TargetItem を作り、Instance EditableをオンにしてCompile・保存します。レベルのNoticeのDetailsから、Managerと同じBP_Itemを指定します。
Event GraphへCustom Event StartListening を作り、そこからManagerと同じ Is Valid → Bind Event to OnItemPickedUp を組みます。Input ObjectとTargetはGet TargetItemです。
Bindの赤いEventピンから HandleNotice を作ります。BeginPlayからは StartListening(Target: self) を呼びます。受信登録を名前付きの入口へまとめておくと、後で登録し直すときにも使えます。
HandleNoticeの白い出力から、NoticeTextの参照を使って Set Text を作ります。Targetは NoticeText で、Actorのselfではありません。Valueには、次のFormat Textの結果を入れます。
| Format Textの設定・入力 | 値 |
|---|---|
| Format | {ItemID} x{Quantity} |
| ItemID | HandleNoticeで受けたItemID |
| Quantity | HandleNoticeで受けたQuantity |
| Resultの接続先 | Set TextのValue |
Format Text は、{名前} の部分へ値を差し込んで文字を作るノードです。Format欄へ入力すると対応するピンが増えます。Gemと3を受け取れば、結果は「Gem x3」になります。

この表示はManagerのTotalCountを読んでいません。アイテムから受け取った情報だけで表示できるので、2つの受信イベントのどちらが先に動くかに依存しません。
⑥ 拾った瞬間にベルを鳴らす
BP_Itemへ戻り、Sphere CollisionのDetails → Eventsから On Component Begin Overlap を追加します。
Other Actorから Cast To BP_ThirdPersonCharacter を作り、Overlapの白い実行出力もCastへつなぎます。Other Actorは領域に入ってきた相手、Castはその相手を指定Classとして扱えるかの確認です。今回はテンプレートの操作キャラクターに反応させます。

Castの成功側から次の順につなぎます。Cast Failed側は何もしません。
- DoOnce。Start Closedはオフ、Resetは未接続。
- Call OnItemPickedUp。Targetはself、ItemIDへGet ItemID、QuantityへGet Quantityを渡す。
- Destroy Actor。Targetはself。

DoOnceで取得を1回にし、通知を送ってから、アイテムを消します。Callでは、取得数を足す処理や文字を変える処理を直接呼んでいません。それらは登録した側が受け持ちます。
⑦ 拾った結果を確認する
3つのBlueprintをCompile・保存してPlayします。ゲーム画面をクリックし、歩いてアイテムへ近づきます。
| 状態 | 期待する結果 |
|---|---|
| 拾う前 | アイテムが見え、表示はWaiting... |
| 球へ触れる | アイテムが消え、TotalCount: 3と出る |
| 同じ取得の通知 | NoticeTextがGem x3へ変わる |
| その場所をもう一度通る | 追加の取得は起きない |
何も起きなければ、Overlap直後のPrintで重なりを検出できているか確認します。アイテムは消えるのに反応がなければ、各TargetItemの割り当て、BeginPlayからBindまでの白線、赤いEvent接続を見ます。
Printは出るのに文字が変わらない場合は、Notice側のBindとSet TextのTargetを調べます。エディタで文字も見えないなら、まず向き・位置・World Sizeを確認してください。
次にPlayを止め、BP_ItemのQuantityを5へ変えてみます。再び拾うとTotalCount: 5、Gem x5になれば、通知に載せた値を両方が使えています。確認後はQuantityを3へ戻します。
Unbind:表示側だけ、受信を止めてみる
表示の監視をやめたい、別の相手を表示したい。そんなときに、今までの通知を受け取る登録を Unbind で外します。Actorを消さなくても、受信だけを止められます。

同じイベントの登録を外す
BP_PickupNoticeの同じEvent Graphへ、Custom Event StopListening を追加します。
- StopListeningからIs Validへつなぎ、Input ObjectにGet TargetItemを入れる。
- Get TargetItemから
Unbind Event from OnItemPickedUpを作る。 - Is Validの成功側をUnbindの白い入力へつなぐ。TargetはTargetItem。
- すでに作ったHandleNoticeの赤いEvent出力 を、Unbindの赤いEvent入力へもつなぐ。
新しい同名イベントを作るのではなく、登録したのと同じHandleNoticeを指定します。赤い出力からBindとUnbindの2か所へ線を伸ばして構い ません。Unbind側のIs Not Validは何もしません。

図では解除に使う線だけを示しています。HandleNoticeからSet Textへつないだ白線や値の線は、そのまま残してください。
Uで止め、Bで登録し直す
レベルに置いたBP_PickupNoticeを選び、Level Blueprintで右クリック →「Create a reference to[名前]」を選びます。その参照から、次の呼び出しを作ります。
| キーのPressed | 呼ぶCustom Event | Target |
|---|---|---|
| U | StopListening | 配置したBP_PickupNotice |
| B | StartListening | 同じBP_PickupNotice |
Compile・保存して、次の2回を別々のPlayで試します。
| 操作 | 個数管理 | 文字表示 |
|---|---|---|
| 拾う前にUを押し、その後に拾う | TotalCount: 3 | Waiting...のまま |
| 新しくPlayし、拾う前にU→Bを押してから拾う | TotalCount: 3 | Gem x3 |
アイテムの取得処理を変えず、表示側の反応だけを止めたり戻したりできました。Manager側はUnbindしていないため、どちらも取得数を受け取ります。
最初の試験で拾った後にBを押しても、 過去の通知は再送されません。今回はアイテム自体も消えているので、StartListening内のIs Validで止まります。やり直すときは、Stopして新しくPlayしてください。
解除の目的は「いつまで受け取るか」を決めること
例えば、UIが監視するアイテムをAからBへ替えるときは、Aの通知をUnbindしてから、BへBindします。Aがまだ存在していても、古い相手の通知で表示が変わるのは困るためです。
また、WidgetをRemove from Parentで画面から外すことと、そのWidgetのオブジェクトが消えることは同じではありません。画面を閉じたら受信も止めたいなら、その場面で解除する形を考えます。
BlueprintのDispatcherについて「Unbindしないと、必ず破棄済みの相手へ届いてクラッシュする」と覚える必要はありません。その相手の通知を、今も受け取りたいか を基準に、登録と解除の場所を決めます。
おまけ:先に知っておくと良いこと
受信者を増やすときは、アイテムを直さなくてよい
例えば音の担当を追加するなら、新しいActorで同じTargetItemへBindし、受信イベントからPlay Sound 2Dで用意した効果音を再生できます。BP_ItemのCallはそのままです。
実際の所持品一覧やHUDも、今回の個数管理・文字表示と同じ場所へ反応を追加できます(→ インベントリ、UMG)。
通知の順番を前提にしない
複数の受信イベントが、Bindした順番どおりに動くとは限りません。今回のNoticeは通知のItemIDとQuantityを直接使い、Managerの処理が終わるのを待つ必要がない形にしました。
「まず所持数を更新し、その確定値をUIへ知らせたい」なら、所持数を管理する側が更新を終えてから、その値を載せた別の通知を送ると順序が明確になります。
同じBindを繰り返したら?
同じDispatcherへ同じイベントを繰り返しBindしても、登録は1つです。反応が2回出るときは、別の受信イベントも登録していないか、発信側のCallが2回実行されていないかを調べます。
今回のDoOnceは発信回数を1回にするためのものです。Bindの重複対策とは役割が違います。
Unbind Allは、ほかの受信者も外す
Unbind Eventは指定したイベントを外します。Unbind Allは、そのDispatcherの登録をまとめて外します。今回の表示だけを止める操作でUnbind Allを使うと、Managerの受信まで外してしまいます。
アイテムが増えたら、通知の置き場所を考える
今は1個のアイテムを、2つの担当から指定しました。実行中に多数のアイテムが生まれるゲームでは、すべてへ個別に登録するより、取得情報をまとめる担当へ通知を集める方法があります。

例えば1つの レベル内なら、GameStateや所持品を管理するComponentへOnAnyItemPickedUpを置き、アイテムはそちらへ情報を渡します。UIは個々のアイテムではなく、その共通の発信元へBindします。
GameStateとGameInstanceでは存続する範囲が違います。「誰もが知っている場所」だけでなく、その情報をいつまで使うか も考えて選びます(→ Game Framework、Componentの再利用)。
通知の名前は、起きたことを表す
OnItemPickedUp、OnHealthChangedのように出来事を名前にすると、表示・音・実績などが同じ通知を使いやすくなります。UpdateUIのように特定の反応を名前にすると、その用途専用の入口に見えます。
なお、UMGのButtonで使うOnClickedも、押されたことを知らせる同じ仲間です。自作Dispatcherも、受け手に反応を登録するという点では身近な仕組みです。
C++との対応を知りたい人へ
BlueprintのEvent Dispatcherに対応するC++の仕組みは、ダイナミック・マルチキャストデリゲート です。「複数の受け手の処理を登録し、まとめて呼べるもの」と捉えるところから始められます。

| Blueprint側の操作 | C++側で使うもの |
|---|---|
| Dispatcherの定義 | DECLARE_DYNAMIC_MULTICAST_DELEGATE系の宣言 |
| Blueprintへ公開 | UPROPERTYのBlueprintAssignable |
| Bind | AddDynamic |
| Call | Broadcast |
| Unbind | RemoveDynamic |
本記事の実践にC++は必要ありません。C++側から通知を公開したくなったら、BlueprintからC++への第一歩と、公式のDynamic Delegatesへ進めます。
まとめ
BP_ItemのOnItemPickedUpへ、個数管理と表示を登録しました。アイテムは名前と個数を知らせ、受け手がそれぞれ加算と表示を行います。表示だけをUnbindしても、アイテムと個数管理は同じように動きました。
通知を作るときは、「何が起きたか」「どの値を渡すか」「誰がいつまで受け取るか」を揃えると、処理の分担を追いやすくなります。相手へ共通の操作を頼む方法は、Blueprint Interfaceで扱います。