> For the complete documentation index, see [llms.txt](https://docs.edgegap.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.edgegap.com/zh/unity/server-browser.md).

# 服务器浏览器

使用 Edgegap 可选的入门套件 SDK，为你的 Unity 游戏添加服务器浏览器，让玩家从列表中查找并加入在线会话。

此 SDK 是面向 Unity 用户的可选入门套件，后续可扩展和自定义。

## 💡 功能

通过安装我们的 SDK，可获得预构建的自动化功能：

{% columns %}
{% column %}

* 完整示例
* 生命周期管理
* 容量管理
  {% endcolumn %}

{% column width="33.33333333333333%" %}

* 筛选查询编译器
* 类型定义（C#）
* 本地开发测试
  {% endcolumn %}

{% column width="33.33333333333333%" %}

* 跨平台
* 易于自定义
* 自动重试
  {% endcolumn %}
  {% endcolumns %}

## ✔️ 准备工作

Unity SDK 包含用于 Deployments、Matchmaking 和 Server Browser 的可选集成工具。此插件官方支持 Unity 2021.3.0f1 及更高版本。

{% hint style="info" %}
**Unity SDK 的最新版本是 `3.5.4`**。本文档中的所有示例均为最新。
{% endhint %}

{% hint style="success" %}
此插件完全免费提供，受 Free Tier 的条款和条件约束。
{% endhint %}

#### 要求

<details>

<summary>安装 Git 客户端（例如 <a href="https://git-scm.com/">git-scm</a>)</summary>

Unity 需要 git 客户端才能自动下载并安装我们的 Unity 包。安装后，你无需直接使用 git。

</details>

#### 安装

1. 打开你的 Unity 项目，
2. 选择 `窗口 > 包管理 > 包管理器` ,
3. 点击 :heavy\_plus\_sign: 图标并选择 `从 git URL 添加包...` ,
4. 在提示时输入我们 SDK 的 URL：

{% code title="" %}

```
https://github.com/edgegap/edgegap-unity-sdk.git
```

{% endcode %}

5. 点击 `添加`  并等待安装完成。

#### 导入示例

此包包含多个示例，旨在单独使用（不要将示例组合使用）。

#### 已验证来源

这是此 SDK 唯一的官方分发渠道，不要相信未经验证的来源！

#### 更新包

在 Unity Package Manager 中导航到 Edgegap SDK 并点击 `更新` .

{% hint style="warning" %}
**已导入的示例不会自动更新！** 备份任何自定义属性值，删除当前场景中使用的示例脚本，然后重新导入示例。
{% endhint %}

{% hint style="info" %}
某些版本可能包含破坏性更改。这将由新的 MAJOR 版本指示。
{% endhint %}

#### 更新到 v3

此更新包含许多新的 [服务器浏览器](/zh/unity/server-browser.md) 工具和示例，改进了匹配错误处理，等等。请参见 [发行说明](/zh/docs/release-notes.md) 了解完整列表。

{% hint style="warning" %}
Unity SDK v3 更新包含一些破坏性更改。请仔细重新测试你的集成。
{% endhint %}

## 🍀 入门

本指南假设你对 [服务器浏览器](/zh/learn/fu-wu-qi-liu-lan-qi.md) 相关概念和正在运行的 Server Browser 有基本了解。

{% hint style="success" %}
**我们强烈建议导入我们的 Auto-Assign 示例** ，以便在阅读本文时跟着代码一起学习。你可以在以下位置执行此操作： `Unity Package Manager > Edgegap SDK > Samples` .
{% endhint %}

{% embed url="<https://youtu.be/P8xWrD4UCxg>" %}

### 概览

我们的 SDK 大量使用 [依赖注入](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/overview#the-concept) 和 [观察者](https://learn.microsoft.com/en-us/dotnet/standard/events/observer-design-pattern) 编程模式。

{% hint style="info" %}
此包整合了这两者 [服务器浏览器](/zh/learn/fu-wu-qi-liu-lan-qi.md) 和 [匹配系统](/zh/learn/pi-pei-xi-tong.md)，它们可以一起使用，也可以单独使用。您可以自由重用任何脚本，用于您自己的定制分支和集成。
{% endhint %}

本包包括：

* 运行时文件 - 将会编译并与你的客户端和服务器构建一起打包：
  * 特定服务的实用工具：
    * [#server-agent](#server-agent "mention") - 一个完整的服务器集成，可供复用/扩展。
    * [#client-agent](#client-agent "mention") - 一个完整的客户端集成，可供复用/扩展。
    * API 函数 - 端点定义、错误处理和日志自动化。
    * 筛选编译器 - 用于构建筛选查询的强类型实用工具。
  * 特定服务 DTO[^1] - Server Browser API 的类型化数据容器。
  * 共享实用工具 - 日志、HTTP、ping、observables 等……
  * 共享 DTO[^1] - 供多个 Edgegap 服务用于传递数据。
* 示例文件 - 仅当导入到你的项目中时才会打包并编译：
  * [#auto-assign](#auto-assign "mention") - 带自动分配预留的示例处理器，
  * [#custom-search](#custom-search "mention") - 带手动实例选择的示例处理器。

### 服务器代理

**服务器生命周期和容量管理** 由 Server Agent 执行。

一旦实例化，代理的 **父 MonoBehaviour（处理器）必须初始化该代理** 并提供：

* `onMonitorUpdate`  回调 - 观察服务健康状况的变化。
* `onInstanceUpdate`  回调 - 观察并响应实例和槽位变化。
* `onConfirmationsUpdate`  回调 - 观察并响应联合身份验证。

初始化后，此代理将自动提供校验并挂接日志观察器，最终只需调用一次监控 API 端点以指示服务健康状况。

从这一点起，预期其处理器将接管控制并调用代理函数：

* `DiscoverInstance`  用于创建初始服务器实例和槽位，并启动心跳。
* `DeleteInstance`  在对局结束后 / 以防止新玩家加入。
* `ConfirmReservation`  当玩家加入时，用于验证其身份和槽位分配。
* `UpdateSlot`  用于更新槽位容量（在玩家加入/放弃时）或修改元数据。
* `UpdateInstance`  用于修改实例元数据。
* `状态`  用于验证 Server Browser 服务健康状况。

{% hint style="success" %}
确认和槽位/实例更新 **默认会被排队并批量执行** （心跳模式）以最大化可扩展性。要在开发测试期间更快迭代，请使用贪婪模式。
{% endhint %}

{% hint style="warning" %}
**更新元数据时，必须定义所有索引。** 要取消未索引键的设置，只需省略它们。
{% endhint %}

代理会自动维持心跳，以便在运行时保持服务器可被发现。如果代理连续若干次无法连接到你的 Server Browser 心跳次数（可配置）：

* 少于最大值 - 实例将自动重新发现，
* 多于最大值 - 实例将自动删除。

当建立新的玩家连接时，预期玩家会使用你的 netcode 将其预留 ID（第三方玩家 ID）发送到游戏服务器，以执行预留确认。

一旦 `onConfirmationsUpdate`  触发，处理器必须执行额外操作：

* 调用 `UpdateSlot`  以减少已确认预留的任何槽位的可用席位，
* 使用特定于 netcode 的方法接受或拒绝连接。

当玩家离开游戏时，预期处理器会增加该槽位的可用席位。

{% hint style="info" %}
在放弃前允许玩家有一小段时间重新连接，以防发生意外崩溃。
{% endhint %}

### 客户端代理

**搜索实例、分页、筛选和预留** 由 Client Agent 执行。

一旦实例化，代理的 **父 MonoBehaviour（处理器）必须初始化该代理** 并提供：

* `onMonitorUpdate`  回调 - 观察服务健康状况的变化。
* `onInstancesUpdate`  回调 - 观察并响应实例列表变化。

初始化后，此代理将自动提供校验并挂接日志观察器，最终只需调用一次监控 API 端点以指示服务健康状况。

从这一点起，预期其处理器将接管控制并调用代理函数：

* `ReserveSeats`  为特定实例/槽位或自动分配创建容量预留。
* `ListInstances`  用于按特定筛选条件、排序、游标和页面大小列出实例。
* `GetNextPage`  用于获取当前参数（筛选等）下的更多实例。
* `RefreshList`  用于清除缓存并重新加载第一页，或使用特定游标刷新。
* `GetInstanceDetails`  用于获取特定实例的实例元数据和槽位信息。
* `状态`  用于验证 Server Browser 服务健康状况。

当建立新的玩家连接时，预期玩家会使用你的 netcode 将其预留 ID（第三方玩家 ID）发送到游戏服务器，以执行预留确认。

{% hint style="success" %}
将连接详情保存在客户端或游戏后端，以便在意外崩溃时重新连接。
{% endhint %}

## 🧪 示例

开始使用示例，包括服务器端和客户端完整可运行的集成。

### 自动分配

利用自动分配的预留，客户端只需指定策略名称。Server Browser 会自动选择一个匹配策略筛选条件且拥有足够席位的实例槽位。

### 自定义搜索

包含完整实现，演示如何搜索实例和槽位、挂接 UI 元素，并让玩家手动选择要在哪里预留容量。

## ⚙️ 自定义

此 SDK 旨在扩展和修改，不过某些修改可能存在风险：

✅ 处理器 - 安全地挂接 UI 观察器并进行少量添加或修改，

⚠️ 代理 - 修改生命周期和容量管理需自担风险，

⚠️ API - 使用挑选出的实用工具从头编写你自己的集成。

处理器可以观察下文所述由 Server 和 Client 代理发出的任何事件。

{% hint style="warning" %}
请务必先熟悉 [Server Browser 深入解析](/zh/learn/fu-wu-qi-liu-lan-qi.md) 在进行自定义之前的相关概念。
{% endhint %}

{% hint style="info" %}
如果你需要帮助， [请通过 Discord 联系我们](https://discord.gg/MmJf8fWjnt)。关于实时游戏支持，请查看我们的 [工单系统](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

### 服务器事件

Server Agent 会发出事件（动作），供父处理器观察和消费。

{% hint style="success" %}
通过访问读取事件载荷  `.Current` 任何可观察对象的状态。🔴 `错误` 事件在主事件消息之后包含由换行符分隔的完整错误消息。
{% endhint %}

预览由 observable 发出的事件 `监控` :

<table data-full-width="true"><thead><tr><th width="125">动作类型</th><th width="450">事件消息</th><th>描述</th></tr></thead><tbody><tr><td>🟢 <code>更新</code> </td><td><code>健康</code></td><td>所有系统正常。</td></tr><tr><td>🟢 <code>更新</code> </td><td><code>不健康</code></td><td>发生了意外问题。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>获取监控失败</code></td><td>配置错误或发生意外问题。</td></tr><tr><td>🟡 <code>警告</code></td><td><code>请求超时已钳制为心跳 [{timeout}]</code></td><td>防止竞争条件。</td></tr></tbody></table>

预览由 observable 发出的事件（动作） `实例`:

<table data-full-width="true"><thead><tr><th width="125">动作类型</th><th width="450">事件消息</th><th>描述</th></tr></thead><tbody><tr><td>🟢 <code>更新</code> </td><td><code>已发现</code></td><td><a href="/zh/learn/fu-wu-qi-liu-lan-qi.md#discover-instance">实例发现</a> 已成功完成。如果实例因临时情况失去连接并被重新发现，则可能触发。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>发现重复</code></td><td>具有此请求 ID 的实例已被发现。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>发现失败</code></td><td>发现过程中出现意外问题。</td></tr><tr><td>🔵 <code>通知</code></td><td><code>心跳正常</code></td><td>心跳已成功完成。</td></tr><tr><td>🟡 <code>警告</code></td><td><code>心跳失败 [{consecutive}/{maximum}]</code></td><td>心跳失败，服务器无法连接到 Server Browser。</td></tr><tr><td>🔵 <code>通知</code></td><td><code>实例更新已入队</code></td><td>实例更新已排入下一批次（心跳/贪婪）。</td></tr><tr><td>🟢 <code>更新</code> </td><td><code>实例已更新</code></td><td>实例元数据已成功更新。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>实例更新失败，正在排队重试</code></td><td>实例更新失败，可能由于速率限制或错误。</td></tr><tr><td>🟢 <code>更新</code> </td><td><code>实例已删除</code></td><td>玩家将无法再发现该实例。</td></tr><tr><td>🟢 <code>更新</code> </td><td><code>实例删除失败（未找到）</code></td><td>实例可能因丢失过多心跳而过期。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>实例删除失败</code></td><td>删除实例失败，可能由于速率限制或错误。</td></tr><tr><td>🔵 <code>通知</code></td><td><code>槽位更新已入队 [{slot}]</code></td><td>槽位更新已排入下一批次（心跳/贪婪）。</td></tr><tr><td>🟢 <code>更新</code> </td><td><code>槽位已更新 [{slot}]</code></td><td>槽位座位容量和/或元数据已成功更新。</td></tr><tr><td>🟡 <code>警告</code></td><td><code>代理已限制并发槽位更新</code></td><td>已阻止并发更新尝试（竞争条件）。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>槽位更新失败（未找到）[{slot}]</code></td><td>此名称的槽位尚未为该实例定义。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>槽位更新失败（席位不足）[{slot}]</code></td><td>槽位更新尝试将可用席位减少到零以下。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>槽位更新失败，正在排队重试 [{slot}]</code></td><td>槽位更新失败，可能由于速率限制或错误。</td></tr></tbody></table>

预览由 observable 发出的事件（动作） `确认`:

<table data-full-width="true"><thead><tr><th width="125">动作类型</th><th width="450">事件消息</th><th>描述</th></tr></thead><tbody><tr><td>🔵 <code>通知</code></td><td><code>已入队 [{player}]</code></td><td>已将确认排入下一批次（心跳/贪婪）。</td></tr><tr><td>🟡 <code>警告</code></td><td><code>重复 [{player}]</code></td><td>已阻止重复确认尝试（已排队）。</td></tr><tr><td>🟢 <code>更新</code> </td><td><code>已确认</code></td><td>已确认各个槽位的预留，也包括过期和未知的玩家 ID，供处理器处理（接受/踢出）。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>失败</code></td><td>确认过程中出现意外问题。请检查服务状态。</td></tr></tbody></table>

### 客户端事件

Client Agent 会发出事件（动作），供父处理器观察和消费。

{% hint style="success" %}
通过访问读取事件载荷  `.Current` 任何可观察对象的状态。🔴 `错误` 事件在主事件消息之后包含由换行符分隔的完整错误消息。
{% endhint %}

预览由 observable 发出的事件 `监控` :

<table data-full-width="true"><thead><tr><th width="125">动作类型</th><th width="450">事件消息</th><th>描述</th></tr></thead><tbody><tr><td>🟢 <code>更新</code> </td><td><code>健康</code></td><td>所有系统正常。</td></tr><tr><td>🟢 <code>更新</code> </td><td><code>不健康</code></td><td>发生了意外问题。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>获取监控失败</code></td><td>配置错误或发生意外问题。</td></tr></tbody></table>

预览由 observable 发出的事件 `实例`:

<table data-full-width="true"><thead><tr><th width="125">动作类型</th><th width="450">事件消息</th><th>描述</th></tr></thead><tbody><tr><td>🔵 <code>通知</code></td><td><code>席位已预留</code></td><td>席位预留成功。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>席位预留失败（未找到）[{slot}]</code></td><td><a data-mention href="#auto-assign">#auto-assign</a> - 未找到策略名称（已删除或未激活）。<br><a data-mention href="#custom-search">#custom-search</a> - 未找到实例或槽位。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>席位预留失败（达到容量上限）</code></td><td><a data-mention href="#auto-assign">#auto-assign</a> - 策略已达到最大容量。<br><a data-mention href="#custom-search">#custom-search</a> - 槽位已达到最大容量。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>席位预留失败</code></td><td>席位预留失败，可能是策略、请求 ID 或槽位 ID 无效。</td></tr><tr><td>🟢 <code>更新</code> </td><td><code>实例列表已获取</code></td><td>已成功获取实例列表。</td></tr><tr><td>🟢 <code>更新</code> </td><td><code>实例列表下一页已获取</code></td><td>已成功获取下一页实例。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>已到达实例列表最后一页</code></td><td>获取下一页失败，请尝试刷新或更改筛选条件。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>获取实例列表下一页失败</code></td><td>获取下一页失败，可能由于游标无效。</td></tr><tr><td>🟢 <code>更新</code> </td><td><code>实例详情已获取</code></td><td>已成功获取列表中实例的详情。</td></tr><tr><td>🟢 <code>更新</code> </td><td><code>实例未缓存，正在前置插入</code></td><td>已获取当前列表之外的实例详情。</td></tr><tr><td>🔴 <code>错误</code></td><td><code>实例详情获取失败</code></td><td>获取详情失败，可能由于请求 ID 无效。</td></tr></tbody></table>

[^1]: 数据传输对象
