凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Unity で実行時にコードやコマンドを補完つきで実行できる uREPL を作ってみた

本エントリは Unity Advent Calnedar 5 日目の記事です。昨日は @tagia0212 さんによる uGUI で透明じゃないところだけに反応するボタンを作った (Unity) - Qiita でした。

はじめに

サマーレッスンの VR コンソールの話を見て、Unreal Engine にはコマンドを実行できるコンソールがあるのに Unity にはない!何故だ!というモチベーションから uREPL(ゆーれぷる) というアセットを作ってみました。2015/12/5 現在リリースしている v0.2.1 では以下の様な機能をサポートしています。

  • (Unity で動く)任意の C# のコードを実行
  • コードや GameObject の名前・パスなどの補完
  • Attribute をつけるだけで作成可能なコマンド
  • Emacs ライクなキーバインド
  • 履歴の呼び出し
  • GameObjectComponent の簡易インスペクタ
  • ログ出力

REPL とは Read Eval Print Loop のことで、コードを入力し、それを評価、結果を出力するインタラクティブシェルです。uREPL は Unity の実行時に直接コードを打ち込んでゲームを制御できたり、というものになっています。

デモ

コード実行や補完

Component のインスペクタ

GameObject のインスペクタ

環境

  • Mac / Win
  • Unity 5

インストール

上記 GitHub の Release ページよりダウンロードして下さい。

メニューの「Asset > Create > uREPL」を選択するか、Project ビューで「uREPL > Resources > uREPL > Prefabs > uREPL」を Hierarchy ビューへドラッグするかで設定は完了です。シーンに EventSystem がない場合は UI の操作ができないので、「Hierarchy ビュー > Create > UI > EventSystem」から作成して下さい。

想定ユースケース

  • 開発時のデバッグの支援・強化
  • Unity の API 及び C# の学習・確認
  • HMD かぶってコンソール出してカチャカチャやってるオレ格好良い状態になる

キーバインド

通常時

Key Description
ctrl + a 先頭へ移動
ctrl + e 行末へ移動
ctrl + f, right arrow 1文字進む
ctrl + b, left arrow 1文字戻る
ctrl + h, backspace カーソル手前の1文字を消す
ctrl + d カーソル後の1文字を消す
ctrl + k カーソル位置から行末までを消す
ctrl + l ログをクリア
ctrl + n, up arrow 1つ後の履歴を表示
ctrl + p, down arrow 1つ前の履歴を表示
ctrl + tab 補完
enter 実行

補完時

Key Description
ctrl + n, up arrow 下の補完候補を選択
ctrl + p, down arrow 上の補完候補を選択
tab, enter 選択している補完を入力
esc 補完ウィンドウを閉じる

コマンド

コマンドの作成

コマンドは、command args1 args2 ... という形式で入力できる構文で、以下の様に static public なメソッドに uREPL.Command アトリビュートを付与することによって作成できます。

public class CommandTest
{
    // 例として GameObject の情報を扱う
    static public GameObject gameObject;

    // アトリビュートをつけるとクラス名、括弧を省略したメソッド名でコマンドを呼び出せる
    // $ ShowCurrentSelectedObject ⏎
    [uREPL.Command]
    static public string ShowCurrentSelectedObject()
    {
        return gameObject.name;
    }

    // name を指定するとメソッド名の代わりにそれを利用できる
    // $ selected ⏎
    [uREPL.Command(name = "selected")]
    static public string ShowCurrentSelectedObject2()
    {
        return gameObject.name;
    }

    // description を指定すると補完時にそのコメントが表示される
    [uREPL.Command(name = "selected2", description = "show the selected gameobject name.")]
    static public void ShowCurrentSelectedObject3()
    {
        Debug.Log(gameObject.name);
    }
}

f:id:hecomi:20151201021126p:plain

引数付きコマンド

引数付きコマンドは引数は () で括ってあげたり、変数を渡したり出来ます。

public class CommandTest
{
    // 同じくアトリビュートをつけるだけ
    [uREPL.Command(name = "print")]
    static public string Print(object obj)
    {
        return obj.ToString();
    }

    // オーバーロードも可
    [uREPL.Command(name = "print")]
    static public string Print(string value1, int value2, float value3)
    {
        return string.Format("string: {0}, int: {1}, float: {2}", value1, value2, value3);
    }
}

f:id:hecomi:20151201022446p:plain

ビルトインコマンド

以下の様なコマンドが用意されています。

  • help
    • キーボードショートカットなど
  • commands
    • 全てのコマンドを確認(ユーザ定義含む)
  • close
    • uREPL のウィンドウを閉じる
  • inspect
    • GameObject または Component を渡すとインスペクタを表示

その他のコマンドについては、commands コマンドから一覧を確認してください。

インスペクタ

inspect コマンドで GameObjectComponent を渡すか、拡張メソッドの GameObject.Inspect() または Component.Inspect() を利用すると、public メンバのみを同期して表示する簡易インスペクタをゲーム内で利用できます。詳細は冒頭のデモムービーを御覧ください。

GameObject

f:id:hecomi:20151203023425p:plain

Component

f:id:hecomi:20151203010528p:plain

ログ

現在は 3 レベルのログが使用できます。

static public class LogTest
{
    [Command(name = "show logs")]
    static public void ShowLogs()
    {
        uREPL.Log.Output("this is normal log.");
        uREPL.Log.Warn("this is warning log.");
        uREPL.Log.Error("this is error log.");
    }
}

f:id:hecomi:20151203011517p:plain

他にもテクスチャの確認のための画像表示やパフォーマンス表示用にグラフの描画など色々なログを追加したいです。

補完プラグインの追加

uREPL ではデフォルトで、コード補完、GameObject の名前・パスの補完、コマンドの補完をサポートしていますが、ユーザ側で CompletionPlugin を継承したクラスを作成し、どこかのオブジェクトにアタッチすれば別の補完手法を追加することも出来ます。

using UnityEngine;
using System.Linq;
using uREPL;

// CompletionPlugin は MonoBehaviour の派生
public class SampleCompletion : CompletionPlugin
{
    string[] gameObjectNames_;

    public void Update()
    {
        gameObjectNames_ = GameObject.FindObjectsOfType<GameObject>()
            .Select(go => go.name)
            .ToArray();
    }

    // メインスレッド以外から呼び出されるため GameObject に関する情報などは
    // 別途収集しておく必要がある
    public override CompletionInfo[] GetCompletions(string input)
    {
        var partialName = input.Substring(input.LastIndexOf("\"") + 1);
        return gameObjectNames_
            .Where(name => name.IndexOf(partialName) == 0)
            .Select(name => new CompletionInfo(partialName, name, "G", Color.red))
            .ToArray();
    }
}

f:id:hecomi:20151201024835p:plain

その他

複数 GUI 対応

対応していたのですが、どこからかバグってしまったので修正します。。

ワールドスペース対応

一部まだバグが有るのですがワールドスペースにも対応していて、VR 内などでも使えると思います。

f:id:hecomi:20151203022640p:plain

ビルド

一部制限がありますがビルドしても動きます。

f:id:hecomi:20151203023349p:plain

仕組み

Mono.CSharp を利用しています。

Mono.CSharp.Evaluator.Evaluate() を利用すると C#eval することが可能になります。

また、同様に補完候補についても Mono.CSharp.Evaluator.GetCompletions() に部分的なコードを渡すと返してくれますので、これを利用しています。

両者ともに string の結果しかもらえないため、型情報も含めた結果の出力やリッチな補間は出来ないのですが、簡易的なものなら作成することが出来ます(出来る方法があったら教えて下さい)。

eval する際に、どのアセンブリ(DLL)の情報を使うか予め Mono.CSharp.Evaluator.ReferenceAssembly() で登録しておかないと行けないのですが、これは以下のフォーラムの回答を参考にしています。

eval すること自体は簡単だったのですが、GUI を作るのが大変でした...。

先行事例

同様に Mono.CSharp を利用して REPL を行うアセットは結構あります。特に Editor 上での利用に関しては先行事例がたくさんあります。

おわりに

個人的には VR の中で自由自在にコンソールから世界を構築する!みたいなイメージではなく、パラメタ調整やコマンド実行といった堅実なデバッグが出来るところを目標にしていますが、色々な要望が貰えればと思っています。他にも色々と使い道があったら是非教えて下さい。個人的にはネットワーク越しにお互いにコマンド送り合ってカチャカチャやって戦うみたいなのを考えたのですが、そのままではセキュリティ駄目そうですね。。

まだまだおもちゃ程度で本当に開発が便利になるところまでは出来てないので、色んな機能や他アセットとの連携等を随時追加していこうと思います。欲しい機能などございましたら GitHub の issues に書いて頂けると嬉しいです(英語が嫌な方は Twitter でも構いません)。

明日の Unity Advent Calendar は @p_chin さんによる「UnityProject内でのコードレビューとComponent設計をする時に気をつけてる事」になります。