Unityで学ぶObserver(オブザーバー)パターン入門
- 1. HP変更通知システムを作ってみよう
- 2. 今回のテーマ:「変化を通知する」
- 3. 今回作るもの
- 4. 完成イメージ
- 5. 新規Unityプロジェクトを作成する
- 6. シーンを準備する
- 7. UIを作成する
- 8. Player.csスクリプトを作成する
- 9. スクリプトをPlayerオブジェクトへ追加する
- 10. Playして動作確認しよう
- 11. 次はUI更新を実装する
- 12. 再生してみよう
- 13. ここが最重要ポイント
- 14. さらに追加してみよう
- 15. 再生してみよう
- 16. ここがObserverの強み
- 17. Observerパターンは万能ではない
- 18. Observerを使うべき場面
- 19. Unityでありがちな失敗
- 20. UnityでObserverが重要な理由
- 21. 今回学んだ重要キーワード
- 22. まとめ
HP変更通知システムを作ってみよう
今回はUnityで「HPが減る→UIが更新される」という仕組みを作りながら、Observer(オブザーバー)パターンを学びます。
ObserverパターンはUnityで非常によく使われる設計パターンです。特にHPバー・スコア表示・ダメージ演出・サウンド再生・クエスト更新など、さまざまな場面で活躍します。今回は「変化を通知する」という考え方を、実際に手を動かしながら理解していきましょう。
今回のテーマ:「変化を通知する」
という考え方を、
実際にUnityで手を動かしながら理解していきます。
今回作るもの
スペースキーを押すと:
- プレイヤーHPが減る
- UI表示が更新される
というシステムです。
完成イメージ
スペースキー押下
↓
Player の HP が減る
↓
OnHpChanged 通知
↓
UI が更新される
新規Unityプロジェクトを作成する
Unity Hubから:
3D Core
で新規作成してください。
プロジェクト名は:
ObserverSample
などでOKです。
シーンを準備する
Hierarchyで右クリック:
Create Empty
を押します。
名前を:
Player
に変更します。
UIを作成する
Hierarchyで右クリック:
UI(Canvas) → Text – TextMeshPro
を選択します。
初回は:
TMP Essential Resources Import
が表示されるので:
Import
を押してください。
UIオブジェクトの名前を変更する
作成されたTextを:
HpText
に変更します。
文字サイズを設定する
Inspectorで:
| 項目 | 値 |
|---|---|
| Font Size | 40 |
| Text | HP : 100 |
くらいに変更してください。
Player.csスクリプトを作成する
Projectビューで:
Scriptsフォルダ
を作成してください。
その中で:
Create → MonoBehaviour Script
を押します。
名前:
Player
Player.csを編集する
ダブルクリックしてVisual Studioを開きます。
中身を全部消して、
以下を書いてください。
Player.cs
using System;
using UnityEngine;
using UnityEngine.InputSystem;
public class Player : MonoBehaviour
{
public event Action<int> OnHpChanged;
private int hp = 100;
private void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
Damage(10);
}
}
public void Damage(int damage)
{
hp -= damage;
Debug.Log($"現在HP : {hp}");
OnHpChanged?.Invoke(hp);
}
}
スクリプトをPlayerオブジェクトへ追加する
Unityへ戻ります。
Hierarchyの:
Player
を選択。
Inspectorへ:
Player.cs
をドラッグしてください。
Playして動作確認しよう
Playを押します。
スペースキーを押すと:
現在HP : 90
現在HP : 80
などがConsoleに表示されます。
まだUIは更新されません。
次はUI更新を実装する
ここから:
Observerパターン
が登場します。
HpUI.csスクリプトを作成する
Scriptsフォルダで:
Create → MonoBehaviour Script
名前:
HpUI
HpUI.cs
using UnityEngine;
using TMPro;
public class HpUI : MonoBehaviour
{
[SerializeField]
private Player player;
[SerializeField]
private TextMeshProUGUI hpText;
private void Start()
{
player.OnHpChanged += UpdateHpText;
UpdateHpText(100);
}
private void UpdateHpText(int hp)
{
hpText.text = $"HP : {hp}";
}
}
HpUIオブジェクトを設定する
Hierarchyの:
HpText
を選択。
Inspectorへ:
HpUI.cs
をドラッグ。
Inspectorで参照を設定する
Inspectorに:
| 項目 | 設定 |
|---|---|
| Player | Playerオブジェクト |
| Hp Text | HpText |
をドラッグして設定します。
再生してみよう
Playを押してください。
スペースキーを押すと:
HP : 90
HP : 80
と画面表示が更新されます。
ここが最重要ポイント
Player.csのコードを確認してください。書かれているのは、
OnHpChanged?.Invoke(hp);
これだけです。Playerは「HPが変わったよ」と通知するだけで、誰が受け取るかは一切知りません。
つまり:
Player.csはこれだけ
と通知しているだけです。
PlayerはUIを知らない
Playerのコードには、
UI更新
Text変更
画面表示
のようなUIを直接操作するコードが一切ありません。つまり、PlayerとUIは直接つながっていないのです。これを疎結合(そけつごう)といいます。「相手を直接知らない」設計のことで、Unityでは非常に重要な考え方です。
Observerパターンの流れ
Player
↓
HP変更通知
↓
UI更新
さらに追加してみよう
例えば:
ダメージエフェクト
も追加できます。
DamageEffect.csを作成する
using UnityEngine;
public class DamageEffect : MonoBehaviour
{
[SerializeField]
private Player player;
private void Start()
{
player.OnHpChanged += PlayEffect;
}
private void PlayEffect(int hp)
{
Debug.Log("ダメージ演出!");
}
}
空のオブジェクトを作成する
Hierarchyで:
Create Empty
名前:
EffectManager
スクリプトを追加・設定する
DamageEffect.cs
を追加。
Player欄へ:
Player
をドラッグ。
再生してみよう
スペースキーを押すと:
現在HP : 90
ダメージ演出!
が表示されます。
ここがObserverの強み
Playerは:
UI
Effect
Audio
を知りません。
それでも:
- UI更新
- 演出
- サウンド
を追加できます。
もし直接つないでいたら?
Playerに:
ui.Update();
effect.Play();
audio.Play();
を書き続ける必要があります。
これが増えると:
- 修正が大変
- バグが増える
- 再利用しづらい
になります。
Observerパターンは万能ではない
Observerパターンは便利ですが、「便利だから全部eventにしよう」と考えるのは危険です。実際の開発でも「Observerの乱用は危険」と言われています。
とも言われます。
なぜ乱用は危険なのか?
例えば:
OnHpChanged
に:
- UI
- Audio
- Effect
- Quest
- Analytics
- Save
- Achievement
など、
大量に登録されるとします。
すると:
HP変更
↓
大量の処理が動く
状態になります。
問題1:影響範囲が見えなくなる
例えば、次のようにひとつのイベントに大量の処理が登録されたとします。
OnHpChanged?.Invoke(hp);
UI・Audio・Effect・Quest・Analytics・Save・Achievementなど、大量の処理が登録されると「影響範囲が見えない」状態になります。たった1行の変更で、裏で大量の処理が動くことになりかねません。
- 音が鳴る
- 演出が出る
- セーブされる
- 実績解除される
状態になります。
問題2:デバッグが難しくなる
例えば「HP変更時にゲームが止まる」という問題が起きたとします。
しかしUI・Audio・Save・Effectのどれが原因かがわかりにくくなります。Observerは「見えない接続」だからです。
- UI?
- Audio?
- Save?
- Effect?
「見えない接続」
だからです。
問題3:登録解除を忘れやすい
Unityでは非常によくあるミスです。イベントへの登録(+=)をしたのに、登録解除(-=)を忘れてしまうケースがあります。
オブジェクトが破棄されても登録が残ったままになると、
のような例外エラーの原因になります。OnDisableやOnDestroyでの解除を習慣にしましょう。
player.OnHpChanged += UpdateHpText;
したのに、
-=
を忘れる。
すると:
- MissingReferenceException
- NullReferenceException
の原因になります。
登録解除の例
private void OnDestroy()
{
player.OnHpChanged -= UpdateHpText;
}
実務ではかなり重要です。
問題4:イベント地獄になる
大規模なプロジェクトになると、イベントが増殖して「流れを追うだけで大変」な状態になります。
次のようなイベントが飛び交うコードを想像してみてください。
OnDead
OnDamage
OnItemAdded
OnLevelUp
OnQuest
OnSceneLoaded
これだけのイベントが絡み合うと、処理の流れを追うだけで大変になります。
「流れを追うだけで大変」
になります。
Observerを使うべき場面
Observerが向いているのは:
「複数が反応する可能性がある」
場合です。
例えば:
| イベント | Observer向き |
|---|---|
| HP変更 | ○ |
| スコア更新 | ○ |
| アイテム取得 | ○ |
| クエスト更新 | ○ |
Observerが向いていない場合
例えば:
Player → Weapon
のように:
1対1で強く関係する
なら、
普通のメソッド呼び出しの方が
わかりやすい場合もあります。
Unityでありがちな失敗
初心者でよくあるのが:
「全部event化」
です。
ですが設計では:
「疎結合 = 正義」
ではありません。
設計で最も重要なことは?
設計で本当に重要なのは:
「あとから読んで理解しやすい」
ことです。
UnityでObserverが重要な理由
それでもObserverは、
Unityでは非常によく使われます。
例えば:
| システム | 通知内容 |
|---|---|
| HP | HP変更 |
| スコア | スコア更新 |
| クエスト | 達成通知 |
| UI | 値変更 |
| Audio | イベント発生 |
などです。
今回学んだ重要キーワード
| 用語 | 意味 |
|---|---|
| event | 通知 |
| Action | 登録できるメソッド型 |
| Invoke | 呼び出し |
| += | 登録 |
| -= | 登録解除 |
| Observer | 変化監視 |
| 疎結合 | 相手を直接知らない |
まとめ
Observerパターンは「変化を通知する」設計です。UnityではUI更新・サウンド・エフェクト・スコア管理など、非常に多くの場所で使われています。
特に重要なのは「通知だけする」という考え方です。送り手(Player)は受け手(UIやエフェクトなど)を直接知らなくてよい——この設計が疎結合を生み出します。
ただし乱用すると「影響範囲不明・デバッグ困難・イベント地獄」を招くことも。「本当に複数へ変化を知らせたいから使う」という意識が、Observerパターンをうまく活かすコツです。
ただし乱用すると:
- 影響範囲不明
- デバッグ困難
- イベント地獄
になることもあります。
重要なのは:
「本当に通知が必要か?」
を考えることです。
Observerは:
「便利だから使う」
ではなく、
「複数へ変化を知らせたいから使う」
という意識が大切になります。





ディスカッション
コメント一覧
まだ、コメントがありません