UnityUnityメモ

Unityの設計パターンを徹底比較!シングルトン・サービスロケーター・イベント駆動の違いと使い方

Unity

1. はじめに

Unityでゲームを作るとき、コードの整理がうまくいかずに困ったことはありませんか?
「このオブジェクトはどこで管理すればいいんだろう?」「あちこちで呼び出されるデータをどう扱えばいいんだろう?」と悩んだことがある人も多いはず。

そんなときに役立つのが シングルトン、サービスロケーター、イベント駆動設計 といった「設計パターン」です。
設計パターンをうまく活用することで、コードを シンプルに管理しやすくし、バグを減らし、開発をスムーズ に進めることができます。

とはいえ、「どの設計パターンを使えばいいの?」という疑問もあるでしょう。
そこで、この記事では シングルトン、サービスロケーター、イベント駆動設計3つの設計パターンの特徴を比較し、それぞれのメリット・デメリットを解説 します!
さらに Unityでの実装例 も紹介するので、実際に試しながら学んでみましょう。

どのパターンが どんな場面に適しているのか を理解すれば、よりスムーズにUnity開発が進められるようになりますよ!




2. シングルトンパターン

Unityで開発をしていると、ゲーム全体を管理するオブジェクトやデータが必要になることがあります。例えば、ゲームのスコア管理、設定データの保持、シーンをまたいでプレイヤーデータを維持したい場合などです。

こういった「ゲーム全体でひとつだけ存在するオブジェクト」を作るのに便利なのがシングルトンパターンです。


シングルトンとは?

シングルトン(Singleton)とは、「アプリケーション全体で1つのインスタンスしか持たない設計パターン」のことです。どのスクリプトからでもアクセスできるため、グローバルな管理が必要なオブジェクトに適しています。

Unityでは、例えば以下のような用途で使われます。

ゲームマネージャー(GameManager)
オーディオマネージャー(AudioManager)
スコア管理(ScoreManager)
シーン管理(SceneManager)


シングルトンのメリット

  • どこからでも簡単にアクセスできる
    • GameManager.Instance のようにして、どのスクリプトからも呼び出せる。
  • インスタンスが1つしかないので、データが統一される
    • 例えばスコアがリセットされる心配がない。
  • シーンをまたいでデータを保持できる
    • DontDestroyOnLoad() を使うことで、シーンが切り替わってもオブジェクトを維持できる。

シングルトンのデメリット

  • 依存性が高くなる
    • どのスクリプトからもアクセスできるため、他のコードと強く結びついてしまい、修正が難しくなる。
  • テストがしにくい
    • 例えば、シングルトンのインスタンスを変更できないため、単体テストが難しくなる。
  • スレッドセーフではない
    • マルチスレッド処理を行う場合、シングルトンが原因で競合が発生することがある。



Unityでのシングルトンの実装方法

それでは、Unityでシングルトンを実装する方法を紹介します。

① シンプルなシングルトン

まずは、基本的なシングルトンの書き方です。

using UnityEngine;

public class GameManager : MonoBehaviour
{
// シングルトンのインスタンス
public static GameManager Instance { get; private set; }

private void Awake()
{
// インスタンスがすでに存在する場合は、重複を防ぐ
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}

// インスタンスを設定
Instance = this;
DontDestroyOnLoad(gameObject);
}

// ゲームのスコアを管理する例
private int score = 0;

public void AddScore(int points)
{
score += points;
Debug.Log("現在のスコア: " + score);
}
}

このコードでは、Instance という静的変数を使って唯一のインスタンスを管理し、Awake() 内でインスタンスの重複を防いでいます。また、DontDestroyOnLoad(gameObject); を使うことで、シーンが変わっても破棄されないようにしています。


② シングルトンを使ってスコアを管理する

実際に GameManager を使ってスコアを増やしてみましょう。

  1. GameManager を空のオブジェクトにアタッチする。
  2. 別のスクリプトから GameManager.Instance.AddScore(10); を呼び出す。
using UnityEngine;

public class Player : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Coin"))
{
// シングルトンを使ってスコアを加算
GameManager.Instance.AddScore(10);
Destroy(other.gameObject);
}
}
}

このように、シングルトンを使うことでどこからでも GameManager のスコアを管理できます。


シングルトンを使うべき場面

シングルトンは非常に便利ですが、使いすぎると依存関係が強くなり、コードの管理が難しくなります。以下のような場面で使うのがおすすめです。

ゲーム全体で1つだけ存在するオブジェクトを管理する場合
シーンをまたいでデータを保持したい場合
グローバルにアクセスできるマネージャーが必要な場合


まとめ

  • シングルトンは、アプリケーション全体で1つのインスタンスだけを持つ設計パターン
  • 簡単にアクセスでき、シーンをまたいでデータを保持できるが、依存性が高くなるリスクがある
  • Instance を使ってどこからでもアクセス可能だが、適切な場面で使用することが重要

次のセクションでは、もう少し柔軟な依存関係の管理ができる「サービスロケーターパターン」について解説していきます!




3. サービスロケーターパターン

サービスロケーターパターン(Service Locator Pattern)は、オブジェクトの依存関係を管理し、必要なサービスを取得するための仕組みです。このパターンを使うことで、クラス間の直接的な依存を減らし、コードの拡張性を高めることができます。

Unityでは、ゲーム内でさまざまなシステム(サウンド管理、データ管理、入力管理など)を統一的に取得できる仕組みとして活用されます。


サービスロケーターパターンのメリット

依存関係を管理しやすい
クラス間の依存関係を明示的に分離できるため、メンテナンスがしやすくなります。

コードの柔軟性が向上する
特定のサービスを簡単に置き換えられるため、ゲームの仕様変更や拡張がスムーズになります。

グローバルアクセスを実現できる
シングルトンと同じように、どこからでも必要なサービスを取得できます。


サービスロケーターパターンのデメリット

実装が複雑になりがち
初めてこのパターンを導入する場合、シンプルなシングルトンよりも設計が複雑になります。

依存関係が見えにくくなる
どのクラスがどのサービスを利用しているかが直接的に見えにくくなるため、適切なドキュメント化が必要です。




Unityでのサービスロケーターパターンの実装

では、Unityでサービスロケーターパターンを実装する方法を紹介します。

① サービスロケーターの基盤を作る

まず、サービスを登録・取得するための「サービスロケータークラス」を作成します。

プロジェクトウィンドウを右クリック「Create」→「C# Script」を選んで、新しいスクリプトを作成し、「ServiceLocator」と名前を付けます。

using System;
using System.Collections.Generic;

public static class ServiceLocator
{
private static Dictionary<Type, object> services = new Dictionary<Type, object>();

// サービスを登録する
public static void Register<T>(T service)
{
var type = typeof(T);
if (!services.ContainsKey(type))
{
services[type] = service;
}
}

// サービスを取得する
public static T Get<T>()
{
var type = typeof(T);
if (services.ContainsKey(type))
{
return (T)services[type];
}
throw new Exception($"Service {type} not found.");
}

// サービスの登録を解除
public static void Unregister<T>()
{
var type = typeof(T);
if (services.ContainsKey(type))
{
services.Remove(type);
}
}
}

このクラスは、**サービスを登録(Register<T>)、取得(Get<T>)、解除(Unregister<T>)**する機能を提供します。


② サービスを作成する

次に、実際にゲームで利用するサービスを作成します。今回は「サウンドマネージャー」を例にします。

プロジェクトウィンドウを右クリック「Create」→「C# Script」を選んで、新しいスクリプトを作成し、「SoundManager」と名前を付けます。

using UnityEngine;

public class SoundManager : MonoBehaviour
{
public void PlaySound(string soundName)
{
Debug.Log($"サウンド再生: {soundName}");
// 実際のサウンド再生処理をここに実装
}
}

このクラスは、ゲーム内のサウンドを管理する役割を持ちます。


③ サービスを登録する

次に、シーンが開始したときに「SoundManager」をサービスロケーターに登録します。

新しいスクリプトを作成し、「GameManager」と名前を付けます。

using UnityEngine;

public class GameManager : MonoBehaviour
{
private void Awake()
{
// サウンドマネージャーを登録
SoundManager soundManager = new GameObject("SoundManager").AddComponent<SoundManager>();
ServiceLocator.Register<SoundManager>(soundManager);
}
}

このスクリプトを空のGameObjectにアタッチして、ゲームの開始時にサービスを登録するようにします。


④ サービスを利用する

他のスクリプトから「SoundManager」を取得し、サウンドを再生してみましょう。

例えば、プレイヤーのアクションにサウンドを追加するスクリプトを作成します。

using UnityEngine;

public class PlayerController : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// サウンドマネージャーを取得してサウンドを再生
var soundManager = ServiceLocator.Get<SoundManager>();
soundManager.PlaySound("Jump");
}
}
}

このコードでは、スペースキーを押すと「Jump」というサウンドが再生されるようになります。


サービスロケーターパターンはいつ使うべき?

プロジェクトが大規模化し、シングルトンでは管理が難しくなったとき
依存関係を明確に分離し、テストしやすい構造を作りたいとき
さまざまなコンポーネントから共通のサービスを呼び出したいとき(サウンド管理、データ管理など)

ただし、小規模なプロジェクトではシングルトンの方がシンプルで使いやすい場合もあります。
設計の目的に応じて適切なパターンを選びましょう!

これでサービスロケーターパターンの基本が理解できました!
次は、イベント駆動設計を活用した実装方法について解説します!




4. イベント駆動設計

イベント駆動設計(Event-Driven Design)は、システム内のコンポーネントがイベントを通じてやり取りする設計パターンです。
Unityでは C#のイベント(delegateやAction)ScriptableObjectを活用したイベントシステム を使って実装できます。
この設計の最大の特徴は、「疎結合(Loose Coupling)」を実現できることです。つまり、オブジェクト同士が直接依存せず、イベントを発火することで柔軟に処理を実行できます。


イベント駆動設計のメリット

疎結合な設計が可能
イベントを通じてオブジェクトがやり取りするため、コンポーネント同士が直接依存せずに済みます。

柔軟な拡張ができる
イベントリスナー(受信側)を追加するだけで、新しい機能を簡単に拡張できます。例えば、スコア更新時にUIとサウンドを同時に変更する処理を追加しても、他のシステムには影響しません。

状態の変更をリアルタイムで通知できる
プレイヤーのHPが減少したタイミングで、UIに反映し、敵の行動を変えるなど、リアルタイムな処理が可能です。


イベント駆動設計のデメリット

デバッグが難しくなる
イベントの発火元とリスナー(受信側)が離れているため、どこでイベントが発生したのかを追いづらくなることがあります。
👉 対策: ログを出力したり、イベント管理用のクラスを作成して整理する。

依存関係が把握しにくくなる
イベントの発火順が保証されていないため、意図しない動作が起こることがあります。
👉 対策: 重要な処理は順番を制御できるように設計する。


Unityでの実装例

ここでは、Unityで C#のイベントを使った基本的なイベント駆動設計 を紹介します。

1. イベントの定義

まずは、スコアが更新されたときに発火するイベントを作成します。

using System;
using UnityEngine;

public class GameEvents : MonoBehaviour
{
public static Action<int> OnScoreUpdated; // スコアが更新されたときのイベント

public static void ScoreUpdated(int newScore)
{
OnScoreUpdated?.Invoke(newScore); // イベントを発火
}
}

2. イベントを発火する(スコアを更新する)

スコアを加算するときに ScoreUpdated を呼び出して、イベントを発火させます。

using UnityEngine;

public class ScoreManager : MonoBehaviour
{
private int score = 0;

public void AddScore(int points)
{
score += points;
GameEvents.ScoreUpdated(score); // スコア更新イベントを発火
}
}

3. イベントを受信してUIを更新する

次に、ScoreText を更新するスクリプトを作成し、イベントをリッスン(受信)します。

using UnityEngine;
using UnityEngine.UI;

public class ScoreUI : MonoBehaviour
{
[SerializeField] private Text scoreText;

private void OnEnable()
{
GameEvents.OnScoreUpdated += UpdateScoreUI; // イベントに登録
}

private void OnDisable()
{
GameEvents.OnScoreUpdated -= UpdateScoreUI; // イベントを解除
}

private void UpdateScoreUI(int newScore)
{
scoreText.text = "Score: " + newScore; // UIを更新
}
}



4. サウンドもイベントで制御する

スコアが加算されたときに効果音を鳴らすには、イベントを利用するだけで簡単に追加できます。

using UnityEngine;

public class ScoreSound : MonoBehaviour
{
[SerializeField] private AudioSource scoreSound;

private void OnEnable()
{
GameEvents.OnScoreUpdated += PlayScoreSound;
}

private void OnDisable()
{
GameEvents.OnScoreUpdated -= PlayScoreSound;
}

private void PlayScoreSound(int score)
{
scoreSound.Play(); // スコアが更新されたら効果音を再生
}
}

イベント駆動設計の応用例

  • UIの更新(HPバーやスコア表示)
  • ゲーム内のオブジェクト間の通信(敵の死亡時にプレイヤーに経験値を加算)
  • アニメーションやサウンドの制御
  • 状態管理(ゲームの開始・終了イベントなど)

イベント駆動設計はどんな場面で使うべき?

  • コンポーネント間の依存関係を減らしたいとき
  • システムの拡張性を高めたいとき
  • リアルタイムで状態を監視・更新する必要があるとき

特に、UI更新・オーディオ制御・ゲーム進行の管理 などに最適です!
ゲームの状態が変わるたびに複数のオブジェクトが影響を受けるような場面では、イベント駆動設計が強力なツールになります。


まとめ

  • イベント駆動設計は、コンポーネント間の依存を減らし、拡張しやすいシステムを作れる。
  • Actiondelegate を使うことで、簡単にイベントを実装できる。
  • デバッグや依存関係の把握には注意が必要! 適切なログ出力や設計で対策しよう。

これで、イベント駆動設計の基礎はバッチリ!🎮
次は、実際のプロジェクトでどのように活用できるか試してみましょう!




5. それぞれの設計パターンの比較

Unityの開発では、シングルトン、サービスロケーター、イベント駆動設計の3つの設計パターンがよく使われます。ここでは、それぞれの特徴やメリット・デメリットを比較し、どのような場面で適しているのかを見ていきましょう。


シングルトンパターン

概要:
シングルトンパターンは、アプリケーション内で1つのインスタンスしか存在しないクラスを作成し、どこからでもアクセスできるようにする設計パターンです。Unityではゲームマネージャーやシーン管理などに利用されることが多いです。

メリット:

  • どのスクリプトからでも簡単にアクセスできる
  • インスタンスの管理がしやすい
  • シンプルな実装で済む

デメリット:

  • 依存性が高くなり、コードの再利用性が低くなる
  • テストが難しくなる
  • グローバル変数のように扱われ、バグの原因になりやすい

Unityでの活用例:

  • ゲームマネージャー: ゲーム全体の進行を管理
  • オーディオマネージャー: 効果音やBGMの管理
  • シーン管理: シーンの切り替え制御

サービスロケーターパターン

概要:
サービスロケーターパターンは、クラス間の依存関係を管理し、特定のサービス(機能)にアクセスしやすくする設計パターンです。依存注入(DI)と似ていますが、サービスロケーターはサービスの取得を専用のクラスを通じて行います。

メリット:

  • クラス間の依存関係を整理しやすい
  • シングルトンよりも拡張性が高い
  • 大規模プロジェクトに適している

デメリット:

  • 実装が複雑になる
  • 依存関係が見えにくくなるため、管理が難しくなる

Unityでの活用例:

  • データ管理: 設定データやゲーム進行の保存・ロード
  • 外部API連携: ネットワーク通信や広告システムの統合
  • インプット管理: ゲームパッドやキーボード入力の統一的な管理



イベント駆動設計

概要:
イベント駆動設計は、特定のイベントが発生したときに、それに応じた処理を実行する設計パターンです。C#のeventdelegate、またはUnityEventを活用して実装できます。

メリット:

  • オブジェクト間の依存関係が低く、コードの柔軟性が高い
  • 状態の変化をスムーズに処理できる
  • UIやアニメーションなど、動的な要素に適している

デメリット:

  • デバッグが難しく、どこでイベントが呼ばれているか把握しにくい
  • イベントが増えると管理が煩雑になる
  • メモリリークのリスクがある(イベントの登録解除を忘れると発生)

Unityでの活用例:

  • UI更新: プレイヤーのスコア変動時にUIを更新
  • アニメーション制御: キャラクターのアニメーション切り替え
  • エフェクト: 攻撃時にパーティクルエフェクトを発生させる

比較表

設計パターンメリットデメリット用途の例
シングルトン簡単にアクセスできる / 実装がシンプル依存性が高くなる / テストが難しいゲームマネージャー、オーディオ管理、シーン管理
サービスロケーター依存関係を整理しやすい / 拡張性が高い実装が複雑 / 依存関係が見えにくいデータ管理、外部API連携、インプット管理
イベント駆動設計疎結合な設計が可能 / 柔軟な拡張性デバッグが難しい / イベント管理が煩雑UI更新、アニメーション制御、エフェクト

どの設計パターンを選ぶべきか?

設計パターンの選択は、プロジェクトの規模や必要な柔軟性に応じて決定するのが重要です。

  • シングルトン: 小規模なプロジェクトや、単純な管理が求められる機能(例: ゲームマネージャー)
  • サービスロケーター: 大規模プロジェクトや、異なるシステム間の依存関係を整理したい場合(例: データ管理)
  • イベント駆動設計: UIやアニメーションなど、状態変化が多いシステム(例: UI更新、エフェクト)

適切に使い分けることで、コードの保守性や拡張性が向上し、よりスムーズな開発が可能になります。




6. まとめ

今回は、シングルトン、サービスロケーター、イベント駆動設計という3つの設計パターンについて紹介しました。それぞれにメリットとデメリットがあり、適切に使い分けることが大切です。

シングルトンはシンプルで実装しやすいですが、依存性が強くなりやすいため、プロジェクトが大きくなると管理が難しくなることがあります。一方で、ゲームマネージャーやシーン管理などどこからでもアクセスする必要があるクラスには有効な手法です。

サービスロケーターは、依存関係を整理しながらも柔軟に拡張できる設計です。特に、データ管理や外部APIとのやり取りなど、複数のクラスが特定のサービスにアクセスする必要がある場合に向いています。ただし、適切に管理しないとブラックボックス化しやすく、メンテナンスが難しくなる点には注意が必要です。

イベント駆動設計は、オブジェクト間の依存関係を最小限に抑えられるため、拡張性の高い設計が可能になります。特に、UIの更新やアニメーション制御など、イベントベースの処理が多いシステムでは便利です。しかし、イベントの管理が複雑になりやすいため、ログの記録やデバッグの工夫が必要になります。

どの設計パターンが最適かは、プロジェクトの規模や目的によって変わります。小規模なゲームならシングルトンでも十分ですが、規模が大きくなるにつれてサービスロケーターやイベント駆動設計を取り入れることで、保守性や拡張性の高いコードを維持しやすくなります。

最も重要なのは、「設計パターンにこだわりすぎず、柔軟に使い分けること」です。プロジェクトの状況に応じて適切な手法を選び、無駄な依存を減らすことで、長く使えるコードを書いていきましょう!




よくある質問(FAQ)

Q
シングルトンを使わずにゲームマネージャーを作る方法はありますか?
A

依存注入(DI)やScriptableObjectを使う方法があります。

Q
Unityでサービスロケーターを実装する際に注意すべき点は?
A

依存関係が見えにくくなるため、適切なドキュメント化や設計が必要です。

Q
イベント駆動設計はすべてのプロジェクトに向いていますか?
A

小規模プロジェクトではオーバーヘッドが増える可能性があるため、適用範囲を考慮する必要があります。

タイトルとURLをコピーしました