【Unity】Google スプレッドシートから直接データを読む方法

この記事では、↑の内容を試してみて、分かりにくかったところや疑問に思って調べたことをまとめています。
補足説明的な感じで読んでいただければと思います。

執筆時に扱った Unity のバージョンは 2021.1.5f1 です。
プラットフォームは Windows 10 (64ビット版) を対象としています。

この記事では、Google スプレッドシートを GSS と略記します。

まず、素晴らしい情報を発信して下さった執筆者のスタジオしまづさんに感謝します。
この情報に出会えなかったら、この先ずっと悶々としていたかも知れません。

あと、この方法を試すに至った経緯を最後に説明していますので、興味がある方は記事の下の方をご覧下さい。

二年以上前の記事だけど、動くの?

Unity は頻繁にアップデートするので、一年以上経つと内容が古くなってサンプルコードが動かなかったり、何らかの機能が非推奨になって警告が出たりします。

現在(2021/05/03)は、問題なく動作しますが、2つ警告が出ます。
警告文の通りに直せば済みます。

※画像をクリック(タップ)すると全体を表示します。

修正前

if(request.isHttpError || request.isNetworkError) {

修正後

var http_error    = request.result == UnityWebRequest.Result.ProtocolError   ? true : false;
var network_error = request.result == UnityWebRequest.Result.ConnectionError ? true : false;
if (http_error || network_error) {
運用上気を付ける点
GSS側

1. 使わない列と行は削除しておく。
2. セルの書式は、「書式なしテキスト」にする。

1. 使わない列と行は削除しておく。

使わない列と行を残しておくと、余計な空文字が混在することがあります。

2. セルの書式は、「書式なしテキスト」にする。


全てのセルを選択して、メニュー→表示形式→数字→書式なしテキストを実行しておきます。
セルの値を削除すると、「自動」に戻ってしまうので、定期的に行う必要があります。
書式が自動だと、アルファベットの e が何故か値なしと判断されたり、整数の 6 が 6.0 と少数付きの数字に変換されることがあります。

Unity 側

今回の C# ソースコード(3つとも)は、ゲームプレイ時には使わないので、ビルド対象から外しておいた方が安全です。
そうしないと、ビルドに失敗するかも知れません。
ビルド対象から外す方法は以下の記事の「エラーが出ないようにするには?」で説明していますので、必要な方はご参照ください。

気になったところ

以下の部分の下線を引いたところ
“https://docs.google.com/spreadsheets/d/”+SHEET_ID+”/gviz/tq?tqx=out:csv&sheet=”+_SHEET_NAME

↓コレ
tqx=out:csv

tqx が何なのかは分かりませんが、csv で out(出力) してるっぽいことが分かります。

じゃあ、これ、csv 以外を指定したらどうなるの?

という疑問がわいたので、

tsv, html, xml, json, js, css, ods, pdf, xls, xlsx, txt で試してみたところ、3種類の違った結果が得られました。

試した GSS のデータはこんな感じです。

1. csv

しまづさんが紹介している方法です。解析するのが一番楽です。

GSS が送ってきたデータ

"","項目名1","項目名2","項目名3"
" ","begefes","0","e"
" ","a","a",""
"","43","","6"
" ","","hhhhhhhh",""
2. html

ホームページなんかで使っているHTML形式で取得できます。解析が面倒です。

GSS が送ってきたデータ

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>シート1</title>
</head>
<body>
<table border="1" cellpadding="2" cellspacing="0">
<tr style="font-weight: bold; background-color: #aaa;">
<td></td><td></td><td></td><td></td>
</tr>
<tr style="background-color: #f0f0f0">
<td> </td><td>項目名1</td><td>項目名2</td><td>項目名3</td>
</tr>
<tr style="background-color: #ffffff">
<td> </td><td>begefes</td><td>0</td><td>e</td>
</tr>
<tr style="background-color: #f0f0f0">
<td> </td><td>a</td><td>a</td><td> </td>
</tr>
<tr style="background-color: #ffffff">
<td> </td><td>43</td><td> </td><td>6</td>
</tr>
<tr style="background-color: #f0f0f0">
<td> </td><td> </td><td>hhhhhhhh</td><td> </td>
</tr>
</table>
</body>
</html>
3. その他

JSON みたいなデータを取得できます。解析が面倒です。
※未指定や無効な値を指定すると、この形式のデータになります。

GSS が送ってきたデータ

/*O_o*/
google.visualization.Query.setResponse({"version":"0.6","reqId":"0","status":"ok","sig":"1016343079","table":{"cols":[{"id":"A","label":"","type":"string"},{"id":"B","label":"","type":"string"},{"id":"C","label":"","type":"string"},{"id":"D","label":"","type":"string"}],"rows":[{"c":[null,{"v":"項目名1"},{"v":"項目名2"},{"v":"項目名3"}]},{"c":[{"v":" "},{"v":"begefes"},{"v":"0"},{"v":"e"}]},{"c":[{"v":" "},{"v":"a"},{"v":"a"},{"v":null}]},{"c":[null,{"v":"43"},null,{"v":"6"}]},{"c":[{"v":" "},null,{"v":"hhhhhhhh"},{"v":null}]}],"parsedNumHeaders":0}});

何らかの理由で、「どうしても html 形式でないとダメなんだ!」みたいな状況では使えるかも知れません。
ただ、csv 以外で受け取るメリットがさっぱり分かりませんw

ソースコード
GSSReader.cs
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Events;

public class GSSReader : MonoBehaviour
{
  public string SheetID   = "読み込むシートのID";
  public string SheetName = "読み込むシート";
  public UnityEvent OnLoadEnd;

  public bool IsLoading { get; private set; }
  public string[][] Datas { get; private set; }

  IEnumerator GetFromWeb()
  {
    IsLoading = true;

    var tqx = "tqx=out:csv";
    var url = "https://docs.google.com/spreadsheets/d/" + SheetID + "/gviz/tq?" + tqx + "&sheet=" + SheetName;
    UnityWebRequest request = UnityWebRequest.Get(url);
    yield return request.SendWebRequest();

    IsLoading = false;

    var protocol_error   = request.result == UnityWebRequest.Result.ProtocolError   ? true : false;
    var connection_error = request.result == UnityWebRequest.Result.ConnectionError ? true : false;
    if (protocol_error || connection_error)
    {
      Debug.LogError(request.error);
    }
    else
    {
      Datas = ConvertCSVtoJaggedArray(request.downloadHandler.text);
      OnLoadEnd.Invoke();
    }

  }

  public void Reload() => StartCoroutine(GetFromWeb());

  static string[][] ConvertCSVtoJaggedArray(string t)
  {
    var reader = new StringReader(t);
    reader.ReadLine();  //ヘッダ読み飛ばし
    var rows = new List<string[]>();
    while (reader.Peek() >= 0)
    {
      var line = reader.ReadLine();        // 一行ずつ読み込み
      var elements = line.Split(',');    // 行のセルは,で区切られる
      for (var i = 0; i < elements.Length; i++)
      {
        elements[i] = elements[i].TrimStart('"').TrimEnd('"');
      }
      rows.Add(elements);
    }
    return rows.ToArray();
  }
}

スプレッドシートのデータを取得して解析するスクリプトです。
解析が終了したら、OnLoadEnd に登録したメソッドを呼び出します。
解析結果はプロパティの Datas から取得できます。
任意の GameObject に AddComponent して使います。

GSSReaderEditor
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(GSSReader))]
public class GSSReaderInspector : Editor
{
  public override void OnInspectorGUI()
  {
    base.OnInspectorGUI();

    var r = target as GSSReader;
    var t = "スプレッドシート読み込み";

    EditorGUI.BeginDisabledGroup(r.IsLoading);
    if (GUILayout.Button(t))
    {
      r.Reload();
    }
    EditorGUI.EndDisabledGroup();
  }
}

GSSReader を AddComponent した GameObject のインスペクターに「スプレッドシート読み込み」ボタンを表示するエディタ拡張スクリプトです。
ボタンを押すとスプレッドシートのデータを取得できます。

NewBehaviourScript
using UnityEngine;

//  GSSReader を追加している GameObject にのみ AddComponent できます。
[RequireComponent(typeof(GSSReader))]
//  クラス名はなんでもOKです。
//  読み込むデータに合わせて適切なクラス名に変更してください。
//  MonoBehaviourを継承している場合、スクリプトファイル名と
//  クラス名を合わせる必要があるので、クラス名を変更したら
//  スクリプトファイル名も変更してください。
public class NewBehaviourScript : MonoBehaviour
{
  //  メソッド名もなんでもOKです。
  //  GSSReader の OnLoadEnd に追加することで
  //  GSS の読み込み完了時にコールバックされます。
  public void OnGSSLoadEnd()
  {
    var r = GetComponent<GSSReader>();
    var d = r.Datas;
    if (d != null)
    {
      //  d をゲームで使うデータに変換する処理をここに書きます。
      //  以下は d の中身をコンソールに表示するサンプルです。
      for (var row = 0; row < d.Length; row++)
      {
        for (var col = 0; col < d[row].Length; col++)
        {
          Debug.Log("[" + row + "][" + col + "]=" + d[row][col]);
        }
      }
    }
  }
}

取得したデータを処理するスクリプトです。
GSSReader.Datas の値を取得して、それを任意の ScriptableObject に値をセットするなどします。
GSSReader を追加している GameObject に AddComponent します。

使い方
1. ソースコードをコピペ

C# スクリプトを3つ新規作成して、↑の3つのソースコードをコピペします。

2. 任意の GameObject に GSSReader と、GSSReader.OnLoadEnd が呼び出すメソッドを持つ C# スクリプトを AddComponent

3. GSSReader の OnLoadEnd にコールバックメソッドを追加


OnLoadEnd の右下にある+を押して、NewBehaviourScript 内にあるメソッドを追加します。
必ず、Editor And Runtime にして下さい。それ以外だと動作しません。

4. GSSReader の SheetID と SheetName を変更


しまづさんの記事にも記載されていますが、下線部分が SheetID です。

SheetName はそのままの意味です。

5. Inspector にある「スプレッドシート読み込み」ボタンを押す

実行結果

経緯

私は結構長くゲーム業界にいます。
これまで様々な企業と関わり、様々な現場を見てきましたが、データ入力はほぼ Excel でした。
ダイヤルアップ接続の時代から変わってません。
その間ずーっとゲーム開発の下支えをしてきたわけですから、すごいですよね。

ここ数年で GSS がすごく使いやすくなったので、Excel 以外の選択肢も欲しいなぁ…と思って、GSS でデータ編集できないかを探っていました。

Excel のダメなところは、複数人で同時編集しにくいところと、起動が遅いところ、サブスクリプションが必要(有料)な所です。
GSS みたいなネットワーク共有機能が一応はあるんですが、反映に時間がかかるので使いにくいです。
編集する人間が増えると GSS のリアルタイム性が欲しくなります。
最近は Excel のクラウド版が出てきたものの、使える機能が少ないので GSS 使った方がずっとマシです。

そこで、複数人での編集がリアルタイムに反映される、読み込みが速い、無料で使える…と三拍子そろった GSS を使う方法も選択肢として持っておきたい…となりました。

最初に、Google Apps Script を使ってコンバータを作ってみたのですが、今はコンバートしたファイルを Google Drive に保存しないとならないようです。
ちょっと前までなら、認証方法をあれこれすれば、直接ダウンロードできたっぽいですが、もっと頑張ればできるのか?頑張ってもできないのか?は分かりません。
ご存じの方がいらっしゃいましたら、ぜひ教えて下さいw

現状分かっているところまでで、GSS と Excel を使った場合の手順を比較すると

GSS
1. コンバート(スクリプト実行)
2. Google Drive からダウンロード
3. インポート

Excel
1. コンバート(マクロ実行)
2. インポート

Excel と比べて、手順が1つ多いので、微妙です。
数回程度なら1つ手順が増えるくらいどうってことはないのですが、実際の作業は何百回、何千回と繰り返すことになるので、たった1つ手順が増えるだけでも、トータルで見ると大きな違いになります。
そもそも、手間が増えるんだったら Excel でいいじゃんって話です。

なので、GSS でこの手順を減らす方法があったらなぁ~…と思い、ゲーム業界の知人に聞いてみたところ、スタジオしまづさんの動画を紹介していただき、この記事を書くまでに至りました。

スタジオしまづさんの方法だと

GSS
1. インポート

手順がインポートだけになります。
これは使わない手はないですよね。

クラウドが抱える重大な欠陥

ここまでの話からすると、この記事で紹介した方法は夢のような解決策に思えますが、GSS には重大な欠陥があります。
というよりも、GSS に限らず、全てのクラウドサービスに共通している欠陥です。

全てのクラウドサービスがネットワークを使う以上、ルータと回線とクラウドサーバーとサービスを提供している企業(プロバイダー)の都合に振り回されます。

ローカルで Excel を使うだけなら、クラウドが抱える欠陥を気にする必要はありません。
クラウドが抱える欠点は、将来的にネットワーク技術が進歩することで解消されるかも知れません。
でも、今はクラウドを使う以上は、常にこのリスクを抱えることになります。

ルータと回線の問題なら、フリーのWifiスポットに行ったり、ネカフェを使ったりで回避できますが、クラウドサーバーとプロバイダーについてはどうにもなりません。
後者がトラブった場合は完全に詰む場合もあります(事例アリ)。
良くても、プロバイダーが問題を解決してくれるまでの数時間~数日間、作業が止まります。

そうなった場合のことを考えると、常にローカルにバックアップを取りながら作業することになります。
トラブったときだけ、Excel と同じように作業して、復旧したらまたクラウドで…っていう、ハイブリッド編集が妥協点になるかなーと思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です