以下のようなWindowsアプリケーションから、UnityアプリケーションをNamedPipeを介して制御する方法を紹介する。なおソースコード全体はGitHubのリポジトリに置いてある。
Windows側
Unity側にメッセージを送るときには、NamedPipeのクライアントであるIPCクラスを利用する。以下では、ViewにバインドされたプロパティでUnity側にメッセージを送っている。SendはUnity側からの応答を返す。
Unity側
まず第一に、NamedPipeをUnityで利用するには、Player SettingsでApi Compatibility Levelを.NET 4.xにする必要があることに注意してほしい。エディターのプレイモードなら.NET Standard 2.0でも動くが、standalone playerでエラーになってしまう。

Unity側ではこちらのIPCクラスを使用する。以下にNamedPipeの待ち受けを行うReceiveメソッドの定義を示す。
UniTaskを用いた非同期メソッドになっている。コルーチンよりもUniTaskのほうがずっと簡単に非同期操作を実装できる。ここでNamedPipeの非同期系(~Async)のメソッドを用いていないのは、Unityには非同期系のメソッドが実装されていないからだ。コンパイルは通るが実行時にエラーになるので注意してほしい。
以下は、Windowsからのメッセージを処理するControllerクラスである。すべてのシーンに空のGameObjectを配置して、Controllerオブジェクトをコンポーネントとして張り付ける。
AwakeでControllerをDontDestroyOnLoadを使用して、シーンが変わったときにControllerが破棄されないようにしている。そうしないとLoadSceneでシーンを切り替えた後にWindows側に応答を返せなくなる。Startではメッセージのハンドラーを与えてIPC.Runを実行する。
OnDisableでは以下に実装を示したIPC.Closeを呼んでいる。IPC.Closeは、シーンの切り替え中はパイプが接続中なので何もせずに終了する。
それ以外は自身のNamedPipeに接続して、IPC.Receiveの待ち受け状態を解除する。エディターのプレイモードで実行したときに、そうしないと待ち受けスレッドが残り、終了時とコードリロード時にエディターがフリーズする。
上で紹介した2つのIPCクラスは私が独自に実装したものだ。UnityでNamedPipeを使う記事はどれも実験レベルで、現実のアプリケーションで問題なく動くように作られていなかったので、自分で実装することにした次第である。
