Posted at
VRChatDay 20

VRChatからVRC_Panoramaを使いESP-WROOM-02経由で外部機器を操作する


はじめに

この記事はVRChat Advent Calendar 2018の20日目の記事です

昨日の記事はヨツミフレーム(@y23586)さんによる、仮想世界のつながり:VRChatにおけるソーシャルネットワークの可視化です。

こんにちは。あきら(@sh_akira, VRC:sh_akira)です。

普段はバーチャルモーションキャプチャーというVRMモデルを操作するアプリを作っています。

今回はVRChatのトリガーをVRC_Panorama経由でローカルHTTPサーバーで受ける方法と、その使用例としてさらに外部のESP-WROOM-02にTCP接続して、

VRChat内でトリガーを引くと、外のLEDが付く仕組みを作ってみます。

記事中のソースコードはパブリックドメインです。プラグインやGitHubのコード等については各ライセンスに従ってください。


完成品


VRChatのワールドを作る


最低限必要な設定だけしたワールドを作る

image.png

まずはUnity 2017.4.15f1で新規プロジェクトを作ります。Untitledシーンを適当な名前(PanoramaRepeater)で保存します。

image.png

まずはメニューのAssets->Import->Custom PackageからVRCSDKをインポートします。

執筆時のパッケージはVRCSDK-2018.12.04.10.25_Public.unitypackageです

image.png

床が無いのでとりあえずPlaneを置きます。

image.png

空のゲームオブジェクトを置いて、VRCPanoramaと名前を付けておきます。

ここにInspectorのAdd Componentからスクリプトを追加してください。


  • VRC_Panorama (追加すると勝手にVRC_DataStorageも入る)

image.png

VRC_PanoramaのPanoramasのSizeを2にしてそれぞれUrlに

と同じものを二つ入れておきます。

VRC_DataStorageのDataを開きSizeを1にして、displayと入れて右側のNoneをIntにします。

image.png

シーンにUIのButtonを追加します。

image.png

表示はいらないのでButtonのRect TransformのPos X, PosY, Width, Heightをそれぞれ0にしてしまいます。

image.png

Button追加時に自動で追加されたCanvasのRender ModeをWorld Spaceにして、こちらもPos X, PosY, Width, Heightをそれぞれ0にしてしまいます。

そのままボタンとして置きたい場合は、CanvasにVRC_Ui Shapeをアタッチして、ボタンを自由にレイアウトしてください

image.png

次にButtonをVRC_Triggerから押すためのAnimationを作ります。

AnimatorControllerとAnimationを新規作成します、それぞれButtonPressAnimatorControllerとButtonPressAnimationと名付けました。

image.png

WindowメニューからAnimationを選んでAnimationウィンドウを開きます。

image.png

image.png

Add Eventボタンを押して、InspectorのAnimation EventのFunctionにPressと入れます。

image.png

image.png

フレーム番号に1を入れて隣のフレームに移動して、またAddEventを押し、今度は空のEventを作成します。何も入力する必要はありません。

これによってアニメーションがすぐに終了し、連打することが可能になります。

0フレーム目のPressだけにすると、アニメーションに1秒かかり、1秒ごとにしか押せなくなります。

image.png

次にメニューのWindowからAnimatorを選び、ProjectからButtonPressAnimatorControllerを選択します。

image.png

適当なところを右クリックして、Create StateからEmptyを選びます。

image.png

ButtonPressAnimationをドラッグしてAnimatorに入れます

image.png

Parameters内の+ボタンからTriggerを選択します。

image.png

名前をOneShotとします

image.png

New Stateを右クリックしてMake Transitionします。出てきた矢印はButtonPressAnimationをクリックしてくっつけてください。

image.png

繋いだ矢印を選択して、InspectorのConditionの+を押してOneShotを選択し、

Has Exit Timeのチェックを外して、Settings内のTransition DurationとOffsetを0にします。

image.png

同じように今度は逆方向にButtonPressAnimationからNew Stateに矢印を繋いで、

Settingsの値を全て0にします。こちらはConditionsは空のままで良いです。

image.png

ButtonPressAnimatorControllerをButtonにドラッグして、ButtonにAnimatorを付けます。

image.png

VRC_TriggerをつけてボタンにするためのCubeを置きます。とりあえず位置はPos (0, 0.6, 0), Scale (0.3, 0.3, 0.3)にしました。

image.png

InspectorのAdd Componentからスクリプトを追加してください。


  • VRC_Trigger (追加すると勝手にVRC_Event Handlerも入る)

image.png

VRC_TriggerのAdvancedModeにチェックを入れ、OnInteractを選択してAddを押します。

image.png

AlwaysBufferOneをAlwaysにします。

分かりやすいようにInteraction TextはGPIO SWにします。

Actionsの+を押して、AnimationTriggerを選択します。

image.png

ButtonをドラッグしてAnimatorが登録されるようにします。

Triggerに先ほどAnimatorで設定したのと同じ名前のOneShotを入れます。

image.png

Buttonを選択し、On Click()の+ボタンを押して、VRCPanoramaをドラッグで登録したら、VRC_Panorama->NextPanoを選択します。

これでVRC_TriggerからVRC_PanoramaのNextPano関数を呼び出す仕組みが出来ました。

VRC_Panoramaの関数、ShowPanoAtやNextPano、PrevPanoはRPCからは呼べず、uGUIからなら呼ぶことが出来ます。

今回はuGUIのButtonのClickイベントでVRCPanoramaのNextPanoを呼び出しています。

そしてこれをTriggerで制御するために、ボタンをPressするだけのアニメーションを作成し、

Trigger時にはこのAnimationを呼んでボタンを押したことにすることで、Trigger->uGUI Button->VRC_Panoramaを呼べるようにしました。

VRC_PanoramaのShowPanoAtは同じIndexでは一度しか呼べない為、わざと同じURLを2個登録して、NextPanoで行ったり来たりしています。

ワールドに入った際に1度一つ目のURLが呼び出されてしまいますが、中継サーバー側で無視します。

image.png

Assets/VRCSDK/Prefabs/WorldのVRCWorldを適当な場所に設置します。設置位置がリスポーン地点です。

image.png

メニューのVRChat SDKからShow Build Control Panelを押します。

まずはSetup Layers for VRChatを押します。

image.png

Do it!

image.png

続いてSetup Collision Layer Matrix for VRChatを押します。

image.png

Do it !!!!

image.png

ついでにEnable 3D spatialization on all 3D AudioSources in scene nowも押します

image.png

Do it!!!!

image.png

Canvasを選択し、LayerをDefaultに変えます。

image.png

Yes, change children

image.png

以上でワールド作成完了です。

TestのNew Buildを押して、VRChat起動させてきちんとワールド読み込まれるか確認してください。

image.png


中継HTTPサーバーを作成する

Visual Studio 2017と.NET Framework 4.7.1が必要です。

まずは中継サーバーのプログラムをダウンロードします。

VRC_Panorama_TCP_Repeaterを開きます。

image.png

緑色のClone or downloadからDownload ZIPを押してダウンロードされるzipを解凍してください。

解凍したら中に入っているVRC_Panorama_TCP_Repeater.slnを開きます。

image.png

そのまま▶開始ボタンを押せば実行できます。

image.png

画面が表示されるので開始ボタンを押してください。

image.png

初回はファイアウォール警告が出ますのでチェックを入れてアクセスを許可するを押してください。

これだけで準備は完了です。

次回からはVisualStudioを立ち上げなくても、VRC_Panorama_TCP_Repeater-master\VRC_Panorama_TCP_Repeater\bin\DebugフォルダのVRC_Panorama_TCP_Repeater.exeを直接実行出来ます。

少しコードの解説をします。


MainWindow.xaml.cs

if (path.StartsWith("?"))// ?で始まる時はコマンドとして処理する

{
var command = path.Substring(1);
if (command == "TRIGGER")
{
if (isFirstTrigger) //初回のTriggerはワールドに入ったときに自動的に発動する
{
isFirstTrigger = false;
Log("ワールドに入りました");
}
else
{
Log("TRIGGERコマンド受信");
var t = SendCommandToESP8266Async("GPIOSW"); //処理に時間がかかる場合があるのでawaitせずに実行
}
}
//コマンド受信時はわざとエラーを返すことで、VRC_Panoramaが再度接続してくるようにする。
Log($"{client.Client.RemoteEndPoint},404,{path}");
writer.Write(enc.GetBytes("HTTP/1.0 404 Not Found\r\n"));
writer.Write(enc.GetBytes("Content-Type: text/plain\r\n"));
writer.Write(enc.GetBytes("\r\n"));
writer.Write(enc.GetBytes("File Not Found\r\n"));
writer.Flush();
return;
}

この部分で、先ほど設定したVRC_Panoramaの通信を処理しています。

VRC_Panoramaではhttp://localhost:61221/?TRIGGERというアドレスを設定しましたが、

最初の行のpathには?TRIGGERが入るようになっていますので、先頭文字が?だった場合コマンドとして処理します。

そしてコマンドがTRIGGERだった場合に

初回はワールドに入ったときなので無視

2回目以降はESP8266に向かってGPIOSWコマンドを送るようになっています。

これでVRC_Panorama->このアプリ->ESP8266の中継を行います。

そして処理した後、404エラーを返しています。このようにすることでVRC_Panoramaにはテクスチャが設定されず、

次回以降も再度問い合わせてくれます(一度テクスチャを読み込めると二度と読み込まない)

ここは404である必要はありません。200でも、テクスチャになる画像を返さなければ良いです。

例えばVRC_Panoramaだけでなく別のアプリからも同じようにトリガーしたいのであれば、404ではなく200にしてOK等の

文字列を返す方が親切かと思います。

この部分を改造して好きなコマンドを実装すれば、VRC_Panoramaから好きなように通信を受けて何かを実行できます。

また、?以外が来たとき、例えばhttp://localhost:61221/image.png等の通常のURLアクセスを行った場合は

exeと同じフォルダにあるwwwフォルダの中身を返すようになっていますので、www/image.png等を置けば

VRC_Panoramaに普通にテクスチャ画像を返すことも可能です。


ESP8266(ESP-WROOM-02)のプログラムを用意する

今回は自作のESP-WROOM-02ボードを使用していますが、どんなボードでも構いません。

IO13ピンに付けたLEDを光らせるプログラムを作成します。

ESP-WROOM-02自体の設定や書き込み方法はボードによって異なる場合があるので、各自調べてください。

Arduino IDEで書き込みます。

早速プログラムを紹介します。


main.ino

#include <ESP8266WiFi.h>


const char ssid[] = "ssid"; //接続先Wi-FiのSSIDを入れる
const char password[] = "pass"; //接続先Wi-Fiのパスワードを入れる
const int SW = 13; //出力するIOピン

//固定IPアドレス(環境ごとに変えてください)
IPAddress ip(192,168,1,200);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);

WiFiServer server(9999); //ポート番号

void setup() {
Serial.begin(115200);
pinMode(SW, OUTPUT);
Serial.println("Booting...");
WiFi.config(ip, gateway, subnet);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
delay(500);

int retrycnt = 0;
while(WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.print(".");
delay(100);
if(retrycnt++ > 100) {
ESP.restart();
}
}
Serial.println("Connect Success");

//Portのlistenを開始
server.begin();
Serial.println("Start Server");
}

void loop() {
String cmd = ReceiveCommand();
if (cmd == "GPIOSW") {
digitalWrite(SW, HIGH);
delay(100); //100ms wait
digitalWrite(SW, LOW);
}
}

String ReceiveCommand() {
WiFiClient client = server.available();
String ret;
if (client.connected()) {
//コマンド受信(来なければタイムアウト
ret = client.readStringUntil('\r');
Serial.print("Receive:");
Serial.println(ret);
//返答する
client.print("OK\r");
//接続をクローズ
client.stop();
}
return ret;
}


各自自分のWi-FiルーターのSSIDとパスワード、固定するためのIPアドレスを書き換えてください。

image.png

参考にテスト時の書き込み設定です。

image.png

最低限動作に必要な参考回路図を置いておきます。


実際に動かしてみる


  1. ESP-WROOM-02の電源を入れます


  2. VRC_Panorama_TCP_Repeater.exeを実行して、開始ボタンを押します。


  3. クライアント設定(ESP8266側)のサーバーIPにESP8266に設定したIPアドレスを入れてください。

    image.png

  4. 作成したVRChatのワールドに入ります(New Buildを押します)


  5. VRC_Panorama_TCP_Repeaterにワールドに入りましたとログが出ることを確認します。

    image.png

  6. ワールド内のCubeでトリガーを引きます。

    image.png

  7. トリガーを引くたびに、画面のログにTRIGGERコマンド受信と、OKが返ってきて、LEDが光ることを確認します。

    ※OKではなくエラーが返ってくる場合はESP8266との通信に失敗しています。Arduinoターミナルでログを確認してください。

無事LEDが押すたびに点灯すれば完成です。


謝辞

AnimatorControllerやAnimationの設定方法について、がとーしょこらさんに教えていただきました。

ありがとうございました。


おわり

VRChatの何かしらのトリガーをローカルで受け取ることが出来るようになりました。これで外部のLEDだけでなく、VRChat外で何かをするアプリケーションや、

例えばワールド内の扇風機のスイッチを押したらリアルの扇風機を動かす装置なんかを作ることも出来ると思います。

使い方色々、面白い使い方待ってます。

今回のVRChatから外部の装置のスイッチを入れる仕組みを使った放送が明日行われます。





是非ご覧ください。

それでは。

明日は、suna(@sunasaji)さんによる記事です。

VRChat Advent Calendar 2018