【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)

## `null`チェックの回避方法

`null`チェックは大変でバグの原因にもなりやすいので
`null`チェックを回避する方法を書き残しておきます

### 変数初期化子で初期化しておく

変数初期化子で空のパラメータを代入したりインスタンス生成したりすることで
メンバ変数が`null`かどうかを意識することなくコーディングできます

```cs
private string str = string.Empty;
private List<int> list = new List<int>();
private Dictionary<int, int> dict = new Dictionary<int, int>();
private Action act = delegate{};
```

`List<T>`や`Dictionary<TKey, TValue>`のメンバ変数は
`readonly`にすることで後から`null`を代入できなくなるのでさらに安全に扱えます

```cs
private readonly List<int> list = new List<int>();
private readonly Dictionary<int, int> dict = new Dictionary<int, int>();
```

### 「`null`もしくは空かどうか」を判断する拡張メソッドを用意する

```cs
// 文字列がnullもしくは空かどうか
if ( str == null || str == "" ){}

// 配列がnullもしくは空かどうか
if ( array == null || array.Length == 0 ){}

// リストがnullもしくは空かどうか
if ( list == null || list.Count == 0 ){}
```

引数やコールバック関数で受け取った文字列やリストが
正常なものかどうかを確認するために上記のようなチェックを毎回書くのは大変ですが
「`null`もしくは空かどうか」を判断する拡張メソッドを用意しておくと
コーディングしやすくなります

#### `string`の場合

```cs
public static bool IsNullOrEmpty( this string self ){
     return string.IsNullOrEmpty( self );
}
```
上記のような拡張メソッドを用意することで次のように記述できます

```cs
if ( str.IsNullOrEmpty() ){}
```

空白文字も対象外にしたい場合は下記のような拡張メソッドを用意することで

```cs
public static bool IsNullOrWhiteSpace( this string self ){
     return self == null || self.Trim() == "";
}
```

次のように記述できます

```cs
if ( str.IsNullOrWhiteSpace() ){}
```

#### 配列やリストの場合

```cs
public static bool IsNullOrEmpty<T>( this IList<T> self ){
     return self == null || self.Count == 0;
}
```

上記のような拡張メソッドを用意することで次のように記述できます

```cs
if ( array.IsNullOrEmpty() ){}
if ( list.IsNullOrEmpty() ){}
```

#### `Action`デリゲートや`Func`デリゲートの場合

```cs
Action act = null;
if ( act != null ){
    act();
}
Func<bool> func = null;
if ( func != null ){
    func();
}
```

`Action`デリゲートや`Func`デリゲートでイベントを定義したり引数を受け取ったりする場合
上記のように毎回`null`チェックを行う必要があります
このような`null`チェックは下記のような拡張メソッドを用意することで省略できます

```cs
public static void Call(this Action action){
    if (action != null){
        action();
    }
}

public static void Call<T>(this Action<T> action, T arg){
    if (action != null){
        action(arg);
    }
}

public static void Call<T1, T2>(this Action<T1, T2> action, T1 arg1, T2 arg2){
    if (action != null){
        action(arg1, arg2);
    }
}

public static void Call<T1, T2, T3>(this Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3){
    if (action != null){
        action(arg1, arg2, arg3);
    }
}

public static TResult Call<TResult>(this Func<TResult> func, TResult result = default(TResult)){
    return func != null ? func() : result;
}

public static TResult Call<T, TResult>(this Func<T, TResult> func, T arg, TResult result = default(TResult)){
    return func != null ? func(arg) : result;
}

public static TResult Call<T1, T2, TResult>(this Func<T1, T2, TResult> func, T1 arg1, T2 arg2, TResult result = default(TResult)){
    return func != null ? func(arg1, arg2) : result;
}

public static TResult Call<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> func, T1 arg1, T2 arg2, T3 arg3, TResult result = default(TResult)){
    return func != null ? func(arg1, arg2, arg3) : result;
}
```

```cs
Action act = null;
act.Call();
Func<bool> func = null;
func.Call();
```

## 関数呼び出しの可読性向上

### 名前付き引数を使う

引数をたくさん取る関数を呼び出す場合
後から見た時にどの引数が何を示しているのかがわかりづらいです

```cs
MessageBox.OpenOkCancel(
    "タイトル",
    "テキスト",
    () => Debug.Log( "OKボタンが押された" ),
    () => Debug.Log( "キャンセルボタンが押された" )
);
```

名前付き引数を使うことで
後から見た時でもどの引数が何を示しているのかがわかりやすくなります

```cs
MessageBox.OpenOkCancel(
    title : "タイトル",
    text : "テキスト",
    onReleasedOkButton : () => Debug.Log( "OKボタンが押された" ),
    onReleasedCancelButton : () => Debug.Log( "キャンセルボタンが押された" )
);
```

### `if`ディレクティブではなく`Conditional`属性を使う

```cs
public static void Log( object message ){
#if DEBUG
     Debug.Log( message );
#endif
}
```

`if`ディレクティブを使うと特定のシンボルが定義されている時に
処理を切り替えることができますが、多用するとソースコードの可読性が落ちます

```cs
[Conditional( "DEBUG" )]
public static void Log( object message ){
     Debug.Log( message );
}
```

`Conditional`属性を使うとソースコードの可読性を保持できます

```cs
[Conditional( "DEBUG_A" ), Conditional( "DEBUG_B" )]
public static void Log( object message ){
     Debug.Log( message );
}
```

いずれかのシンボルが定義されている場合に関数を有効にしたい場合は
上記のように`Conditional`属性を複数個適用します

```cs
#if DEBUG_A && DEBUG_B
#define DEBUG
#endif

...

[Conditional( "DEBUG" )]
public static void Log( object message ){
     Debug.Log( message );
}
```

すべてのシンボルが定義されている場合に関数を有効にしたい場合は
上記のようにソースコードの先頭で`define`を使用します

## デバッグ出力を楽にする

### JSONを使う

クラスの情報をログに出力したいことはよくあります

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

```cs
public class PlayerData
{
     public int Id;
     public string Name;

     public override string ToString()
     {
          var builder = new StringBuilder();
          builder.AppendFormat( "Id:{0},", Id );
          builder.AppendFormat( "Name:{0}", Name );
          return builder.ToString();
     }
}
```

しかしすべてのクラスでこのような`ToString`関数を定義するのは大変です
こんな時はJSONを使用することをオススメします

#### LitJSON

1. http://lbv.github.io/litjson/ にアクセスする
2. 「Download>File releases>Latest release>dll」を選択してDLLをダウンロードする
3. ダウンロードした「LitJson.dll」をUnityプロジェクトのPluginsフォルダに追加する
4. 次のようなクラスを作成する

```cs
using LitJson;

public static class JsonManager
{
    static JsonManager()
    {
        JsonMapper.RegisterExporter<float>( (obj, writer) => writer.Write( Convert.ToDouble( obj ) ) );
        JsonMapper.RegisterImporter<double,float>( input => Convert.ToSingle( input ) );
        JsonMapper.RegisterImporter<int,long>( input => Convert.ToInt64( input ) );
    }
    public static string ToJson<T>( T obj, int indentLevel = 4 )
    {
        var builder = new StringBuilder();
        var writer = new JsonWriter( builder );
        writer.PrettyPrint = true;
        writer.IndentValue = indentLevel;
        JsonMapper.ToJson( obj, writer );
        return builder.ToString();
    }
}
```

後は文字列としてデバッグメニューに出力したいクラスのインスタンスを
`JsonManager.ToJson`でJSON形式の文字列に変換します

```cs
var obj = new PlayerData();
var json = JsonManager.ToJson( obj );
Debug.Log( json );
```

```
{
    "Id" : 25,
    "Name" : "ピカチュウ"
}
```

Unity APIのクラスのインスタンスをJSON形式の文字列に変換することも可能です

```cs
var obj = new Application();
var json = JsonManager.ToJson( obj );
Debug.Log( json );
```

```
{
    "loadedLevel" : 0,
    "loadedLevelName" : "Title",
    "isLoadingLevel"  : true,
    "levelCount"      : 1,
    "streamedBytes"   : 0,
    ...
```

独自に定義したクラスの場合は`ToString`関数をオーバーライドしておくと便利です

```cs
public class PlayerData
{
     public int Id;
     public string Name;

     public override string ToString()
     {
          JsonManager.ToJson( this );
     }
}
```
```cs
var obj = new PlayerData();
Debug.Log( obj );
```

## 処理速度の向上

### `StringBuilder`クラスで文字列連結する

```cs
var str = "";
for ( int i = 0; i < 1000; i++ ){
     str += i.ToString();
}
Debug.Log( str );
```

通常の`string`クラスの文字列連結は処理が遅く、さらにGCが発生してしまいます
`StringBuilder`クラスを使用することで処理速度を速くし、GCの発生を抑えられます

```cs
var builder = new StringBuilder();
for ( int i = 0; i < 1000; i++ ){
     builder.Append( i.ToString() );
}
Debug.Log( builder.ToString() );
```

### 何もしない`Debug`クラスを定義する

Unityの`Debug.Log`はリリース版でもログを出力してしまいます
そのため、例えばAndroidで`logcat`すると製品版でもログの出力が見れてしまいます
また、ログ出力は重たい処理なのでゲームの処理速度も遅くなってしまいます

リリース版でログを出力しないようにしたい場合は
次のように、特定のシンボルが定義されている場合にのみ
何もしない`Debug`クラスをグローバルな名前空間に定義されるようにします

```cs
#if RELEASE

using System.Diagnostics;
using UnityEngine;

public static class Debug
{
     [Conditional("RELEASE")] public static void Break(){}
     [Conditional("RELEASE")] public static void ClearDeveloperConsole(){}
     [Conditional("RELEASE")] public static void DebugBreak(){}
     [Conditional("RELEASE")] public static void DrawLine(Vector3 start, Vector3 end){}
     [Conditional("RELEASE")] public static void DrawLine(Vector3 start, Vector3 end, Color color){}
     [Conditional("RELEASE")] public static void DrawLine(Vector3 start, Vector3 end, Color color, float duration){}
     [Conditional("RELEASE")] public static void DrawLine(Vector3 start, Vector3 end, Color color, float duration, bool depthTest){}
     [Conditional("RELEASE")] public static void DrawRay(Vector3 start, Vector3 dir){}
     [Conditional("RELEASE")] public static void DrawRay(Vector3 start, Vector3 dir, Color color){}
     [Conditional("RELEASE")] public static void DrawRay(Vector3 start, Vector3 dir, Color color, float duration){}
     [Conditional("RELEASE")] public static void DrawRay(Vector3 start, Vector3 dir, Color color, float duration, bool depthTest){}

     [Conditional("RELEASE")] public static void Log(object message){}
     [Conditional("RELEASE")] public static void Log(object message, UnityEngine.Object context){}
     [Conditional("RELEASE")] public static void LogError(object message){}
     [Conditional("RELEASE")] public static void LogError(object message, UnityEngine.Object context){}
     [Conditional("RELEASE")] public static void LogException(System.Exception exception){}
     [Conditional("RELEASE")] public static void LogException(System.Exception exception, UnityEngine.Object context){}
     [Conditional("RELEASE")] public static void LogWarning(object message){}
     [Conditional("RELEASE")] public static void LogWarning(object message, UnityEngine.Object context){}
}
#endif
```

このような`Debug`クラスを定義しておき
リリース時は特定のシンボルを定義してからビルドすることで
リリース版でログ出力を無効化できます