实体组

PlayFab 公会解决方案

假设您需要公会、部落、企业、公司、宗族或您的游戏中对此的任何其他称呼 - PlayFab 有此功能。

PlayFab 使用新的实体编程模型(或者更具体地说是组的实体类型)构建公会。 实体组是一个比公会更广泛的概念,但从根本上说,实体组是成熟的公会解决方案。

实体组

实体组是受部落/公会需求启发而诞生的根本概念。

实体组的核心是可用于任何目的的任何实体逻辑组。 实体组可在游戏中同时提供多种用途。

示例

  • 部落/公会 - 这是起点,也是主要的驱动需求。 实体组可用于描述一组经常一起玩游戏的玩家 - 不管是何种社会凝聚力让他们长期聚集在一起。

  • - 实体组可用于创建短期组,允许个人玩家实现直接目标,并可轻松解散。

  • 聊天频道 - 短期或长期聊天频道可以定义为实体组。

  • 游戏内信息订阅 - 您的游戏中是否有单实例传奇物品? 玩家是否希望持续获取有关此物品的最新信息? 可以创建一个看重此物品的实体组,并让所有对此物品感兴趣的玩家实体成为其成员。

简而言之,实体组可以是任何 实体集合(不管是 NPC 还是玩家控制角色、真实还是抽象 - 只要他们需要与此组进行持久状态的绑定)。

好友列表是一个组。 三人私聊也是一个组。 任意发挥吧!

注意

如果您在尝试我们可能没想到的东西,请在论坛中发帖讨论,我们万分感激...

此外,由于实体组本身也是实体,因此它们将包含实体的所有初始特征:

  • 对象数据
  • 文件数据
  • 档案

实体组有资格获得新的实体特征(如果这些特征与组有关)。

注意

组的默认限制为每个组 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 - 加入组是一个两步过程,可以从两个方向激活:

    • 玩家可以请求加入组。
    • 组也可以邀请玩家。
  • AcceptGroupInvitation / AcceptGroupApplication - 联接过程的第二步。 响应实体接受邀请,完成使玩家成为此组成员的过程。

  • RemoveMembers - 有权执行此操作的成员(由其角色权限定义)将能够从组中启动成员。

服务器与客户端

与所有新的实体 API 方法一样,服务器 API 和客户端 API 之间没有区别。

操作由调用方根据进程的身份验证方式执行。 将以此方式识别客户端,并作为作品玩家实体调用这些方法,每个调用都将评估玩家在组中的角色和权限,确保他们有权执行此操作。

服务器使用相同的 developerSecretKey进行身份验证,后者将该进程标识为标题实体。 作品绕过角色检查,并且作品执行的 API 调用仅在无法执行操作时才会失败,例如:如果某实体并非成员,则无法删除此实体。