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
これで、
こーいうデータから
こんな感じのScriptableObjectにできます。
自分のゲームでは、例えばゲーム内ショップのラインナップとか、スコア計算用の係数郡とかをGoogleSpreadSheetで管理して、それをScriptableObjectに流し込んで使っています。
Google Play Service系のプラグインを更新したら ClassNotFoundException: Didn't find class “android.support.v4.content.FileProvider”というエラーがでてハングした
PlayServiceResolverでプラグインを最新にしたら、アプリがクラッシュするようになっちゃいました。
どうもSocialConnector関係のところで止まっているようでした。
たぶん、android.support.v4.aarとかが無くなったからのようです。
最新では、androidx.core.content.FileProviderが代わりのものっぽいので、AndroidManifest.xml内の
android:name="android.support.v4.content.FileProvider"
ってところを
android:name="androidx.core.content.FileProvider"
こんな感じに修正して、
Asset/SocialConnector/SocialConnector.cs内の中の51行目
var fileProvider = new AndroidJavaClass("android.support.v4.content.FileProvider");
を
var fileProvider = new AndroidJavaClass("androidx.core.content.FileProvider");
と修正したら動くようになりました。
ビッグローブ光にて、何故かMacでだけIPv6接続できなかった。
うちのネットワーク環境、ビッグローブ光でiPv6対応したのに、なぜかIPv4でしかアクセスできない状態だった。
ちなみに、所有機はMacbook Pro。
この問題、ずーっと放置していたのだけど、今日Windows機を導入したので試してみたらIPv6でアクセスできた。
なんでMacでIPv6接続できずWindowsでだけ接続できるんやー!っと思ったので、重い腰を上げてビッグローブに問い合わせてみた。
しかし、分からないのでAppleに聞いてくれと言われてしまう。オーマイガッ。
Appleに聞くのも腰が重いなぁと思って、プチプチっとネットワーク設定をいじっていたら、意外と簡単にIPv6アクセスできるようになった。
System Preference -> Network -> WiFi -> Advanced -> TCP/IP
の
Configure IPv6でLink-local onlyにしてたのをAutomaticallyにするだけだった。
ちなみにIPv6になって爆速になったかと言われるとそうでもなかった。
UnityのRandom.Range(min,max)はfloatとintで挙動が違う
Random.Range(min,max)は、引数がfloatの場合、maxはinclusiveになりますが、intの場合はmaxがexclusiveになります。
つまり、Random.Range(0.0f,10.0f)の場合、10.0fが返ってくるときがありますが、Random.Range(0,10)では10は返ってきません。最大でも9です。
なんでやねん!
知ったとき驚いたんですが、プログラミング界では普通なんですかね?
参考:
docs.unity3d.com
アプリ内課金実装周りで躓いたこと
アプリ内課金の実装にはUnityIAPを使用しています。
実装方法は、詳しい記事が色々とあるので、そちらをご参考下さい。
参考までに私が参考にした記事を1つ挙げておきます。
ただ、こちらの参考サイトでStart()で呼んでいるものをAwakeにしたほうが良いと思います。
理由は下記に。
UnityIAP初期化されない時がある問題
UnityIAPが、なぜか初期化されないことがありました。かと言って初期化失敗のコールバックが呼ばれているわけでもない。
色々とググッていたら、Startに書いている初期化をAwakeにしてみろ的なコメントを見つけました。
試してみたところ、それでうまくいきました。
Android版で課金しようとすると「認証が必要です。Googleのアカウントにログインしてください」っと出る。
英語版だと「Authentication is required. You need to sign into your Google Account.」
これ、色々と発生する可能性がありまして、ググると、
Google Developer Consoleにアップした直後だと駄目だから、ちょっと待て
とか
新しく追加したアカウントの場合は、端末を初期化しないと駄目だ
とか、
書かれています。
しかし、自分は、そのどれにも当てはまらず。
Google Developer Consoleではchatで相談に乗ってくれるので、質問してみました。
そうすると直ぐに解決。
購入商品が「有効」になっていなかったから
でした。
Google Developer Consoleの、「ストアでの表示」->「アプリ内サービス」にあります。
このスクショの右の方のやつですね。
こんなんに数時間ハマってしまった。
UnityでEditor実行時も30FPS
基本的にフレームレートを指定したいときはApplication.targetFrameRateに設定すれば良いです。
Application.targetFrameRate = 30;
とすると30FPSになります。
しかし、これだけだとEditorで実行していると、なんかヌルヌル動くなぁと思っていました。
調べてみるとどうもスクリプトで指定するだけじゃあかんらしく、QualityでV Sync Countを"Don't Sync"にせんとあかんそうです。
Edit -> Project Settings -> Quality
を選択し、
ここで設定できます。
こうしないとVSyncのタイミングに合わせて更新しちゃうってことなんですね。
だいたいのPCのVSyncは60FPSで行われるので60FPSになってしまうと。
知らんかった...