Summer!http://unityassetjp.doorkeeper.jp/events/12843先日はyandoさんによる「Detonatorと爆発の未来」でした。本題Unityでコード編集するときに使ってる、自作のAssetがありまして。今回はその裏側で、こんなテクニックを使ってるよー、みたいなのを紹介する。肝心のAssetはこんなのです。AssetStoreに上がってるので良かったらどうぞ!http://u3d.as/content/sassembla/sublime-socket-asset/4SPコンパイルエラーをエディタ上に表示したり、ログをコード上の発生箇所に時系列でマッピングしたり、プロジェクト完全依存な補完出したりできる。Editorと、連携用のUnityAsset(上記のやつ)と、外部エディタ、そして外部エディタ内で動くプラグインで実現されている。次のような機能で、Unityと外部エディタの連携を実現している。1.Unityと外部エディタを接続する2.Unityにコード全体をコンパイルさせる3.Unityに外部プロセスから情報を送り込む4.Unityからコンパイル時の情報を引き出す5.Unityからコード補完時の情報を引き出す6.Unityから引き出した情報をもとに、エディタに働きかけるそれぞれ作ってておもしろかったポイントがあるので、順に書いていく。1.Unityと外部エディタを接続するMonoDevelopと違って、Unityが提供しているアタッチ系の機能を使用していない。WinとMacで違ってて大変うっとうしかったためだ。リバースエンジニアリングに近かったし。いうわけで、Unityの力を借りず、接続にはWebSocketを使っている。図にするとこんな感じ。プラグインがwsサーバになっている。アセット側がwsクライアントなの?やんごとなき理由があった。サーバになれない・wsだったら双方向同期/非同期で通信できる・Mac/Winとかで統一的な方法が欲しい・別サーバで動かしているUnityを使役したいみたいな欲がある(リモートコンパイラ)みたいなニーズと理由があったため。サーバになれない理由を、もうちょっとちゃんと紹介する。持つコードコンパイルの構造にある。適当なコンパイル構造は次のような感じ。(正式名称は知らない)UnityでのC#コードとかのコンパイルほかを管理しているレイヤ。appとかexeとして実行されているコア。名称のフォルダ以下、エディタのパーツとしてコンパイルされるコードが居るレイヤ。Layerゲームのコードとかが居るレイヤ。★この図はいろいろ端折ってあるので、詳しくはここをみるといいと思う。言い換えれば毎回Unityのコンパイルに巻き込まれる。これはゲームスクリプトのレイヤのコンパイルが発生しただけでも、Editorから再度コンパイルされることを指している。別に悪いことじゃないんだけど、外部との接続を行う際、ここがネックになる。コンパイルが走る度に、通信をしていたプロセス自体がぶっ殺されて新たにコンパイルされて、コネクションが途切れるためだ。再接続がスムーズにいくようにデザインされてて、かつ規格として実装流通が多いもの、としてwsを採用した向きが強い。動かしているwsサーバへと接続される。リデザインする機会があったら、wsの代わりにMQTTを使う。Textのプラグイン側には実験中のリポジトリがある。レイヤのコードを纏めた上でコンパイルしているので、Editorレイヤのコンパイルが終わったら、Game側のコードのコンパイル前にPrecompile処理を仕掛ける、みたいなのはできない。強制的にPrecompileする処理は可能なんだけど、どっちにしてもコンパイル後に動き出す感じになる。ことはそれ無限ループするんじゃね?という感じ。エディタでの保存処理をトリガーにして、Unityにコンパイルを実行させることは、実はめっちゃ難しかった。いうのも、公式な手段が無い。コントロールできない。合流してコンパイルを実行する、みたいな処理は存在しない。フォーカスを移すと、Unityはコンパイルを始めるという事象を利用している。もってバカバカしい悪手なんだけど、これが一番案配がよかった。このへんはさすがにプラットフォームごとに互換性が無かったので、同じインターフェースで動くようにコマンドラインツールを用意した。Mac用https://github.com/sassembla/SwitchAppWin用https://github.com/sassembla/WinSwitchAppもうちょい正しくマシな手段ができないかなー、と思っている。生成した.slnファイルを使用することでコンパイルパスとかリソースをプロジェクトに組み込んでいて、MonoDevelopのコンパイルは、Slnを通じてUnityのライブラリのパスとかを使って実行しているに過ぎないので、Unityでコンパイルしたときとは異なる挙動になる。Editorに直接コンパイルを命令しているので、Unityそのもののコンパイルがそのまま実行される形になる。この際、assetの読み込みなどに関連する情報も出力されるため、外部に対してより多い情報や処理を持ち出すことができる。そのかわり、単純にコンパイルしてる訳ではないので、重い。以降だと、コードのhash値かなにかに差分がないとコンパイルされないようになったので、コメント内のタイムスタンプのみを更新するファイル、とかを使って、強制的にコンパイルが発生するようにしている。それ以前だと、保存した時刻をみてたみたいだ。うーんまあこれも悪手。カットできるようにはしたけど。的なエラーが発生する。動作できないスクリプトは必然的に全く使えない。呼べない、みたいなのが結構ある。GetPath系とか。エディタで動くコードなので、Coroutineでなんとかする、みたいな合流手段も使えない。このへんは割と設計全体に関わる感じになる。取得できないパラメータへのアクセスが可能っちゃあ可能になっている。本当にクリティカルなタイミングだと、何のエラーも吐いてくれない。合流手段が提供されるかもなーと思っている。それ自体が完全に停止するから。流れを阻害してはいけない。コンパイル命令を入力できれば、Editorのログファイルにコンパイル状況が書き込まれる。ログファイルはMacとWinでパスが異なるが、複数プロジェクトがあっても基本一つのファイルをみている。読み込んだ結果をwsでエディタに送り込んで、コンパイルエラーなどをエディタに表示させている。この方法の優れたところは、コンパイル中、更にはゲーム実行時のログをストリーミングしてコード上に反映させることができるため、リアルタイムにコード上のどの値がどう変わったか、をその発生箇所ごとに記録できること。「該当ログの実行記録をこの行に時系列順に表示する」ということを行っている。ゲーム作りの途中で、挙動が複雑なところがあって、そのコードや通過しているかどうかの判定がしたい場合、どの順番でどこを通ったか、などが目で見て判定可能になる。複数の非同期/同期通信を組み合わせて実現している。・補完候補を出力するといった事をしている。図にするとこんな感じ。側で解析と補完情報を出している。編集中のコードを相手にするので、ほとんどキャッシュがあるはずが無い感じになる。コードが中途半端でも解析できないといけない。この解析まわりは、NRefactoryをUnity用に大改造することで対応している。UnityのC♯のバージョンにあわせて改変しまくったものを使用している。たびにドカッとコード全体を送りつけている。そこはWebSocket、十分に高速。プロジェクト構造の中から、下記DLLを読み込んでいる。・/Applications/Unity/Unity.app/Contents/Frameworks/Managed/UnityEngine.dll・/Applications/Unity/Unity.app/Contents/Frameworks/Mono/lib/mono/unity/mscorlib.dllUnityのライブラリ一式と、C♯用のライブラリ。・Project/Library/ScriptAssemblies/Assembly-CSharp.dllプロジェクトのコンパイル結果が入るDLL。部分のコードのほか、コンパイルに巻き込まれたすべてのdllが含まれている。型を特定して、補完情報を送付している。ちなみに件数が膨大になった場合、ある程度の流量になるように補完情報を分割して送付する、みたいなことをしている。側のUI的なロックは無いようになっている。側に、入力に応じてエディタの動作を設計できるパイプライン的な設定がなされている。Unity関係ないけど、方法の一つとして紹介だけ。内容にはSushiJSONという「JSONを書いたらフローが決定される」自作のDSLを使っている。側からセットして実現している。内容によってfilteringして、入力された内容が正規表現にマッチしたら、あらかじめ記述していたフローに沿ってAPIを発動させる。その結果出てきたインターフェースに対してアクションをして、何が発生するか、みたいなのも指定できる。→表示された内容がクリックされたら何かするイベントをセットする→クリックされたら、、、みたいに、エディタのAPIフローを予約できる。filteringも多重化できるので、やり放題。いうのがいろんなエディタにフロー型の記述方法で入らないかなー。反逆を通じてわかったこと、得たものエディタには我慢しない方がいいなーということなど。派生物がかなり多くなったので、やってみてよかった。0になるまでにかかった時間をグラフにする」とか、ゲーム作りの中で計測すべきあらゆる物事を、事前に計測しておくことができるようになったりする。通信機構入れて逐一解析DBにぶち込んで解析すれば一緒だと思うけど。こまかい粒度のことは、コードを書いてるそのときに知りたい。