前提と問題
UnityゲームのバックエンドにPlayFabを使って、スコアランキング機能の実装を試みた。
この辺を見て、1ゲームが終わった直後にUpdatePlayerStatistics()
でハイスコア(PlayerStatistics)を更新して、そのレスポンスが返ってきた後にGetLeaderboard()
でランキング(Leaderboard)を取得したのだけど、今送信したばかりのスコアがランキングに反映されていない。
しかし、暫くしてからGetLeaderboard()
したら反映されているし、PlayFab管理画面でも更新されている。
Unity仮想コード
void Start()
{
PlayFabClientAPI.UpdatePlayerStatistics(
new UpdatePlayerStatisticsRequest() {
Statistics = new List<StatisticUpdate>() {
new StatisticUpdate() {
StatisticName = myStatisticName,
Value = myScore
}
}
}, OnUpdatePlayerStatisticsSuccess, OnFailure);
}
void OnUpdatePlayerStatisticsSuccess(UpdatePlayerStatisticsResult result)
{
GetLeaderboard();
}
void GetLeaderboard()
{
PlayFabClientAPI.GetLeaderboard(
new GetLeaderboardRequest() {
StatisticName = myStatisticName,
}, OnGetLeaderboardSuccess, OnFailure);
}
void OnGetLeaderboardSuccess(GetLeaderboardResult result)
{
var entries = result.Leaderboard; // これにmyScoreが反映されていないことがある
// entries を画面に表示する処理など
}
環境
- Unity 2018.2.19f1
- PlayFab
forumを検索してみる(英語)
StatisticsをUpdateしてからLeaderboardが最新の状態になるまでに1秒かそれ以上かかるので、Cloud ScriptでStatisticsをUpdateしつつLeaderboardのUpdateを行って、Cloud Scriptが返ってきた後でLeaderboardのクライアントクエリを取得するのがオススメとのこと。
けどserverのAPIリファレンス見てもLeaderboardをUpdateするメソッドが見つからない。
- Leaderboard update time
- Realtime leaderboard solution?
- Safe time between statstics update and leaderboard download
この辺には数秒(具体的な数字としては2秒)待つと書いてある。
えー。
対策
- スコア確定次第なるはやで
UpdatePlayerStatistics()
を呼び出し、GetLeaderboard()
まで時間が空くような画面、UXデザインにする - 以下のいずれかの場合、未反映状態とみなして(少し時間を空けてから)取得し直す
- 取得したLeaderboardの範囲内に自身のPlayFabIdのLeaderboardEntryがある、かつスコアが更新されているはずなのに更新されていない
- 取得範囲内に自身のLeaderboardEntryがないが、LeaderboardEntryの個数が取得Request数に満たない(反映済みなら自身のLeaderboardが含まれるはず)
- 取得範囲内に自身のLeaderboardがないが、自身のスコアが範囲内の最低点より高い(同上)
- 指定PlayFabId(大体は自分)を中心にしたLeaderboardを返す
GetLeaderboardAroundPlayer()
なんてのもあり、こちらを使う場合は自身のLeaderboardEntryが必ず含まれるはずなので、前者の条件だけで良い
Unity仮想コード
さっきと同じ処理のところは割愛
void OnUpdatePlayerStatisticsSuccess(UpdatePlayerStatisticsResult result)
{
StartCoroutine("WaitAndGetLeaderboard", 2f);
}
void WaitAndGetLeaderboard(float waitSeconds)
{
yield return new WaitForSeconds(waitSeconds);
GetLeaderboard();
}
void OnGetLeaderboardSuccess(GetLeaderboardResult result)
{
var entries = result.Leaderboard;
var myEntry = entries.Find(_entry => _entry.PlayFabId == myPlayFabId);
if (myEntry != null && myEntry.StatValue < myScore
|| myEntry == null && entries.Count < requestedEntriesCount
|| myEntry == null && entries[entries.Count - 1].StatValue < myScore)
{
StartCoroutine("WaitAndGetLeaderboard", 1f);
return;
}
// entries を画面に表示する処理など
}
Cloud Scriptで対処する場合
本来は、クライアントでのチートを防止するため、UpdatePlayerStatistics()
ではなくCloud ScriptでStatisticsをUpdateするのが良いそうで(そもそも意図的にAllow client to post player statistic
を設定しないとUpdatePlayerStatistics()
のリクエストは無効)
それを踏まえると、1個目のforum検索結果の
Cloud ScriptでStatisticsをUpdateしつつLeaderboardのUpdateを行って、Cloud Scriptが返ってきた後でLeaderboardのクライアントクエリを取得するのがオススメとのこと。
を「StatisticsをUpdateしてからLeaderboardのUpdateを待って」と読み替えてCloud Scriptを実装すると良いような気が。
APIの単位としてはそれが正解だと思うのですが、レスポンスが返ってくるのに毎度2秒以上待つのも変なので…悩ましい。
もっと良い手があれば誰か教えてください!
せっかくなので
そんなハイスコア機能を組み込んだゲーム、WebGL版をunityroomさんで、Android版をGoogle Play Storeで公開しているので、良かったら遊んでってください。
unityroomさんのunity1週間ゲームジャムに投稿後、今回の手法でハイスコア機能を追加したものとなります。
https://unityroom.com/games/10to10
https://play.google.com/store/apps/details?id=com.zurachu.TenToTen