Help us understand the problem. What is going on with this article?

VRChatのWebPanelでバッファ同期する変数を使う

More than 1 year has passed since last update.

2018/9/3追記: WebPanelは2018年8月に無効化されました。いまのところ復活の目処は立っていません。

概要

  • VRChatのWebPanel上でLate-Joinersにもバッファで同期する変数の使い方を解説
  • その発展形として、自前のリダイレクトサーバー等を使わずLate-JoinersにもURIを同期させ、任意のウェブページを閲覧できるようにする

前提知識

  • VRC_WebPanelは現状バグだらけでまともに動く部分のほうが少ない
  • 確実に信頼できるのはSynchronize URIオプションによるURI同期だけ(※個人の感想です)
  • 4月のアップデートから発生している入力フォームにフォーカスが移ると操作不能になるバグは完全に放置されており、WebPanel界隈では諦めムードが漂っている
  • SetWebPanelURIBroadcastTypeをAlwaysにしていても最後の1回しか実行されない(ログを見るとAlwaysBufferOneと同じく、いったんイベントが削除されてから再登録されているので、おそらくこれは不具合ではなく仕様)

WebPanelに関する資料

ハッシュ同期

 前回の記事ではJavaScript疑似プロトコルを用いた同期方法を解説したが、今回はハッシュによる同期方法を紹介する。
 まずは以下のようなHTMLを作成し、Chromeで開いてみる。

<!DOCTYPE html>
<html>
<head>
<title>Sync Var on WebPanel</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
$(function() {
  window.onhashchange = function() {
    $('#var').text(location.hash);
  };
  window.onhashchange();
});
</script>
</head>
<body>
<div id="var"></div>
</body>
</html>

 最初は何も表示されないが、URIの末尾に #1 のようにハッシュを追加すると、その値がページに表示される。#a のようにしても動作するはずである。

Chrome画面

 ここではonhashchangeイベントハンドラに、ハッシュの値をページ上に書き込む処理をセットしているが、このイベントはハッシュが書き換えられたときにのみ発火するので、ハッシュ付きでページが開かれた場合に対処するため、それを即時実行している。
 次はこのHTMLをUnity上に持っていく。前回の記事に書いたように、VRChatのWebPanelはHTTP経由でHTMLを読み込む方が安定して動作するので、今回はローカルにサーバを立てて開発を進める。HTTPサーバはNginxをおすすめしたい。ワールド公開時のホスティング先はGitHub Pagesがおすすめだ。

Unity画面

 まずはさきほどのHTMLをVRC_WebPanelのStart Pageにセットし、Synchronize URIにチェックを入れる。

Unity画面

 ボタンとして使うオブジェクトのVRC_TriggerにAlwaysBufferOneのOnInteractを追加。WebPanelに対して http://localhost/sync_var.html#1 のようにハッシュ付きのURI置換を行うようにする。
 動作をわかりやすくするため、もうひとつボタンを作成し http://localhost/sync_var.html#2 としておく。

VRChat画面

 VRChat上でローカルテストしてみると、ブラウザ上でURIを直接書き換えたときと同じように動作することが確認できる。マルチ環境でも、後から入室した人、いわゆるLate-Joinersにちゃんと同期する。
 これがハッシュによる同期の基本である。
 JavaScript疑似プロトコルによる同期も、AlwaysBufferOneで実行すれば最後の1回が実行されるので、現段階ではハッシュによる同期とたいした違いはないが、ページ遷移をともなう処理を行った場合に違いが明確となる。JavaScript疑似プロトコルは現在開いているページの状態をもとに、現在開いているページに対して処理を実行するが、今回紹介した方法では、VRChat上でボタンを押すたびに最初の状態に巻き戻され、その上で処理が実行される。だからページ遷移をともなう処理があっても、問題なく再実行が可能なのだ。

変数格納先の選定

 いくつかの固定ページを見たいだけなら、ワールド内にボタンをたくさん置いて、それぞれのSetWebPanelURIにURIを設定し、それをAlwaysBufferOneで実行するだけですむが、任意のウェブサイトを閲覧するとなると少しハードルが上がる。
 これの解決方法としては、リダイレクトサーバーを使う方法がある。WebPanelからアクセスするURIは固定で、そのURIが返すリダイレクト先を可変にする、というものだが、サーバーを用意したりAPIを用意したりといった作業はなかなかにハードルが高い。なので代替としてGoogle Spreadsheetを使う方法をおすすめしたい。
 Google Spreadsheetを「変数格納先」として使う利点は、以下のようなものが挙げられる。

  • Googleアカウントがあれば誰でも無料で利用可能
  • WebAPIが用意されており、シートの値をJSON形式で取得できる
  • データの書き換えが容易・直感的
  • 自分だけが編集可能、特定の人だけが編集可能、誰でも編集可能、といった権限設定が容易

 前節で紹介したハッシュを用いた同期でシート名や行番号を指定し、Google Spreadsheetから値を取得し、それをもとに何らかの処理を行う、という手法を次節から解説していく。

Google Spreadsheet

 まずはスプレッドシートを新規作成し、共有設定をする。共有相手は「リンクを知っている全員」、権限はとりあえず「閲覧」にしておこう。他の人にもシート編集を許可する場合はここを「編集」にしておけばOKだ。

GoogleDrive画面

 共有設定が終わると https://docs.google.com/spreadsheets/d/xxxx/edit?usp=sharing のようなリンクを取得できるが、xxxx の部分がこのスプレッドシートのIDとなる。シートには動作確認用の文字列を適当に何か入力しておこう。シート名は Default に変更しておく。

GoogleDrive画面

 この文字列をAPIから取得するにはアクセス用のAPIキーが必要となるが、これはGoogle Cloud Platformのコンソールから取得できる。プロジェクトがない場合は新規作成し、認証情報を作成→APIキーと選択することでAPIキーが新規作成される。その後、ライブラリからGoogle Sheets APIを有効化することで、このAPIキーを用いてスプレッドシートにアクセスできるようになる。

Googleコンソール画面

 これでスプレッドシートのIDとAPIキーが手に入ったので、以下のようなURIにHTTP GETリクエストを投げることで、シートの値をJSON形式で取得できる。

https://sheets.googleapis.com/v4/spreadsheets/{Spreadsheet ID}/?key={API Key}&includeGridData=true&ranges=Default!A1:A1

 とりあえずはブラウザでこのURIを開いてみる。

API結果画面

 正常に値を取得できている。
 エラーコード404が返ってくる場合は、スプレッドシートのIDが間違っている。400の場合はAPIキーが間違っているか、セルの指定が間違っているので再確認しよう。

JavaScriptでAPIにアクセスする

 さきほどブラウザで直接確認したJSONをJavaScriptから取得し、それをページ上に表示させてみる。
 まずはWebPanelにセットしているHTMLを以下のように書き換える。スプレッドシートIDとAPIキーを書き換えないと動かないので注意。

<!DOCTYPE html>
<html>
<head>
<title>Sync Var on WebPanel</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
$(function() {
  var db = 'https://sheets.googleapis.com/v4/spreadsheets/{Spreadsheet ID}/?key={API Key}&includeGridData=true&ranges=';

  var getValue = function(row, sheet) {
    $.get([db, sheet, '!', 'A', row, ':', 'A', row].join(''))
      .done(function(data) {
        var value = '';
        try {
          value = data.sheets[0].data[0].rowData[0].values[0].formattedValue;
        } catch(e) {
          // Do nothing
        }
        if (!value) {
          // Write error handling code here
          return;
        }
        $('#var').text(value);
      })
      .fail(function() {
        // Write error handling code here
      });
  };

  window.onhashchange = function() {
    if (location.hash.length < 2) return;
    var hash = location.hash.slice(1);
    var options = hash.split(';');
    var row = options[0];
    if (!row.match(/^\d+$/)) return;
    row -= 0;
    var sheet = 'Default';
    if (options.length > 1) {
      sheet = options[1];
      if (!sheet.match(/^\w+$/)) return;
    }
    getValue(row, sheet);
  };
  window.onhashchange();
});
</script>
</head>
<body>
<div id="var"></div>
</body>
</html>

 これをブラウザで開いて、以前確認したときと同じく、URIの末尾に #1 のようにハッシュを追加すると、シートのA1セルに入力した値が表示される。#2 にするとA2セルの値が表示されるはずである。
 このプログラムは別シートにも対応している。指定がなければ Default シートを参照するが、ハッシュを #1;Sheet2 のようにすると、Sheet2 のA1セルを参照するようになっているので試してみてほしい。
 これをVRChatでローカルテストしてみると、押したボタンに対応した値が表示されるはずである。

VRChat画面

 別シートを参照したい場合はSetWebPanelURIを http://localhost/sync_var.html#1;Sheet2 のようにするだけでいい。プログラムを書き換えることなく、ただUnity上でボタンを増やすだけで、「変数の格納先」をどんどん増やすことが可能だ。
 そしてこれが最も重要なのだが、最初に確認したように、これはバッファで同期する。Late-Joinersにも、この変数は伝わるのである。この性質を用いれば、かなり多様な表現が可能になる。その例として、任意のウェブページを閲覧する方法を最後に紹介する。

任意のウェブページを閲覧する

 もうすでにやり方は予想が付いていると思うので、特に詳しい解説はしない。取得した値で location.href を書き換えているだけである。

<!DOCTYPE html>
<html>
<head>
<title>Sync Var on WebPanel</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
$(function() {
  var db = 'https://sheets.googleapis.com/v4/spreadsheets/{Spreadsheet ID}/?key={API Key}&includeGridData=true&ranges=';

  var getValue = function(row, sheet) {
    $.get([db, sheet, '!', 'A', row, ':', 'A', row].join(''))
      .done(function(data) {
        var value = '';
        try {
          value = data.sheets[0].data[0].rowData[0].values[0].formattedValue;
        } catch(e) {
          // Do nothing
        }
        if (!value) {
          // Write error handling code here
          return;
        }
        location.href = value;
      })
      .fail(function() {
        // Write error handling code here
      });
  };

  window.onhashchange = function() {
    if (location.hash.length < 2) return;
    var hash = location.hash.slice(1);
    var options = hash.split(';');
    var row = options[0];
    if (!row.match(/^\d+$/)) return;
    row -= 0;
    var sheet = 'Default';
    if (options.length > 1) {
      sheet = options[1];
      if (!sheet.match(/^\w+$/)) return;
    }
    getValue(row, sheet);
  };
  window.onhashchange();
});
</script>
</head>
<body>
</body>
</html>

 シートのA1セルに http://example.com/ と入力しておき、ローカルテストしてみる。

VRChat画面

 ちゃんと動いているようだ。マルチ環境でもLate-Joinersにちゃんと同期されることを確認した。

注意点

 スプレッドシートを誰でも編集可にした場合は悪意あるコードが実行される可能性があるので、サニタイズをきっちりやる必要がある。

実例

 拙作のTwitchチャットワールドでは、実際にハッシュを用いた同期を使用している。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした