ゲーム開発
Unity
UnrealEngine
C++
Blender
Houdini
ゲーム数学
ゲームAI
グラフィックス
サウンド
アニメーション
GBDK
制作日記
IT関連
ツール開発
フロントエンド関連
サーバサイド関連
WordPress関連
ソフトウェア設計
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
ラーメン日記
四コマ漫画
その他
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • Blender
    • Houdini
    • ゲーム数学
    • ゲームAI
    • グラフィックス
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • IT関連
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • WordPress関連
    • ソフトウェア設計
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • ラーメン日記
    • 四コマ漫画
      • その他
        • おすすめアイテム
        • おもしろコラム
      1. ホーム
      2. 20220227_01

      【Unity】ServiceLocatorを使ってサービスクラスをまとめる

      Unityソフトウェア設計デザインパターン
      2022-02-27

      マイケル
      マイケル
      みなさんこんにちは!
      マイケルです!
      エレキベア
      エレキベア
      こんにちクマ〜〜〜
      マイケル
      マイケル
      今日は サービスロケータ(Service Locator)
      というデザインパターンを紹介するぜ!
      エレキベア
      エレキベア
      なんだそれはクマ
      マイケル
      マイケル
      簡単に言うと クラス全般から呼ばれるようなサービスクラスを一箇所に集めて
      依存関係をまとめることのできるパターン
      のことだね・・・。
      エレキベア
      エレキベア
      よくわからんクマ・・・
      マイケル
      マイケル
      便利で手軽に使えるパターンだから、Unityで使って覚えてみよう!
      ただ、今はDIコンテナを使う方が推奨されることが多いようだから、その辺も踏まえて解説するね!
      エレキベア
      エレキベア
      そこまで便利なら聞いてやるクマ

      参考書籍

      マイケル
      マイケル
      参考書籍は以下になります!
      こちらはゲーム開発に特化したデザインパターンを扱った書籍で、WEBでも公開されているから一読の価値ありです!

      Game Programming Patterns ソフトウェア開発の問題解決メニュー impress top gearシリーズ


      WEB版:

      Game Programming Patterns

      マイケル
      マイケル
      ちなみにデザインパターン全般については下記記事でも
      まとめてあるのでこちらもよければ見てみてください!
      エレキベア
      エレキベア
      懐かしいクマ〜〜〜

      サービスロケータとは

      サービスロケータの概要

      マイケル
      マイケル
      サービスロケータはさっきも言った通り、
      クラス全般から呼ばれるようなサービスクラスを一箇所に集めて
      依存関係をまとめることのできるパターン
      のことだ!
      マイケル
      マイケル
      例えば、いろんなクラスから呼び出されるクラスがあったとして、
      これを直接呼び出した場合には下記のように複雑な依存関係になってしまいます。

      ↑直接呼び出した例
      マイケル
      マイケル
      ここでServiceLocatorクラスに各サービスクラスを登録して
      間に挟むことで、依存関係をまとめることができるというわけです!

      ↑Service Locatorを挟んだ例
      エレキベア
      エレキベア
      おお〜〜
      確かにこれなら綺麗クマ〜〜〜〜
      マイケル
      マイケル
      更に 各サービスクラスのインタフェースを登録して
      呼び出し元からはインタフェースを指定
      するようにすれば、
      サービスクラスの差し替えも簡単に行えるようになります!

      ↑各サービスクラスのインタフェースを登録する
      エレキベア
      エレキベア
      差し替えることができると何うれしいクマ?
      マイケル
      マイケル
      使うサービスを変更したくなった時に応用が効くのはもちろん、
      Dummyクラスに差し替えることでテストができるようにしたり、
      開発時にだけログ出力するクラスに差し替える
      といったことができるようになるよ!

      ↑サービスの差し替えが可能
      エレキベア
      エレキベア
      なるほどクマ
      それなら柔軟性があっていいクマね

      DIコンテナとどちらを使用すべきか?

      マイケル
      マイケル
      他に依存性を管理する手法としてDIコンテナが有名です。
      ServiceLocatorとDIコンテナどちらを使用するべきか?については下記の記事が分かりやすくまとまっていました!

      Service LocatorとDependency InjectionパターンとDI Container

      マイケル
      マイケル
      本来不要なServiceLocatorクラスへの依存が発生してしまうこと
      呼び出し側のコードが少し複雑になることから、
      基本的にはDIコンテナの使用が推奨されることが多いみたいです。
      マイケル
      マイケル
      ただUnityでDIコンテナを利用するには
      ZenjectVContainerといったDIライブラリを使用することが多く、学習コストと導入コストもそれなりに高いです。
      エレキベア
      エレキベア
      Zenjectは最近よく聞くクマ〜〜
      マイケル
      マイケル
      そのため個人レベルで規模が小さい場合にはServiceLocatorを使用し、
      慣れてきたら上記のようなDIライブラリを使うことに挑戦するというのも一つの手かと思います!
      エレキベア
      エレキベア
      クマはいきなりDIライブラリを使ってみるから
      この記事は見なくていいクマ
      マイケル
      マイケル
      そうか・・・。

      Unityでの実装

      マイケル
      マイケル
      概要をざっと説明したところで、
      実際にコードを書いてみましょう!
      ServiceLocatorクラスの例としては下記のようになります!
      using System;
      using System.Collections.Generic;
      
      namespace Services
      {
          /// <summary>
          /// サービスロケータ
          /// </summary>
          public static class ServiceLocator
          {
              /// <summary>
              /// コンテナ
              /// </summary>
              private static readonly Dictionary<Type, object> Container;
      
              /// <summary>
              /// コンストラクタ
              /// </summary>
              static ServiceLocator()
              {
                  Container = new Dictionary<Type, object>();
              }
      
              /// <summary>
              /// サービス取得
              /// </summary>
              public static T Resolve<T>()
              {
                  return (T) Container[typeof(T)];
              }
      
              /// <summary>
              /// サービス登録
              /// </summary>
              public static void Register<T>(T instance)
              {
                  Container[typeof(T)] = instance;
              }
      
              /// <summary>
              /// サービス登録解除
              /// </summary>
              public static void UnRegister<T>()
              {
                  Container.Remove(typeof(T));
              }
          }
      }
      
      ↑ServiceLocatorクラス
      エレキベア
      エレキベア
      思ったよりシンプルクマね
      マイケル
      マイケル
      型をkeyとしてDictionaryに登録することで、
      呼び出し側からは型を指定して呼び出すことができるようになっているよ。
      マイケル
      マイケル
      例として、下記のPlayerPrefsServiceクラスを登録してみましょう!
      using UnityEngine;
      
      namespace Services
      {
          /// <summary>
          /// PlayerPrefsService
          /// </summary>
          public class PlayerPrefsService : IPlayerPrefsService
          {
              public void SetInt(string key, int value)
              {
                  PlayerPrefs.SetInt(key, value);
              }
      
              public int GetInt(string key)
              {
                  return PlayerPrefs.GetInt(key);
              }
          }
      }
      
      ↑PlayerPrefsServiceクラス
      
      namespace Services
      {
          /// <summary>
          /// PlayerPrefsService
          /// </summary>
          public interface IPlayerPrefsService
          {
              public void SetInt(string key, int value);
              public int GetInt(string key);
          }
      }
      
      ↑PlayerPrefsServiceインタフェース
      マイケル
      マイケル
      このクラスをプロジェクト初期化等のタイミングでServiceLocatorに登録します。
      using Services;
      using UnityEngine;
      
      /// <summary>
      /// プロジェクト初期化クラス
      /// </summary>
      public static class ProjectInitializer
      {
          /// <summary>
          /// 初期化処理(シーンのロード前に呼ばれる)
          /// </summary>
          [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
          private static void Initialize()
          {
              // サービス登録
              ServiceLocator.Register<IPlayerPrefsService>(new PlayerPrefsService());
          }
      }
      
      ↑プロジェクト初期化時に登録
      マイケル
      マイケル
      あとは各クラスでServiceLocator.Resolveを呼び出すことで
      各サービスを使用することができます!
      using Services;
      using UnityEngine;
      using Utils;
      
      namespace Scenes.Common
      {
          /// <summary>
          /// PlayerPrefs管理クラス
          /// </summary>
          public static class SamplePlayerPrefs
          {
              /// <summary>
              /// スコア
              /// </summary>
              public static int Score
              {
                  get => GetPlayerPrefsIntValue(KeyScore);
                  set => SetPlayerPrefsIntValue(KeyScore, value);
              }
              private const string KeyScore = "Score";
              
              private static void SetPlayerPrefsIntValue(string key, int value)
              {
                  ServiceLocator.Resolve<IPlayerPrefsService>().SetInt(key, value);
              }
      
              private static int GetPlayerPrefsIntValue(string key)
              {
                  return ServiceLocator.Resolve<IPlayerPrefsService>().GetInt(key);
              }
          }
      }
      
      ↑サービスクラスの呼び出し例
      エレキベア
      エレキベア
      呼び出し側も簡単に呼び出せるクマね
      マイケル
      マイケル
      ちなみにMonoBehaviorクラスを継承したサービスクラスを登録したい場合には、
      下記のようにAddComponentした戻り値を登録するようにすればOKです!
      using Services;
      using UnityEngine;
      
      /// <summary>
      /// プロジェクト初期化クラス
      /// </summary>
      public static class ProjectInitializer
      {
          /// <summary>
          /// 初期化処理
          /// シーンのロード前に呼ばれる
          /// </summary>
          [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
          private static void Initialize()
          {
              // サービス登録
              var serviceLocator = new GameObject("ServiceLocator");
              ServiceLocator.Register<IXXXService>(serviceLocator.AddComponent<XXXService>());
          }
      }
      
      ↑MonoBehaviorクラスを登録した例
      マイケル
      マイケル
      とはいえ共通となるサービスクラスにMonoBehaviorクラスを継承させるのは
      極力避けた方がよさそうですね・・・。
      エレキベア
      エレキベア
      やむを得ない時もあるのだろうかクマ・・・。

      おわりに

      マイケル
      マイケル
      というわけで今日はサービスロケータの実装について紹介しました!
      どうだったかな??
      エレキベア
      エレキベア
      想像していたよりもシンプルで使いやすそうだったクマ
      ただ慣れてきたらDIコンテナも使ってみたいクマね
      マイケル
      マイケル
      シンプルで手軽に使えるサービスロケータ!
      呼び出し処理が散らばっている時、テスト処理が書けない時には使ってみてはいかがでしょうか?
      マイケル
      マイケル
      それでは今日はこの辺で!
      アデュー!!!
      エレキベア
      エレキベア
      クマ〜〜〜〜〜

      Unityソフトウェア設計デザインパターン
      2022-02-27

      関連記事
      【Unity】Timeline × Excelでスライドショーを効率よく制作する
      2024-10-31
      【Unity】Boidsアルゴリズムを用いて魚の群集シミュレーションを実装する
      2024-05-28
      【Unity】GoでのランキングAPI実装とVPSへのデプロイ方法についてまとめる【Go言語】
      2024-04-14
      【Unity】第二回 Wwiseを使用したサウンド制御 〜インタラクティブミュージック編〜
      2024-03-30
      【Unity】第一回 Wwiseを使用したサウンド制御 〜基本動作編〜
      2024-03-30
      【Unity】第二回 CRI ADXを使用したサウンド制御 〜インタラクティブミュージック編〜
      2024-03-28
      【Unity】第一回 CRI ADXを使用したサウンド制御 〜基本動作、周波数解析編〜
      2024-03-28
      【Unity】サウンドミドルウェアに依存しない設計を考える【CRI ADX・Wwise】
      2024-03-27