GoogleSpreadSheetを使ったVive対スマホ

執筆者 / イチクマ

はじめに

VRゴーグル被ってゲームしている人を横で見てるのっておもしろいですよね。

見ているとついちょっかいを出したくなる時ってありませんか?

でも相手に気づかれずに接近するとリアルにダメージをもらったりします。

ということでVR空間にスマホを使って参戦!

みたいなノリで VRとスマホの簡易対戦ゲームを作ってみました。

(もともとVRコンテンツ開発時にパラメータ調整を簡単にしたかったので、

こちらを参考にいろいろ試していた副産物です。)

この記事の内容

Unityを使用して Viveとスマホでそれぞれ別のゲームを作っていきます。

1体の巨人(Vive)に複数の小人(スマホ)が立ち向かうゲームになります。

全3回に分けて説明していきたいと思います。

今回はデータ共有方法についてです。

1. スプレッドシートでデータを共有する(いまここ)

2. VR側のゲーム画面を実装する

3. スマホ側のゲーム画面を実装する

用意するもの

今回はUnityでViveとスマホのデータ共有をできるようにするまでを説明します。

必要なものはUnityとスプレッドシートです。

Unityのバージョンは2019.3.15f1を使ってます。

スプレッドシートはGoogleアカウントを作れば使えるようになります。

スプレッドシートの準備

Unityとスプレッドシートの連携はすでにいろいろな方が試されています。

用途はデータ共有なので、こちらを参考にさせていただきました。

ここでは自分用にカスタマイズしたところを抜粋していきます。

スプレッドシートには以下のような情報を記載します。

状態:接続の状態(ready、playなど)

識別:プラットフォームの識別情報(Vive、Android、iOSなど)

職業:キャラクタメイキング要素(おまけ)

色味:キャラクタメイキング要素(おまけ)

位置:バトルシーンでの位置情報

角度:バトルシーンでの角度情報

スプレッドシートから[ツール] > [スクリプト エディタ]を選択、

表示された画面のコード.gsに以下を記載します。

function doPost(e)
{
  var request = e.parameter;
  var mode = request[“MODE”];
  var sheetName = request[“SHEETNAME”];
  var strdata = request[“STRDATA”];
  var jsonData = JSON.parse(strdata);
  var spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadSheet.getSheetByName(sheetName);
 
  if(mode === “GET”)
  {
    var row = jsonData[“ROW”];
    var clm = jsonData[“CLM”];
    var rowNum = jsonData[“ROWNUM”];
    var clmNum = jsonData[“CLMNUM”];
   
    var values = sheet.getRange(row, clm, rowNum, clmNum).getValues();
    var data = []
    for( var i=0; i<rowNum; i++)
    {
      for( var j=0; j<clmNum; j++ )
      {
        data = data.concat(values[i][j]);
      }
    }
   
    return ContentService.createTextOutput(JSON.stringify(data));
  }
  else if( mode === “SET” )
  {
    var row = jsonData[“ROW”];
    var clm = jsonData[“CLM”];
    var rowNum = jsonData[“ROWNUM”];
    var clmNum = jsonData[“CLMNUM”];
   
    var values = [];
    for( var i=0; i<rowNum; i++)
    {
      var clmvals = [];
      for( var j=0; j<clmNum; j++ )
      {
        var chk = ‘X’+j+‘Y’+i;
        if(jsonData[chk] === undefined ){ continue; }
        clmvals.push(jsonData[chk]);
      }
      values.push(clmvals);
    }
    sheet.getRange(row, clm, rowNum, clmNum).setValues(values);
  }
}

今回はスプレッドシートの複数行を一括で取得、設定する部分だけになります。

使い勝手に合わせて、この辺りはいろいろカスタマイズしていくと面白いです。

Unityでスプレッドシートの操作

上記GoogleAppsScriptで記載した内容に合わせて、

Unity側では以下のように記載します。

private Dictionary<string, object> m_sendData = new Dictionary<string, object>();

private int row = 0;
private int clm = 0;
private int rowMin = 0;
private int clmMin = 0;
private int rowMax = 0;
private int clmMax = 0;
private int rowNum = 0;
private int clmNum = 0;

public void ClearSD()
{
    m_sendData.Clear();
    row = 0;
    clm = 0;
    rowMin = 0;
    clmMin = 0;
    rowMax = 0;
    clmMax = 0;
    rowNum = 0;
    clmNum = 0;
}

public void SetSD_Pos(int x, int y)
{
    row = y;
    clm = x;
    m_sendData[“ROW”] = row;
    m_sendData[“CLM”] = clm;
}

public void SetSD_Num(int w, int h)
{
    rowNum = h;
    clmNum = w;
    m_sendData[“ROWNUM”] = rowNum;
    m_sendData[“CLMNUM”] = clmNum;
}

public void SetSD_Range(int x, int y, int w, int h)
{
    this.SetSD_Pos(x, y);
    this.SetSD_Num(w, h);
}

public void SetSD_Value(int x, int y, string val)
{
    string key = “X” + x.ToString() + “Y” + y.ToString();
    m_sendData[key] = val;
    this.CheckSD_RCNum(x, y);
}

public void SetSD_Value(int x, int y, float val)
{
    string key = “X” + x.ToString() + “Y” + y.ToString();
    m_sendData[key] = val;
    this.CheckSD_RCNum(x, y);
}

private void CheckSD_RCNum(int x, int y)
{
    if (x <= clmMin) { clmMin = x; }
    if (x >= clmMax) { clmMax = x; }
    if (y <= rowMin) { rowMin = y; }
    if (y >= rowMax) { rowMax = y; }
    rowNum = rowMax – rowMin + 1;
    clmNum = clmMax – clmMin + 1;
    m_sendData[“ROWNUM”] = rowNum;
    m_sendData[“CLMNUM”] = clmNum;
}

public CustomYieldInstruction SendDataAsync(string sheetName, Action<string> handleResponse = null)
{
    return SendDataAsync(“SET”, sheetName, m_sendData, response =>
    {
        if (handleResponse == null) return;

        var results = (IList)response;
        if (results == null || results.Count == 0)
        {
            handleResponse.Invoke(“”);
            return;
        }

        var result = results[0].ToString();
        handleResponse.Invoke(result);
    });
}

public CustomYieldInstruction GetDataAsync(string sheetName, Action<IList<string>> handleResponse)
{
    return SendDataAsync(GPS_GET, sheetName, m_sendData, response =>
    {
        if (handleResponse == null) return;

        var results = (IList)response;
        if (results == null || results.Count == 0) return;

        var values = new List<string>();
        foreach (var result in results) values.Add(result.ToString());
        handleResponse.Invoke(values);
    });
}

private CustomYieldInstruction SendDataAsync(string mode, string sheetName, Dictionary<string, object> data, Action<object> handleResponse = null)
{
    var strData = Json.Serialize(data);

    var formData = new List<IMultipartFormSection>();
    formData.Add(new MultipartFormDataSection(“MODE”, mode));
    formData.Add(new MultipartFormDataSection(“SHEETNAME”, sheetName));
    formData.Add(new MultipartFormDataSection(“STRDATA”, strData));

    bool complete = false;
    this.StartCoroutine(CT_SendData(formData, status => complete = status, handleResponse));

    return new WaitUntil(() => complete);
}

private IEnumerator CT_SendData(List<IMultipartFormSection> formData, Action<bool> updateStatus, Action<object> handleResponse = null)
{
    updateStatus(false);

    var www = UnityWebRequest.Post(m_gsappURL, formData);

    yield return www.SendWebRequest();
    if (www.isNetworkError || www.isHttpError)
    {
        Debug.LogError(“NetworkError:”+ www.error);
    }
    else
    {
        var response = Json.Deserialize(www.downloadHandler.text);
        handleResponse.Invoke(response);
        Debug.Log(www.downloadHandler.text);
    }

    updateStatus(true);
}

だいぶ自分好みに書いていますので、ざっくりで見てもらえたらと思います。

セルの横方向をX、縦方向をYとして二次元座標みたいに扱っています。

取得、設定いずれも起点となる左上の座標を指定します。

設定時は起点座標を0,0としたオフセット座標のように扱っています。

データの取得はVR側、スマホ側で異なります。

VR側では3~6行目(図の青枠)のデータを取得するので、以下となります。

ClearSD();
SetSD_Range(1, 3, 10, 4);
GetDataAsync(“シート名”, null);

スマホ側は2~6行目(図の青枠と赤枠)のデータを取得するので、以下となります。

ClearSD();
SetSD_Range(1, 2, 10, 5);
GetDataAsync(“シート名”, null);

まとめ

今回はスプレッドシートで簡易的なデータ共有を実装しました。

同時アクセス時のエラー処理などを考慮しない作りですが、

試作用として手軽に実装する際に役に立つかと思います。

他にもデータ共有サービスとしてPhotonなども比較的簡単でしたので、

機会があれば紹介したいと思います。

次回はVR側のゲーム画面の実装をしていきます。

参考リンク

ブログ一覧へ戻る