最近は、ほとんどのスマホゲームでフレンド機能がついていますよね。
実は PlayFab を使うことで、フレンド機能をかんたんに実装することができるんです。
フレンド機能には2種類あります。
- 申請をして相手が承認したらフレンドになれる
- 特に承認なくフレンドになれる(フォロー機能)
PlayFab では承認制ではなく、一方的にフォローする Twitter 形式のフレンド機能が提供されています。
公式コミュニティサイトでも触れられていますが、将来的には承認制のフレンド機能も実装される予定とのことです。
本記事では、単純な API の使い方だけでなく、Twitter のフォロー機能を実現するところまで解説しました。
この記事を読み終える頃には、フレンド機能のマスターになっているはずです。
では、さっそく見ていきましょう。
フレンド機能を実装する方法
API の呼び出し方法だけであれば公式ドキュメントを読むだけで理解できると思います。
それだけだと面白くないので、本記事では実践的な内容まで踏み込んで解説しました。
公式コミュニティの Two-way friend confirmation with Cloud Script を少し参考にしました。
承認制のフレンド機能を実装したい人は、上記をチェックしてみてください。
友達を検索する
まずは、以下のような友達を検索する機能についてです。
友達から教えてもらったIDを入力して検索するのが一般的かと思います。
入力チェック
入力欄には PlayFabId を入力します。
何でもかんでも入力されたら困るので、最低限の入力チェックをしておきましょう。
// 入力する友達の PlayFabId
[SerializeField] TMP_InputField friendCode;
// 検索ボタン
[SerializeField] Button friendSearch;
// 検索ボタンの入力可否
public void ChangeButtonEnabled()
{
// PlayFabId は16桁固定
bool isValidCode = friendCode.text.Length == 16;
friendSearch.interactable = isValidCode;
}
// 検索ボタンが押されたとき
public void Search()
{
SearchFriend(friendCode.text);
}
ここは特に詳しい解説は不要ですね。
フレンドの検索処理
検索は GetPlayerProfile というメソッドを使用します。
このメソッドに PlayFabId を渡すことで、プレイヤーが存在するかどうかの確認ができます。
実際のコードを見てみましょう。
public void SearchFriend(string friendPlayFabId)
{
PlayFabClientAPI.GetPlayerProfile(new GetPlayerProfileRequest
{
PlayFabId = friendPlayFabId,
ProfileConstraints = new PlayerProfileViewConstraints
{
ShowDisplayName = true,
}
}, result =>
{
Debug.Log("プレイヤーが見つかりました。");
}
, error =>
{
if (error.Error == PlayFabErrorCode.PlayerNotInGame ||
error.Error == PlayFabErrorCode.InvalidParams)
{
Debug.Log("プレイヤーが見つかりません。");
}
});
}
PlayerProfileViewConstraints というのは、「検索したプレイヤーについて、どの情報まで公開するか」を指定できるクラスです。
例えば、「ShowDisplayName = true」とすれば、検索したプレイヤーの表示名も一緒に取ってきてくれる、ということになります。
初期値は false なので、必要に応じて true にする感じですね。
実はタイトルごとの設定で「クライアントプロフィールオプション」というものがあります。
ここでチェックを付けていないプロパティは、常に null で取れてしまうので注意してください。
もう一つ解説するポイントは、エラー処理についてです。
if (error.Error == PlayFabErrorCode.PlayerNotInGame ||
error.Error == PlayFabErrorCode.InvalidParams)
{
Debug.Log("プレイヤーが見つかりません。");
}
PlayerNotInGame というのは文字通り「検索した結果、プレイヤーが存在しない」というエラーです。
気になるのは InvalidParams というエラーについてですね。
どうやら PlayFabId は Hexadecimal という16進数から構成された文字列になっているようなんですが…
PlayFabId として渡す文字列が16進数の範囲を超えた場合に、InvalidParams が発生する仕組みになっています。
16進数は、アルファベットで言うと A〜F が正常な値なので、例えば S とかが入力されたケースですね。
入力欄で制御するのは面倒になりそうなので、検索結果でエラー処理をするようにしました。
相手を友達に追加する(フォロー)
次に、相手をフォローする機能についてです。
全体を画像で確認してみましょう。
「AさんがBさんをフォローする」という流れを示しています。
ポイントとしては、以下の2つです。
- 片方のフォローが発生した場合は、双方を友達にする
- フォロワーかどうかはタグで管理する
上記の通り。
フォローされた人は相手のことを知らないですが、いったんは友達として追加します。
ただこれだけだと友達になってしまうので、フォロワーなのかどうかはタグで管理するようにします。
ちょっとわかりにくいと思うので、「nekoneko が、ねこじょーかーをフォローする」という操作をした場合のイメージを作成しました。
ねこじょーかーをフォローしたので、友達として追加されて Follow のタグが付いています。
フォローされたねこじょーかーは、友達ではないですが Follower のタグで友達として追加します。
これが相互フォローになると、お互いに Follow と Follower のタグが付くことになります。
このタグで絞り込みをすることで、フォローの一覧とフォロワーの一覧が取得できる仕組みです。
イメージが付いてきましたか?
では、実際のコードを見てみましょう。
クライアント側のコード
クライアント側から渡す情報は、「相手からフォローされているかどうか」で変わってきます。
フォロワーの一覧からフォローする場合、前もって取得している FriendInfo が利用できるので、その情報を引数として渡してあげます。
// フォロワーの一覧から追加する場合の引数
FunctionParameter = new
{
FriendInfo = new PlayFab.ClientModels.FriendInfo
{
FriendPlayFabId = friendInfo.FriendPlayFabId,
Tags = friendInfo.Tags
}
}
上記とは別に、ID検索からフォローする場合は、相手からフォローされているかどうかの事前チェックが必要です。
// 相手からフォローされているかどうか(Followers は List<FriendInfo> の前提)
var friendFollowInfo = Followers.Find(x => x.FriendPlayFabId == PlayFab.PlayFabSettings.staticPlayer.PlayFabId);
// ID検索から追加する場合の引数
FunctionParameter = new
{
FriendInfo = new PlayFab.ClientModels.FriendInfo
{
FriendPlayFabId = friendCode.text,
Tags = friendFollowInfo?.Tags
}
}
フォローされている場合は保持している Tags を渡して、フォローされていない場合は null を渡します。
これでクライアント側の準備は完了です。
サーバー側のコード
クライアントだとコールバック地獄になるため、中身はサーバー処理としました。
[FunctionName("AddFriend")]
public static async Task<dynamic> AddFriend(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
string body = await req.ReadAsStringAsync();
var context = JsonConvert.DeserializeObject<FunctionExecutionContext<dynamic>>(body);
var args = context.FunctionArgument;
var serverApi = new PlayFabServerInstanceAPI(context.ApiSettings, context.AuthenticationContext);
// FriendInfo に直接キャストできないため、JsonObject を経由
JsonObject info = args["FriendInfo"];
var friendInfo = PlayFabSimpleJson.DeserializeObject<FriendInfo>(info.ToString());
string myPlayFabId = context.CallerEntityProfile.Lineage.MasterPlayerAccountId;
// ID でフレンド検索したときに、相手からフォローされているかどうかをチェック
if (friendInfo.Tags == null)
{
var getFriend = await serverApi.GetFriendsListAsync(new GetFriendsListRequest
{
PlayFabId = friendInfo.FriendPlayFabId,
IncludeSteamFriends = false,
IncludeFacebookFriends = false,
XboxToken = null,
});
// フォローされている場合は Follower タグを追加
// [NOTE]フォローされているかどうかはクライアント側でも判定しているが、
// クライアント側でフォロワーの再取得は頻繁に実施しないため、
// 念のためサーバー側でも再チェックしておく(後勝ちの上書きを防ぐ)
if (getFriend.Result.Friends.Any(x => x.FriendPlayFabId == myPlayFabId))
friendInfo.Tags = new List<string> { "Follower" };
else
friendInfo.Tags = new List<string> { };
}
var tagsToYou = new List<string>(); // 相手向けのタグ
var tagsToMe = new List<string>(); // 自分向けのタグ
// 相手からフォローされていない場合は、お互いに友だち追加
if (friendInfo.Tags.TrueForAll(x => x != "Follower"))
{
// 自分から相手
var addFriendToYou = await serverApi.AddFriendAsync(new AddFriendRequest
{
PlayFabId = myPlayFabId,
FriendPlayFabId = friendInfo.FriendPlayFabId
});
// 相手から自分(ID を逆転させて実行)
var addFriendToMe = await serverApi.AddFriendAsync(new AddFriendRequest
{
PlayFabId = friendInfo.FriendPlayFabId,
FriendPlayFabId = myPlayFabId
});
}
else
{
// 相手からフォローされている場合
tagsToYou.Add("Follower");
tagsToMe.Add("Follow");
}
tagsToYou.Add("Follow"); // 自分はフォローする側
tagsToMe.Add("Follower"); // 相手はフォローされる側
// 自分から相手にタグ付け
var addTagsToYou = await serverApi.SetFriendTagsAsync(new SetFriendTagsRequest
{
PlayFabId = myPlayFabId,
FriendPlayFabId = friendInfo.FriendPlayFabId,
Tags = tagsToYou
});
// 相手から自分にタグ付け
var addTagsToMe = await serverApi.SetFriendTagsAsync(new SetFriendTagsRequest
{
PlayFabId = friendInfo.FriendPlayFabId,
FriendPlayFabId = myPlayFabId,
Tags = tagsToMe
});
return new { result = "Success!!" };
}
タグの処理が少しわかりにくいので、少し解説します。
登録されているフレンド情報を取ってきたあとにタグを追加する、という流れがいいのですが、いちいちフレンド情報を取ってくるのが大変です。
なので、処理後のタグの状態を想定して、完全に上書きで更新するようにしています。
else
{
// 相手からフォローされている場合
tagsToYou.Add("Follower");
tagsToMe.Add("Follow");
}
tagsToYou.Add("Follow"); // 自分はフォローする側
tagsToMe.Add("Follower"); // 相手はフォローされる側
すでにフォローされている場合は、自分から見て相手は Follower 、相手から見て自分は Follow の状態になっています。
なのでその場合に限り、タグを追加しているというわけです。
フォローしている友達を一覧表示する
友達の一覧表示は以下のイメージですね。
実際の処理としては、GetFriendList で友達の一覧を取得して、その結果を保持しておけばOKです。
public List<FriendInfo> Follows { get; private set; }
public List<FriendInfo> Followers { get; private set; }
private void GetFriends()
{
PlayFabClientAPI.GetFriendsList(new GetFriendsListRequest
{
IncludeSteamFriends = false,
IncludeFacebookFriends = false,
XboxToken = null,
ProfileConstraints = new PlayerProfileViewConstraints
{
ShowDisplayName = true,
ShowLastLogin = true,
}
}, result =>
{
Debug.Log("フレンド取得成功!");
Follows = result.Friends.Where(x => x.Tags.Any(y => y == "Follow")).ToList();
Followers = result.Friends.Where(x => x.Tags.Any(y => y == "Follower")).ToList();
}, error => Debug.Log(error.GenerateErrorReport()));
}
フォロワーやフォローの一覧表示は、それぞれのタグで絞り込みをすればよいですね。
相手を友達から削除する(フォロー解除)
次に、フォロー解除についてです。
こちらも処理イメージを作成しました。
ポイントとしては、以下の2つです。
- フォローされていない場合は、フレンドリストから削除
- フォローされている場合は、タグのみ削除
上記の通り。
フォローされていない場合は、そのままフレンドリストから削除して問題ありません。
しかし、フォローされている場合にフレンドリストから削除すると、相手のフォローも解除されてしまいます。
なのでその場合は、タグの削除のみ実行することが必要です。
実際のコード
実際の処理は以下のようになります。
全体的な流れはフォローのときと似ていますね。
[FunctionName("RemoveFriend")]
public static async Task<dynamic> RemoveFriend(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
string body = await req.ReadAsStringAsync();
var context = JsonConvert.DeserializeObject<FunctionExecutionContext<dynamic>>(body);
var args = context.FunctionArgument;
var serverApi = new PlayFabServerInstanceAPI(context.ApiSettings, context.AuthenticationContext);
// FriendInfo に直接キャストできないため、JsonObject を経由
JsonObject info = args["FriendInfo"];
var friendInfo = PlayFabSimpleJson.DeserializeObject<FriendInfo>(info.ToString());
string myPlayFabId = context.CallerEntityProfile.Lineage.MasterPlayerAccountId;
var tagsToYou = new List<string>(); // 相手向けのタグ
var tagsToMe = new List<string>(); // 自分向けのタグ
// 相手からフォローされていない場合は、お互いに友だち削除
if (friendInfo.Tags.TrueForAll(x => x != "Follower"))
{
// 自分から相手
var addFriendToYou = await serverApi.RemoveFriendAsync(new RemoveFriendRequest
{
PlayFabId = myPlayFabId,
FriendPlayFabId = friendInfo.FriendPlayFabId
});
// 相手から自分(ID を逆転させて実行)
var addFriendToMe = await serverApi.RemoveFriendAsync(new RemoveFriendRequest
{
PlayFabId = friendInfo.FriendPlayFabId,
FriendPlayFabId = myPlayFabId
});
}
// 相手からフォローされている場合は、友達を維持したままタグの削除を実行
else
{
tagsToYou.Add("Follower");
tagsToMe.Add("Follow");
// 自分から相手にタグ付け
var addTagsToYou = await serverApi.SetFriendTagsAsync(new SetFriendTagsRequest
{
PlayFabId = myPlayFabId,
FriendPlayFabId = friendInfo.FriendPlayFabId,
Tags = tagsToYou
});
// 相手から自分にタグ付け
var addTagsToMe = await serverApi.SetFriendTagsAsync(new SetFriendTagsRequest
{
PlayFabId = friendInfo.FriendPlayFabId,
FriendPlayFabId = myPlayFabId,
Tags = tagsToMe
});
}
return new { result = "Success!!" };
}
タグの削除という操作がないため、「タグを上書きする」という処理をしています。
あとは特に解説不要でしょう。
以上でフレンド機能の実装は完了です。
最後に
フレンド機能の実装方法について、以下の4点を具体的に解説しました。
- 友達を検索する
- 相手を友達に追加する(フォロー)
- フォローしている友達を一覧表示する
- 相手を友達から削除する(フォロー解除)
上記の機能がそろっていれば、とりあえずは大丈夫ですね。
この記事ではフォロワーの機能を実現するために少し複雑になっていますが、単純にフォローだけならもっとシンプルになります。
まずはそこから始めてもいいかもしれませんね。
みなさんもフレンド機能を実装して、ゲームを盛り上げていきましょう。
PlayFab のことをもっと皆さんに知ってもらいたくて、合計500ページ以上にもなる書籍を5冊に分けて執筆しました。
私の知識をすべて詰め込んでいるので、ゲーム開発をさらに加速させたい方はぜひご覧ください。