Unity3Dでシングルトンを使用する



エントリー



少し深刻なプロジェクトでも、コードを適切に編成する必要がある場合でも、あらゆるものを編成します。 Unity3D環境で開発されたプロジェクトも例外ではなく、プロジェクトが成長するにつれて、その組織はソース製品として小さな役割を果たすことができます。



この記事では、シングルトン(一般にデザインパターンと呼ばれます)としてコードを整理するためのこのようなアプローチを説明するだけでなく、イベント処理に対する最も快適で正しいアプローチを検討し、コード全体の利便性について話しました。



そのため、この記事では次の点に対処します。



  1. はじめに
  2. シングルトンの仕組み
  3. Unity3Dでのシングルトン実装
  4. シングルトン相互作用
  5. シングルトンの長所と短所について
  6. いくつかの実用的な例
  7. おわりに


シングルトンの仕組み



シングルトンパターンの動作スキームを理解する前に、それが何であるかを理解する必要があります。 シングルトン (Singleton)は、ゲームスクリプトを管理する特定のマネージャーです。 原則として、シングルトーンは再初期化せずにシーン間で保存されます(グローバルオブジェクトなど)。



最も単純な例を使用すると、シングルトンは次のように説明できます。

ゲームには、常にゲーム内にあり、任意のスクリプトからアクセスできるグローバルオブジェクト(マネージャー)が含まれています。これは、音楽、ネットワーク機能、ローカリゼーション、および単一コピーで使用されるすべてを管理するためのクラスの作成に役立ちます。 マネージャーに加えて、ゲームでは、インターフェイス、ゲームキャラクター、ゲームワールドのオブジェクトなど、複数のオブジェクトが使用されます。 これらのオブジェクトはすべて、マネージャーと密接に連携して、究極の目標を達成します。



たとえば、モバイルゲームの仕事の編成を考えてみましょう。



この場合、シングルトンはシーンからシーンへ移動するオブジェクトであり、ゲームシーン(ゲーム全体)内の特定のタイプのすべてのオブジェクトを制御するために使用されます。



次の図では、モバイルターンベースのオンラインゲームの例として、操作スキームを示しています。

画像

全体像を把握するには、このゲームのアーキテクチャを検討してください。 この場合、シングルトンオブジェクトに加えて、次の要素があります。



  1. シングルトンをロードするためのオブジェクト(いわゆるBootstrapクラス)
  2. ゲームロジックオブジェクト(スクリプト管理オブジェクト)
  3. コントローラー(例:プレーヤーコントローラー)
  4. データモデル(サーバーから受信したデータをシリアル化するためのオブジェクト)
  5. インターフェースオブジェクト
  6. その他の静的なゲームオブジェクト


したがって、プロジェクトの便利でクリーンなアーキテクチャを作成することができます。これにより、将来的にスケーリングが困難になることはありません。



Unity3Dでのシングルトン実装



わかりやすくするために、モバイルオンラインゲームのアーキテクチャを引き続き検討し、上記で説明したすべてが実際にどのように見えるかを確認します。



管理クラス



設計方法全体の基礎は、クラスマネージャー自身です。このマネージャーは、1つのコピーでゲームに参加しており、いつでも呼び出すことができます。 このようなマネージャークラスを作成するには、次のコードを記述できます。



using System.Collections; using System.Collections.Generic; using UnityEngine; //============================================= // Audio Manager //============================================= public class AudioManager: MonoBehaviour { public static AudioManager instance = null; //   // ,     void Start () { // ,    if (instance == null) { //     instance = this; //      } else if(instance == this){ //       Destroy(gameObject); //   } //    ,     //       DontDestroyOnLoad(gameObject); //     InitializeManager(); } //    private void InitializeManager(){ /* TODO:      */ } }
      
      





上記の例では、ゲームマネージャーの1人の基礎を作成しました(この場合、これはオーディオマネージャーです)。 Start()メソッドで初期化する必要はありません。 このためにAwake()メソッドを使用して、シーンの開始前にオブジェクトの準備を整えることもできます。



次に、ゲームにサウンドと音楽のパラメーターをロードして保存できるようにクラスを追加します。



 using System.Collections; using System.Collections.Generic; using UnityEngine; //============================================= // Audio Manager //============================================= public class AudioManager: MonoBehaviour { public static AudioManager instance = null; //   public static bool music = true; //    public static bool sounds = true; //    // ,     void Start () { // ,    if (instance == null) { //     instance = this; //      } else if(instance == this){ //       Destroy(gameObject); //   } //    ,     //       DontDestroyOnLoad(gameObject); //     InitializeManager(); } //    private void InitializeManager(){ //        PlayerPrefs music = System.Convert.ToBoolean (PlayerPrefs.GetString ("music", "true")); sounds = System.Convert.ToBoolean (PlayerPrefs.GetString ("sounds", "true")); } //      public static void saveSettings(){ PlayerPrefs.SetString ("music", music.ToString ()); //    PlayerPrefs.SetString ("sounds", sounds.ToString ()); //    PlayerPrefs.Save(); //   } }
      
      





できた これで、オーディオマネージャーはサウンドと音楽の設定を読み込んで保存できます。 次の質問は、どのように使用できるかです。 以下の例では、マネージャーとの対話の簡単な例を示しました。



 using System.Collections; using System.Collections.Generic; using UnityEngine; //============================================= // Audio Muter Class //============================================= public class AudioMuter : MonoBehaviour { //   public bool is_music = false; //    ? //   private AudioSource _as; // Audio Source private float base_volume = 1F; //   //      void Start () { _as = this.gameObject.GetComponent<AudioSource> (); //   AS base_volume = _as.volume; //     AS } //         void Update () { //   ,     if (is_music) { _as.volume = (AudioManager.music)?base_volume:0F; } else { _as.volume = (AudioManager.sounds)?base_volume:0F; } } }
      
      





上記の例では、マネージャーの静的フィールドmusicおよびsoundに基づいて、オブジェクトのAudioSourceを自動的に有効化/無効化できるコンポーネントを作成しました。



ご注意
シングルトンをデリゲートおよびコルーチン関数と組み合わせて使用​​すると、たとえば、エラー処理やネットワーク要求を実装するための理想的なマネージャーを作成できます。



ブートストラップクラス



既に複数のマネージャーがいるとします。 オブジェクトとしてオブジェクトを各シーンに個別にアップロードしないために、事前に作成されたプレハブからオブジェクトをフックする、いわゆるBootstrapクラスを作成できます。 Boostrapオブジェクトには義務はありませんが、利便性のためだけに使用することをお勧めします。



Boostrapクラスを検討してください。

 using System.Collections; using System.Collections.Generic; using UnityEngine; //============================================= // Game Classes Loader //============================================= public class GameLoader : MonoBehaviour { //    public GameObject game_manager; // Game Base Manager public GameObject audio_manager; // Audio Manager public GameObject lang_manager; // Language Manager public GameObject net_manager; // Network Manager //    (  ) void Awake () { //    if (GameBase.instance == null) { Instantiate (game_manager); } //    if (AudioManager.instance == null) { Instantiate (audio_manager); } //    if (LangManager.instance == null) { Instantiate (lang_manager); } //    if (NetworkManager.instance == null) { Instantiate (net_manager); } } }
      
      





Boostrapを使用して、ゲームのすべてのシーンに配置することなく、新しいマネージャープレハブを追加できます。



データモデル



データモデルの使用はオプションですが、それらを作成して、サーバーからのデータを迅速に処理し、繰り返し要求することなくクライアントにデータを保存できます。 (たとえば、ゲーム内のユーザーに関するデータをキャッシュするため)。



私たちの場合、サーバーへのリクエストの後、モデル内の受信データをアンロードし、それらのデータを処理します。 最も単純なデータモデルを考えてみましょう。



 using System.Collections; using System.Collections.Generic; using UnityEngine; [System.Serializable] public class responceModel{ public bool complete = false; //   public string message = ""; //    (   complete = false) }
      
      





上記の例では、JSON形式でサーバーから受信した基本的なステータスを処理するために使用されるデータモデルがあります。 したがって、ゲームサーバーにアクセスすると、2種類の応答が返されます。



連絡が成功すると、次の形式の回答が得られます。



 { complete: true, //   data: {} //     }
      
      





エラーが発生すると、次の形式の回答が得られます。



 { complete: false, //   message: "" //    }
      
      





したがって、JSONデシリアライゼーションとデータモデルを使用して、サーバーの応答を解析できます。



 responceModel responce = JsonUtility.FromJson<responceModel>(request.text); //  JSON if(responce.complete){ /* TODO:  -    */ Debug.Log(responce.data); }else{ /* TODO:   */ Debug.Log(responce.message); }
      
      





コントローラー



コントローラーは、ゲーム内の複数のオブジェクト(ゲーム内の対戦相手、プレイヤーのコントローラーなど)の操作に役立ちます。 コントローラーは最も一般的な方法で作成され、ゲーム内のコンポーネントとしてコンポーネントにしがみつきます。



シンプルなプレーヤーコントローラーの例:

 using System.Collections; using System.Collections.Generic; using UnityEngine; //============================================= // PLAYER CONTROLLER //============================================= public class PlayerController : MonoBehaviour { //   [Header ("Player Body Parts")] public GameObject[] hairs; public GameObject[] faces; public GameObject[] special; //   void Start () { } //   void Update () { } //       public void updateParts (){ //    for (int i = 0; i < hairs.Length; i++) { if (i == NetworkManager.instance.auth.player_data.profile_data.body.hairs) { hairs [i].SetActive (true); } else { hairs [i].SetActive (false); } } /* TODO:       */ } }
      
      





上記の例では、プレーヤーの体の部分を更新するコードの部分で、ネットワークマネージャーに直接接続されたプレーヤーのプロファイルに関する情報を含むデータモデルを使用します。



次の行を検討してください。



 if (i == NetworkManager.instance.auth.player_data.profile_data.body.hairs){
      
      





ここでは、サイクルのインデックスがプレーヤーデータモデルのヘア識別子と比較されることがわかります。 このモデルは、Network Managerオブジェクト( NetworkManager )のインスタンスに提示されます。このインスタンスでは、認証( auth )を操作するオブジェクトが初期化され、その内部にデータモデルが配置されます( player_data => profile_data => body )。



シングルトン相互作用



マネージャーと対話するには、オブジェクトのインスタンス(インスタンス)または静的パラメーターの直接呼び出しを使用します。



インスタンスの使用例:



 public bool _hair = NetworkManager.instance.auth.player_data.profile_data.body.hairs;
      
      





上記の例では、 インスタンスプロパティを使用して、 NetworkManagerマネージャーでプレーヤーの髪のデータを取得しました。



静的パラメーターとの直接相互作用の例:



 public bool _sounds = AudioManager.sounds;
      
      





上記の例では、 AudioManagerマネージャーのサウンドの静的プロパティを直接使用しました。



シングルトンの長所と短所について



長所:



+インスペクタのスクリプトフィールドの定数設定と説明は不要

+マネージャーには、インスタンスプロパティからアクセスできます。

+便利なコードリファクタリング

+コンパクトなコード



短所:



-強力なコード依存性

-単一のコピー内のスクリプトマネージャーのみへのアクセス





いくつかの実用的な例



デリゲートを使用する

デリゲート関数をマネージャーに追加することで、コードの応答性を高めることができます。 したがって、コールバックメソッドは関数ごとに作成できます。



この例を考えてみましょう:



 //  - public delegate void OnComplete(); public delegate void OnError(string message); //   ,   public void checkNumber(int number, OnComplete success, OnError fail){ if(number<10){ success(); //   OnComplete }else{ fail("    10!"); //     } }
      
      





上記の簡単な例では、数値パラメーターが10より小さい場合に成功関数を、パラメーターが10以上の場合にエラー関数をそれぞれ呼び出すメソッドを作成しました。



このメソッドは次の方法で使用できます。



 public void testMethod(){ int _number = Random.Range(0,50); //   //       checkNumber(_number, (()=>{ //    Success /* TODO:  -    */ Debug.Log(" !"); }), ((string text)=>{ //    Fail Debug.Log(text); //  ,    Callback  testMethod(); //     ,     <10 })); }
      
      





このようにして、制御された結果を持つコードを作成できます。 現在、Singletonを使用したユースケースにスムーズに移行しています。



シングルトンのコルーチンデリゲート



サーバーとの最も便利で正しい対話のために、多数のコルーチン関数とデリゲートを使用することができます。これにより、非同期リクエストを送信し、サーバーの応答を処理することができます。 以下では、コルーチン関数とデリゲートを使用したNetworkManagerの例を準備しました。



このNetworkManagerの例を考えてみましょう。



 using System.Collections; using System.Collections.Generic; using UnityEngine; //============================================= // Network Manager //============================================= public class NetworkManager : MonoBehaviour { //   public static NetworkManager instance = null; //   public static string server = "https://mysite.com/api"; // URL  //      public APIAuth auth; //   public APIUtils utils; //   //   void Awake () { //    if (instance == null) { instance = this; } else if(instance == this){ Destroy(gameObject); } //   ,      DontDestroyOnLoad(gameObject); //    InitializeManager(); } //   public void InitializeManager(){ auth = new APIAuth (server + "/auth/"); //    utils = new APIUtils (server + "/utils/"); //    } } //============================================= // API Auth Manager //============================================= public class APIAuth{ //   private string controllerURL = ""; // Controller URL //============================================= //   //============================================= public APIAuth(string controller){ controllerURL = controller; } //============================================= //    //============================================= public delegate void OnLoginComplete(); public delegate void OnLoginError(string message); public IEnumerator SingIn(string login, string password, OnLoginComplete complete, OnLoginError error){ //     WWWForm data = new WWWForm(); data.AddField("login", login); data.AddField("password", password); data.AddField("lang", LangManager.language); //     WWW request = new WWW(controllerURL + "/login/", data); yield return request; //    if (request.error != null) { //    error ("     "); } else { //      try{ responceModel responce = JsonUtility.FromJson<responceModel>(request.text); if(responce.complete){ complete(); //  Success Callback }else{ error (responce.message); // Do error Debug.Log("API Error: " + responce.message); } }catch{ error ("    "); Debug.Log("   .  : " + request.text); } } } /* TODO:         */ } //============================================= //          //============================================= public class APIUtils{ private string controllerURL = ""; //    public APIUtils(string controller){ controllerURL = controller; } //============================================= //     //============================================= public delegate void OnClientVersionChecked(); public delegate void OnClientVersionError(string message); public IEnumerator CheckClientVersion(string version, OnClientVersionChecked complete, OnClientVersionError error){ //   WWWForm data = new WWWForm(); data.AddField("version", version); data.AddField("lang", LangManager.language); //   WWW request = new WWW(controllerURL + "/checkVersion/", data); yield return request; //   if (request.error != null) { error ("     "); } else { try{ responceModel responce = JsonUtility.FromJson<responceModel>(request.text); if(responce.complete){ complete(); }else{ error (responce.message); Debug.Log("API Error: " + responce.message); } }catch{ error ("    "); Debug.Log("   .  : " + request.text); } } } }
      
      





これで、意図した目的に使用できます。



 //      public void checkMyGame(){ StartCoroutine(NetworkManager.instance.utils.CheckClientVersion(Application.version, (()=>{ //     /* TODO:           */ }), ((string msg) => { //    /* TODO:         */ Debug.Log(msg); }))); }
      
      





したがって、NetworkManagerコードを実行し、ゲームの任意のシーンからコールバック関数を使用してそのメソッドを制御できます。



おわりに



一般に、Unity3Dのプロジェクトのフレームワーク内のシングルトンとパターンの一般的なテーマは別の本に値し、1つの記事ですべてを伝えることはできません。 以下に、これについての詳細を読むことができるいくつかの有用な資料を添付しました。



有用な材料のリスト:






All Articles