35
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

VRChatAdvent Calendar 2018

Day 20

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

Posted at

はじめに

この記事は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

35
21
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?