> 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/fu-wu-qi-liu-lan-qi.md).

# 服务器浏览器

这个 SDK 是面向 Unity 用户的可选入门套件，之后可以扩展和自定义。

## 💡 功能

安装我们的 SDK 即可使用预构建的自动化功能：

{% columns %}
{% column %}

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

{% column width="33.33333333333333%" %}

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

{% column width="33.33333333333333%" %}

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

## ✔️ 准备工作

Unity SDK 包含用于部署、配对和服务器浏览器的可选集成工具。此插件官方支持 Unity 2021.3.0f1 及更高版本。

{% hint style="success" %}
此插件依据免费版条款与条件，100% 免费提供。
{% endhint %}

#### 要求

<details>

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

Unity 需要 Git 客户端来自动下载并安装我们的 Unity 包。安装完成后，您将不需要直接使用 git。

</details>

#### 安装

1. 打开您的 Unity 项目，
2. 选择 `Window > Package Management > Package Manager` ,
3. 点击 :heavy\_plus\_sign: 图标并选择 `Add package from git URL...` ,
4. 在提示时输入我们 SDK 的 URL：

{% code title="" %}

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

{% endcode %}

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

#### 导入示例

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

#### 已验证来源

这是此 SDK 唯一的官方分发渠道，请勿信任未经验证的来源！

#### 更新包

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

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

{% hint style="info" %}
某些版本可能包含破坏性变更。这将由新的主版本号标明。
{% endhint %}

#### 更新到 v3

此更新包含许多新的 [服务器浏览器](/zh/unity/fu-wu-qi-liu-lan-qi.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" %}
**我们强烈建议导入我们的自动分配示例** ，这样你在阅读本文档时可以跟着代码一起查看。你可以在以下位置进行此操作： `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.md)，它们可以一起使用，也可以单独使用。您可以自由重用任何脚本，用于您自己的定制分支和集成。
{% endhint %}

此包包括：

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

### 服务器代理

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

实例化后，代理的 **父级 Monobehaviour（处理器）必须初始化代理** 并提供：

* `onMonitorUpdate`  回调 - 观察服务健康状态变化，
* `onInstanceUpdate`  回调 - 观察并响应实例和槽位变化，
* `onConfirmationsUpdate`  回调 - 观察并响应联邦认证。

初始化后，该代理将自动提供验证并挂接日志观察器，最后通过一次对监控 API 端点的调用来指示服务健康状态。

从此时起，代理的处理器应接管控制并调用代理函数：

* `发现实例`  用于创建初始服务器实例和槽位并启动心跳，
* `删除实例`  在匹配结束后 / 以阻止新玩家加入，
* `确认预留`  当玩家加入时，用于通过你的 netcode 验证其身份和槽位分配，
* `更新槽位`  用于更新槽位容量（玩家加入/离开时）或修改元数据，
* `更新实例`  用于修改实例元数据，
* `状态`  用于验证 Server Browser 服务健康状态。

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

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

代理会自动维护心跳，以在运行期间保持服务器可被发现。如果代理连续若干次心跳都无法连接到你的服务器浏览器（可配置）：

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

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

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

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

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

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

### 客户端代理

**实例搜索、分页、筛选和预留** 由客户端代理执行。

实例化后，代理的 **父级 Monobehaviour（处理器）必须初始化代理** 并提供：

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

初始化后，该代理将自动提供验证并挂接日志观察器，最后通过一次对监控 API 端点的调用来指示服务健康状态。

从此时起，代理的处理器应接管控制并调用代理函数：

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

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

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

## 🧪 示例

通过示例快速入门，其中包括服务器端和客户端完整可运行的集成。

### 自动分配

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

### 自定义搜索

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

## ⚙️ 自定义

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

✅ 处理器 - 可安全地连接 UI 观察器并进行小幅添加或修改，

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

⚠️ API - 使用精选工具从零开始编写你自己的集成。

如下所述，处理器可以观察服务器和客户端代理发出的任何事件。

{% 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 %}

### 服务器事件

服务器代理会发出事件（动作）供父级处理器观察和消费。

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

预览可观察对象发出的事件 `监控` :

<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>

预览可观察对象发出的事件（动作） `实例`:

<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="/pages/0a5645e84dbc0a67378f659ab24ef5120c1f6ab1#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>

预览可观察对象发出的事件（动作） `确认`:

<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>

### 客户端事件

客户端代理会发出事件（动作）供父级处理器观察和消费。

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

预览可观察对象发出的事件 `监控` :

<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>

预览可观察对象发出的事件 `实例`:

<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><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]: 数据传输对象
