エンティティ グループ

PlayFab ギルド ソリューション

ギルド、クラン、企業、会社、トライブなど、ゲームで何を呼び出すとしても、PlayFab には用意されています。

PlayFab は、新しいエンティティ プログラミング モデル、またはより具体的にはグループのエンティティの種類を使ってギルドをビルドします。 エンティティ グループは、ギルドより広範な概念ですが、基本的にはエンティティ グループはギルドのソリューションとして作成されています。

エンティティ グループ

エンティティ グループは、クラン/ギルドの必要性に端を発した本質的な概念です。

根本的には、エンティティ グループはあらゆる目的に役立つエンティティの論理グループです。 エンティティ グループは、ゲーム内の多くの目的に同時に利用することができます。

  • クラン/ギルド - これがスタート地点であり、主な動機付けとなったニーズでした。 エンティティ グループは、プレイヤーたちが長期的に保ち続けるソーシャルな結びつきのために、定期的に共同でプレイしているプレイヤーのセットを説明するために使用できます。

  • パーティー - エンティティ グループは、個々のプレイヤーが緊急の目標を達成し、後で簡単に解体できるようにするために作成された短期的なグループに使用できます。

  • チャット チャンネル - 短期的または長期的なチャット チャンネルをエンティティ グループとして定義できます。

  • 情報へのゲーム内サブスクリプション - ゲームには単一インスタンスの伝説のアイテムがありますか。 プレイヤーは、そのアイテムに何が起こっているに関する定期的な最新情報を欲しがっていますか。 すべてプレイヤー エンティティがメンバーとしてアイテムに関心を抱いている、そのアイテムに重点を置いたエンティティ グループを作成します。

つまり、エンティティ グループは、そのグループに固定状態をバインドする必要があるどのようなエンティティ コレクションにもなり得ます (NPC かプレイヤー制御か、現実的か抽象的かにかかわらず)。

フレンド リストはグループです。 3 人が参加するプライベート チャットはグループです。 自由に使ってください。

注意

何か予期しない動作が見られた場合は、フォーラムで教えていただけると助かります。

さらに、エンティティ グループもエンティティ自体であるため、エンティティのすべての初期機能が含められます。

  • オブジェクト データ
  • ファイル データ
  • プロフィール

新しいエンティティ機能がグループに関連する場合、それらの機能を利用する資格を得ます。

注意

グループの既定の制限は、グループあたり 1000 メンバーです

エンティティ グループの使用

現在、エンティティ グループにはプレイヤーやキャラクターを含めることができます。 グループを作成すると、グループに追加された最初のエンティティが管理者ロールに配置されます (このガイドでは、わかりやすくするためにそのエンティティを所有者と呼びます)。 その後、所有者は新しいメンバーの招待、さまざまなカスタマイズ可能アクセス許可を持つ新しいロールの作成、メンバー ロールの変更、メンバーの除外などを行うことができるようになります。

さらに、エンティティに存在する同じエンティティ機能もグループで機能するため、JSON オブジェクトとファイルをグループに直接保存し、ゲーム固有の任意のデータを保存できるようになります。

以下に示すコード例では、ギルドの基本的な操作を簡単に始めることができます。

グループの作成、メンバーの追加と削除、グループの削除を行うことができます。 あくまでスタート地点であるため、ロールやアクセス許可をデモンストレーションするものではありません。

using PlayFab;
using PlayFab.GroupsModels;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace TestGuildController
{
    /// <summary>
    /// Assumptions for this controller:
    /// + Entities can be in multiple groups
    ///   - This is game specific, many games would only allow 1 group, meaning you'd have to perform some additional checks to validate this.
    /// </summary>
    [Serializable]
    public class GuildTestController
    {
        // A local cache of some bits of PlayFab data
        // This cache pretty much only serves this example , and assumes that entities are uniquely identifiable by EntityId alone, which isn't technically true. Your data cache will have to be better.
        public readonly HashSet<KeyValuePair<string, string>> EntityGroupPairs = new HashSet<KeyValuePair<string, string>>();
        public readonly Dictionary<string, string> GroupNameById = new Dictionary<string, string>();

        public static EntityKey EntityKeyMaker(string entityId)
        {
            return new EntityKey { Id = entityId };
        }

        private void OnSharedError(PlayFab.PlayFabError error)
        {
            Debug.LogError(error.GenerateErrorReport());
        }

        public void ListGroups(EntityKey entityKey)
        {
            var request = new ListMembershipRequest { Entity = entityKey };
            PlayFabGroupsAPI.ListMembership(request, OnListGroups, OnSharedError);
        }
        private void OnListGroups(ListMembershipResponse response)
        {
            var prevRequest = (ListMembershipRequest)response.Request;
            foreach (var pair in response.Groups)
            {
                GroupNameById[pair.Group.Id] = pair.GroupName;
                EntityGroupPairs.Add(new KeyValuePair<string, string>(prevRequest.Entity.Id, pair.Group.Id));
            }
        }

        public void CreateGroup(string groupName, EntityKey entityKey)
        {
            // A player-controlled entity creates a new group
            var request = new CreateGroupRequest { GroupName = groupName, Entity = entityKey };
            PlayFabGroupsAPI.CreateGroup(request, OnCreateGroup, OnSharedError);
        }
        private void OnCreateGroup(CreateGroupResponse response)
        {
            Debug.Log("Group Created: " + response.GroupName + " - " + response.Group.Id);

            var prevRequest = (CreateGroupRequest)response.Request;
            EntityGroupPairs.Add(new KeyValuePair<string, string>(prevRequest.Entity.Id, response.Group.Id));
            GroupNameById[response.Group.Id] = response.GroupName;
        }
        public void DeleteGroup(string groupId)
        {
            // A title, or player-controlled entity with authority to do so, decides to destroy an existing group
            var request = new DeleteGroupRequest { Group = EntityKeyMaker(groupId) };
            PlayFabGroupsAPI.DeleteGroup(request, OnDeleteGroup, OnSharedError);
        }
        private void OnDeleteGroup(EmptyResponse response)
        {
            var prevRequest = (DeleteGroupRequest)response.Request;
            Debug.Log("Group Deleted: " + prevRequest.Group.Id);

            var temp = new HashSet<KeyValuePair<string, string>>();
            foreach (var each in EntityGroupPairs)
                if (each.Value != prevRequest.Group.Id)
                    temp.Add(each);
            EntityGroupPairs.IntersectWith(temp);
            GroupNameById.Remove(prevRequest.Group.Id);
        }

        public void InviteToGroup(string groupId, EntityKey entityKey)
        {
            // A player-controlled entity invites another player-controlled entity to an existing group
            var request = new InviteToGroupRequest { Group = EntityKeyMaker(groupId), Entity = entityKey };
            PlayFabGroupsAPI.InviteToGroup(request, OnInvite, OnSharedError);
        }
        public void OnInvite(InviteToGroupResponse response)
        {
            var prevRequest = (InviteToGroupRequest)response.Request;

            // Presumably, this would be part of a separate process where the recipient reviews and accepts the request
            var request = new AcceptGroupInvitationRequest { Group = EntityKeyMaker(prevRequest.Group.Id), Entity = prevRequest.Entity };
            PlayFabGroupsAPI.AcceptGroupInvitation(request, OnAcceptInvite, OnSharedError);
        }
        public void OnAcceptInvite(EmptyResponse response)
        {
            var prevRequest = (AcceptGroupInvitationRequest)response.Request;
            Debug.Log("Entity Added to Group: " + prevRequest.Entity.Id + " to " + prevRequest.Group.Id);
            EntityGroupPairs.Add(new KeyValuePair<string, string>(prevRequest.Entity.Id, prevRequest.Group.Id));
        }

        public void ApplyToGroup(string groupId, EntityKey entityKey)
        {
            // A player-controlled entity applies to join an existing group (of which they are not already a member)
            var request = new ApplyToGroupRequest { Group = EntityKeyMaker(groupId), Entity = entityKey };
            PlayFabGroupsAPI.ApplyToGroup(request, OnApply, OnSharedError);
        }
        public void OnApply(ApplyToGroupResponse response)
        {
            var prevRequest = (ApplyToGroupRequest)response.Request;

            // Presumably, this would be part of a separate process where the recipient reviews and accepts the request
            var request = new AcceptGroupApplicationRequest { Group = prevRequest.Group, Entity = prevRequest.Entity };
            PlayFabGroupsAPI.AcceptGroupApplication(request, OnAcceptApplication, OnSharedError);
        }
        public void OnAcceptApplication(EmptyResponse response)
        {
            var prevRequest = (AcceptGroupApplicationRequest)response.Request;
            Debug.Log("Entity Added to Group: " + prevRequest.Entity.Id + " to " + prevRequest.Group.Id);
        }
        public void KickMember(string groupId, EntityKey entityKey)
        {
            var request = new RemoveMembersRequest { Group = EntityKeyMaker(groupId), Members = new List<EntityKey> { entityKey } };
            PlayFabGroupsAPI.RemoveMembers(request, OnKickMembers, OnSharedError);
        }
        private void OnKickMembers(EmptyResponse response)
        {
            var prevRequest= (RemoveMembersRequest)response.Request;
            
            Debug.Log("Entity kicked from Group: " + prevRequest.Members[0].Id + " to " + prevRequest.Group.Id);
            EntityGroupPairs.Remove(new KeyValuePair<string, string>(prevRequest.Members[0].Id, prevRequest.Group.Id));
        }
    }
}

例の説明

この例は、最小限のデータをローカル キャッシュに保存し (PlayFab が権限のあるデータ レイヤーとなります)、グループで CRUD 操作を実行する方法を提供するコントローラーとしてビルドされます。

用意されている例で機能の一部を見てみましょう。

  • OnSharedError - これは、PlayFab の例を含む一般的なパターンです。 エラーを処理する最も簡単な方法は、報告することです。 ゲーム クライアントには、おそらくかなり高度なエラー処理ロジックが備わっています。

  • ListMembership - ListMembership を呼び出し、指定されたエンティティが属するすべてのグループを調べます。 プレイヤーは、既に参加しているグループを知ることができます。

  • CreateGroup / DeleteGroup - ほとんどが自明のことです。 この例では、これらの呼び出しが正常に実行されたときのローカル グループ情報キャッシュの更新をデモンストレーションします。

  • InviteToGroup / ApplyToGroup - グループへの参加は 2 段階のプロセスであり、双方向にアクティブ化できます。

    • プレイヤーは、グループへの参加を要求できます。
    • グループは、プレイヤーを招待できます。
  • AcceptGroupInvitation / AcceptGroupApplication - 参加プロセスの第二段階です。 応答側のエンティティが招待を受け入れ、プレイヤーをグループの一部にするプロセスを完了します。

  • RemoveMembers - 権限を持つメンバー (そのロール アクセス許可によって定義されます) がグループからメンバーを除外できます。

サーバーとクライアント

あらゆる新しいエンティティ API メソッドと同様、サーバー API とクライアント API に区別はありません。

アクションは、プロセスの認証方法に従って呼び出し元によって実行されます。 クライアントはそのように識別され、タイトル プレイヤー エンティティとしてこれらのメソッドを呼び出します。グループ内のロールとアクセス許可は、呼び出しごとに評価され、このアクションを実行するアクセス許可があることが確認されます。

サーバーは、タイトル エンティティとしてそのプロセスを識別する同じ認証 developerSecretKey を使って認証されます。 タイトルはロールの確認をバイパスし、タイトルにより実行される API 呼び出しは、メンバーではないためにエンティティを削除できない場合などのインスタンスでアクションが実行不可能な場合のみ失敗します。