GoogleSpreadSheetからデータを取ってきてScriptableObjectに値を突っ込む

基本的にはこちらを参考にしてます
7081.hatenablog.com

まずは上記のサイトを参考にしつつGoogle API Consoleでの設定を行います。

次に認証コードからアクセストークンとリフレッシュトークンを取得します。

WWWForm form = new WWWForm();
if (!form.headers.ContainsKey("Content-Type"))
{
    form.headers.Add("Content-Type", "application/x-www-form-urlencoded");
}
else
{
    form.headers["Content-Type"] = "application/x-www-form-urlencoded";
}
form.AddField("code", "OAuthの認証コード");
form.AddField("client_id", "OAuthのクライアントID");
form.AddField("client_secret", "クライアントシークレット");
form.AddField("redirect_uri", "http://localhost");
form.AddField("grant_type", "authorization_code");
form.AddField("access_type", "offline");
Dictionary<string, string> headers = form.headers;
byte[] rawData = form.data;
WWW www = new WWW("https://www.googleapis.com/oauth2/v4/token", rawData, headers);
while (!www.isDone) ;
Debug.Log("GetAccessJsonData, www.text : " + www.text);

アクセストークンさえ手に入れば、

//とってくるセルの範囲
string start_cell = "A1";
string end_cell = "Z100";//範囲は大きめにとっておいても大丈夫

string send_text = "https://sheets.googleapis.com/v4/spreadsheets/スプレッドシートのID/values/"
    + start_cell + ":" + end_cell + "?access_token=" + access_token;

WWW www = new WWW(send_text);
while (!www.isDone) ;

Debug.Log( "json data : " + www.text );

っという感じで、セルのデータをjsonデータとしてとってこれます。

ちなみにここで「スプレッドシートのID」とは、スプレッドシートのページのURLが

https://docs.google.com/spreadsheets/d/ほげほげ/edit#gid=0

だとしたら、この「ほげほげ」の部分です。

アクセストークンは寿命があるので、PlayerPrefsに覚えておいて、使えなくなったらまた発行してもらうっていうふうにしてみます。

//アクセストークンを取得
//覚えていたら、それを返す。
//それが無効になってたらリフレッシュして再取得したものを返す
public static string getAccessToken()
{
    const string KEY_ACCESS_TOKEN = "KEY_ACCESS_TOKEN";

    string access_token = PlayerPrefs.GetString( KEY_ACCESS_TOKEN, "" );
    //アクセストークンがまだ有効かどうか調べる
    string access_token_status = CheckAccessTokenStatus( (string)access_token );
    if( access_token_status == "Invalid Value" || access_token == "" )
    {
        //アクセストークンが無効だったので取り直す
        Debug.Log( "Refresh Access Token!!!" );
        access_token = RefreshAccessToken();
    }

    //アクセストークンを覚えておく
    Debug.Log( "access_token : " + access_token );
    PlayerPrefs.SetString( KEY_ACCESS_TOKEN, access_token );

    return access_token;
}

//Access Tokenが有効かどうかチェック
static string CheckAccessTokenStatus( string access_token )
{
    WWW www = new WWW("https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=" + access_token );
    while (!www.isDone) ;

    JsonData jsonData = JsonMapper.ToObject(www.text);
    IDictionary idict = jsonData;
    if( idict.Contains("error_description") )
        return (string)jsonData["error_description"];
    else
        return "";
}

static string RefreshAccessToken()
{
    WWWForm form = new WWWForm();
    form.AddField("refresh_token", "リフレッシュトークン" );
    form.AddField("client_id", "クライアントID");
    form.AddField("client_secret", "クライアントシークレット");
    form.AddField("grant_type", "refresh_token");

    Dictionary<string, string> headers = form.headers;
    byte[] rawData = form.data;
    WWW www = new WWW("https://www.googleapis.com/oauth2/v4/token", rawData);

    while (!www.isDone) ;

    JsonData jsonData = JsonMapper.ToObject(www.text);
    return (string)jsonData["access_token"];
}

ちなみにJsonのパースにはLitJsonを使っています。
GitHub - Mervill/UnityLitJson: JSON library for Unity3D

今回はゲーム内テキストの日本文と英語文をMessageDatabaseというScriptableObjectに持たせることを想定し、そこにデータをぶち込もうと思います。

[System.Serializable]
public class TranslateText {
    public string key;
    public string jp;
    public string eng;
    public TranslateText( string key_, string jp_, string eng_ )
    {
        key = key_;
        jp  = jp_;
        eng = eng_;
    }
}

[CreateAssetMenu(menuName = "CreateMessageDatabase")]
public class MessageDatabase : ScriptableObject {
    public TranslateText[] translateTexts = new TranslateText[0];

#if UNITY_EDITOR
    //google spreadsheet から読み込んで登録
    public void RegisterData( string[][] data, int num_data )
    {
        translateTexts = new TranslateText[num_data];
        for( int i = 0; i < num_data; ++i )
        {
            string key = data[i][0];
            string jp  = data[i][1];
            string eng = data[i][2];
            translateTexts[i] = new TranslateText( key, jp, eng );
        }
    }
#endif
}


データをぶち込んでいるところも含めて、全体は以下のような感じ。

#if UNITY_EDITOR
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using LitJson;

public class SpreadsheetDataLoader
{
    private const int MAX_COLUMN = 5;//多めに取る。足りなかったら増やす
    private const int MAX_ROW = 1000;//多めに取る。足りなかったら増やす。

    [System.Serializable]
    public class SpreadsheetData
    {
        public string range;
        public string majorDimension;
        public SpreadsheetData()
        {
            for (int i = 0; i < values.Length; ++i)
            {
                values[i] = new string[MAX_COLUMN];
            }
        }

        //多めにとっててもToObjectするときにshrinkされるっぽい
        public string[][] values = new string[MAX_ROW][];

        public int NumColumns { get { return values.Length; } }
    }

    [MenuItem("Assets/Load Spreadsheet")]
    static void LoadSpreadsheet()
    {
        string access_token = getAccessToken();

        //とってくるセルの範囲
        string start_cell = "A1";
        string end_cell = "Z100";//範囲は大きめにとっておいても大丈夫

        string send_text = "https://sheets.googleapis.com/v4/spreadsheets/スプレッドシートのID/values/"
            + start_cell + ":" + end_cell + "?access_token=" + access_token;

        WWW www = new WWW(send_text);
        while (!www.isDone) ;

        SpreadsheetData spreadsheet = null;
        spreadsheet = JsonMapper.ToObject<SpreadsheetData>(www.text);

        int num_columns = spreadsheet.NumColumns;
        //Scriptable Objectのインスタンスを作る
        MessageDatabase messages = AssetDatabase.LoadAssetAtPath<MessageDatabase>("Assets/MessageDatabase.asset");
        messages.RegisterData(spreadsheet.values, num_columns);//データを登録

        //セーブ
        UnityEditor.EditorUtility.SetDirty(messages);
        UnityEditor.AssetDatabase.SaveAssets();
    }

    //アクセストークンを取得
    //覚えていたら、それを返す。
    //それが無効になってたらリフレッシュして再取得したものを返す
    public static string getAccessToken()
    {
        const string KEY_ACCESS_TOKEN = "KEY_ACCESS_TOKEN";

        string access_token = PlayerPrefs.GetString( KEY_ACCESS_TOKEN, "" );
        //アクセストークンがまだ有効かどうか調べる
        string access_token_status = CheckAccessTokenStatus( (string)access_token );
        if( access_token_status == "Invalid Value" || access_token == "" )
        {
            //アクセストークンが無効だったので取り直す
            Debug.Log( "Refresh Access Token!!!" );
            access_token = RefreshAccessToken();
        }

        //アクセストークンを覚えておく
        Debug.Log( "access_token : " + access_token );
        PlayerPrefs.SetString( KEY_ACCESS_TOKEN, access_token );

        return access_token;
    }

    //Access Tokenが有効かどうかチェック
    static string CheckAccessTokenStatus( string access_token )
    {
        WWW www = new WWW("https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=" + access_token );
        while (!www.isDone) ;

        JsonData jsonData = JsonMapper.ToObject(www.text);
        IDictionary idict = jsonData;
        if( idict.Contains("error_description") )
            return (string)jsonData["error_description"];
        else
            return "";
    }

    static string RefreshAccessToken()
    {
        WWWForm form = new WWWForm();
        form.AddField("refresh_token", "リフレッシュトークン" );
        form.AddField("client_id", "クライアントID");
        form.AddField("client_secret", "クライアントシークレット");
        form.AddField("grant_type", "refresh_token");

        Dictionary<string, string> headers = form.headers;
        byte[] rawData = form.data;
        WWW www = new WWW("https://www.googleapis.com/oauth2/v4/token", rawData);

        while (!www.isDone) ;

        JsonData jsonData = JsonMapper.ToObject(www.text);
        return (string)jsonData["access_token"];
    }

    //一番最初にRefleshTokenとかを調べるときに使う
    [MenuItem("Assets/GetRefleshToken")]
    static void GetRefleshToken()
    {
        WWWForm form = new WWWForm();
        if (!form.headers.ContainsKey("Content-Type"))
        {
            form.headers.Add("Content-Type", "application/x-www-form-urlencoded");
        }
        else
        {
            form.headers["Content-Type"] = "application/x-www-form-urlencoded";
        }
        form.AddField("code", "OAuthの認証コード");
        form.AddField("client_id", "OAuthのクライアントID");
        form.AddField("client_secret", "クライアントシークレット");
        form.AddField("redirect_uri", "http://localhost");
        form.AddField("grant_type", "authorization_code");
        form.AddField("access_type", "offline");
        Dictionary<string, string> headers = form.headers;
        byte[] rawData = form.data;
        WWW www = new WWW("https://www.googleapis.com/oauth2/v4/token", rawData, headers);
        while (!www.isDone) ;
        Debug.Log("GetAccessJsonData, www.text : " + www.text);
    }
}
#endif

これで、
f:id:wkpn:20191025224641p:plain
こーいうデータから
f:id:wkpn:20191025224707p:plain
こんな感じのScriptableObjectにできます。

自分のゲームでは、例えばゲーム内ショップのラインナップとか、スコア計算用の係数郡とかをGoogleSpreadSheetで管理して、それをScriptableObjectに流し込んで使っています。