# 服务器浏览器

此 SDK 是为 Unity 用户提供的一个可选入门套件，之后可以扩展和自定义。

## 💡 功能

安装我们的 SDK，即可访问预构建的自动化功能：

{% columns %}
{% column %}

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

{% column width="33.33333333333333%" %}

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

{% column width="33.33333333333333%" %}

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

## ✔️ 准备工作

## 🍀 开始使用

本指南假设你已具备以下基础知识： [服务器浏览器](/zh/learn/fu-wu-qi-liu-lan-qi.md) 相关概念以及正在运行的 Server Browser。

{% hint style="success" %}
**我们强烈建议导入我们的自动分配示例** ，以便在阅读本文档时跟着代码操作。你可以在以下位置进行： `Unity Package Manager > Edgegap SDK > Samples` .
{% 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 端点来指示服务健康状态。

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

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

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

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

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

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

当建立新的玩家连接时，玩家应通过你的 netcode 向游戏服务器发送其预订 ID（第三方玩家 ID），以执行预订确认。

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

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

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

{% hint style="info" %}
在判定放弃之前，为玩家保留一小段时间以便在意外崩溃时重新连接。
{% endhint %}

### 客户端代理

**实例搜索、分页、过滤和预订** 由客户端代理执行。

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

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

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

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

* `ReserveSeats`  用于为特定实例/槽位或自动分配创建容量预订，
* `ListInstances`  用于按特定过滤条件、排序、游标和分页大小列出实例，
* `GetNextPage`  用于根据当前参数（过滤器等）获取更多实例，
* `RefreshList`  用于清除缓存并重新加载第一页，或使用特定游标刷新，
* `GetInstanceDetails`  用于获取特定实例的元数据和槽位信息，
* `Status`  用于验证 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 %}

### 服务器事件

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

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

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

### 客户端事件

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

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

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.edgegap.com/zh/unity/fu-wu-qi-liu-lan-qi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
