> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 实体组

> 了解 PlayFab 实体组如何为公会、氏族、队伍和聊天频道建模，涵盖成员角色、邀请以及共享的对象和文件数据。

假设您需要公会、氏族、公司、企业、部落 — 或者您的游戏对它们的任何称呼 — PlayFab 可以通过[实体编程模型](/services/playfab/live-service-management/game-configuration/entities)来满足您对持久性玩家分组的需求。

组实体可用于存储其他实体的集合（包括玩家或角色），可以在游戏中用于多种用途。

## 示例

* **氏族/公会** - 实体组可用于描述一组经常一起玩游戏的玩家，无论以何种社交纽带长期将他们联系在一起。

* **队伍** - 实体组可用于短期组，允许个人玩家实现即时目标，然后事后轻松解散。

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

* **游戏内信息订阅** - 您的游戏中是否有某个只有单一实例的传奇物品？玩家是否希望持续获取有关该物品的最新动态？创建一个围绕该物品的实体组，将所有对该物品感兴趣的玩家实体加入为成员。

简而言之，实体组可以是*任何*实体集合（无论是 NPC 还是玩家控制的实体，无论是真实的还是抽象的），只要它们需要绑定到该组的持久状态。

此外，由于实体组本身也是实体，因此它们将包含实体的所有初始功能：

* **对象数据**
* **文件数据**
* **配置文件**

<Note>
  组的默认限制为每组 1000 名成员，且仅支持玩家和角色作为成员
</Note>

## 使用实体组

创建组时，首个添加到组中的实体会被赋予 Admin 角色（本指南为简单起见将该实体称为所有者）。然后，所有者可以邀请新成员、创建具有各种可自定义权限的新角色、修改成员角色、踢出成员等。

此外，实体所具有的相同实体函数*也适用于组*，因此您可以直接将 JSON 对象和文件保存到组中，以保存任意特定于游戏的数据。

下面提供的代码示例将帮助您快速开始基本的公会交互。

它允许您创建组、添加和移除成员，以及删除组。它旨在作为一个起点，并未演示任何角色或权限。

```csharp theme={null}
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 调用只有在无法执行该操作时才会失败，例如，如果某个实体不是成员，则无法将其移除。

## 另请参阅

若要为您的组、公会或氏族存储数据：

* [对象](/services/playfab/live-service-management/game-configuration/entities/entity-objects)
* [文件](/services/playfab/live-service-management/game-configuration/entities/entity-files)


## Related topics

- [PlayFab 的 PlayStream 事件模型参考](/zh-CN/services/playfab/api-references/events/index.md)
- [group_created](/zh-CN/services/playfab/api-references/events/Groups/group-created.md)
- [group_updated](/zh-CN/services/playfab/api-references/events/Groups/group-updated.md)
- [group_members_added](/zh-CN/services/playfab/api-references/events/Groups/group-members-added.md)
- [group_members_removed](/zh-CN/services/playfab/api-references/events/Groups/group-members-removed.md)
