はじめに
この記事はK3 Advent Calender 2024の19日目です☆
あと、少しでクリスマスらしいですよ。
皆様はクリスマス、どう過ごす予定ですか?
私は研究です。ゴミですね 楽しみだな!
余談はさておき、この記事ではUnityのオフラインランキングとオンラインランキングの処理について解説していきます。他の方のAdvent Calenderは技術のさわりだけ解説してる人もいましたが、私の記事はガッツリ実装手法についてお話をする予定です。「Unityの操作方法とC#の書き方がなんとな~く分かる人に100理解してもらう」 くらいの気持ちで書いてるのでバカなげぇです。
右側の目次で見たい所だけ適宜見てください。
あ、ちなみにUnity 6にも対応してるので、安心して読み進めてください。
それでは皆さん、Unityを開きましょう。
オフラインランキングを実装する
オフラインランキングを実装する手法は様々ありますが、今回はPlayerPrefsという機能を使います。まずはそれの解説からさせていただきます。
PlayerPrefsって何?
簡単に言ってしまうと、Unityが提供しているセーブ機能の一種です。
通常、ゲームを終了させるとデータは消えてしまいますが、PlayerPrefsを利用するとゲームを閉じてもデータを保存させることが可能です。
以下がPlayerPrefsの関数の一覧になります。
セーブに関する関数
// SampleKey1 という名前のキーに float 型の値 10.0f を保存する
PlayerPrefs.SetFloat("SampleKey1", 10.0f);
// SampleKey2 という名前のキーに int 型の値 20 を保存する
PlayerPrefs.SetInt("SampleKey2", 20);
// SampleKey3 という名前のキーに string 型の値 "Data" を保存する
PlayerPrefs.SetString("SampleKey3", "Data");
PlayerPrefsのキーについて
今回はSampleKey1/2/3というキーを使っていますが、キーの名前は何でもOKです。
おすすめはしませんが「さんぷるきー」とかでも動くはずです。
ロードに関する関数
// SampleKey1 という名前のキーに保存されている値をGetFloatに代入する
// 保存されている値が無ければ 30.0f を代入する
float GetFloat = PlayerPrefs.GetFloat("SampleKey1", 30.0f);
// SampleKey2 という名前のキーに保存されている値をGetIntに代入する
// 保存されている値が無ければ 40 を代入する
int GetInt = PlayerPrefs.GetInt("SampleKey2", 40);
// SampleKey3 という名前のキーに保存されている値をGetStringに代入する
// 保存されている値が無ければ "DefaultData" を代入する
string GetString = PlayerPrefs.GetString("SampleKey3", "DefaultData");
さらに詳しく!
PlayerPrefs.Get〇〇("〇〇"); と、右側の値を省略して書くこともできます。
保存されている値がない状態でこの書き方をした場合、GetFloatとGetIntだったら0、GetStringだったら空文字が代入されます。
削除に関する関数
// 全てのキーとそのデータを削除
PlayerPrefs.DeleteAll();
// SampleKey という名前のキーとそのデータを削除
PlayerPrefs.DeleteKey("SampleKey");
以上8つしかありません。めちゃめちゃ少ないんですよね。
PlayerPrefsの注意点
PlayerPrefsには同じキーは使用できないという、大きな注意点があります。
例えば、以下のプログラムを実行したとします。
// sample という名前のキーに float 型の値 10.0f を保存する
PlayerPrefs.SetFloat("sample", 10.0f);
// sample という名前のキーに int 型の値 20 を保存する
PlayerPrefs.SetInt("sample", 20);
この場合、SetFloatとSetIntで同じキーを使用しているため、PlayerPrefs.SetInt("sample", 20);を実行した段階で、SetFloatで保存された10.0fという値は消滅します(上書きされます)。
違う型であっても同じキーを使うのは控えるようにしましょう。
PlayerPrefsの説明は以上です。オフラインランキングではSetStringとGetStringを利用してランキングを作成します。
座学の時間はここまでだ。今からガッツリプログラミングです。覚悟を決めてください。
オフラインランキングの概要
基本的には、ランキング情報をまとめた文字列をSetStringで保存する形になります。
ランキングの更新が行われる度にSetStringを呼び出し、ランキングを取得する度にGetStringを呼び出す感じです。
では、実際にプログラミングの方に取り掛かりたいと思います。
実装
ランキングの更新に関する実装と取得に関する実装に分けて解説します。
ランキング更新の実装
ランキングデータは次のような string 型の文字列で保存します。
"500,400,300,200,100"
1位の人のスコアが500、2位の人が400、... 、5位の人が100という感じです。タイムアタックの様にデータを昇順で並べることも可能です。実装に合わせてsortメソッド等を使い、並び替えを行ってください。
更新に使うプログラムは次の様になります。
void RankingUpdate(string rankingKey, int newScore)
{
// rankingText にランキングデータを代入する
// 今回は初期値として "500,400,300,200,100" を代入する
string rankingText = PlayerPrefs.GetString(rankingKey, "500,400,300,200,100");
// splitメソッドを使い、"," で区切られている値を rankingArr 配列に代入する
// rankingArrの中身:["500","400","300","200","100"]
string[] rankingTextArr = rankingText.Split(',');
// ランキングの人数を topNScores に代入する
int topNScores = rankingTextArr.Length;
// ランキングより 1 大きいランキング配列を作成する
// サイズが 1 大きいランキングを作る理由は後述
int[] rankingIntArr = new int[topNScores + 1];
// rankingIntArr 配列に int 変換したスコアを代入する
for (int i = 0; i < topNScores; i++)
{
rankingIntArr[i] = int.Parse(rankingTextArr[i]);
}
// rankingIntArrの一番後ろに新しいスコアを代入する
rankingIntArr[topNScores] = newScore;
// rankingIntArrを昇順ソート
Array.Sort(rankingIntArr);
// rankingIntArrの順序を反転させる (降順のランキングを作る場合は Array.Reverse(); は不要)
Array.Reverse(rankingIntArr);
// ランキング保存用テキストの初期化
string uploadRankingText = "";
// ランキング保存用テキストの作成
for (int i = 0; i < topNScores; i++)
{
// rankingIntArr配列を保存用のテキストに代入していく(最後だけ , を外す)
if (i < topNScores - 1)
{
uploadRankingText += rankingIntArr[i].ToString() + ",";
} else
{
uploadRankingText += rankingIntArr[i].ToString();
}
}
// ランキングの保存
PlayerPrefs.SetString(rankingKey, uploadRankingText);
return;
}
上記のコードをコピペしていただければランキング機能は完成です。
PlayerPrefsで扱うキーを常に変更できるように、キーと更新用のスコアを引数にしています。
主な解説はコード内のコメントの通りです。rankingIntArr配列を作る時に「1」大きいサイズの配列を作っている理由ですが、例えば、上位5人のランキングを作成する場合、
サイズ6の配列を作る
↓
0番目から4番目に現状のスコアを代入する
↓
5番目に新しいスコアを代入する
↓
ソート(昇順や降順に並び替える)をする
↓
0番目~4番目が更新後のスコア、5番目が不要なスコアになる
という理由です。このような処理をするおかげで、自分でソートのメソッドを作ったりする必要がなくなるため、非常に楽な更新作業ができます。
このような仕組みを取ることのメリットは、新しいスコアが出るたびにとりあえず RankingUpdateメソッドを使っておけば、常に新しいランキングになるということです。
いちいちランキングを取得して、今回のスコアがランキング圏内か確認したりする必要はありません。とりあえず結果が出るたびにRankingUpdate関数を使っておけばいいのです。
非常に楽で良いですね。
ランキング取得の実装
次はランキングの取得(ランキング表とかに使う)に関するメソッドについてです。
void RankingLoad(string rankingKey)
{
// rankingText にランキングデータを代入する
// 今回は初期値として "500,400,300,200,100" を代入する
string rankingText = PlayerPrefs.GetString(rankingKey, "500,400,300,200,100");
// splitメソッドを使い、"," で区切られている値を rankingArr 配列に代入する
// rankingArrの中身:["500","400","300","200","100"]
string[] rankingTextArr = rankingText.Split(',');
// ランキングの人数を topNScores に代入する
int topNScores = rankingTextArr.Length;
// ランキングを出力する(今回は Debug.Log(); で仮出力)
for (int i = 0; i < topNScores; i++)
{
Debug.Log($"{i + 1}位:{rankingTextArr[i]});
}
return;
}
基本的には、ランキングの更新で作成したメソッドと同じです。
今回は取得した値をDebug.Log();に出力させてますが、実際の運用の時はこの部分をTextMeshPro等に代入してあげる形になります。
オフラインランキング全体の実装
以上でオフラインランキングに必要なメソッド作成は終わりです。
仮のプログラムとして以下のようなコードを作成して動かしてみましょう。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// <summary>
// PlayerPrefsを使った、オフラインランキング処理を行うクラス
// </summary>
public class OffLineRanking : MonoBehaviour
{
// =============================================
// ランキング更新・取得処理
// =============================================
void Start()
{
// 初期のランキングの出力
RankingLoad("testRanking");
// ランキング更新
RankingUpdate("testRanking", 1000);
// 更新後のランキングの出力
RankingLoad("testRanking");
}
// =============================================
// ランキング処理に関わるメソッド
// =============================================
// ランキング更新メソッド
void RankingUpdate(string rankingKey, int newScore)
{
// rankingText にランキングデータを代入する
// 今回は初期値として "500,400,300,200,100" を代入する
string rankingText = PlayerPrefs.GetString(rankingKey, "500,400,300,200,100");
// splitメソッドを使い、"," で区切られている値を rankingArr 配列に代入する
// rankingArrの中身:["500","400","300","200","100"]
string[] rankingTextArr = rankingText.Split(',');
// ランキングの人数を topNScores に代入する
int topNScores = rankingTextArr.Length;
// ランキングより 1 大きいランキング配列を作成する
// サイズが 1 大きいランキングを作る理由は後述
int[] rankingIntArr = new int[topNScores + 1];
// rankingIntArr 配列に int 変換したスコアを代入する
for (int i = 0; i < topNScores; i++)
{
rankingIntArr[i] = int.Parse(rankingTextArr[i]);
}
// rankingIntArrの一番後ろに新しいスコアを代入する
rankingIntArr[topNScores] = newScore;
// rankingIntArrを昇順ソート
Array.Sort(rankingIntArr);
// rankingIntArrの順序を反転させる (降順のランキングを作る場合は Array.Reverse(); は不要)
Array.Reverse(rankingIntArr);
// 保存用のランキングテキストの初期化
string uploadRankingText = "";
for (int i = 0; i < topNScores; i++)
{
// rankingIntArr配列を保存用のテキストに代入していく(最後だけ , を外す)
if (i < topNScores - 1)
{
uploadRankingText += rankingIntArr[i].ToString() + ",";
} else
{
uploadRankingText += rankingIntArr[i].ToString();
}
}
// ランキングの保存
PlayerPrefs.SetString(rankingKey, uploadRankingText);
return;
}
// ランキング取得メソッド
void RankingLoad(string rankingKey)
{
// rankingText にランキングデータを代入する
// 今回は初期値として "500,400,300,200,100" を代入する
string rankingText = PlayerPrefs.GetString(rankingKey, "500,400,300,200,100");
// splitメソッドを使い、"," で区切られている値を rankingArr 配列に代入する
// rankingArrの中身:["500","400","300","200","100"]
string[] rankingTextArr = rankingText.Split(',');
// ランキングの人数を topNScores に代入する
int topNScores = rankingTextArr.Length;
// ランキングを出力する(今回は Debug.Log(); で仮出力)
for (int i = 0; i < topNScores; i++)
{
Debug.Log($"{i + 1}位:{rankingTextArr[i]});
}
return;
}
}
Startメソッド にRankingLoad("testRanking");、RankingUpdate("testRanking", 1000);、RankingLoad("testRanking");を順に置くことで、ランキングがしっかり更新されていることを確認します。
以下が出力結果です。
ちゃんと、最初のランキングでは500,400,300,200,100のスコアが、更新後のランキングでは1000,500,400,300,200のスコアが代入されていることが確認できましたね。
後はお好みでUI等を作成してみてください。
以上でオフラインランキングの実装は終わりです。
オンラインランキングを実装する
オフラインランキングの実装お疲れ様でした。休憩したいですか?
残念、まだかかります。覚悟を決めましょう。
オンラインランキングの実装にはMicrosoftが提供しているPlayFabというクラウドベースのバックエンドサービスを使っていきます。
まぁ、無料で使えるオンラインのデータベースみたいなものだと考えて頂ければそれでOKです。
無料版と有料版がありますが、大きな違いは以下です。
無料版
累計10万ユーザーまで登録可能
有料版
月10万ユーザーまで登録可能
なので、小中規模のゲームであれば無料版で全く問題ないと思います。
今回はUnityで実装しますが、PlayFab自体はUnreal Engineでも使えるので、気になる方は調べてみてください。というかUnreal Engineのやり方は知らないです
まずはPlayFabを使う準備から始めていきましょう。
PlayFabの準備
PlayFabを使うためにはアカウントを作る必要があります。
こちらからアカウントの作成ができます。
作成に当たって、Microsoftアカウントが必要になるので、予め準備しておいてください。
必要事項を記入すると、上のような画面に遷移するはずです。
これは、PlayFabの管理画面のようなものです。
しばらくポチポチしていきます。
赤枠のNew titleを選択してください。
New titleを選択すると以下のような画面に遷移すると思います。
Nameを入力したらCreate titleを選択してください。
今回は例としてTestRankingをNameにしています。
他2つに関しては記入しなくて大丈夫です。
Create titleを押すと、上の様にMy Game Studioの欄にTestRankingが追加されているはずです。では、TestRankingの設定を行っていきましょう。
TestRankingを開くと下のような画面に遷移します。Settingsを押しましょう。
上の欄にあるAPI Featuresを選択すると上のような画面に遷移します。
そのまま下にスクロールしたらENABLE AIP FEATURESが出てくると思うので、そこにあるAllow client to post player statisticsにチェックを入れてください。
これは、プレイヤーが統計情報(スコア等)を送信することを許可しますよ~という内容です。PlayFabでは、チート対策とかを兼ねてクライアントが統計情報を送信することをデフォルトで禁止してます。
個人開発のゲームなのでまぁ良いでしょう。
ここにチェックを入れたらSaveを押して保存しましょう。
それでは上に戻って、今度はGeneralを選択してください。
PLAYER ACCOUNT FEATURES欄のAllow non-unique player display namesにチェックを入れましょう。
これは、Playerの名前がかぶってもOKだよ~という意味です。
PlayFabではプレイヤーごとにスコアだけでなく、名前を設定することができます。もちろん、プレイヤー名が被るのを防ぐ場合はここにチェックを入れる必要はありません。ただ名前が被った場合エラーが発生するので、各々対応するようにお願いします。
後は、Default Languageを選択してSaveを押せば完成です。
次にランキングの保存先を作っていきます。左の欄からProgressionを選択してください。選択すると、上のような画面になるはずです。
上の欄からLeaderboards(Legacy)を選択し、New leaderboardを押してください。
LeaderboardsとLeaderboards(Legacy)って何?
(Legacy)は古いやり方です。(現在だと非推奨のやり方になってる)
ただ最新版のLeaderboardsの資料が少なく、エラーが多発したため、旧式の(Legacy)のやり方を採用しています。
旧式のやり方でもランキング機能は正しく機能します。
New leaderboardを選択すると、上のような画面に遷移します。
それぞれ必要事項を記入していきます。
- Statistic name
- ランキングの名前です。今回はTestRankingという名前にしてます
- Reset frequency
- ランキングをリセットする頻度を決められます。「Manually(手動) / Hourly(毎時) / Daily(毎日) / Weekly(毎月) / Monthly(毎月)」から選べます。今回はManually(手動)を採用してます。
- Aggregation method
- スコアの採用方法を決めます。「Last(最新のスコア) / Minimum(最小のスコア) / Maximum(最大のスコア) / Sum(合計)」から選べます。昇順ランキング、降順ランキング関係なくMaximumを採用するようにして下さい。
昇順ランキングでもMaximumを採用する理由は、Leaderboards (Legacy)の構造上、昇順ランキングを作成する場合に特殊な処理が必要になるためです。この点については、実装編で詳しく解説します。なお、プログラムコードは昇順ランキング用に作成していますが、降順ランキングのコードの書き方についても解説する予定です。
各々の目的に合った項目を設定してください。
最後にSaveを押したら完成です。
ここまでやると、上のようにProgressionのLeaderboards(Legacy)にTestRankingが追加されています。
以上でPlayFab側の設定は終わりです。
Unityの準備
次にUnity側の準備をしていきます。
まずは、PlayFabの機能をUnityで使えるようにするため、PlayFabのGitHubからUnityPackageをダウンロードします。
こちらがPlayFabのGitHubになります。
下にスクロールし、UnitySDKE READMEにある、PlayFab Unity Editor Extensionsをダウンロードしてください。
ダウンロードしたPackageをインポートすると、左の赤枠のような物が出てくると思います。もし出てこなかったら、一回Unityを再起動してください。
正常に表示されているのが確認出来たら、赤枠の中のLOG INを押してください。この時、表示されているEMAIL / PASSWORD / CONFIRM PASSWORD / STUDIO NAME は新しくPlayFabのアカウントを作る時に入力する項目なので、無記入のままLOG INを押してください。
また、下の矢印の様にエラーが表示されていると思います。このエラーはPlayFabを動かすためのファイルがないという内容のエラーになります。Unityエディターの中でPlayFabにログインした際、必要なファイルをダウンロードすることができるので、このエラーは無視して頂いて構いません。
恐らくですが、初期のUIだとCREATE AN ACCOUNTとLOG INのボタンが重なっていることが多いと思うので、Unityエディターをいじって上のように調節してあげてください。
MicrosoftアカウントでPlayFabを作成した方はEMAILとPASSWORDに無記入のままLOG IN WITH MICROSOFTを押してください。そうで無い方は、EMAILとPASSWORDを記入し、LOG INを押してください。
ログイン画面がない
おそらくPlayFabのアップデート?(or Unityのアップデート)でログイン画面が表示されないかもしれないです。
その際の対処法なんですが、おそらく内部的にログインは済んでいる?らしいので、この処理はかっ飛ばしていただいて構いません。
PlayFabがしょっちゅうアップデート入るので、多少、記事と違う部分があるかと思いますが、ご了承していただけると幸いです。
ログインに成功すると上のような画面になります。ここではInstall PlayFab SDKを押してください。これで、PlayFabを扱うために必要なファイルが全てインストールされるので、先ほどのエラーも解消します。
もし間違えてインストールをしなかった場合でも、先ほどのGitHubから同内容のPackageをインポートできるので、そちらも活用してみてください。
このような画面が出力されたら成功です。
次に先ほど用意したランキングの保存先とUnityを同期させる作業を行います。
SETTINGS・PROJECTを選び、STUDIOとTITLE IDを先ほどの物に設定してください。
ここ↓で作ったやつですね。

今回だと、STUDIOがMy Game Studio、TITLE IDが(A14E9) TestRankingになります。プルダウンで表示されるはずなので、TITLE ID等を覚える必要はないです。
TITLE IDを入力してもUnityが正しく動作しない場合
Unity 6になったからか、PlayFabがアップデートされたからかは分かりませんが、Unity側でTITLE IDを入力する際、プルダウンじゃなくなりました。
そのため、手打ちでTITLE IDを入力する必要があるんですが、この際、TITLE IDは「A14E9」(右下の5桁の英数字)のみを入力してください。
画像と同じように「(A14E9) TestRanking」と入力してしまうと、Unity側から「TITLE IDが違うよ〜」と怒られてしまいます。
紛らわしくて申し訳ありません。仕様変更した向こうを恨んでください。
以上でUnityの準備が終わりです。
実装
下準備お疲れ様です。
ではここからプログラミングコードの実装について解説していきます。
疲れました?まだあるんで気合い入れていきましょう。
ログイン処理
まずはログイン処理を解説していきます。ログインメソッドは以下の様になります。
void Login()
{
// ログインに使うデータの代入
var request = new LoginWithCustomIDRequest { CustomId = "testID", CreateAccount = true};
// ログイン処理の実行
// ログインに成功したら「OnLoginSuccess」、失敗したら「OnLoginFailure」を実行する
PlayFabClientAPI.LoginWithCustomID(request, OnLoginSuccess, OnLoginFailure);
//ログイン成功時の処理
private void OnLoginSuccess(LoginResult result)
{
Debug.Log("ログインに成功しました");
}
//ログイン失敗時の処理
private void OnLoginFailure(PlayFabError error)
{
Debug.Log("ログインに失敗しました");
}
}
request 変数にログインで使うデータを代入します。CustomId はいわゆるログインIDです。ユーザーごとに固有のIDを振り分ける必要があります。やり方は後述します。CreateAccount = trueはアカウントが存在しない場合は新規作成するという意味です。
PlayFabClientAPI.LoginWithCustomIDがログイン処理、requestがログイン情報、OnLoginSuccessがログイン成功時の処理、OnLoginFailureがログイン失敗時の処理となっております。
ログイン状態が切れるタイミングは主に4点あります。
- セッションの有効期限切れ
- 一定時間経過するとセッションが無効化されます。デフォルトだと24時間に設定されています。
- 新しいログインが発生した場合
- 同じアカウントで別デバイスからログインすると、古いセッションが無効化される場合があります。
- ユーザーアクションによる明示的な切断
- 明示的にログイン状態をリセットしたり、別のユーザーでログインすると、現在のセッションを終了します。
- 通信エラー
- ネット環境が不安定な場合や高頻度でPlayFabへリクエスト(ログイン処理やスコア送信)をすると失敗することがあります。リクエスト過多に関しては、10秒程度待ってから再試行すると直ることが多いです。
このままでは、CustomIdが"testID"となっているため、すべてのユーザーが同じログインIDでログインするというカスのゲームが出来上がります。
ポケモンを放置してたら兄弟に勝手にプレイされて、いつの間にかチャンピオンが倒されてるような感覚です。許せませんね。
このような悲しい事件を防ぐために、ユーザーごとに一意のCustomIdを決定するプログラムを書いていきます。
とても長くなるので、全体のプログラムを先に見せます。
using PlayFab.ClientModels;
using PlayFab;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text;
// <summary>
// PlayFabで一意なcustomIDのログイン処理を行うクラス
// </summary>
public class PlayFabLogin : MonoBehaviour
{
// アカウントを新規作成するかどうか
bool shouldCreateAccount;
// customIDを代入しておく変数
string customID;
// ==============================================================
// ログイン処理
// ==============================================================
void Start()
{
// ログイン処理の実行
Login();
}
// ログインメソッド
void Login()
{
// customIDを読み込む
customID = LoadCustomID();
// ログイン情報の代入
var request = new LoginWithCustomIDRequest { CustomId = customID, CreateAccount = shouldCreateAccount };
// ログイン処理
PlayFabClientAPI.LoginWithCustomID(request, OnLoginSuccess, OnLoginFailure);
}
// ログイン成功時の処理
void OnLoginSuccess(LoginResult result)
{
// 新規でアカウントを作成しようとしたが、既にcustomIDが使われていた場合
if (shouldCreateAccount && !result.NewlyCreated)
{
// 再度ログインし直す
Login();
return;
}
// 新規でアカウントを作成した場合
if (result.NewlyCreated)
{
// デバイスにcustomIDを保存する
SaveCustomID();
}
Debug.Log("ログインに成功しました");
Debug.Log($"CustomID:{customID}");
}
// ログイン失敗時の処理
void OnLoginFailure(PlayFabError error)
{
Debug.Log("ログインに失敗しました");
}
// ==============================================================
// customIDの取得
// ==============================================================
// デバイスにcustomIDを保存する場合に使うキーの設定
// PlayerPrefsを使って保存をcustomIDの保存を行うので、そのキーの設定です
// 詳しくは「オフラインランキングを実装する」の「PlayerPrefsって何?」をご覧ください
static readonly string CUSTOM_ID_SAVE_KEY = "TEST_RANKING_SAVE_KEY";
// 自分のIDを取得するメソッド
string LoadCustomID()
{
// PlayerPrefsを使って、customIDを取得する
// もし保存されていない場合は空文字を返す
string id = PlayerPrefs.GetString(CUSTOM_ID_SAVE_KEY);
// もしidが空文字だったらshouldCreateAccountにtrueを代入、そうでないならfalseを代入する
shouldCreateAccount = string.IsNullOrEmpty(id);
// shouldCreateAccountがtrueならcustomIDを新規作成、falseなら保存されていたcustomIDを返す
return shouldCreateAccount ? GenerateCustomID() : id;
}
// customIDをデバイスに保存するメソッド
void SaveCustomID()
{
// PlayerPrefsを使って、customIDを保存する
PlayerPrefs.SetString(CUSTOM_ID_SAVE_KEY, customID);
}
// ==============================================================
// customIDの生成
// ==============================================================
// customIDに使用する文字一覧(好きに設定してOKです)
static readonly string ID_CHARACTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
// customIDを生成するメソッド
string GenerateCustomID()
{
// customIDの長さ
int idLength = 32;
// 生成したcustomIDを代入する変数の初期化
StringBuilder stringBuilder = new StringBuilder(idLength);
// customIDをランダム出力するために乱数を使う
var random = new System.Random();
// customIDの生成
for (int i = 0; i < idLength; i++)
{
// 乱数を使ってランダムに文字列を代入する
// 代入された文字列をcustomIDにする
stringBuilder.Append(ID_CHARACTERS[random.Next(ID_CHARACTERS.Length)]);
}
// 生成したcustomIDを返す
return stringBuilder.ToString();
}
}
なっがいですね。一応、コピペでも動きはするはずです。各メソッドや変数がどのような役割をしているかはコメントで書いているので、それを参考にしてください。
エラーが起きる場合
恐らくですが、using PlayFab;とusing PlayFab.ClientModels;が抜けているのではないかと思います。基本的にPlayFabをインポートしたら勝手に書いてくれるんですが、たまに書かれないときがあるので、その際は自分で書いてあげてください。
全体の処理の流れをフローチャートで表すと以下の様になります。

それでは実際に動かしてみましょう。
コンソールを見てあげると実際に動いたことが確認できると思います。また、2回以上動かしても同じCustomIDが使われていることが分かるはずです。
どうしても失敗してしまう
まずはネット接続環境を確認してみましょう。オンライン実装なのでネット環境が整っていない場合、処理に失敗します。
それでも直らない場合はOnLoginFailureの所でDebug.LogError(error.GenerateErrorReport()}");を実行してみて下さい。これをすることでエラーの内容が出力されます。
PlayFabの方も確認してみましょう。
このように、赤枠の部分の数字が0から1に増えていると思います。
同じCustomIDを使っているため、2回以上実行してもユーザー数が1から2に増えることはありません。CustomIDを別のものにしたら、ユーザー数は増えます。
以上でログイン処理の実装は終わりです。
プレイヤー名を設定する場合や、スコアの送信等を行う場合はログインが必須になるので、ゲームの最初で組み込むようにしましょう。
プレイヤー名の設定
次にプレイヤー名の設定方法について解説していきます。
ここから先はログイン処理と比べるとコードが少ないので多少楽です。頑張りましょう。
以下がプレイヤー名設定に関するコードになります。
// 変更後のプレイヤー名を引数にする
void SetUserName(string name)
{
// ユーザー名を設定するリクエストを作る
var request = new UpdateUserTitleDisplayNameRequest
{
// ユーザー名の設定
DisplayName = name
};
// リクエストをPlayFabに送信する
PlayFabClientAPI.UpdateUserTitleDisplayName(request, OnSetUserNameSuccess, OnSetUserNameFailure);
// 送信成功時の処理
void OnSetUserNameSuccess(UpdateUserTitleDisplayNameResult result)
{
Debug.Log("プレイヤー名の変更に成功しました");
}
// 送信失敗時の処理
void OnSetUserNameFailure(PlayFabError error)
{
Debug.Log("プレイヤー名の変更に失敗しました");
}
}
各処理が何をしているかはコメントを確認してください。これを行うことで、「1位:〇〇、スコア:××」のように名前とスコアを出力することが可能です。PlayFab側でも登録されたプレイヤー名の確認ができます。
PlayFabの設定をしている時に、プレイヤー名が被る事を考慮するかどうかのチェックがあったと思います。もし、同じプレイヤー名を受け付けない設定にしているなら、ユーザーが誰かと同じプレイヤー名を設定しようとした際、変更に失敗するはずです。その場合はOnSetUserNameFailureにそれ専用の処理を記述するようにして下さい。
スコアの送信
いよいよスコアの送信に関するコードを書いていきます。
降順のランキングを作成する際は問題ないのですが、昇順のランキングを作成する際は少し特殊な操作をする必要があります。
以下が昇順ランキングのスコア送信のコードになります。
// 送信するスコアを引数にする
void SubmitScore(int score)
{
// 昇順ランキング用にスコアを修正する
// 降順ランキングの場合はこの作業は省略する
// 処理の内容と理由は後述
int modifiedScore = int.MaxValue - score;
// 送信する内容を作る
var statisticUpdate = new StatisticUpdate
{
// 統計情報名の指定(LeaderBoards(Legace)で設定した名前)
StatisticName = "TestRanking",
// 送信するスコアの代入
Value = modifiedScore,
};
// 作った内容をリクエストにまとめる
var request = new UpdatePlayerStatisticsRequest
{
Statistics = new List<StatisticUpdate>
{
statisticUpdate
}
};
// リクエストをPlayFabに送信する
PlayFabClientAPI.UpdatePlayerStatistics(request, OnSubmitScoreSuccess, OnSubmitScoreFailure);
// 送信成功時の処理
void OnSubmitScoreSuccess(UpdatePlayerStatisticsResult result)
{
Debug.Log("スコアの送信に成功しました");
}
// 送信失敗時の処理
void OnSubmitScoreFailure(PlayFabError error)
{
Debug.Log("スコアの送信に失敗しました");
}
}
PlayFabに送信する用のリクエストを作成して、送信する点はプレイヤー名の登録処理やログイン処理と同じです。
異なる点はint modifiedScore = int.MaxValue - score;です。この点について解説していきます。
まずint.MaxValueですが、ここは int 型の最大値を取得しています。int 型の最大値から送信するスコアを引いた値をmodifiedScoreに代入し、PlayFabに送信しています。
このような処理をする理由ですが、PlayFabのLeaderboards(Legace)では統計情報を降順でしか取得できないのが理由となっています。そのため、int 型の最大値から送信するスコアを引くことで、スコアが小さければ小さいほどPlayFabに大きい値が送信される、という状況を作って無理やり昇順ランキングを作成しています。
Leaderboradsの構造上、最下位から統計情報を取得するのも難しいため、このような特殊な処理を採用しています。
降順ランキングを作成する際はこのような作業をせず、単純にscoreの値をPlayFabに送信するでOKです。
送信する時は int 型に!
PlayFabのLeaderboardは int 型以外を認めていません。そのため、string 型だけでなく、float 型や double 型なども受け付けていません。
もし、小数点以下の値もスコアとして扱いたい場合は、1000倍して無理やり int 型にする等の修正を行ってください。
送信時に1000倍し、ランキング取得時に1000で割れば元の形式を保つことができます。
ランキングの取得
いよいよ最後です。これさえ終わればすべての実装が終わります。もう少し頑張りましょう。
昇順ランキングでスコアを送信する際に特殊な処理をしたため、ランキングの取得でも同じ様な処理を行います。
以下が昇順ランキングのスコア取得のコードになります。
void GetRanking()
{
// PlayFabに送信するリクエストを作成する
var request = new GetLeaderboardRequest
{
// 統計情報名の指定(LeaderBoards(Legace)で設定した名前)
StatisticName = "TestRanking",
// 何位以降のランキングを取得するか指定する
// 1位から取得する場合は0を代入
StartPosition = 0,
// 何件分のランキングデータを取得するか指定する
// 最大は100
MaxResultsCount = 10
};
// PlayFabにリクエストを送信する
PlayFabClientAPI.GetLeaderboard(request, OnGetRankingSuccess, OnGetRankingFailure);
// 送信成功時の処理
void OnGetRankingSuccess(GetLeaderboardResult leaderboardResult)
{
// ランキングを表示する仮コード
foreach (var item in leaderboardResult.Leaderboard)
{
// Positionは順位。0から始まるので+1して表示する
// intの最大値から取得したスコアを引いて、本来のスコアを出力する
Debug.Log($"{item.Position + 1}位 プレイヤー名:{item.DisplayName} スコア:{int.MaxValue - item.StatValue}");
}
}
// 送信失敗時の処理
void OnGetRankingFailure(PlayFabError error)
{
Debug.Log("ランキングの取得に失敗しました");
}
}
以上がランキング取得のコードになります。
スコアを出力する際、送信時と同じように int の最大値から取得したスコアを引くことで、本来のスコアを出力しています。
また、GetLeaderboardではなくGetLeaderboardAroundPlayerを使うことで、上位のプレイヤーの順位だけでなく、ユーザーごとの現在の順位を取得することが可能です。
参考程度にGetLeaderboardAroundPlayerのコードも掲載しておきます。
void GetRankingAroundPlayer()
{
// PlayFabに送信するリクエストを作成する
var request = new GetLeaderboardAroundPlayerRequest
{
// 統計情報名の指定(LeaderBoards(Legace)で設定した名前)
StatisticName = "TestRanking",
// 自分と+-5位をあわせて合計11件を取得
MaxResultsCount = 11
};
// PlayFabにリクエストを送信する
PlayFabClientAPI.GetLeaderboardAroundPlayer(request, OnGetRankingAroundPlayerSuccess, OnGetRankingAroundPlayerFailure);
// 送信成功時の処理
void OnGetRankingAroundPlayerSuccess(GetLeaderboardAroundPlayerResult leaderboardResult)
{
// ランキングを表示する仮コード
foreach (var item in leaderboardResult.Leaderboard)
{
// Positionは順位。0から始まるので+1して表示する
// intの最大値から取得したスコアを引いて、本来のスコアを出力する
Debug.Log($"{item.Position + 1}位 プレイヤー名:{item.DisplayName} スコア:{int.MaxValue - item.StatValue}");
}
}
// 送信失敗時の処理
void OnGetRankingAroundPlayerFailure(PlayFabError error)
{
Debug.Log("ランキングの取得に失敗しました");
}
}
以上で全てのメソッドの実装は終わりです。
オンラインランキング全体の実装
最後にこれらを組み合わせて動作するか確認します。
以下がテスト用のコードになります。
using PlayFab.ClientModels;
using PlayFab;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// <summary>
// PlayFabでオンラインランキング処理を行うクラス
// </summary>
public class Ranking : MonoBehaviour
{
// ==============================================================
// テストプログラム
// ==============================================================
// テスト用の仮プログラム
// ランキングの送受信を行う
void Update()
{
// キーボードで「A」が入力されたらプレイヤー名を「菅田将暉」にする
if (Input.GetKeyDown(KeyCode.A))
{
SetUserName("菅田将暉");
}
// キーボードで「B」が入力されたらスコアとして1000を送信する
if (Input.GetKeyDown(KeyCode.B))
{
SubmitScore(1000);
}
// キーボードで「C」が入力されたらランキングを取得する
if (Input.GetKeyDown(KeyCode.C))
{
GetRanking();
}
}
// ==============================================================
// ランキング更新メソッド
// ==============================================================
// プレイヤー名を設定するメソッド
void SetUserName(string name)
{
// ユーザー名を設定するリクエストを作る
var request = new UpdateUserTitleDisplayNameRequest
{
// ユーザー名の設定
DisplayName = name
};
// リクエストをPlayFabに送信する
PlayFabClientAPI.UpdateUserTitleDisplayName(request, OnSetUserNameSuccess, OnSetUserNameFailure);
// 送信成功時の処理
void OnSetUserNameSuccess(UpdateUserTitleDisplayNameResult result)
{
Debug.Log("プレイヤー名の変更に成功しました");
}
// 送信失敗時の処理
void OnSetUserNameFailure(PlayFabError error)
{
Debug.Log("プレイヤー名の変更に失敗しました");
}
}
// 昇順ランキングのスコア送信メソッド
void SubmitScore(int score)
{
// 昇順ランキング用にスコアを修正する
// 降順ランキングの場合はこの作業は省略する
// 処理の内容と理由は後述
int modifiedScore = int.MaxValue - score;
// 送信する内容を作る
var statisticUpdate = new StatisticUpdate
{
// 統計情報名の指定(LeaderBoards(Legace)で設定した名前)
StatisticName = "TestRanking",
// 送信するスコアの代入
Value = modifiedScore,
};
// 作った内容をリクエストにまとめる
var request = new UpdatePlayerStatisticsRequest
{
Statistics = new List<StatisticUpdate>
{
statisticUpdate
}
};
// リクエストをPlayFabに送信する
PlayFabClientAPI.UpdatePlayerStatistics(request, OnSubmitScoreSuccess, OnSubmitScoreFailure);
// 送信成功時の処理
void OnSubmitScoreSuccess(UpdatePlayerStatisticsResult result)
{
Debug.Log("スコアの送信に成功しました");
}
// 送信失敗時の処理
void OnSubmitScoreFailure(PlayFabError error)
{
Debug.Log("スコアの送信に失敗しました");
}
}
// ==============================================================
// ランキング取得メソッド
// ==============================================================
// 昇順ランキングのスコア取得メソッド
void GetRanking()
{
// PlayFabに送信するリクエストを作成する
var request = new GetLeaderboardRequest
{
// 統計情報名の指定(LeaderBoards(Legace)で設定した名前)
StatisticName = "TestRanking",
// 何位以降のランキングを取得するか指定する
// 1位から取得する場合は0を代入
StartPosition = 0,
// 何件分のランキングデータを取得するか指定する
// 最大は100
MaxResultsCount = 10
};
// PlayFabにリクエストを送信する
PlayFabClientAPI.GetLeaderboard(request, OnGetRankingSuccess, OnGetRankingFailure);
// 送信成功時の処理
void OnGetRankingSuccess(GetLeaderboardResult leaderboardResult)
{
// ランキングを表示する仮コード
foreach (var item in leaderboardResult.Leaderboard)
{
// Positionは順位。0から始まるので+1して表示する
// intの最大値から取得したスコアを引いて、本来のスコアを出力する
Debug.Log($"{item.Position + 1}位 プレイヤー名:{item.DisplayName} スコア:{int.MaxValue - item.StatValue}");
}
}
// 送信失敗時の処理
void OnGetRankingFailure(PlayFabError error)
{
Debug.Log("ランキングの取得に失敗しました");
}
}
// ユーザー周辺の昇順ランキングのスコア取得メソッド
void GetRankingAroundPlayer()
{
// PlayFabに送信するリクエストを作成する
var request = new GetLeaderboardAroundPlayerRequest
{
// 統計情報名の指定(LeaderBoards(Legace)で設定した名前)
StatisticName = "TestRanking",
// 自分と+-5位をあわせて合計11件を取得
MaxResultsCount = 11
};
// PlayFabにリクエストを送信する
PlayFabClientAPI.GetLeaderboardAroundPlayer(request, OnGetRankingAroundPlayerSuccess, OnGetRankingAroundPlayerFailure);
// 送信成功時の処理
void OnGetRankingAroundPlayerSuccess(GetLeaderboardAroundPlayerResult leaderboardResult)
{
// ランキングを表示する仮コード
foreach (var item in leaderboardResult.Leaderboard)
{
// Positionは順位。0から始まるので+1して表示する
// intの最大値から取得したスコアを引いて、本来のスコアを出力する
Debug.Log($"{item.Position + 1}位 プレイヤー名:{item.DisplayName} スコア:{int.MaxValue - item.StatValue}");
}
}
// 送信失敗時の処理
void OnGetRankingAroundPlayerFailure(PlayFabError error)
{
Debug.Log("ランキングの取得に失敗しました");
}
}
}
使用しているメソッドは先ほど解説したものと全く同じものです。テストコードを動かすために、プレイヤー名を「菅田将暉」にしてます。菅田将暉が私のゲームをプレイしてくれてたら嬉しいので。
Aを押したら名前変更、Bを押したらスコア送信、Cを押したらランキング表示というコードとなっております。デバッグ開始後、A→B→Cの順でキーボードを操作することで、プログラムが正常に動いている事を確認できると思います。
実行結果は以下です。
無事、菅田将暉がゲームを遊んでくれてます。
やりましたね。
CustomIDをいじって、正常に昇順ランキングができているかも確認します。
無事、昇順ランキングも機能していることが確認できました。
山崎賢人も来てくれてます。嬉しいですね。
念の為、より順位が高くなるスコアを送った場合の動作も確認しましたが、無事動作確認が取れました。
PlayFabのTestRankingを見てあげると、このようにランキングに追加されているのも確認できます。
以上となります。お疲れさまでした。
まとめ
今回はUnityでオフラインとオンラインのランキング処理の仕方についてまとめました。勉強不足で間違っている箇所もあるかもしれませんが、温かい目で見ていただけると嬉しいです。
非常に長くなってしまいましたが、ここまで読んで頂き誠にありがとうございます。皆様の助けになったら幸いです。
参考文献

