UnityUnityメモ

Unity初心者必見!シングルトンの正しい実装方法とよくあるミス

Unity

1. 導入

Unityでゲームを作っていると、「ゲーム全体で共有したいデータ」や「どのシーンでも使いたいオブジェクト」が出てくることってありますよね? 例えば、ゲームの進行状況を管理する GameManager や、サウンドを一括管理する AudioManager など、どのシーンでも同じデータや機能を保持したい場面は多いはずです。

そんなときに便利なのが 「シングルトン」(Singleton) パターン です。シングルトンを使うことで、特定のオブジェクトを 1つだけ作成し、どこからでもアクセス可能 にできます。このおかげで、ゲームの管理がシンプルになり、コードの可読性も向上します。


ただし、シングルトンは便利な反面、使い方を間違えると 予期せぬバグや管理が難しくなるリスク があります。たとえば、シングルトンが意図せず 複数生成 されてしまったり、不要になったオブジェクトが メモリに残り続ける ことでパフォーマンスに悪影響を与えることも…。

そこでこの記事では、
✅ シングルトンの基本と正しい使い方
✅ シングルトンを使う際の危険な落とし穴
✅ 失敗しないための対策

をわかりやすく解説していきます! Unity初心者の方でも安心して実装できるように、具体的なコード例とともに説明 するので、ぜひ最後まで読んでみてくださいね! 🚀




2. シングルトンとは?

シングルトン(Singleton)とは、プログラム内で 1つのインスタンスだけを作成し、どこからでもアクセスできる ようにするデザインパターンのことです。ゲーム開発において、グローバルなデータ管理 に役立つため、Unityでもよく使われます。


💡 シングルトンを使うと便利な場面

Unityでは、ゲーム全体で 1つだけ存在するべきオブジェクト を作るときにシングルトンが役立ちます。例えば、次のようなケースです。

GameManager(ゲーム全体の進行管理)
 → ゲームのスコア、ステージ進行、設定情報などを管理する

AudioManager(サウンド管理)
 → BGMや効果音の再生・停止を統一的に制御する

SettingsManager(設定データ管理)
 → プレイヤーの操作設定や音量設定を保存し、全シーンで適用する

これらのオブジェクトは、どのシーンでも1つだけ存在し、常にアクセスできることが望ましい ですよね? シングルトンを使えば、この要件を簡単に満たせます。


🔧 Unityでの基本的なシングルトンの実装方法

シングルトンを実装するには、次のようなC#スクリプトを作成します。

📌 シンプルなシングルトンの実装

public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }

private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject); // シーンをまたいでもオブジェクトを維持
}
else
{
Destroy(gameObject); // すでに存在する場合は破棄
}
}
}

このコードのポイントは次の3つです。

1️⃣ static 変数を使って1つのインスタンスを保持する
Instance を通じて、どのスクリプトからでも GameManager にアクセス可能

2️⃣ DontDestroyOnLoad() を使ってシーンをまたいで保持する
→ シーンが切り替わっても GameManager が削除されないようにする

3️⃣ 重複生成を防ぐ
Awake() の中で すでに Instance が存在する場合は削除 し、1つだけ存在する状態を保証


📝 シングルトンを使うメリット

どこからでもアクセスできるGameManager.Instance のように簡単にアクセス可能)
データをシーン間で保持できるDontDestroyOnLoad() で保持)
同じ処理を繰り返さず、一元管理できる(設定や管理データを統一)

ただし、シングルトンには危険な落とし穴もある ので、次のセクションで 誤った使い方のリスクと対策 について解説していきます!




3. シングルトンの正しい使い方

シングルトンは便利なデザインパターンですが、適切に実装しないと 意図しないバグや管理の難しさ を引き起こす可能性があります。
ここでは、Unityでの 正しいシングルトンの使い方 を解説します。


✅ 1. シングルトンを使うべきケース

シングルトンは 「どのシーンでも1つだけ存在し、全体で共有されるべきオブジェクト」 に適しています。

🔹 シングルトンが適したオブジェクト

  • GameManager(ゲームの進行やスコア管理)
  • AudioManager(BGMや効果音の管理)
  • SettingsManager(プレイヤー設定、セーブデータの管理)
  • InputManager(入力管理を統一する)

🔹 シングルトンが不向きなオブジェクト

  • シーンごとに異なるオブジェクト
    → 例: 敵キャラ、アイテム、プレイヤーのインスタンス
  • インスタンスが複数必要なオブジェクト
    → 例: 複数のプレイヤー、ネットワーク接続されたキャラクター

⚠ 無闇にシングルトンを使うと、依存関係が増えて管理が難しくなるので注意!


✅ 2. 正しいシングルトンの実装

シングルトンを適切に管理するための基本的なC#コードを見てみましょう。

📌 シングルトンの正しい書き方

public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }

private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject); // シーンをまたいでもオブジェクトを保持
}
else
{
Destroy(gameObject); // すでに存在する場合は削除(重複生成を防ぐ)
}
}
}

📝 この実装のポイント

static 変数 Instance を使い、どこからでもアクセス可能にする
DontDestroyOnLoad(gameObject); を使い、シーンをまたいで保持する
Awake() 内で、すでに存在する場合は Destroy(gameObject); で削除

このコードを使うことで、GameManagerが1つだけ存在し、どのシーンからでもアクセスできるようになります。




✅ 3. シングルトンをより安全に使うテクニック

シングルトンの安全性を高めるために、以下のテクニックを活用しましょう。

🔹 private set; を使って外部から書き換えを防ぐ

public static GameManager Instance { get; private set; }

👉 これにより、他のスクリプトから Instance を変更できなくなり、安全性が向上!


🔹 public なメソッドを作り、データを管理する

シングルトンを使うと、どこからでも Instance にアクセスできるため、直接データを変更するのではなく メソッド経由で変更 するのがベスト。

💡 例: スコアを管理するGameManager

public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
private int score = 0;

private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}

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

public int GetScore()
{
return score;
}
}

👉 GameManager.Instance.AddScore(10); でスコアを加算するようにし、直接 score を変更しないことで安全に管理!


✅ 4. シングルトンをテストしやすくする

シングルトンは「どこからでもアクセスできる」という特性上、依存関係が増えてしまいテストが難しくなる ことがあります。

🔹 インターフェース を使って依存性を下げる

シングルトンに直接依存するのではなく、インターフェースを使って柔軟に管理 しましょう。

💡 例: IGameManager インターフェースを作成

public interface IGameManager
{
void AddScore(int amount);
int GetScore();
}

💡 例: GameManager にインターフェースを適用

public class GameManager : MonoBehaviour, IGameManager
{
public static GameManager Instance { get; private set; }
private int score = 0;

private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}

public void AddScore(int amount)
{
score += amount;
}

public int GetScore()
{
return score;
}
}

👉 こうすることで、他のスクリプトは GameManager ではなく IGameManager に依存するようになり、テストしやすくなる!

シングルトンを使うと、コードがグローバルなデータに依存しすぎることがあります。特に設定管理などでシングルトンを使う場合、エディター上で視覚的に管理できると便利です。
「Odin Inspector and Serializer」 を使えば、シングルトンのプロパティをエディターで簡単にカスタマイズでき、データ管理の負担を減らすことができます!
Odin Inspectorの詳細はこちら


✅ 5. シングルトンの使用を避ける選択肢

「本当にシングルトンが必要なのか?」を考え、代替手段 も検討しましょう。

🔹 ScriptableObject を使ってデータを管理

シングルトンを使わずに、ScriptableObject を活用してデータを共有 する方法もあります。

💡 例: スコアを管理する ScriptableObject

using UnityEngine;

[CreateAssetMenu(fileName = "GameData", menuName = "Game/GameData")]
public class GameData : ScriptableObject
{
public int score;
}

👉 ScriptableObject を使えば、シーンごとにデータを簡単に管理できる!


🎯 まとめ

シングルトンの正しい使い方は、以下のポイントを守ること!

シングルトンは「どのシーンでも1つだけ存在するべきオブジェクト」にのみ使う
正しい実装をし、DontDestroyOnLoad() で管理する
private set; で安全性を高め、メソッド経由でデータを管理する
依存性を下げるためにインターフェースを活用する
場合によっては ScriptableObject を使い、シングルトンを避ける

シングルトンは便利なツールですが、誤った使い方をするとトラブルの原因になります。適切に実装し、ゲーム開発をスムーズに進めましょう! 🚀




4. シングルトンの危険な落とし穴

シングルトンは 「どこからでもアクセスできる」「一つのインスタンスを保持する」 という特性を持つため、便利な反面、誤った使い方をすると バグの原因やメンテナンスの難しさ を引き起こします。
ここでは、シングルトンを使う際に注意すべき「危険な落とし穴」 を紹介します。


⚠ 1. 依存関係が増えすぎる

問題点

シングルトンはどこからでもアクセスできるため、多くのスクリプトがシングルトンに依存してしまう ことがあります。
これにより、以下のような問題が発生します。

  • 修正が難しくなる → 1つの変更が他の複数のスクリプトに影響を与える
  • テストがしにくいGameManager.Instance のようなコードが増えると、個別のテストが困難になる
  • スパゲッティコード化 → すべてのコードがシングルトンに依存し、整理が難しくなる

対策

✅ 直接 GameManager.Instance を参照するのではなく、インターフェースを利用する
✅ 必要な情報は シングルトンを経由せずに引数として渡す
可能な限りローカルな管理を意識し、グローバルなデータを減らす


⚠ 2. マルチシーンでの問題

問題点

シングルトンは シーンをまたいでオブジェクトを保持する ことができますが、それが原因で 意図しない動作 をすることがあります。

例えば…

  • DontDestroyOnLoad() を使ったシングルトンが、シーン遷移後も古いデータを保持 してしまう
  • シーンを再読み込みしても 前のシングルトンが残り続ける
  • シーン開始時に GameManager.Instance を使おうとしたら、まだ初期化されていないことがある(NullReferenceException)

対策

✅ シーンごとに異なる管理が必要なら、シングルトンを使わず、ScriptableObjectを活用 する
SceneManager.sceneLoaded を利用して シーン遷移後にデータをリセット する

using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }

private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
SceneManager.sceneLoaded += OnSceneLoaded;
}
else
{
Destroy(gameObject);
}
}

private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// シーン切り替え時に特定のデータをリセット
}
}

✅ シングルトンを 一部のシーンでは使わないように設計する
特定のシーンでのみ機能するシングルトンを作る


⚠ 3. シングルトンが意図せず複数生成される

問題点

本来1つだけ存在するはずのシングルトンが、複数生成されることがある という問題があります。

例えば…

  • シーンをリロードすると、新しいシングルトンが作成される
  • プレハブを複数インスタンス化してしまい、複数のシングルトンが動作する
  • シングルトンを別のスクリプトでnew してしまうMonoBehaviournew でインスタンス化できない)

対策

Awake() 内で既存のインスタンスがある場合は Destroy(this) する

private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}

✅ シングルトンのプレハブを 手動でシーンに配置しない ようにする
new キーワードでシングルトンを作らない




⚠ 4. マルチスレッド環境での競合

問題点

Unityでは基本的にメインスレッドで処理を行いますが、マルチスレッド処理(TaskやCoroutine)を使うと、シングルトンが競合する可能性 があります。

例えば…

  • 複数のスレッドからシングルトンのインスタンスを変更しようとしてエラーが発生
  • マルチスレッド環境で Instance にアクセスしたら、想定外の値になっていた

対策

lock を使ってスレッドセーフにする

private static readonly object _lock = new object();

public static GameManager Instance
{
get
{
lock (_lock)
{
return _instance;
}
}
}

Unityのメインスレッドでのみシングルトンを操作するように設計する


⚠ 5. メモリリークのリスク

問題点

DontDestroyOnLoad() を使うと、シーンをまたいでもオブジェクトが残るため、不要なシングルトンがメモリに残り続ける ことがあります。

例えば…

  • 長時間プレイすると不要なオブジェクトが蓄積し、メモリ使用量が増加
  • 古いデータが残り続け、意図しないバグを引き起こす

対策

✅ シングルトンを破棄するタイミングを決める(特定のシーンで削除 する)

public void DestroySingleton()
{
Destroy(gameObject);
Instance = null;
}

不要になったシングルトンは明示的に破棄する


🎯 まとめ

シングルトンは便利ですが、以下のような落とし穴に注意しましょう!

🚨 シングルトンの危険な落とし穴と対策

落とし穴問題点対策
依存関係が増えすぎる修正・テストが難しくなるインターフェースを活用し、直接アクセスを減らす
マルチシーンでの問題シーン遷移後もデータが残るSceneManager.sceneLoaded を使って適切にリセット
意図せず複数生成されるシーンをリロードするとシングルトンが増えるAwake()Destroy(gameObject) を実装
マルチスレッドの競合スレッド間でインスタンスが競合lock を使ってスレッドセーフにする
メモリリークのリスクシーン遷移後も不要なオブジェクトが残るDestroy(gameObject) で適切に削除

シングルトンを 正しく使うこと で、バグを防ぎ、快適なゲーム開発を進めていきましょう! 🚀




5. シングルトンを安全に使うための対策

シングルトンは便利ですが、前のセクションで解説したように 誤った使い方をするとバグや管理の難しさを引き起こします
ここでは、シングルトンを 安全に使うための具体的な対策 を紹介します!


✅ 1. 依存関係を減らす

問題点

シングルトンはどこからでもアクセスできるため、他のスクリプトがシングルトンに依存しすぎると、修正やテストが難しくなる という問題があります。

対策

インターフェース(Interface)を活用する
シングルトンを直接参照せず、必要な情報は引数で渡す
特定の処理に限定してシングルトンを使う

💡 インターフェースを使った設計

public interface IGameManager
{
void AddScore(int amount);
int GetScore();
}

public class GameManager : MonoBehaviour, IGameManager
{
public static GameManager Instance { get; private set; }
private int score = 0;

private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}

public void AddScore(int amount) { score += amount; }
public int GetScore() { return score; }
}

他のスクリプトは GameManager ではなく IGameManager に依存することで、シングルトンの影響を最小限に抑えられます!


✅ 2. シングルトンの生成を適切に管理

問題点

シングルトンが 意図せず複数生成されると、バグの原因 になります。

対策

Awake() で既にインスタンスが存在する場合は 削除する ✅ シングルトンを 手動でシーンに配置しないnew を使わず、適切にインスタンスを作成する

💡 シングルトンの適切な管理

private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject); // すでに存在する場合は削除
}
}

👉 これで、意図しないシングルトンの増殖を防げます!

シングルトンのデータ管理は、適切な方法を取らないとバグの原因になります。特にインスペクターでシングルトンのプロパティを簡単に管理できると、設定ミスを減らし、開発スピードを向上させられます。
そこでおすすめなのが 「Odin Inspector and Serializer」 です!
Odin Inspectorの詳細はこちら


✅ 3. マルチシーン対応

問題点

  • シーン遷移時に シングルトンが不要になっても削除されない
  • データがリセットされず、バグの原因 になる

対策

シーンごとに管理する必要があるなら、ScriptableObject を使う
SceneManager.sceneLoaded を利用して、シーン遷移時にデータをリセットする

💡 シーンごとにデータをリセットする例

using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
private int score = 0;

private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
SceneManager.sceneLoaded += OnSceneLoaded;
}
else
{
Destroy(gameObject);
}
}

private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// シーンが切り替わるたびにスコアをリセット
score = 0;
}
}

👉 これで、シーン遷移時のデータ管理がスムーズになります!




✅ 4. スレッドセーフなシングルトン

問題点

マルチスレッド環境でシングルトンを使うと、複数のスレッドが同時に Instance にアクセスしてしまい、意図しない動作を引き起こす 可能性があります。

対策

lock を使ってスレッドセーフにする
✅ Unityのメインスレッドでのみシングルトンを操作する

💡 lock を使ってスレッドセーフにする

private static readonly object _lock = new object();

public static GameManager Instance
{
get
{
lock (_lock)
{
return _instance;
}
}
}

👉 これで、スレッドが競合しない安全なシングルトンが作れます!


✅ 5. メモリリークを防ぐ

問題点

DontDestroyOnLoad() を使うと、シーンをまたいでもオブジェクトが削除されないため、不要になったシングルトンがメモリに残り続ける 可能性があります。

対策

不要になったシングルトンは適切に破棄するDestroy(gameObject) を使って 明示的に削除する

💡 シングルトンを手動で削除する

public void DestroySingleton()
{
Destroy(gameObject);
Instance = null;
}

👉 これで、不要になったシングルトンを適切に削除できます!


🎯 まとめ

シングルトンを安全に使うためには、以下のポイントを押さえましょう!

対策実装ポイント
依存関係を減らすインターフェースを使って他のスクリプトとの結合を最小限に
シングルトンの生成を管理Awake() で既存のインスタンスがある場合は削除
マルチシーン対応SceneManager.sceneLoaded でデータをリセット
スレッドセーフにするlock を使って競合を防ぐ
メモリリークを防ぐDestroy(gameObject) を適切に使う

シングルトンは便利なデザインパターンですが、使い方を間違えるとバグの原因になります。
適切に管理して、安全なUnity開発を進めていきましょう! 🚀


6. まとめ

シングルトンは、「どのシーンでも1つだけ存在し、どこからでもアクセス可能なオブジェクト」 を作るのに便利なデザインパターンです。
ゲーム全体の管理や設定データの保持 などに役立ちますが、使い方を間違えると バグの原因やコードの可読性低下 につながることもあります。


✅ この記事で学んだポイント

項目内容
シングルトンとは?1つのインスタンスを保持し、どこからでもアクセス可能にする設計
シングルトンの正しい使い方static 変数を使い、DontDestroyOnLoad() で管理する
シングルトンの危険な落とし穴依存関係の増大、マルチシーンでの問題、意図しない複数生成、スレッド競合、メモリリークなど
安全に使うための対策インターフェースの活用、インスタンス管理、データリセット、スレッドセーフ化、適切な破棄

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

GameManager(ゲームの進行管理)
AudioManager(BGM・SEの管理)
SettingsManager(設定データの保存)
InputManager(入力管理の統一)

→ ゲーム全体で共通する情報を管理するのに最適!


⚠ シングルトンを避けるべき場面

シーンごとに異なるオブジェクト(例: 敵キャラ、アイテム)
複数のインスタンスが必要なオブジェクト
ローカルな処理に関わるデータ管理

→ 代替として ScriptableObject を活用するのもアリ!


💡 シングルトンを正しく使って、バグのない開発を!

シングルトンは、適切に使えば ゲームの管理をシンプルにし、開発を効率化 できます。
しかし、無計画に使うと「シングルトン地獄」になり、メンテナンスが困難に…。

シングルトンを導入する前に、本当に必要か? 代替手段はないか? を考え、最適な設計を心がけましょう! 🎮✨




よくある質問(FAQ)

Q
シングルトンはどんな場面で使うべき?
A

ゲーム全体で共有すべきデータ(スコア、設定、オーディオ管理など)に適しています。

Q
DontDestroyOnLoad() を使ったのにシングルトンが消えてしまうのはなぜ?
A

new キーワードでインスタンスを生成すると、MonoBehaviour で管理されず、DontDestroyOnLoad() の影響を受けません。

Q
シングルトンとScriptableObjectの違いは?
A

ScriptableObject はデータを管理するためのクラスで、シーン間でデータを保存する際に有効です。シングルトンはオブジェクトの管理に向いています。

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