Facebookを使用したUnity3Dのシンプルなリーダーボード

Ludum Dare 31に参加した後、友達と競うことができるゲームを手に入れ、Facebookを介して承認を得て、リーダーボードを追加することにしました。 ゲームでどのような困難が生じる可能性があり、どのように似たようなことをするのか、カットの下で読んでください。



画像



フェイスブック



私たちが最初にしたことは、Facebook SDKをプラグインすることでした。 Asset Storeから無料でダウンロードできます。 SDKは長い間書かれており、積極的に更新されていないことに注意してください。 特に、Unity 4.6との互換性のために何かを修正する必要があります。 ファイルFB.csを開き、411行目でUNITY_4_5行をUNITY_4_6に変更します。 今すぐ動作します。 さて、または、この定義を正しいものに書き換えて、4.5、4.6、およびそれ以降のすべてでも機能するようにします。



次に、 https://developers.facebook.comでアプリケーションを作成および構成する必要があります 。 その後、App IDを取得し、インスペクターを使用して設定に入力します。







次に、UnityでFacebookを初期化する必要があります。 これを行うには、 FB.Init()関数を呼び出す必要がありますが、ドキュメントには、ゲームの最初の起動時に一度だけ呼び出す必要があると書かれています。 そして、ここで最初の困難が生じるかもしれません。 実際には、2つのコールバックを渡す必要があります。 初期化を完了し、ゲームライブラリを最小化します。 問題は、この関数をどこで呼び出すかです。 ゲーム全体が1つのステージにあり、このシーンがリブートしない場合、問題はありません。 Awake()で GameControllerを呼び出すだけです。



それ以外の場合は、静的関数またはシングルトンをすぐに実行することをお勧めします。 この実装を使用しました。 それは非常に簡単でした。



public class SocialController : Singleton<SocialController> { public void Awake() { FB.Init(FacebookInited, OnHideUnity); } private void FacebookInited() { Debug.Log("FacebookInited"); if (FB.IsLoggedIn) { Debug.Log("Already logged in"); OnLoggedIn(); } } private void OnHideUnity(bool isGameShown) { Debug.Log("OnHideUnity"); if (!isGameShown) { GameController gameController = FindObjectOfType<GameController>(); if (gameController != null) { gameController.SetPause(); } } } ... }
      
      





その後、SocialControllerのいくつかの関数を呼び出すと、自動的に作成されます。 どのシーンでも、シーンを移動しても破壊されません。 OnHideUnity()関数では、アクティブなゲームプレイが進行中の場合はゲームを一時停止でき、 FacebookInited()ではユーザーが既にログインしているかどうかを確認できます(これはゲームの再起動時に発生します)。 ユーザーがまだログインしていない場合、この機会を彼に与える必要があります。 このため、ユーザーがログインしていない場合にゲームに表示されるボタンを追加しました(これはFB.IsLoggedInで確認できます)。



  public void LoginToFaceBook() { if (!FB.IsLoggedIn) { FB.Login("user_friends", LoginCallback); } }
      
      





必要な許可はFB.Login()関数に渡されます。 そして、ここで疑問が生じます。どのような許可が必要ですか? そして、それは私たちがFacebookで何を望むかにかかっています。 最初は、リーダーボードにFacebook Scores APIを使用したいと考えていました。 ゲームのリーダーボード用に特別に作成され、非常にシンプルなものとして位置付けられています。 そして、最初はそうでした。 ポイントと友達のポイントの両方を簡単に取得でき、すぐにリストをソートできます。 しかし、さらなる研究により、すべてがそれほど単純ではありませんでした。 まず、ユーザーごとに1つの番号のみを保存できます。 そのため、リーダーボードはたった1つで、友達の間でのみです。



しかし、最悪の部分は、独自のスコアを更新するために、アプリケーションがpublish_actionsパーミッションを取得する必要があることです 。 また、この許可は、ユーザーがテープなどに書き込むことができることを意味します。 また、ユーザーにこの許可を要求できるようにするには、アプリケーションをレビューする必要があります。 そして、ユーザーはまだあなたにそれを与えないかもしれません。 結果は非常に難しく、可能性は最小限です。 それで、私はそのような決定を拒否しなければなりませんでした。

この場合、Facebookから必要なもの:





これに基づいて、 FB.Login()関数で権限のリストを作成します。 user_friendsだけが必要です。

ログインすると、必要な情報をリクエストできます:



  void OnLoggedIn() { Debug.Log("Logged in. ID: " + FB.UserId); FB.API("/me?fields=name,friends", Facebook.HttpMethod.GET, FacebookCallback); } void FacebookCallback(FBResult result) { if (result.Error != null) { return; } string get_data = result.Text; var dict = Json.Deserialize(get_data) as IDictionary; _userName = dict["name"].ToString(); friends = Util.DeserializeJSONFriends(result.Text); GotUser(); GetBestScoresFriends(); }
      
      





Util.DeserializeJSONFriends()関数は公式の例から取られており、 ここから入手できます 。 その結果、変数_userNameにはプレーヤーの名前が、 友達に友達のリストが表示されます。



解析



次のステップは、ポイントとプレイヤーのリストを保存することです。 Facebook Score APIを拒否したため、サーバーが必要です。 取得する最も簡単な方法は、 Parseを使用することです。 彼はUnity専用のライブラリを持っていて、 ここで利用できます 。 ただし、ライブラリは明らかにUnity専用に作成されたものではなく、.NETバージョンのみが使用されたため、特定の問題が発生します。



公式ガイドは十分に書かれているため、Parseのセットアップはそれほど難しくありません。 Parse Initialize Behaviorをゲームコントローラではなく新しいオブジェクトに追加する価値があることに注意してください。シーンをリロードしてもオブジェクトは破棄されないためです。



Parseは開発者に素晴らしい機会を提供しますが、何が必要でしょうか? 最初に使用したいと考えたのは、 ParseUserというParse用語のユーザーです。 新しいものを作成するか、既存のものを更新できます。 異なるタイプのユーザーを統合し、1人のユーザーの異なるプロファイルをリンクするために必要です。 たとえば、ユーザーが最初にメールでログインしてから、Facebookアカウントも指定することにした場合です。 その後、プレーヤーのFBアカウントに関する情報をプロファイルに追加できます。 ただし、 ParseUserを少しいじってみたところ、実際には必要ないことがわかったため、これ以上使用しません。



しかし、私たちが絶対に必要なのはParseObjectです。 このような各オブジェクトは、基本的にデータテーブル内のレコードです。 オブジェクトを作成するときに名前が付けられるテーブル。 したがって、 新しいParseObject( "DataTable")を記述します-Save ()メソッドを呼び出した後、DataTableテーブルに新しいレコードを取得します。 リーダーボードの単純なアルゴリズムが判明しました。 現在のユーザーのテーブル内のレコードを探しています。見つからない場合は、新しいレコードを作成します。 いずれの場合でも、現在のユーザーを含むParseObjectがあります。 プレーヤーの名前と記録を書き留めて保存します。



  private void GotUser() { var query = ParseObject.GetQuery("GameScore") .WhereEqualTo("playerFacebookID", FB.UserId); query.FindAsync().ContinueWith(t => { IEnumerable<ParseObject> result = t.Result; if (!result.Any()) { Debug.Log("UserScoreParseObject not found. Create one!"); _userScoreParseObject = new ParseObject("GameScore"); _userScoreParseObject["score"] = 0; if (string.IsNullOrEmpty(_userName)) _userScoreParseObject["playerName"] = "Player"; else _userScoreParseObject["playerName"] = _userName; _userScoreParseObject["playerFacebookID"] = FB.UserId; _userScoreParseObject.SaveAsync(); } else { Debug.Log("Found score on Parse!"); _userScoreParseObject = result.ElementAt(0); int score = _userScoreParseObject.Get<int>("score"); if (score > GameController.BestScore) { GameController.BestScore = score; GameController.UpdateBestScore = true; } } }); } public void SaveScore(int score) { if (_userScoreParseObject == null) return; Debug.Log("Save new score on Parse! " + score); int oldScore = _userScoreParseObject.Get<int>("score"); if (score > oldScore) { _userScoreParseObject["score"] = score; _userScoreParseObject.SaveAsync(); } }
      
      





GameController.BestScoreおよびGameController.UpdateBestScoreは、 GameControllerクラスの静的フィールドです。 先ほど言ったように、Parseは元々Unity用に設計されたものではないため、その動作のロジックはやや不便です。 Unityプログラマーに馴染みのあるCoroutineプログラマーの代わりに、ここではTaskシステムクラスが使用されます。 非同期で動作し、 ContinueWith()の内容は別のスレッドで呼び出されます。 ある種のMonoBehaviourメソッドを呼び出そうとすると、エラーが発生します。 静的フィールドは、この問題を回避するための最も美しい方法ではありませんが、私たちのケースでは役立ちました。 Habr-usersの誰かがコメントでアプリケーションのメインスレッドに戻る通常の方法(AndroidプログラミングのLooperのようなもの)を教えてくれたら、ありがたいです。



すべての友人の間で最高の選手のリストを取得するだけです。 これを行うには、Parseに適切なリクエストを行うだけです。



  public void GetBestScoresOverall() { var query = ParseObject.GetQuery("GameScore") .OrderByDescending("score") .Limit(5); query.FindAsync().ContinueWith(t => { IEnumerable<ParseObject> result = t.Result; string leaderboardString = ""; foreach (ParseObject parseObject in result) { leaderboardString += parseObject.Get<string>("playerName"); leaderboardString += " - "; leaderboardString += parseObject.Get<int>("score").ToString(); leaderboardString += "\n"; } GameController.overallLeaderboardString = leaderboardString; }); } public void GetBestScoresFriends() { if (friends != null && friends.Any()) { List<string> friendIds = new List<string>(); foreach (Dictionary<string, object> friend in friends) { friendIds.Add((string)friend["id"]); } if (friendIds.Any()) { string regexp = FB.UserId; for (int i = 0; i < friendIds.Count; i++) { regexp += "|"; regexp += friendIds[i]; } var queryFriends = ParseObject.GetQuery("GameScore") .OrderByDescending("score") .WhereMatches("playerFacebookID", regexp, "") .Limit(5); queryFriends.FindAsync().ContinueWith(t => { IEnumerable<ParseObject> result = t.Result; string leaderboardString = ""; foreach (ParseObject parseObject in result) { leaderboardString += parseObject.Get<string>("playerName"); leaderboardString += " - "; leaderboardString += parseObject.Get<int>("score").ToString(); leaderboardString += "\n"; } GameController.friendsLeaderboardString = leaderboardString; }); } } }
      
      





グローバルリーダーボードを取得する機能が多かれ少なかれ明確であれば、友人を選択する機能を書くのは簡単ではないかもしれません。 この場合、テーブルから自分と友人を選択する正規表現を作成します。 ドキュメントのWhereMatches()関数の動作が遅いだけでなく、正規表現も非常に長くなる場合があります(このゲームをプレイする友人の数にも依存します)。 この方法は大規模なプロジェクトには適していないと思いますが、これまでのところ、小さなゲームにはうまく機能し、問題は発生しません。 しかし、この場合、「賢明に」行動する必要があることを誰かが説明してくれれば感謝します。



ボーナス



このすべての後、Facebook経由で承認された有効なリーダーボードを取得しました。 そして、すでに承認されているので、その結果を友人と共有できるようにしてください。 クールなのは、これを自動的に行わず、ユーザーに標準ダイアログを提供する場合、特別な権限は必要ないということです。



  public void ShareResults() { string socialText = string.Format("I scored {0} in Sentinel. Can you beat it?", GameController.BestScore); FB.Feed( link: "https://apps.facebook.com/306586236197672", linkName: "Sentinel", linkCaption: "Sentinel @ LudumDare#31", linkDescription: socialText, picture: "https://www.dropbox.com/s/nmo2z079w90vnf0/icon.png?dl=1", callback: LogCallback ); }
      
      





最後まで読んでくれてありがとう。 優れたゲームを作成し、プレーヤーにリーダーボードとの競争を促します。



これが最終的にどのように機能するかを誰が気にしますか-最終的な結果です。 ここで、元のバージョンを再生できます。



All Articles