C#
Unity

Unity: デバッグ実行時のみコンポーネントをアタッチするやつ

例えば、FPSコントローラーかなんかアタッチしたオブジェクトがあったとする。
実行時に、まぁ色々と操作してオブジェクトを移動させるとした。
この時、プレイヤーの座標をコンソールに出力したいと思った。
でもコード中になるべくデバッグ用のコードを混在させたくない・・・!
そんな時の対処方法。

#if UNITY_EDITOR
#define MY_DEBUG
#endif

using UnityEngine;
using System.Diagnostics;

public static class DebugFunctions
{
    public static void AddComponent<T>(GameObject target) where T : Component
    {
        _AddComponent<T>(target);
    }

    [Conditional("MY_DEBUG")]
    private static void _AddComponent<T>(GameObject target) where T : Component
    {
        if (target == null) return;
        target.AddComponent<T>();
    }
}
using UnityEngine;

public class Player : MonoBehaviour
{
    public void Start()
    {
        DebugFunctions.AddComponent<PlayerDebugger>(this.gameObject);
    }

    public void Update()
    {
        ~~~何かと移動したりジャンプしたりする処理
    }
} 
using UnityEngine;

public class PlayerDebugger : MonoBehaviour
{
    public void Update()
    {
        Debug.Log("player position : " + this.transform.position);
    }
} 

こんな感じ。

注目ポイント

①独自のデバッグ用defineをする
例えば、Conditional["UNITY_EDITOR"]でメソッドを宣言していった場合、あとから「このデバッグ機能群、他のプラットフォームでも有効化したいなぁ」って時に全てのConditionalを修正する必要がある。
別に必須では無いけど、せっかくデバッグ機能を優秀にしていくならやったほうが良いんじゃない?って感じ。

②Conditional属性の挙動について
なぜAddComponentをAddComponentでラップするような実装になっているかを説明する。
例えば、AddComponentをpublicにしてPlayerから呼び出してみると、指定のコンポーネントはアタッチされない。
何故かと言うと、まぁ独自のデバッグ用シンボルのせいです。
C#では#defineで宣言したシンボルの有効範囲はそのファイル内だけなので、DebugFunctions.csで宣言したMY
DEBUGはPlayerクラスからは見えません。
しかも、Conditionalの判定がメソッドの呼び出し側に依存するらしく、PlayerからDebugFunctions.AddComponentを呼び出した際、呼び出し側のPlayerがMY
DEBUGのスコープ外なのでメソッドは実行されません。
そこで、Conditional属性のメソッドの呼び出しをスコープ内で行い、属性の付いていない通常のメソッドを外部から呼び出すことで、独自のデバッグ用シンボルの宣言とConditionalでデバッグ用のメソッドを宣言可能という感じ。

免責

・はっきり言って、Unityなら似たような要望を標準機能で叶えてくれそうな気がするけどよくわかりません。プログラミングとかしたことない。
・Conditionalの特性上、おそらくコンパイル時に指定のシンボルが宣言されているか否かで生成するコードを判断していると思われるけど、そうなるとAddComponentの実装に意味が無くなるので最適化でうまく消えてくれると嬉しいけどそれも知りません。
・特に下調べして実装したわけじゃないので、C#の機能解説とかコーディングのすゝめとかの意図で書いた記事じゃないです。メモです。でもなんかあったら指摘してもらえるとありがてえっす。
・文中でちょいちょい文字が斜めになっているのは、アンダーバーを使った変数とか定数の名前を書いた時にMarkdownちゃんが斜めしたらええんやろ?と勘違いしてsin(10)を加算してしまったせいです。かわいい。

終わり。