はじめに
LLMなどでUnity C#を扱う際、構文エラー情報、必要ですよね。
ただUnityスクリプトに対応したRoslynを用意するのも煩雑だし正確性が怪しい。
そこで、TCP通信経由でUnityエディタにコンパイル(というより構文エラーチェック)をリクエストして、構文エラー情報を返してもらいましょう。
このレポジトリについて話します
https://github.com/konbraphat51/UnityCompilerTcpServer
導入
CompilerServer.csをコピペするだけ
使い方
1. Window -> Compiler TCP Serverを開く
2. サーバーを走らせる
3. TCP通信を送る
下記のPythonスクリプトがテスト用途で便利だと思います。
import socket
def main():
with socket.create_connection(("localhost", 5000)) as sock:
sock.sendall(b"testing")
response = sock.recv(1024)
print(response.decode("utf-8", errors="replace"))
if __name__ == "__main__":
main()
結果
エラー情報がJSONで得られます。
{
"messages": [
{
"type":"Error",
"message":"Assets\\Dodgeball\\Scripts\\AgentCubeGroundCheck.cs(14,94): error CS1585: Member modifier 'public' must precede the member type and name",
"file":"Assets\\Dodgeball\\Scripts\\AgentCubeGroundCheck.cs",
"line":14,
"column":94
}
]
}
実装上の書き残し
Unityのコンパイルを巡る仕様と戦う後世、あるいは自分自身のための書き残し
基本アイデア
-
TCP通信はEditorTcpServerを踏襲
参考: (ほぼコピペだけで)Unityエディタ内にTCPサーバーを作成する(ほぼコピペだけで)Unityエディタ内にTCPサーバーを作成する -
コンパイル開始は
CompilationPipeline.RequestScriptCompilation()で -
エラーが無い時は
CompilationPipeline.assemblyCompilationNotRequiredでイベント駆動 -
エラーが有る時は
CompilationPipeline.assemblyCompilationFinishedCompilationPipeline.assemblyCompilationFinishedでイベント駆動
環境
Unity 2022.3.62f3
コンパイルを開始させる方法
こんな関数を書いています
private static void RequestRecompilation(NetworkStream stream)
{
// hold the stream to send response after compilation
pendingStream = stream;
// assets will not be updated in background
AssetDatabase.Refresh();
// recompilation
CompilationPipeline.RequestScriptCompilation();
Debug.Log("Recompilation requested");
}
Unityエディタをクリックしてフォーカスするときに、Unityエディタは
- アセット変更を検知して
- コンパイル
をします。
そのため、コンパイル前のAssetDatabase.Refresh()を忘れてはいけません。
コンパイル時のドメインリロードについて
コンパイルを扱うUnityエディタコーディングの一番の厄介者です。
コンパイルが完了すると、多くの変数がリセットされます。
リセットされないのは、Serializeされた変数です。今回の場合、EditorWindowのSerialize変数でステートを保持します。
が、ドメインリロードのせいでTCPソケットが消失し、TCP通信が打ち切られてしまいます。なので、ドメインリロードの前までにレスポンスを返す必要があります。
コンパイルに関するイベント駆動
こんな感じで書きました。
// when there is a compilation error (ex. syntax error)
CompilationPipeline.assemblyCompilationFinished += OnCompileError;
// when compilation starts because of no error
CompilationPipeline.assemblyCompilationNotRequired += OnCompileSuccess;
AssemblyReloadEvents.beforeAssemblyReload += OnCompileSuccess;
構文エラーが有る場合
構文エラーが存在する場合、コンパイルは完遂せず、ドメインリロードは行われません。なので、TCPソケットは消失されず、CompilationPipeline.assemblyCompilationFinishedでレスポンスを返せばいいだけです。
構文エラー内容の取得
CompilationPipeline.assemblyCompilationFinishedに対して下記の形式の関数を登録すればいいだけです。
using UnityEditor.Compilation;
void Hoge(string assemblyPath, CompilerMessage[] compilerMessages)
参考: CompilerMessage
構文エラーが無い場合
コードに変更がある場合
構文エラーが無い場合、そのままコンパイルが完遂し、ドメインリロードが始まってしまいます。なのでCompilationPipeline.assemblyCompilationFinishedは使えません。(というよりも、ドメインリロードでCompilationPipeline.assemblyCompilationFinishedすらリセットされ呼び出しすらされない様子が見られました)
ドメインリロードが始まる直前にレスポンスを返さないといけません
そのイベントトリガーがAssemblyReloadEvents.beforeAssemblyReloadです。
コードに変更がない場合
この場合、コンパイルすらされません。これを検知できるイベントトリガーがCompilationPipeline.assemblyCompilationNotRequiredです。

