Unityで学ぶObserver(オブザーバー)パターン入門

2026年5月26日

広告

目次

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 Size40
TextHP : 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に:

項目設定
PlayerPlayerオブジェクト
Hp TextHpText

をドラッグして設定します。


再生してみよう

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では非常によくあるミスです。イベントへの登録(+=)をしたのに、登録解除(-=)を忘れてしまうケースがあります。

オブジェクトが破棄されても登録が残ったままになると、

のような例外エラーの原因になります。OnDisableOnDestroyでの解除を習慣にしましょう。

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では非常によく使われます。

例えば:

システム通知内容
HPHP変更
スコアスコア更新
クエスト達成通知
UI値変更
Audioイベント発生

などです。


今回学んだ重要キーワード

用語意味
event通知
Action登録できるメソッド型
Invoke呼び出し
+=登録
-=登録解除
Observer変化監視
疎結合相手を直接知らない

まとめ

Observerパターンは「変化を通知する」設計です。UnityではUI更新・サウンド・エフェクト・スコア管理など、非常に多くの場所で使われています。

特に重要なのは「通知だけする」という考え方です。送り手(Player)は受け手(UIやエフェクトなど)を直接知らなくてよい——この設計が疎結合を生み出します。

ただし乱用すると「影響範囲不明・デバッグ困難・イベント地獄」を招くことも。「本当に複数へ変化を知らせたいから使う」という意識が、Observerパターンをうまく活かすコツです。

ただし乱用すると:

  • 影響範囲不明
  • デバッグ困難
  • イベント地獄

になることもあります。

重要なのは:

「本当に通知が必要か?」

を考えることです。

Observerは:

「便利だから使う」

ではなく、

「複数へ変化を知らせたいから使う」

という意識が大切になります。

訪問数 29 回, 今日の訪問数 1回

広告