# 部落

**URL:** https://heroiclabs.com/docs/zh/nakama/tutorials/unity/pirate-panic/clans/
**Summary:** 学习如何在Pirate Panic教程游戏中添加部落。

---


# 部落

Nakama 可用于创建群组，又称为部落，这是一种小社区，其中玩家可以结队或一起出去玩。

通过 Pirate Panic，我们将学习如何让玩家：

- 创建新部落
- 加入或解散部落
- 搜索公开部落
- 自定义共享部落信息，例如名称和徽章
- 与部落中的其他成员聊天

本教程不涵盖私密群组，这些群组要求新玩家提交加入请求然后才会被允许加入。您可以通过[官方 Nakama 文档](../../../../concepts/groups/)对此做进一步了解。

## 部落服务器模块

在进入 Unity 客户端实现之前，让我们先看看服务器端。

与其他模块一样，我们可以为特定事件注册自定义服务器行为，例如当有人创建新部落时。对于群组，我们关注以下各项：

- `registerAfterJoinGroup`
- `registerAfterKickGroupUsers`
- `registerAfterLeaveGroup`
- `registerAfterPromoteGroupUsers`
- `registerAfterAddFriends`
- `registerBeforeDeleteGroup`

请参阅[这些事件的完整列表](https://github.com/heroiclabs/nakama-common/blob/master/runtime/runtime.go)。

### 发送通知

例如，当新玩家加入时，向部落中的每个人发送通知的函数如下所示：

**clans.ts**

```typescript
enum ClanNotificationCode {
    Refresh = 2,
    Delete = 3
}

function sendGroupNotification(nk: nkruntime.Nakama, groupId: string, code: ClanNotificationCode, subject: string) {
    const members = nk.groupUsersList(groupId, 100);
    const count = (members.groupUsers ?? []).length;
    if (count < 1) {
        return;
    }

    const notifications: nkruntime.NotificationRequest[] = new Array(count);
    members.groupUsers?.forEach(function {
        const n: nkruntime.NotificationRequest = {
            code: code,
            content: {},
            persistent: false,
            subject: subject,
            userId: user.user.userId,
        }
        notifications.push(n);
    });

    nk.notificationsSend(notifications);
}
```

此处我们创建自定义通知代码来区分不同类型的通知。这些可以是任意正数。然后我们用 `groupUsersList` 获取所有成员的列表，且对于列表中的每个用户，我们用 `user.user.id` 获取他们的 ID，将 `NotificationRequest` 发送给他们。

### 设置运行时挂钩

现在，我们需要调用我们刚刚创建的函数，这样每次有人加入群组时，Nakama 都会自动调用它：

**clans.ts**

```typescript
/**
* Send an in-app notification to all clan members when a new member joins.
*/
const afterJoinGroupFn: nkruntime.AfterHookFunction<void, nkruntime.JoinGroupRequest> =
    function(
        ctx: nkruntime.Context,
        logger: nkruntime.Logger,
        nk: nkruntime.Nakama,
        data: void, request: nkruntime.JoinGroupRequest) {
            sendGroupNotification(nk, request.groupId ?? "", ClanNotificationCode.Refresh, "New Member Joined!");
        }
```

此处我们处理 `JoinGroupRequest`，其中包含 `groupId`，我们可以用来判断应当将此通知发送到何处。

我们可以使用类似的代码将通知功能扩展到其他事件，例如，如果有人被踢出，则通知群组中的每个人：

**clans.ts**

```typescript
/**
* Send an in-app notification to all clan members when one or more members are kicked.
*/
const afterKickGroupUsersFn: nkruntime.AfterHookFunction<void, nkruntime.KickGroupUsersRequest> =
        function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: void, request: nkruntime.KickGroupUsersRequest) {
    sendGroupNotification(nk, request.groupId ?? "", ClanNotificationCode.Refresh, "Member(s) Have Been Kicked!");
}
```

### 注册挂钩

使函数按预期工作的最后一步是在主模块中注册它们。对于我们刚刚创建的每个函数，我们可以按如下方式添加它们：

**main.ts**

```typescript
    initializer.registerAfterKickGroupUsers(afterKickGroupUsersFn);
    initializer.registerAfterLeaveGroup(afterLeaveGroupFn);
    initializer.registerAfterPromoteGroupUsers(afterPromoteGroupUsersFn);
    initializer.registerAfterAddFriends(afterAddFriendsFn);
    initializer.registerBeforeDeleteGroup(beforeDeleteGroupFn);
    ...
```

此处的每个 `register` 函数都来自[运行时事件列表](https://github.com/heroiclabs/nakama-common/blob/master/runtime/runtime.go)，我们传入的参数是我们刚才在 `clans.ts` 中创建的函数之一。

## 创建客户端

接下来，我们将在 Unity 中创建一个界面，供玩家与服务器交互。在 Pirate Panic 中形如：

![ClansPanel]({{< fingerprint_image "/images/pages/nakama/tutorials/unity/pirate-panic/clans-panel.png" >}})


本教程并不详细介绍 Unity UI 组件的创建。请参阅[官方 Unity UI 教程](https://learn.unity.com/tutorial/ui-components#5c7f8528edbc2a002053b4d6)，对此做进一步了解。

[Pirate Panic 项目](https://github.com/heroiclabs/unity-sampleproject/tree/master/PiratePanic/Assets/PiratePanic/Scripts/Menus/Clans)中有完整的部落代码示例。


### 创建部落

为创建新部落，我们使用 `CreateGroupAsync(session, name, ...)` 函数，其中 `session` 是一个服务器连接，`name` 是新的部落。该函数还包含额外的可选参数，我们可以使用这些参数来存储信息，例如描述或化身图像。

对于 Pirate Panic 部落创建面板，其形如：

**ClanCreationPanel.cs**

```csharp
private async void CreateClan()
{
    string name = _clanName.text;

    try
    {
        IApiGroup group = await _connection.Client.CreateGroupAsync(_connection.Session, name, "A super great clan.", _avatarImage.name);
        if (OnClanCreated != null)
        {
            OnClanCreated(group);
        }
    }
    catch (ApiResponseException e)
    {
        Debug.LogError("Error creating clan: " + e.Message);
    }
}
```

在上述片段中：

- `_clanName`：Unity 文本框，玩家可在此输入新部落的名称。此处我们获取其文本。
- `_connection`：较早在代码中初始化的 `GameConnection` 对象。
- 提供的两个可选参数是描述和化身名称。
- 创建部落后，即调用 `OnClanCreated` 函数。此属性分配给 [ClansMenuUI.cs] 中的 `Awake` 。


最后，我们用这个异步函数包裹住一个函数，并将其绑定到一个按钮动作：

**ClanCreationPanel.cs**

```csharp
...
_doneButton.onClick.AddListener(() =>
    { Hide(); });
...
public override void Hide (bool isMuteSoundManager = false) {
    CreateClan();
    base.Hide(isMuteSoundManager);
}
```

### 部落聊天

我们可以创建一个频道，让整个群组互相聊天。在 Pirate Panic 中，其形如：

![ClansChatPanel]({{< fingerprint_image "/images/pages/nakama/tutorials/unity/pirate-panic/clans-chat.png" >}})

为进行部落聊天，我们在加入聊天频道时传入上方部落的群组 ID，这样部落中的每个人都会呆在同一频道中。

为加入聊天频道，我们使用 `JoinChatAsync`：

**ClansMenuUI.cs**

```csharp
private async void StartChat(ClanMenuUIState state)
    ...
    channel = await _connection.Socket.JoinChatAsync(state.UserClan.Id, ChannelType.Group, persistence: true, hidden: true);
    ...
}
```

此处我们加入 ID 为 `state.UserClan.Id` 的群组频道。
此频道是**持久**的，消息将被保存到数据库中，即使您断开连接再重新连接，消息也会显示。玩家以**隐藏**成员形式加入，即他们不出现在成员列表中。

可为不同类型的聊天室配置这些设置。在[实时聊天文档](../../../../concepts/chat/)中进一步了解不同的设置。

玩家加入聊天频道后，发送和接收消息的方式与[直接消息](../friends/#chatting-with-friends)相同。

### 退出或解散部落

当玩家不想继续呆在部落中时，有两种可能的操作：**退出**或**解散**。

![ClansMainPanel]({{< fingerprint_image "/images/pages/nakama/tutorials/unity/pirate-panic/clans-main.png" >}})

退出部落会让部落继续处于活动状态，只有当部落中还有另外一个 `superadmin`（所有者）时，才可以退出。解散部落后，将从服务器删除部落，并删除所有成员。

例如，退出部落：

**ClansMenuUI.cs**

```csharp
await _connection.Client.LeaveGroupAsync(_connection.Session, _state.DisplayedClan.Id);
```

此处，`_state_` 是 `ClansMenuUIState`，包含当前玩家屏幕显示内容信息的自定义类（以 `ClansMenuUIState.cs` 定义）。`DisplayedClan.Id` 是玩家想要退出的部落的 ID。

解散部落：

**ClansMenuUI.cs**

```csharp
await _connection.Client.DeleteGroupAsync(_connection.Session, _state.UserClan.Id);
```

## 下一主题

[存储](../storage/)