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



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