Unityとスプレッドシートを連携させてみた

こんにちは!

Coading課のguuです。

今回は、僕がよくやるスプレッドシートとUnityを連携させる方法を共有します‼︎

この記事では、昔にサークルのLT会で話したUnityからスプレッドシートに文章を送信する方法に加えて、スプレッドシートの情報をUnityで取得する方法を紹介します。

目次

資料

資料を作成したのが、1年以上前のためコルーチンを使用していますが、UniTaskで代用可能です。

Google Apps Script(GAS)とは 

GASは、Googleが提供しているスプレッドシートやドキュメントといったサービスの拡張機能を自分で簡単に作成できるサービスです。

連携メリット

  • 非プログラマーも含めた全メンバーが確認できる
  • データの管理をCSVファイルのようなわかりやすい形で扱える

連携デメリット

  • http通信を行うので、インターネット通信が必須

GASの使い方

スプレッドシートにGASを導入する

スプレッドシートにGASを導入する手順を説明していきます。

  1. GoogleDriveを開いて、スプレドシートを作成します
  2. スプレッドシートのツールバーの「拡張機能」から「Apps Script」を選択します

これで、スプレッドシートにGASを導入できました。

GASで作成した機能を使えるようにする

スプレッドシートにGASのコードを書いても、Unityのような他の場所からスプレッドシートにアクセスできなければ意味がありません。

次に、外部に公開するやり方を説明します。

  1. GASで機能を制作します
  2. 右上の「デプロイ」ボタンの「新しいデプロイ」を選択します
  3. 新しいデプロイ画面の種類の選択の右側の歯車マークから「ウェブアプリ」を選択します。
  4. 次のユーザとして実行」を「自分」にする
  5. アクセスできるユーザー」を「全員」にする(誰でも使える状態にしたくない場合は、別の設定を選択してください)
  6. 最後に右下の「デプロイ」ボタンを押して完了です

注意事項

ユーザー認証が解決できない

GASのドキュメントを参照してください。

実行する関数があっているか

GASにアクセスされた時に最初に呼び出す関数を選択できます。

設定は、GASの画面の上のところの関数名が書かれたところからできます。

この最初に呼び出す関数が想定しているものか確認しておきましょう。

実行するウェブアプリは、きちんと最新のものになっているか

GASを更新する際には、実行するウェブアプリが最新のデプロイになっているか確認しましょう。

GASの画面の右上の「デプロイ」ボタンの「デプロイを管理」から確認できます。

デプロイの管理画面のバージョンが「新バージョン」になっていれば、基本的に問題はありません。

Unityからスプレッドシートにデータを送信する

概要

今回は、Unityで制作したフォームに書かれた文章をスプレッドシートに保存するシステムを例に紹介します。

このシステムの流れは以下のとおりです。

送信側(Unity側)

受信側(GAS側)

送信側の処理(Unity側)

GASに送信するデータを作成

送信するデータの作成は、WWWFormを使用します。

WWWFormは、Dictionaryのような形になっており、keyからvalueを検索できるようになっています。

今回の場合は、「バグ報告」や「日時」といった見出しから実際の文章や日付のような内容を検索できるようにしました。

リクエストの作成

UnityWebRequest」を使用して、フォームを実際に送信できる形にしましょう。

今回は、送信を行うので「UnityWebRequest.Post(API, フォーム)」という形で指定します。

APIのURLは、GAS画面の右上の「デプロイ」ボタンの「デプロイを管理」から「ウェブアプリ」という項目の「URL」に書かれています。

ヘッダーの作成

リクエストを送信する時、ヘッダーというものを含める必要があります。

ヘッダーでは、受信側の情報や受信側と送信側での決め事が書いてあります。

Unityは、「SetRequestHeader」を使うことでヘッダーを設定できます。

設定すべきヘッダー要素

Origin

アクセス認証のための設定。

https://GASID.script.googleusercontent.com」を指定します。

GASIDは、GASの画面の左側のツールバーの歯車マークを押した画面の「スクリプトID」という項目にあります。

Content-Type

今回はテキストを送信するため、「application/x-www-form-urlencoded」を指定します。

リクエストの送信

SendWebRequest」メソッドで、リクエストを送信できます。

リクエストの返信には、送信成功したかの状態が格納されているので、UniTaskやコルーチンで返信を待ちましょう。

受信側の処理(GAS側)

POSTメソッドを受けるための関数の準備

GASには、POSTメソッドを受けるための関数である「doPost(e)」というものが用意されています。

この関数内に、処理を書いていきます。

ちなみに、引数の「e」には送られてきたデータが含まれています。

最初に実行される関数を「doPost」に変更するのも忘れず設定しましょう。

クロスオリジンリソース共有(CORS)の対応(WebGLの場合)

WebGLでUnityをビルドする場合は、CORSに対応する必要があります。

Webサイト同士の通信を行う際には、CORSにより受信するリクエストが制限されています。

そのため、通信を行う前に送られるリクエストであるOPTIONSメソッドの返信で、適切に権限を付与してあげる必要があります。

権限の付与は、ヘッダーに必要な権限を記述することで行います。

付与する権限

Access-Control-Allow-Origin

リクエストを許可する相手を設定できます。

今回は、全員を許可するので、”*“を設定します。

Access-Control-Allow-Methods

許可するリクエストのメソッドを設定できます。

今回は、データの送信(POST)通信の設定変更(OPTIONS)を許可します。

Access-Control-Allow-Headers

許可するヘッダーの設定を設定できます。

今回は、データ形式の設定とリクエストを認証する、「Content-Type」、「Authorization」を許可します。

書き込むスプレッドシートを取得する

書き込むスプレッドシートは、「SpreadsheetApp.openById(スプレッドシートのID).getSheetByName(シートの名前)」で取得できます。

スプレッドシートのIDは、スプレッドシートのURLに含まれており、「spreadsheets/d/」の後から「/edit」の前までの部分です。

シートの名前は、スプレッドシートの下側に書いてあります。

シートの名前は、日本語だとうまくいかないので、英語にしておきましょう。

スプレッドシートに書き込む

まず書き込む行を「getLastRow」メソッドで取得します。

このメソッドは、文字が書かれた行のうち最後の行を返すので、この行に1足すことで、続きにデータを書き込めます。

実際に書き込む処理は、「シート.getRange(最後の行数, 列数).setValue(e.parameter.設定したキー名)」でできます。

GetRange」でシートのどこに書き込むのかを決め、「setValue」で実際に書き込んでいます。

返信する

成功したというメッセージをdoPostの返り値にします。

ソースコード(実装例)

送信側(Unity側)

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class ButtonSetting : MonoBehaviour
{
    /*GASで作成したWebアプリケーションのURL*/
    private const string APIURL = "GASで作成したWebアプリケーションのURL";
    /*GoogleスプレッドシートのID*/
    private const string GASID = "GoogleスプレッドシートのID";
    private const string GASPOSTURL = "https://" + GASID + ".script.googleusercontent.com";

    /*バグ報告フォーム*/
    [SerializeField]
    private InputField bugForm;
    /*感想・要望フォーム*/
    [SerializeField]
    private InputField requestForm;
    /*フォームに書かれたテキスト*/
    private string bugText;
    private string requestText;

    /*フォームのUI*/
    [SerializeField]
    private GameObject formUI;
    /*ローディングアニメーションのUI*/
    [SerializeField]
    private GameObject loading;
    /*送信結果を表示するテキスト*/
    [SerializeField]
    private Text resultText;

    /*ボタン押された時*/
    public void onClick()
    {
        StartCoroutine(Upload());
    }

    IEnumerator Upload()
    {
        /*GASに送信するフォームの作成*/
        WWWForm form = new WWWForm();

        /*InputFieldから文字列を取り出す*/
        bugText = bugForm.text.ToString();
        requestText = requestForm.text.ToString();

        /*GASに送信するためのフォームに[キー:文字列]という形で挿入*/
        form.AddField("bug", bugText);
        form.AddField("request", requestText);

        /*リクエストの作成*/
        UnityWebRequest request = UnityWebRequest.Post(APIURL, form);

        /*リクエストのヘッダー設定*/
        request.SetRequestHeader("Origin", GASPOSTURL);
        request.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        
        /*ローディングアニメーションを表示*/
        formUI.SetActive(false);
        loading.SetActive(true);

        /*フォームの送信が完了するまで待機*/
        yield return request.SendWebRequest();

        /*ローディングアニメーションを非表示*/
        loading.SetActive(false);

        /*送信したフォームが正常に送信できたか表示*/
        if (request.result != UnityWebRequest.Result.Success)
        {
            Debug.Log(request.error);
            resultText.text = "フォームを送信できませんでした";
        }
        else
        {
            resultText.text = "フォームを送信しました";
        }
    }
}

受信側(GAS側)

function doPost(e) {
 // ヘッダーの設定
  var headers = {
    'Access-Control-Allow-Origin': '*', //全員許可
    'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', // POST, GET, OPTIONSメソッドの許可
    'Access-Control-Allow-Headers': 'Content-Type, Authorization' // データ形式の設定とリクエストの認証を許可
  };

  // リクエストがOPTIONSメソッドの場合は、CORS設定を返す
  if (e.requestMethod == 'OPTIONS') {
    return ContentService.createTextOutput('')
    .setMimeType(ContentService.MimeType.TEXT)
    .setHeaders(headers);
  }

  // シート取得
  var sheet = SpreadsheetApp.openById(スプレッドシートのID).getSheetByName(シート名);
  //送信されたデータを取得
  var params = JSON.parse(e.postData.getDataAsString());
  // 書き込む行を取得
  var newRow = sheet.getLastRow()+1;

  // 実際に書き込む
  sheet.getRange(newRow, 1).setValue(params.bug);
  sheet.getRange(newRow, 2).setValue(params.request);
  sheet.getRange(newRow, 3).setValue(new Date());

  // 返信する
  const output = ContentService.createTextOutput();
  output.setMimeType(ContentService.MimeType.JSON);
  output.setContent(JSON.stringify({"message":"success"}));

  return output;
}

スプレッドシートのデータをUnityで取得する

概要

送信側の処理(GAS側)

データ送信の準備をする関数の準備

GASには、GETメソッドを受けるための関数である「doGet(e)」というものが用意されています。

この関数内に、処理を書いていきます。

ちなみに、引数の「e」にはリクエストのデータが含まれています。

最初に実行される関数を「doGet」に変更するのも忘れず設定しましょう。

スプレッドシートからデータを取得

基本は、「書き込むスプレッドシートを取得する」、「スプレッドシートに書き込む」の内容と同じです。

「setValue」を「getValue」に変えましょう。

データをJSON形式にする

項目の要素が行で並んでいるので、項目名と項目の全要素を「,」区切りにしたものペアを追加していきます。

そして、最後にこの並びをJSON形式でdoGetの返り値にします。

受信側の処理(Unity側)

リクエストの作成

UnityWebRequest.Get(API)」を使用して、データのリクエストを作成します。

APIは、「Unityからスプレッドシートに送信する」の「リクエストの作成」のものと同じです。

スプレッドシートのデータ取得

Unityからスプレッドシートに送信する」の「リクエストの送信」のものと大体同じで、この結果にスプレッドシートのデータが含まれています。

ソースコード(実装例)

送信側(GAS側)

// 項目名
const DATE = "date";
const TOPIC = "topic";
const INFO = "info";

// 項目の行数
const DATE_COLUMN = 1;
const TOPIC_COLUMN = 2;
const INFO_COLUMN = 3;

//シート
const SHEET = SpreadsheetApp.openById(シートのID).getSheetByName(シート名);

const IMAGE_TAG = /<i\s*([^>]*)>/g;

// データを送信
function doGet(e) {
  var payload = CreateJSONData();

  //データ形式をJSONにする
  var output = ContentService.createTextOutput(JSON.stringify(payload));
  output.setMimeType(ContentService.MimeType.JSON);
  
  return output; 
}

// 送信するデータ作成
function CreateJSONData() {
  const res = {};
  res[DATE] = CreateJsonRow(DATE_COLUMN);
  res[TOPIC] = CreateJsonRow(TOPIC_COLUMN);
  res[INFO] = ProcessWithImages(CreateJsonRow(INFO_COLUMN));

  Logger.log(res);
  return res;
}

// 項目ごとにまとめる
function CreateJsonRow(column) {
  const lastRow = SHEET.getLastRow();
  if (lastRow <= 1) {
    return [];
  }
  const targetColumn = SHEET.getRange(2, column, lastRow - 1, 1);
  const values = targetColumn.getValues();

  return values.map(row => row[0]).join(',');
}

// 省略

受信側(Unity側)

public class InfoSetter
{
    private const string APIURL = APIのURL;

    // スプレッドシートのデータを辞書型で取得
    private async UniTask<Dictionary<string, object>> GetRequestAsync(CancellationToken ct)
    {
        Dictionary<string, object> response = new Dictionary<string, object>();
        using (UnityWebRequest request = UnityWebRequest.Get(APIURL))
        {
            await request.SendWebRequest().ToUniTask(cancellationToken: ct);

            if (request.result != UnityWebRequest.Result.Success)
            {
                Debug.Log("GET Request Failure");
                Debug.Log(request.error);
            }
            else
            {
                Debug.Log("GET Request Success");
                Debug.Log(request.downloadHandler.text);
                response = Json.Deserialize(request.downloadHandler.text) as Dictionary<string, object>;
            }
        }

        return response;
    }

    // 省略
}

おわりに

長々とお付き合いいただきありがとうございました。

今回使用しているHTTP通信は、スプレッドシート以外にもたくさんのAPIと通信できるので、ぜひ使ってみてください‼︎

また、今回紹介したコードは、コピペをして自由に改変してもらって大丈夫です。

よき、Unityライフを!

参考記事

コメントを残す

CAPTCHA


アドカレ

次の記事

部誌の制作