【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 デバッグメニュー編

2年目のゲームプログラマがゲーム会社でUnityを使用して
スマートフォン向けのゲームを開発して感じたことをまとめておきます

* [【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 コーディング編](http://qiita.com/baba_s/items/f2ad850dd7fc84165e96)
* [【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 拡張メソッド編](http://qiita.com/baba_s/items/a0490ea36f6a5bcf8b07)
* [【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 デバッグメニュー編](http://qiita.com/baba_s/items/08cecb26e656900c3bb3)
* [【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 UI編](http://qiita.com/baba_s/items/c8d25456c0ee4ef52f94)
* [【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 アセットバンドル編](http://qiita.com/baba_s/items/a64b97de1b3cd41460f9)

# デバッグメニュー

デバッグメニューを作成することでゲームのデバッグが容易になります
今回は実際にどんな感じでデバッグメニューを作成したのかを紹介していきます

## デバッグメニュー用のシーンの枠組み作成

 デバッグメニュー用のシーンファイルとスクリプトを作成します
 
![](https://dl.dropboxusercontent.com/u/52568393/debugmenu/0001.png)

デバッグメニュー用のシーンに「DebugMenu」オブジェクトを作成します

![](https://dl.dropboxusercontent.com/u/52568393/debugmenu/0003.png)

「DebugMenu」オブジェクトに「DebugMenu.cs」をアタッチします

![](https://dl.dropboxusercontent.com/u/52568393/debugmenu/0002.png)

「DebugMenu.cs」を下記のように編集します

```cs
using UnityEngine;

public class DebugMenu : MonoBehaviour
{
     private void Awake()
     {
          DontDestroyOnLoad( gameObject );
     }
}
```
`Object.DontDestroyOnLoad`関数にデバッグメニューのルートのオブジェクトを指定することで
シーンを遷移してもデバッグメニューが消えないようにしています

あとはゲーム起動時に呼び出されるコンポーネントの`Awake`関数で
`Application.LoadLevelAdditive`関数を使用して読み込みます

```cs
using UnityEngine;

public class Global : MonoBehaviour
{
     private void Awake()
     {
          // RELEASE シンボルが定義されている場合は
          // デバッグメニューのシーンを読み込みません
#if RELEASE
#else
          Application.LoadLevelAdditive( "DebugMenu" );
#endif
     }
}
```

この時に特定のシンボルが定義されている場合はデバッグメニューを読み込まないようにすることで
リリース版のアプリではデバッグメニューが表示されないようにします

これでデバッグメニューを作成する準備が完了です
あとは作成したデバッグメニューのシーンにOnGUIやuGUI、NGUIを使用してUIを配置していきます

### 注意：可能ならOnGUIで作成しない

デバッグメニューのUIを作成する時はuGUIやNGUIを使用することをオススメします
OnGUIは下記のような問題があるためです

* ドローコールが多くて重い
	* デバッグメニューがアプリの処理速度に影響してしまいます
* 実機で動作確認する時に解像度の調整が必要
	* 特にタブレットの場合は文字がほぼ見えない状態になってしまいます

（この記事では便宜上OnGUIでコードサンプルを記述しています）

### デバッグメニューのシーンをビルド対象外にする

前述の方法だと、リリース版のアプリでデバッグメニューを表示しないようにできますが
デバッグメニューのシーンファイル自体はアプリに含まれてしまいます

JenkinsなどのCIツールを使用してアプリをビルドしている場合は
下記のようなスクリプトでアプリをビルドすることで
デバッグメニューのシーンファイルをビルドの対象から外すことができます

```cs
using System.Linq;
using UnityEditor;

public static class BuildBatch
{
     public static void ReleaseBuildAndroid()
     {
          var levels = EditorBuildSettings.scenes
               .Select( c => c.path )
               .ToList();

          levels.Remove( "DebugMenu" );

          BuildPipeline.BuildPlayer(
               levels.ToArray(),
               "app.apk",
               BuildTarget.Android,
               BuildOptions.None
          );
     }
}
```

## 見える化

Unityエディタ上であればInspectorやMonoDevelopのデバッグ機能を使用することで素早くパラメータを確認できますが
実機で動作確認するときは基本的にはログ出力でしかパラメータを確認できないので
デバッグメニューで各種パラメータを見れるようにしておくと実機でデバッグする時に役に立ちます

### Unityのバージョン

`Application.unityVersion`

多くのプラグインを使用している場合、想定していないバージョンのUnityでアプリをビルドしてしまうと
挙動がおかしくなる可能性があるので、デバッグメニューで見れるようにしておくといざというときに役に立ちます

```cs
GUILayout.Label( Application.unityVersion );
```

### タイムスケール

`Time.timeScale`

`Time.timeScale`を使用してゲームの一時停止を実装している場合`Time.timeScale`に0を代入したままになってしまい
ゲームがフリーズしてしまったように見えることがあったのでこれも表示しておくと良いかもしれません

```cs
GUILayout.Label( Time.timeScale.ToString() );
```

### ゲーム独自の情報

- プレイヤーの情報
- 所持ユニットの一覧
- 所持アイテムの一覧
- フレンドの一覧
- マスタの情報
- etc.

このようなゲーム独自の情報もデバッグメニューで見れるようにしておくと
いざ不具合が発生した時にパラメータが正常かどうかをすぐに確認できます
クラスのパラメータを手早く表示したい場合は[こちら](http://qiita.com/baba_s/items/f2ad850dd7fc84165e96)で紹介している方法でJSON形式で表示するのがオススメです

```cs
var obj = new PlayerData();
var json = JsonManager.ToJson( obj );
GUILayout.Label( json );
```
```
{
    "Id" : 25,
    "Name" : "ピカチュウ"
}
```

### Unity APIの情報

Unity APIのクラスの情報もJSON形式で表示しておくと良いかなと思います
特に`Application`クラスと`SystemInfo`クラスの情報は表示しておいて損はないです

#### `Application`クラス

`Application`クラスを参照することで現在のシーン名やビルドしたシーンの数を確認することができます

```cs
var obj = new Application();
var json = JsonManager.ToJson( obj );
GUILayout.Label( json );
```
```
{
    "loadedLevel" : 0,
    "loadedLevelName" : "DebugMenu",
    "isLoadingLevel"  : true,
    "levelCount"      : 1,
    "streamedBytes"   : 0,
    ...
```

#### `SystemInfo`クラス

`SystemInfo`クラスを参照すると端末のOSバージョンやメモリ情報を確認できます
UnityでAndroidやiOSのアプリを開発していると端末によって不具合が発生したりしなかったりするため
`SystemInfo`の情報もデバッグメニューで見れるようにしておくと役に立ちます

例)
NGUIのUIInputクラスをAndroid4.2.2で使用すると不具合が発生することがある
https://www.facebook.com/groups/unityuserj/permalink/727691427290800/

```cs
var obj = new SystemInfo();
var json = JsonManager.ToJson( obj );
GUILayout.Label( json );
```
```
{
    "operatingSystem" : "Windows 7 Service Pack 1 (6.1.7601) 64bit",
    "processorType"   : "Intel(R) Core(TM) i5 CPU M 480 @ 2.67GHz",
    "processorCount"  : 4,
    "systemMemorySize" : 3893,
    "graphicsMemorySize" : 128,
    ...
```

### シーン遷移の情報

現在のシーンは`Application.loadedLevelName`で取得可能です

現在のシーン名をデバッグメニューに表示するのはあまり役に立たないかもしれませんが
シーン遷移時にシーン名を配列やリストに記憶しておき
シーン遷移の履歴をデバッグメニューで見れるようにしておくと不具合が発生した時に再現しやすくなります

```cs
using System.Collections.Generic;
using UnityEngine;

public static class LevelLoader
{
    private static readonly List<string> mHistory = new List<string>();
    
    public static IList<string> History
    {
         get { return mHistory; }
     }
    
    public static void Load( string name )
    {
        Application.LoadLevel( name );
        mHistory.Insert( 0, name );
    }
}
```

このようなクラスを作成しておき、実際にシーンを遷移するときは`LevelLoader.Load`関数を呼び出すようにすることで
`LevelLoader.History`プロパティにシーン遷移の履歴が溜まっていきます
あとは`LevelLoader.History`プロパティの内容をデバッグメニューで表示します

```cs
foreach ( var n in LevelLoader.History )
{
     GUILayout.Label( n );
}
```

### FPS

ゲームを作成していると実装方法によってはゲームの処理速度が低下してしまうことがあるので
下記のようなスクリプトを作成していつでもFPSを見られるようにしておくことをオススメします

```cs
using UnityEngine;

public class FpsMeasure : MonoBehaviour
{
     public float Interval = 0.5f;
    
     private int mFrame;
     private float mOldTime;
     private float mFrameRate;

     public float FrameRate { get; private set; }

     private void Awake()
     {
          mOldTime = Time.realtimeSinceStartup;
     }

     private void Update()
     {
          mFrame++;
          var time = Time.realtimeSinceStartup - mOldTime;
          if ( time < Interval )
          {
               return;
          }
          mFrameRate = mFrame / time;
          mOldTime = Time.realtimeSinceStartup;
          mFrame = 0;
     }
}
```

```cs
var fps = GetComponent<FpsMeasure>();
GUILayout.Label( fps.FrameRate.ToString( "0.00" ) );
```

### ゲームオブジェクトの一覧

現在、シーン上に存在するゲームオブジェクトの一覧をデバッグメニューで見られるようにしておくと
実機でも不要なゲームオブジェクトが生成されていないかどうかを確認できるようになります
また、一部のオブジェクトが表示されていない場合に

* `activeSelf`が`false`になっているのか
* 前後関係がおかしくなっているのか
* そもそもオブジェクトが存在しないのか

といったこともわかるようになります

```cs
var list = Object.FindObjectsOfType<GameObject>();
foreach ( var n in list )
{
     GUILayout.Label( n.name );
}
```

`Object.FindObjectsOfType`関数を使用することで
Hierarchyから全てのゲームオブジェクトを取得可能ですが
この関数は非アクティブのゲームオブジェクトを取得できません

`GameObject.GetComponentsInChildren`関数を使用すると
非アクティブなゲームオブジェクトを取得可能なので
次のように`Object.FindObjectsOfType`関数と
`GameObject.GetComponentsInChildren`関数を併用することで
概ねほぼ全てのゲームオブジェクトを取得可能です

```cs
var list = Object
     .FindObjectsOfType<GameObject>()
     .Where( c => c.transform.parent == null )
     .SelectMany( c => c.GetComponentsInChildren<Transform>( true ) )
     .Select( c => c.gameObject )
     .ToArray();
```

あとは表示したいパラメータをデバッグメニューで表示するだけです
例えば親子関係の一覧を表示するだけでも、どのオブジェクトがシーン上に存在するのかが確認できるようになります

```cs
using System.Collections.Generic;
using UnityEngine;

public static class GameObjectExtensions
{
     public static string GetPath( this GameObject self )
     {
          var list = new List<GameObject>();
          for ( var n = self.transform.parent; n != null; n = n.parent )
          {
               list.Add( n.gameObject );
          }
          list.Reverse();
          list.Add( self );
          var names = list.Select( c => c.name ).ToArray();
          return string.Join( "/", names );
     }
}
```

このような拡張メソッドを用意してループ構文で表示するだけで親子関係の一覧が表示できます

```cs
foreach ( var n in list )
{
     GUILayout.Label( n.GetPath() );
}
```

![](https://dl.dropboxusercontent.com/u/52568393/debugmenu/0007.png)

```
Main Camera
UIRoot
UIRoot/Base
UIRoot/Base/Button
UIRoot/Base/Button/Label
```

### デバッグログの表示

![](https://dl.dropboxusercontent.com/u/52568393/debugmenu/0008.png)

実機でもAndroidであればlogcatを使用すれば、iOSであればXcodeを使用すればログを確認できますが
ログを確認したい時に毎回PCと実機をつなぐのは手間になります

Unity APIの`Application.RegisterLogCallback`を使用することで
出力されるすべてのログを検知することができます

```cs
class LogData
{
	public string Condition;
	public string StackTrace;
	public LogType Type;
	
	public LogData( string condition, string stackTrace, LogType type )
	{
		Condition = condition;
		StackTrace= stackTrace;
		Type = type;
	}
}

List<LogData> mList = new List<LogData>();

void Awake()
{
	Application.RegisterLogCallback( HandleLog );
}

void HandleLog( string condition, string stackTrace, LogType type )
{
	mList.Add( new LogData( condition, stackTrace, type ) );
}

void OnGUI()
{
	foreach ( var n in mList )
	{
		GUILayout.Label( n.Condition );
	}
}
```