はじめに
以前公開した uREPL という Unity で実行時に C# のコードや予め登録したコマンドを実行するプラグインのアップデートをして、複数行編集に対応しました。
以前の記事はこちらです。
本エントリではちょっとした紹介と、備忘録として苦労した点を書いておきます。
デモ
ダウンロード
- GitHub - hecomi/uREPL: In-game powerful REPL environment for Unity3D.
- Releases · hecomi/uREPL · GitHub
リリースページより unitypackage をダウンロードして展開した後、Hierarchy の Create > Create Other > uREPL で配置すれば使えます。テスト用の
コマンド
以前の記事でも紹介しましたが、uREPL.Command
アトリビュートを static
な関数につけることで任意のコマンドを引数付きで作成することが出来ます。型や引数名がわかるように表示を少し改善しました。
[uREPL.Command(name = "hoge", description = "hoge 1")] static public void Hoge() { uREPL.Log.Output("Hoge"); } [uREPL.Command(name = "hoge", description = "hoge 2")] static public void Hoge(string msg) { uREPL.Log.Output(msg); } [uREPL.Command(name = "hoge", description = "hoge 2")] static public void Hoge(string msg, int code) { uREPL.Log.Output(msg + ": " + code); }
C# は型や引数の数など様々な情報を簡単に取得できるので、実行時に色々出来て面白いですね。コードはこのあたりです。
Mono.CSharp による補完
それに対して Mono.CSharp.Evaluator
は基本的に文字列のリストとしてのみ補完のリストや宣言されている変数のリストを返してくるので、あまり拡張し甲斐がないのが寂しいですね...。また、そのままの入力を与えると余り補間してくれないので、複数行対応のためにいろいろとゴニョゴニョやる必要がありました。
GUI
今回一番苦労したのがこの複数行の GUI 対応です。
Ctrl-M で一行編集モードと複数行編集モードを切り替えられます。コマンド入力エリア右上のアイコン(単数行時は −、 複数行時は ≡)をクリックしても切り替えられます。
UnityEngine.UI
の Input Field
をカスタマイズして使用しているのですが、一行編集時と複数行編集時で色々と挙動を変えないとならなかった(Emacs ライクなキーバインディングを使う際に、Ctrl-P は一行編集時では前の履歴の表示、複数行編集時では上へ移動など)、結構時間を使ってしまいました。
Input Field
話に出てきたので Input Field
のカスタマイズですが、これは以下のコードを直接見て整合が取れるように色々と試行錯誤しながらやっていく形になります。
例えば Emacs ライクなキーを作るには、該当のキー押下イベントを取得した後、それに対応するように caretPosition
を移動する形になります。例えば以下の様な形になります。
bool isCtrlPressed = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); if (isCtrlPressed && Input.GetKey(KeyCode.F)) { caretPosition = Mathf.Min(caretPosition + 1, text.Length); }
これだけだと複数行編集時にカーソルが画面外に行きスクロールする際、スクロールが更新されずにエラーとなることが有ります。なので caretPosition
を更新した後、protected
なメンバとして公開されている UpdateLabel()
を呼ぶことで、スクロールされるエリア範囲内のm_DrawStart
や m_DrawEnd
、テキストをレンダリングするための TextGenerator
の更新などが行われます。
TextGenerator
テキストをレンダリングするためのコンポーネントで、テキストを描画するコンポーネントのソースを覗いてみると大体ついています。
テキストの情報が色々と入っており、例えば指定された位置の文字の横幅や行の情報(どの文字の位置が行頭か等)が含まれています。例えば補完ウィンドウを表示する位置を指定するために指定した文字の Canvas
上の位置を取得したい、となったときに次のようなコードを書きました。
public Vector3 GetPositionBeforeCaret(int offsetLen) { if (isFocused) { var characters = textComponent.cachedTextGenerator.characters; var len = m_CaretPosition - m_DrawStart; if (len > 0 && len < characters.Count) { var info = characters[len]; var ppu = textComponent.pixelsPerUnit; var x = info.cursorPos.x / ppu; var y = info.cursorPos.y / ppu; var z = 0f; var prefixWidth = 0f; for (int i = 0; i < offsetLen && i < len; ++i) { prefixWidth += characters[len - 1 - i].charWidth; } prefixWidth /= ppu; var inputTform = GetComponent<RectTransform>(); return inputTform.localPosition + new Vector3(x - prefixWidth, y, z); } } return -9999f * Vector3.one; }
おわりに
issue で HoloLens で使いたい、とのお話があったので頑張って対応しましたが、前のコードがあまり綺麗でなかったのもあり、修正が思ったより大変でした...。
現状の機能だけだと、コマンドはそこそこ便利ですが、コードの実行時の評価は特に便利ではなくておもちゃ程度にしかならないので...、もう少し開発の支援となるような機能を追加していきたいです。またしばらく更新意欲が高まるまで放置かもしれないですがちまちまとアップデートは続けていこうと思いますので、機能のご要望などありましたら教えて下さい。