2年目のゲームプログラマがゲーム会社でUnityを使用して
スマートフォン向けのゲームを開発して感じたことをまとめておきます
- 【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 コーディング編
- 【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 拡張メソッド編
- 【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 デバッグメニュー編
- 【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 UI編
- 【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 アセットバンドル編
デバッグメニュー
デバッグメニューを作成することでゲームのデバッグが容易になります
今回は実際にどんな感じでデバッグメニューを作成したのかを紹介していきます
デバッグメニュー用のシーンの枠組み作成
デバッグメニュー用のシーンファイルとスクリプトを作成します
デバッグメニュー用のシーンに「DebugMenu」オブジェクトを作成します
「DebugMenu」オブジェクトに「DebugMenu.cs」をアタッチします
「DebugMenu.cs」を下記のように編集します
using UnityEngine;
public class DebugMenu : MonoBehaviour
{
private void Awake()
{
DontDestroyOnLoad( gameObject );
}
}
Object.DontDestroyOnLoad
関数にデバッグメニューのルートのオブジェクトを指定することで
シーンを遷移してもデバッグメニューが消えないようにしています
あとはゲーム起動時に呼び出されるコンポーネントのAwake
関数で
Application.LoadLevelAdditive
関数を使用して読み込みます
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ツールを使用してアプリをビルドしている場合は
下記のようなスクリプトでアプリをビルドすることで
デバッグメニューのシーンファイルをビルドの対象から外すことができます
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でアプリをビルドしてしまうと
挙動がおかしくなる可能性があるので、デバッグメニューで見れるようにしておくといざというときに役に立ちます
GUILayout.Label( Application.unityVersion );
タイムスケール
Time.timeScale
Time.timeScale
を使用してゲームの一時停止を実装している場合Time.timeScale
に0を代入したままになってしまい
ゲームがフリーズしてしまったように見えることがあったのでこれも表示しておくと良いかもしれません
GUILayout.Label( Time.timeScale.ToString() );
ゲーム独自の情報
- プレイヤーの情報
- 所持ユニットの一覧
- 所持アイテムの一覧
- フレンドの一覧
- マスタの情報
- etc.
このようなゲーム独自の情報もデバッグメニューで見れるようにしておくと
いざ不具合が発生した時にパラメータが正常かどうかをすぐに確認できます
クラスのパラメータを手早く表示したい場合はこちらで紹介している方法でJSON形式で表示するのがオススメです
var obj = new PlayerData();
var json = JsonManager.ToJson( obj );
GUILayout.Label( json );
{
"Id" : 25,
"Name" : "ピカチュウ"
}
Unity APIの情報
Unity APIのクラスの情報もJSON形式で表示しておくと良いかなと思います
特にApplication
クラスとSystemInfo
クラスの情報は表示しておいて損はないです
Application
クラス
Application
クラスを参照することで現在のシーン名やビルドしたシーンの数を確認することができます
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/
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
で取得可能です
現在のシーン名をデバッグメニューに表示するのはあまり役に立たないかもしれませんが
シーン遷移時にシーン名を配列やリストに記憶しておき
シーン遷移の履歴をデバッグメニューで見れるようにしておくと不具合が発生した時に再現しやすくなります
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
プロパティの内容をデバッグメニューで表示します
foreach ( var n in LevelLoader.History )
{
GUILayout.Label( n );
}
FPS
ゲームを作成していると実装方法によってはゲームの処理速度が低下してしまうことがあるので
下記のようなスクリプトを作成していつでもFPSを見られるようにしておくことをオススメします
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;
}
}
var fps = GetComponent<FpsMeasure>();
GUILayout.Label( fps.FrameRate.ToString( "0.00" ) );
ゲームオブジェクトの一覧
現在、シーン上に存在するゲームオブジェクトの一覧をデバッグメニューで見られるようにしておくと
実機でも不要なゲームオブジェクトが生成されていないかどうかを確認できるようになります
また、一部のオブジェクトが表示されていない場合に
-
activeSelf
がfalse
になっているのか - 前後関係がおかしくなっているのか
- そもそもオブジェクトが存在しないのか
といったこともわかるようになります
var list = Object.FindObjectsOfType<GameObject>();
foreach ( var n in list )
{
GUILayout.Label( n.name );
}
Object.FindObjectsOfType
関数を使用することで
Hierarchyから全てのゲームオブジェクトを取得可能ですが
この関数は非アクティブのゲームオブジェクトを取得できません
GameObject.GetComponentsInChildren
関数を使用すると
非アクティブなゲームオブジェクトを取得可能なので
次のようにObject.FindObjectsOfType
関数と
GameObject.GetComponentsInChildren
関数を併用することで
概ねほぼ全てのゲームオブジェクトを取得可能です
var list = Object
.FindObjectsOfType<GameObject>()
.Where( c => c.transform.parent == null )
.SelectMany( c => c.GetComponentsInChildren<Transform>( true ) )
.Select( c => c.gameObject )
.ToArray();
あとは表示したいパラメータをデバッグメニューで表示するだけです
例えば親子関係の一覧を表示するだけでも、どのオブジェクトがシーン上に存在するのかが確認できるようになります
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 );
}
}
このような拡張メソッドを用意してループ構文で表示するだけで親子関係の一覧が表示できます
foreach ( var n in list )
{
GUILayout.Label( n.GetPath() );
}
Main Camera
UIRoot
UIRoot/Base
UIRoot/Base/Button
UIRoot/Base/Button/Label
デバッグログの表示
実機でもAndroidであればlogcatを使用すれば、iOSであればXcodeを使用すればログを確認できますが
ログを確認したい時に毎回PCと実機をつなぐのは手間になります
Unity APIのApplication.RegisterLogCallback
を使用することで
出力されるすべてのログを検知することができます
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 );
}
}