【unity游戏开发——网络】大大简化你的Steam对接 —— Toolkit for Steamworks
本文介绍了Unity中使用Heathen Steamworks工具包对接Steam平台的配置方法。该工具基于Steamworks.NET构建,提供更友好的Unity集成方案。主要内容包括:插件安装步骤、Unity项目配置(设置Steam应用ID、生成API封装器)、多应用管理(主应用/Demo/测试版)以及DLC和专用服务器的配置说明。重点强调了更改App ID后必须重启Unity和VS编辑器,并
文章目录
- 一、前言
- 二、插件下载安装
- 三、Unity 配置
- 四、Unity 初始化
- 五、Unity 多人游戏
- 六、构建测试
- 七、部署
- 八、发布
- 九、成就
- 十、排行榜
- 十一、游戏大厅
- 专栏推荐
- 完结
一、前言
这份知识库也是任何计划在Steam上发布游戏的开发者能拥有的最佳资源。它涵盖了Steam平台及其SDK的每一个方面,包括但不限于:设置商店页面、运营社区中心、理解推荐算法,以及对Steamworks SDK每一项功能的完整剖析。如果您真的想使用 Steam,它会大大简化您的工作。
感兴趣可以去先去看看Steamworks.NET的流程,再来对比:【unity游戏开发——网络】unity对接steam,并上传发布游戏版本——Steamworks.NET
Unity版本的Heathen工具包是基于 Steamworks.NET 构建的。 Steamworks.NET 项目旨在成为Valve Steam API的直接一对一封装,将原生的C/C++接口包装成C#插件。
Steamworks.NET 提供了对Valve API的最佳访问,并忠实地保持了原始API的风格,因此原始的官方文档和数十年的社区指导仍然适用。
Steamworks.NET 的缺点在于它忠实地反映了原始API,使用了大多数游戏开发者不熟悉的编程风格和方式,在Unity或Godot脚本中使用起来非常笨拙。
Heathen扩展了Steamworks.NET中所有相关的接口,提供了一套更简单、更健壮的工具集。如果您有需要,原版Steam API的所有功能依然触手可及。
Heathen的Steamworks没有取代Steamworks.NET —— 而是在其之上构建,为您提供了一套从Unity角度出发、经过实战检验且受开发者好评的工具和系统。
二、插件下载安装
https://assetstore.unity.com/packages/tools/integration/toolkit-for-steamworks-2026-331101#description
三、Unity 配置
打开项目设置(Project Settings)并选择 Player > Steamworks。
首次操作时,它会在你项目的 Settings 文件夹中创建一个 SteamToolSettings 资源。这个文件存储你在 Project Settings > Steamworks 窗口中的所有设置,可以被版本控制,并能在不同项目间共享。

1、活动应用程序 (Active Application)

重要:更改 App ID 后(如果之前已经初始化过 Steamworks),你 — 必须 — 重启 Unity 和 Visual Studio。
为什么? 这是 Steam.exe 强制要求。 当你初始化 Steamworks SDK 时,Unity 编辑器本身及其所有相关进程都会被 Steam.exe 视为“游戏”…
并且在你完全退出这些进程之前,会一直被视为“正在运行”。因此,在不完全重启 Unity 的情况下,你无法更改已初始化的 App ID。
这里显示当前活动的 Steam 应用程序。默认情况下,系统至少会有“Main”(主应用)和“Demo”(演示应用)。这会为选中的应用 ID 在 Player 设置中设置一个“脚本定义符号”(Scripting Define Symbol)。

这个定义符号用于生成的代码中,以处理初始化以及对成就、统计数据、排行榜等游戏内容的访问。你也可以在自己的代码中使用它,以便让某些逻辑仅对某个特定的应用 ID 生效。
例如:
#if APP480
// 仅当 APP480 是活动应用时才会编译的代码
#else
// 仅当 APP480 不是活动应用时才会编译的代码
#endif
2、全局设置 (Global)

全局设置部分处理“主应用”(Parent App,例如你的主游戏)特有的设置,这些设置在子应用(如 Demo 和 Play Tests)中不会被复制。它还提供了关键资源的快速访问,以及为你的特定应用生成 API 封装器的功能。
生成封装器 (Generate Wrapper)

按下此按钮将读取你当前的配置并生成 SteamTools.Game 静态类。这个类为我们的组件、工具和你的程序员提供了强大的功能。它封装了你所有的配置,并遵循“活动应用程序”的设定,极大地简化了你在 Steam 上的工作,即使在多应用项目中(大多数项目都是如此)也是如此。
后续文章会更详细地介绍,但举个例子,你可以通过成就的名称在游戏逻辑的任何地方访问它。
Spacewars 示例:
SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.Unlock();
可下载内容 (Downloadable Content)
每次更改此值后,你都应该“生成封装器 (Generate Wrapper)”。

如果你的游戏有可下载内容(DLC),你可以从 Steamworks 导入这些信息。
操作步骤:
- 你需要先设置你的 App ID。
- 在编辑器中按下播放按钮并初始化 Steamworks。
- 游戏正在运行且 Steamworks 已初始化后,点击“导入 (Import)”。
- 你现在可以停止模拟,然后重新生成封装器来添加 DLC 数据。
Steam 游戏服务器配置 (Steam Game Server Configuration)

仅在计划发布“专用服务器 (Dedicated Server)”构建时使用。此配置允许你的专用服务器在没有 Steam.exe 实例登录和运行的情况下也能初始化 Steamworks SDK。你可以在 Steam 游戏服务器 一文中了解更多信息。
3、主应用、演示、试玩测试 (Main, Demo, Playtests)

你在 Steam 上的项目由一个“主 (Main)”应用程序组成;这是你首次激活应用额度时创建的 App ID。
你还可以(也应该)创建一个 Demo。Demo 是主应用的“子应用 (child app)”,不会消耗额外的应用额度。Demo 是参与 Steam 活动(如 Fest)的必要条件… 你可以在 发布 (Launch) 一文中了解更多信息。
试玩测试 (Playtests) 与 Demo 类似,也是主应用的“子应用”;你可以有多个试玩测试。这些通常是在游戏开发阶段的免费体验期,玩家可以访问“试玩测试”应用,为你提供反馈。每个试玩测试和 Demo 一样,都有自己的“构建版本”… 你可以在 试玩测试 (Playtest) 一文中了解更多信息。
每个这些“App ID”都有自己的配置:
Steam 开发者门户 (Steam Portal)
此链接会直接在浏览器中打开你应用的 Steam 开发者门户。注意,这对 App 480 无效,因为你不是该应用的开发者。
打开调试窗口 (Open Debug Window)
这会打开 Steamworks 检查器 (Inspector)… 仅当你在编辑器中运行游戏时有用。

这个窗口让你可以查看各种 Steamworks 内容的内部状态和值:
- 统计数据 (Stats)
- 成就 (Achievements)
- 排行榜 (Leaderboards)
- DLC
- 库存物品 (Inventory Items)
- 游戏大厅 (Lobbies)
清除并设置测试值 (Clear & Set Test Values)
“清除 (Clear)”和“设置测试值 (Set Test Values)”按钮如其所述,分别用于清除你当前的设置或将它们默认设置为 Spacewars 的测试值。
应用 ID (Application ID)
每次更改此值后,你都应该“生成封装器 (Generate Wrapper)”。
在此处设置此应用的 ID。注意,更改活动应用 (Active Application) 的值会导致 Unity 重新编译,因为这也改变了脚本定义符号 (Scripting Define Symbol)。
游戏内容 (Artifacts)
每次更改此值后,你都应该“生成封装器 (Generate Wrapper)”。
游戏内容部分列出了你 Steam 应用的所有“组成部分”。
- 输入 (Input)
- 输入集 (Sets)
- 输入集层级 (Set Layers)
- 输入动作 (Actions)
- 统计数据 (Stats)
- 排行榜 (Leaderboards)
- 成就 (Achievements) (可以导入)
- 库存 (Inventory) (可以导入)
- 物品 (Items)
- 捆绑包 (Bundles)
- 生成器 (Generators)
- 游戏时长生成器 (Playtime Generators)
- 标签生成器 (Tag Generators)
2025 版中的 Steam 库存物品定义编辑器功能将在 2026 版作为完全重构的功能回归。2026 版编辑器目前正在开发中。
其他系统的更改使得旧的基于 ScriptableObject 的编辑器无法工作,因此 2026 版中暂不可用。
四、Unity 初始化
旧版 (Legacy) 的初始化工作方式略有不同…
请参阅 旧版快速入门指南 了解更多信息。
首要且最重要的一点是… 如果你的项目中有
SteamManager.cs脚本,请移除它。那个脚本根本不应该被使用;它不是 Heathen 工具包的一部分,并且它最初就不打算被拖放到生产项目中。
纯代码方式 |
|
image.png | #pure-code-1 |
无代码方式 |
|
image.png | #component |
1、纯代码方式
你不需要任何游戏对象 (GameObject) 来使 Steamworks 运行。你可以直接调用 生成的封装器。
SteamTools.Game.Initialize();
就绪检查 (Ready Check)
你可以使用 Interface 类来检查接口是否已就绪可供使用。
private void Start()
{
if (SteamTools.Interface.IsReady)
{
// 已准备就绪
}
else
{
// 尚未就绪,监听 OnReady 事件
SteamTools.Interface.OnReady += Interface_OnReady;
}
}
private void Interface_OnReady()
{
// 现在已就绪
}
或者,你可以使用“When Ready”功能。
private void Start()
{
SteamTools.Interface.WhenReady(Interface_OnReady);
}
private void Interface_OnReady()
{
// 现在已就绪
}
调试 (Debugging)
要通过代码启用调试,你可以设置:
SteamTools.Interface.IsDebugging = true;
这将使其在日志中输出更详细的信息。通常,你应该在初始化之前设置此项,以便在初始化步骤中也获得更详细的日志。
2、无代码方式
如果你需要一个无需编写代码的解决方案,我们为你提供了一个组件脚本,它会根据你的配置设置自动初始化 Steamworks SDK。

不需要任何设置;所有设置都由你的 配置 处理。你不需要将此对象标记为“不销毁 (Do Not Destroy)”,它的存在只是为了调用以下代码,别无他用:
SteamTools.Game.Initialize();
五、Unity 多人游戏
本指南旨在帮助 Unity 开发者快速入门使用 Steamworks 进行多人游戏开发。它涵盖了需要了解的关键概念和架构,包括高层级 (High-Level) 和低层级 (Low-Level) 网络 API 的区别,以及 Steamworks 如何融入其中。
至于“如何操作 (How To)”,这由你选择的高层级 API 来具体说明。Steamworks 是一项实现功能的技术… 这些功能会被你选择的高层级 API 使用。
1、理解网络层级
高层级 API (HLAPI)
- 它是什么:
HLAPI 是处理游戏状态复制和数据同步的框架。它们定义了同步什么、何时同步、由谁同步以及消息如何在网络中传输。 - 常用工具:
- 关键点:
所有这些 HLAPI 的工作方式都相似。你选择哪个 HLAPI 主要取决于“风格”或个人偏好。一旦选定,你将根据其文档进行详细的实现指导。
低层级 API (LLAPI)
- 它是什么:
通常被称为“传输 (Transport)”层,LLAPI 处理实际的数据发送和接收,管理连接细节,例如建立、维护和终止网络连接。 - Steamworks 集成:
- Steamworks SDK 包含 Steam Networking Sockets 协议。
- 主流的 HLAPI 都提供了支持 Steam Networking Sockets 的传输模块。
- 关键点:
虽然 LLAPI 管理连接的技术细节(可以将其视为其他引擎中的“网络驱动 (NetDrivers)”),但它们在幕后运行。一旦你的 HLAPI 配置了正确的传输层,你就可以像往常一样使用 HLAPI,几乎不需要改变工作流程。
2、设置你的多人游戏架构
选择 HLAPI 和传输层
- 步骤 1:
选择一个适合你项目需求的 HLAPI。记住,HLAPI 处理游戏的大部分网络逻辑。 - 步骤 2:
安装相应的传输模块,该模块能够与 Steam Networking Sockets 集成。这种配置确保你的 HLAPI 使用 Steam 的协议进行通信,而不会影响你的标准开发实践。
通用网络架构
- 星型拓扑 (Star Topology):
大多数 Unity HLAPI 使用由中央 网络管理器 (Network Manager) 管理的星型拓扑。该管理器处理所有网络连接,确保客户端和服务器之间的数据流保持有序。 - 服务器类型:
- 监听服务器 (Listen Server):
经常与 P2P 混淆,监听服务器是指一个实例同时充当客户端和服务器。这是正确的术语;一些 HLAPI 可能(错误地)将此称为点对点 (Peer to Peer),然而,对于 Valve 来说,这个词有不同的含义,所以要明白,在 HLAPI 的文档之外,P2P 指的是其他东西。 - 专用服务器 (Dedicated Server):
专用服务器独立运行,处理来自多个客户端的连接。当使用 Steamworks 初始化专用服务器时,它会通过 Steam 游戏服务器端点(由 Heathen’s Toolkit for Steamworks 处理)获得一个 Steam ID。
- 监听服务器 (Listen Server):
3、Steamworks 集成的关键点
Steam ID 作为连接地址
- 连接方式:
所有使用 Steam Networking Sockets 的 Unity HLAPI 都将 Steam ID 视为网络地址。 - 含义:
玩家不是通过 IP:端口 组合进行连接,而是直接在 Steam ID 之间建立连接(用户和服务器都是如此)。这就是 Valve 所说的 P2P 的含义… 在两个相同类型的地址之间,无论它们是客户端、服务器还是两者兼有。 - 连接到 ID:
如何知道要连接的 ID 取决于你为游戏选择的“发现 (discovery)”方法。Steamworks 提供了许多工具来帮助你实现这一点:- 游戏大厅 (Lobby)
- 游戏服务器浏览器 (Game Server Browser)
- 丰富状态 (Rich Presence)
- 好友邀请 (Friend Invite)
- 聚会信标 (Party Beacons)
- 输入十六进制 ID(请参阅将用户 ID 与十六进制相互转换的工具)
专用服务器和 Steam ID
- 服务器初始化:
专用服务器使用 Steam 游戏服务器端点进行初始化,并接收一个 Steam ID。Heathen’s Toolkit for Steamworks 在你的服务器构建中自动化了这个过程。
Steam 游戏大厅的角色
- 澄清:
- Steam 游戏大厅 (Steam Lobby): 一个主要用于匹配的工具。它帮助玩家找到彼此并商定会话参数。
- 网络连接: Steam 游戏大厅不是一个网络连接功能。
- 关键点:
一旦网络会话建立,你直接使用 Steam ID 进行连接——而不是通过游戏大厅。游戏大厅的角色在会话设置完成后就结束了。
六、构建测试
Steamworks 集成要求正确设置你的游戏构建和环境。本指南解释了如何正确地测试你的游戏,无论是在本地还是通过 Steam 客户端,并解决了常见的陷阱,如启动时崩溃或意外行为。
1、Steam 依赖性
一个典型的 Steam 集成游戏 需要 Steam 客户端 已安装、正在运行,并且使用一个有权限访问游戏 App ID 的用户登录。如果没有这些条件,Steamworks API 将无法正确初始化,导致崩溃或其他意外行为。
本地测试 vs. Steam 客户端测试
✅ 开发测试(本地构建)
要在 Steam 之外测试你的构建(例如,在上传到 SteamCMD 之前),你必须在游戏目录的根目录下放置一个 steam_appid.txt 文件。该文件应仅包含你的 App ID 作为纯文本。
- 对于 Unity 或 Unreal Engine 构建,将
steam_appid.txt放在可执行文件旁边。 - 这允许 Steam API 在即使游戏是从资源管理器、终端或其他非 Steam 启动器启动的情况下也能初始化。
📝 示例:
steam_appid.txt
内容:123456
✅ 通过 Steam 进行构建测试(推荐)
为了正确集成和测试:
- 使用 SteamCMD(可在 Steamworks SDK 中找到)将你的构建上传到 Steam。
- 设置 测试分支 (beta branches),将开发/测试构建与公共版本分开。
- 通过 Steamworks 后台添加内部测试人员。
这能很好地模拟线上环境,是在发布前识别部署问题的理想方法。
2、常见陷阱与修复
❌ 游戏启动时崩溃
原因: 你正在运行一个没有 steam_appid.txt 的本地构建,或者该文件包含错误的 ID。
修复: 添加包含正确 App ID 的 steam_appid.txt。如果你使用的是 App ID 480(Spacewar),请注意它不适合用于生产环境。
❌ 游戏尝试下载Spacewar
原因: 你在使用 App ID 480(Valve 的示例游戏)但没有有效的部署。
修复: 替换为你游戏实际的 App ID,并设置好 steam_appid.txt。
❌ Steam 自动重启游戏
原因: Steam 检测到游戏不是通过客户端启动的。
修复: 这是有意为之的行为(RestartAppIfNecessary)。在开发测试期间使用 steam_appid.txt,或者直接从 Steam 客户端启动游戏。
3、内部测试设置
要在 Steam 上测试构建:
- 通过 Steamworks 后台设置一个测试分支 (beta branch) 或私有测试分支。
- 通过“管理用户和合作伙伴 (Manage Users & Partners)”部分添加你的测试人员。
- 确保测试人员已登录 Steam 并具有相应的权限。
| 操作 | 所需设置 |
|---|---|
| 本地测试 | 游戏根目录下放置 steam_appid.txt |
| Steam 测试 | 通过 SteamCMD 上传,分配 App ID |
| 避免崩溃 / 重启 | 使用正确的 App ID,并在不通过 Steam 启动时包含 steam_appid.txt |
| 虚幻引擎构建 | 确保 .ini 配置文件正确,即使不使用 OnlineSubsystemSteam |
始终在与你的玩家相同的环境中测试你的最终构建——即通过 Steam。及早发现问题,避免因启动崩溃导致的负面评价,并充分利用 Steam 的测试工具来确保顺利发布。
七、部署
你已经构建好了你的游戏,准备将其推送到 Steam,但不太确定具体怎么做。本文将介绍两种主要的游戏推送方法,以及一些与主题相关的 Steam 构建和仓库的管理功能。
Valve 关于此主题的官方文档是必读的!
有用的链接1、Steam Pipe GUI
这是 Steamworks SDK 的一个标准功能:
它取代了我们旧版 Toolkit for Steamworks 中的“Steam Build”按钮,因为它做的是完全相同的事情,只是由 Valve 开发。实际上,这个工具只是为你调用了 Steam CMD 工具。我们建议你阅读本文中的 Steam CMD 部分,以了解实际发生了什么。

你可以在注册成为 Steam 开发者时下载的 Steamworks SDK 压缩包中找到 SteamPipe GUI 工具…
你肯定是在围绕 Steam 开始构建游戏之前就注册过了吧 😏
你可以在 SDK 的 tools 文件夹中找到这个工具。
2、压缩包上传
如果你的游戏足够小,你可以通过 Steam 开发者门户以 zip 文件的形式上传。

简而言之,如果生成的 zip 文件小于 2 GB,你可以在 Steam Pipe -> Builds -> Upload Depots 选项中上传它。
3、Steam CMD
Steam CMD 有一个“内容构建器 (Content Builder)”功能,这是上传游戏的传统方式,在我们看来也是更好的方法。第一步是下载 Steamworks SDK,这是一个包含大量工具和示例的压缩文件,旨在帮助你熟悉 Steamworks 工具。
“app build vdf”文件只是一个脚本,描述了我们想要上传的是哪个应用,以及我们要上传到哪些仓库。
app build vdf 文件进一步引用了“仓库构建配置 vdf (depot build configuration vdf)”文件,这些文件详细说明了将使用的每个仓库以及该仓库读取的内容。
你对使用命令行工具有抵触情绪吗?
虽然我们鼓励你学习使用,因为它会为你打开一个全新的能力世界,但也有一些 GUI 工具可以帮助你使用 Steam CMD 上传构建。
这里有一个例子 https://github.com/RPicster/Steam-Upload-GUI
这个工具未经 Heathen Engineering 测试、构建或支持;它只是当时搜索 Steam CMD GUI 时的首要结果。
下载 Steamworks SDK
下载后,你应该将其解压到本地计算机上的某个位置。SDK 中你需要关注的文件夹是 SDK -> Tools -> ContentBuilder 文件夹。

解释这一切如何运作的视频
简而言之,你可以使用几个简单的命令通过 Steam CMD 将你的构建上传到 Steam。这个设置甚至可以通过脚本自动化,这样你只需要运行脚本即可。
运行构建脚本
让我们从创建用于上传构建的脚本开始。以下是一个你可以使用的模板。
builder\steamcmd.exe +login [Username] [Password] +run_app_build_http ..\scripts\app_build_[appid].vdf +quit
pause
要使用上述模板,请打开记事本或你喜欢的纯文本编辑器,粘贴该命令。接下来,我们将替换以下文本。
- [Username]
这应该是你用于 Steam 开发者门户的 Steam 用户名。 - [Password]
这是同一帐户的密码。 - [appid]
这是你想要上传的数字 App ID。
接下来,将文件保存到 ContentBuilder 文件夹… 我们建议你将其命名为类似 run_build_MyGameName.bat 的名字。这将使你更容易排序、搜索和记住这个脚本对应哪个应用。
这个脚本执行以下操作:
- 运行 SteamCmd
- 以指定用户身份登录
- 指定脚本
..\scripts\app_build_#####.vdf
最后的命令是传递一个文件作为参数的一部分… 这个文件位于 scripts 文件夹中,正是那个 .vdf 文件描述了要上传什么、从哪里读取内容以及应该推送到哪些仓库。
你可以为不同的构建和不同的应用创建这个脚本的变体。这为你提供了一种一键上传所有内容的方法,你甚至可以一次性上传多个构建。
怎么做?
下一节会讲到。
VDF 文件
Valve 的 .vdf 格式文件有两种类型:
- “appbuild”(应用构建)
这些 VDF 文件描述要上传的内容,例如应用 ID、构建描述、可以找到构建内容的内容文件夹等。 - “DepotBuildConfig”(仓库构建配置)
这些 VDF 文件描述了上传应该推送到的仓库本身,包括仓库 ID、其文件映射信息,以及任何文件排除规则,例如排除 *.pdb 文件被上传。
应用构建 VDF 模板
此文件应保存在 scripts 文件夹中,名称类似 app_build_####.vdf,其中 #### 是你的 App ID。
"appbuild"
{
"appid" "[appId]"
"desc" "[description]" // 此构建的描述
"buildoutput" "..\output\" // .log, .csm & .csd 文件的构建输出文件夹,相对于此文件的位置
"contentroot" "..\content\[gameFolder]\" // 根内容文件夹,相对于此文件的位置
"setlive" "" // 构建成功后设置为上线状态的分支,如果为空则不设置
"preview" "0" // 启用预览构建
"local" "" // 设置为本地内容服务器的文件路径
"depots"
{
"[DepotId]" "depot_build_[DepotId].vdf"
}
}
你应该用适合你游戏的值替换下面定义的文本:
- [appId]
此构建对应的 App ID。 - [description]
此构建的描述… 通常类似于 “MyGame’s base build” 或 “MyGame’s Windows Build”。 - [gameFolder]
这是你构建内容的位置,如你所见,我们假设你将构建放在 SDK -> Tools -> ContentBuilder -> content 文件夹中。我们建议你在其中为每个游戏… 以及每个平台创建子文件夹… 例如:- sdk/tools/ContentBuilder/content/MyGame/Windows
- sdk/tools/ContentBuilder/content/MyGame/Linux
- sdk/tools/ContentBuilder/content/MyOtherGame/Windows
- [DepotId]
这是要包含的 DepotBuildConfig 文件列表,它是一个数组,因此你可以包含多个,例如:
"depots"
{
"123456" "depot_build_123456.vdf",
"234567" "depot_build_234567.vdf"
}
仓库构建配置定义了它们将从指向的内容文件夹中读取哪些内容(或不读取哪些内容)。在你阅读仓库构建配置模板时,这将变得更清晰。
仓库构建配置 VDF 模板
此文件应保存在 scripts 文件夹中,名称类似 depot_build_####.vdf,其中 #### 是你的仓库 ID。
"DepotBuildConfig"
{
"DepotID" "[DepotId]"
"ContentRoot" "[RootFolderOfTheGame ... where the platform folders live]"
"FileMapping"
{
// 这可以是完整路径,也可以是相对于 ContentRoot 的路径
"LocalPath" ".\[Platform]\*"
"DepotPath" "."
"recursive" "1"
}
"FileExclusion" "*.pdb"
}
在这里,我们将替换以下文本:
- [DepotId]
这是在 Steam 开发者门户中看到的仓库 ID,是一个数字,例如 123456。 - [RootFolderOfTheGame … where the platform folders live]
这是将从中读取内容的根文件夹… 例如:C:\Builds\ContentBuilder\content\MyGame - [Platform]
这是内容根目录中的一个子文件夹,所以如果我输入值.\Windows\*,那么我就是在告诉它读取C:\Builds\ContentBuilder\content\MyGame\Windows中的所有文件和文件夹。
运行它
一旦你创建了三层脚本,即运行构建脚本、应用构建 VDF 和仓库构建配置 VDF,现在你就可以通过简单地运行你设置的“运行构建脚本”来上传你的构建了。
如果你正在使用构建流程,这现在可以轻松地集成到你的流程中,甚至可以通过双击 .bat 文件手动运行。
八、发布
本文汇集了关于发布游戏时需要考虑的事项和建议。在这里,“发布”不仅仅指正式发行,还包括任何让产品对公众可见的行为——无论是通过商店页面、博客、销售产品、Kickstarter 众筹活动,还是其他任何方式。只要人们可以公开讨论你的产品,它就属于“已发布”。考虑到这一点,有几个关键因素需要规划。
1、发现度与可见性
愿望单与排名
- 设定基准目标:
在你游戏距离发布还有一周时,至少拥有 7,000 个愿望单。虽然绝对数量并非全部,但愿望单直接影响你在 Steam 即将发行游戏排行榜上的位置。 - 为什么这很重要:
Steam 根据愿望单数量对即将发行的游戏进行排名。更高的排名会增加你进入“热门即将推出”列表的机会,这可以带来额外的愿望单并提升整体可见性。 - 发行商的目标:
你的发行商可能会根据你的目标市场和转化率,设定数万个愿望单的目标。7,000 是一个起点;成功的营销活动应该能将你的数字推向更高。
行动步骤
- 参与游戏节:
将你的游戏提交到所有符合条件的游戏节。避免参与那些与你的游戏类型或风格不匹配的游戏节,因为这可能会产生负面影响。 - Steam Next Fest:
选择一个在你发布日期至少一周前结束的 Next Fest,并且尽量靠近你的发布周。确保你的游戏有可玩的演示版本,因为这是参与的必要条件,并可以帮助你获得所需的愿望单。
2、商店页面
何时发布商店页面
- 时机:
一旦你确定了游戏的最终形态,就立即发布商店页面。这可以让你立即开始积累愿望单。 - 关键组成部分:
- 展示图(胶囊图)
- 屏幕截图
- 预告片(非完整预告片)
- 简短和完整的描述
- 内容分级
- 系统要求
- 功能列表
为什么这很重要
一个完整且精美的商店页面至关重要,因为:
- 它是潜在愿望单添加者的第一接触点。
- 发布后对商店页面进行重大更改可能会使用户困惑或望而却步。
- 一个强大的商店页面为所有后续营销活动奠定基础。
有关优化商店页面的详细建议,请参阅我们关于 Steam 商店页面 的专门文章。
3、即将推出 (Coming Soon)
设定发布日期
- 重要性:
商店页面上线后,设定一个具体的发布日期。一个明确的发布日期有助于 Steam 将你的游戏归类为“即将推出”,这对于出现在热门即将推出列表中至关重要。 - 提示:
确保你的发布日期在足够长的时间内(大约在发布前一周)保持固定,以积累愿望单并建立势头。
4、演示版本 (Demo)
早期建立兴趣
- 为什么需要演示版本:
演示版本通常是参与 Steam Next Fest 和许多游戏节的必要条件。即使你的游戏是免费游戏,发布前的演示版本也是激发兴趣和推动愿望单的有效方式。 - 质量很重要:
你的演示版本应展示游戏的最佳方面,以吸引潜在玩家的注意。虽然将演示版本设为游戏的某个子集很诱人,但请考虑一个独立的演示项目,一个精心构建的“垂直切片”,展示你游戏的最佳系统——从角色创建器到主要终局 BOSS——这在你仅仅将开头或特定关卡作为演示时是无法实现的。
5、抢先体验 (Early Access)
谨慎行事
- 考虑因素:
抢先体验是一把双刃剑。虽然如果执行得好,它可以建立势头,但它需要大量的人力和管理,而这可能是大多数独立开发者所不具备的。 - 建议:
除非你是一个专业的小型工作室,拥有成功管理抢先体验的成熟经验,否则请考虑替代方法,例如演示版本或 试玩测试 (Playtests)。
6、试玩测试 (Playtest)、Alpha 和 Beta
测试与社区互动
- 试玩测试 (Playtest):
用于收集早期反馈和建立社区互动,特别是如果你的游戏有独特的“亮点”。然而,对于许多游戏来说,演示版本通常是更有效的营销工具。 - Alpha / Beta:
虽然这些阶段非常适合技术测试,但用于营销目的则不太理想。作为营销工具的公开测试,请专注于试玩测试和演示版本;对于受控测试,请使用 Alpha/Beta 分支。
7、总结与关键要点
- 可见性至关重要:
在发布前实现可观的愿望单数量,以确保在 Steam 热门即将推出排行榜上获得高排名。 - 仔细规划商店页面:
尽早发布一个完整且精美的商店页面,因为它是你营销活动的基石。 - 设定固定的发布日期:
确保你的游戏在足够长的时间内被标记为“即将推出”,以建立足够的势头。 - 选择合适的预发布策略:
根据你的游戏需求和团队能力,使用演示版本、试玩测试或受控的 Alpha/Beta 测试。 - 抢先体验并不适合所有人:
在选择抢先体验路线之前,请考虑所需的挑战和资源。
这份清单旨在帮助你为游戏发布做好准备,重点关注推动可见性和玩家参与度的关键领域。有关更多详细信息,请参阅“必读资料”部分中的链接,以探索 Valve 的官方文档和行业专家的进一步见解。
九、成就
1、简介
成就是游戏中一个简单而传统的功能,可以帮助提高玩家参与度。它也可以充当一个简易的“游戏统计”功能,例如,你可以设置成就在特定里程碑解锁,然后监控游戏的全局统计数据,查看有多少比例的玩家解锁了每个成就,从而了解游戏的玩家留存情况。
有用链接2、快速入门
首先,你需要在 Steam 开发者门户上创建你的成就。https://partner.steamgames.com/
创建
登录你的 Steam 开发者门户,进入你应用的管理页面。找到“Technical Tools”部分,选择“Edit Steamworks Settings”选项。

然后,选择 Stats & Achievements > Achievements 选项并创建你的新成就。
记下你在 API 名称 (API Name) 字段中使用的值。你将在代码中使用它来处理成就。
在 Unity 中,如果你更喜欢通过对象引用来处理成就,你可以使用我们的 AchievementObject,它是一个 Unity ScriptableObject,可以像任何其他 Unity 对象一样被引用和访问。

发布
你 **必须 **在 Steam 开发者门户中发布你的更改,然后才能通过 Steam API 访问它们。在 Steam 开发者门户中,当你有待处理的更改时,你会看到屏幕顶部有一个红色横幅… 点击它并按照说明操作。

3、使用成就
首先要理解的是,对于统计数据和成就,设置它们的过程分为两步。
- 你分配 (assign) 值
- 你将更改存储 (store) 到后端
要明白,并非每个游戏都需要成就,如果你打算拥有成就,它们应该是游戏的一部分,而不是因为“为什么不呢”而随便加上的后增功能。无意义的成就可能会破坏沉浸感,或仅仅是在游戏过程中分散注意力,从而损害用户体验。
以下是一些有意义成就的想法,以及你如何将这些成就与游戏的其他方面联系起来。
存储统计数据和成就
这是将游戏过程中所做的任何更改提交到 Steam 后端的过程。也就是说,你可以在游戏过程中自由地“设置”成就和统计数据的状态,例如增加击杀敌人单位数量、玩家分数等。这些更改被写入本地缓存,而不是直接写入后端。
当游戏关闭或当你调用“存储 (Store)”时,更改将被提交到后端。这应该在游戏的关键点进行,例如在关卡结束时、玩家死亡时、击败 BOSS 时或其他合适的时机。通常不建议你随着每次更改不断调用 Store()。
考虑一下你希望弹出窗口何时显示
它会破坏沉浸感,可能会遮挡部分屏幕,并且可能导致诸如 Windows 自动 HDR 等功能引起屏幕闪烁… 通常你希望它只在玩家没有积极参与游戏时显示,例如在菜单、任务简报、“你已死亡”屏幕等情况下。
有几种方法可以存储统计数据和成就,它们都做同样的事情。它们只是你可以触发效果的不同方式,具体取决于内存中可用的对象以及你作为开发者更习惯的方式。
弹出窗口
当你解锁成就或收到其他通知时,你习惯看到的 Steam 弹出窗口并不是游戏中的代码,而是 Steam 客户端在游戏窗口之上渲染的。
玩家可以强制禁用此功能,因此不要假设它总是存在。这是用户在 Steam 客户端中的配置选择,而不是你的游戏。这不是你可以控制的。
对于成就,此弹出窗口在成就被存储时触发,而不是在设置时触发。另外,请注意,因为它是在游戏窗口上渲染的,所以在 Unity 编辑器中测试时,或者如果你有调试器或其他应用程序挂接到进程上时,它可能无法正常工作,因为这可能会限制窗口更新和/或向进程添加额外的窗口。
新玩家体验
将你的成就想象成一个元任务 (meta quest)…
“元 (Meta)”意味着超越或在…之上等,当我们使用它时,我们指的是一个概念、信息等,它不是主要内容的一部分,而是额外的、外在于它的。
就像一个元任务,这是玩家(而不是游戏世界中的角色)学习你游戏的任务。鼓励他们以结构化的方式探索你游戏的功能和玩法,并为这样做提供一些不影响游戏玩法的奖励。
关于这个的实际例子,请参见 DOTA 2 的“新玩家体验 (New Player Experience)”,又名“欢迎任务 (Welcome Quests)”,他们为新手玩家提供“任务”来完成简单的事情,如打开菜单、使用功能、查看信息、玩每种游戏模式等。重要的是,这不是对玩家隐藏的,而是像任务一样在菜单中呈现给玩家,显示哪些步骤尚未完成,哪些已经完成。
玩家教程
最好的教程是有趣且引人入胜的游戏部分,这些部分碰巧也教授玩家机制、概念、展示战术和策略等。类似于新玩家体验的方法,你可以使用成就作为一种方式来引起对这些说明的注意,并在不强迫玩家的情况下奖励他们的完成。
物品奖励
Steam 库存本身就是一个巨大的话题,需要自己的指南。与成就的重叠之处在于,你可以根据已解锁的成就来限制物品促销掉落 (Item Promotion drops) 的资格。你可以将此与新玩家体验或教程风格的成就结合使用,以奖励游戏内物品完成任务。
成就猎人
有许多类型的玩家,所有游戏类型中常见的一种是“收集者 (Collector)”或“猎人 (Hunter)” 这是一种喜欢“100% 完成”游戏的玩家类型。注意不要用无意义的成就来膨胀你的游戏,因为这只会让收集者感到沮丧,但要确保跟踪并奖励他们充分探索你的游戏。
玩家留存监控
你知道吗?你可以查看拥有成就的游戏的全局统计数据,了解有多少比例的玩家群体达成了每个成就?
现在你知道了,这意味着你可以使用成就来(粗略地)了解你游戏的哪些部分被使用了,哪些没有,被很好地使用了,并且你可以跟踪玩家在何处“流失 (fall off)”。
4、创建成就
Valve
Steam 统计数据和成就提供了一种简单的方法,让你的游戏为用户提供持久、漫游的成就和统计跟踪。用户的数据与其 Steam 账户相关联,每个用户的成就和统计数据都可以格式化并显示在其 Steam 社区资料中。
成就和统计数据一样,是在你的 Steam 开发者门户中创建的,在那里创建后,你可以通过其 ID 访问它们,如果你没有使用 Heathen 的 Steamworks… 为什么不用呢,它有免费版本。然后你可以将统计数据和成就导入 Unity,或者使用我们的 AchievementData 结构在代码中轻松处理你的成就。
Valve 关于统计数据和成就功能的文档是一个很好的入门资料。
5、示例
5.1 设置成就
设置成就是一个 2 步过程,首先你“设置”成就,即将其标记为“已解锁”。此步骤可以在游戏实时进行,甚至每帧调用多次,对性能没有不利影响,因为这根本不会调用后端,它只是在本地记录该成就应该被设置。
然后,在你的游戏的适当时机,例如在 BOSS 战后、玩家死亡时、任务结束时、过场动画前后等,你会调用“存储 (Store)”,又名“存储统计数据 (Store Stats)”。这将导致 Steam 将所有统计数据和成就的更改提交到后端。正是在此时,通知会弹出。这不应该被快速调用,例如每帧调用,它只应在你游戏的关键点调用。
无代码方式
你可以使用成就模块化组件 (Achievement modular component) 来处理游戏中的成就。

你也可以使用它从 Unity 事件或类似功能解锁成就。

C#
通过生成的封装器 (Generated Wrapper) 访问你的成就。
// 解锁成就
SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.Unlock();
5.2 读取成就
通常,当“读取”成就时,你只是检查该成就对用户是否“已达成 (achieved)”,即“已解锁 (unlocked)”,这是一个简单的布尔值。但是,你也可以读取其他信息,例如它何时解锁,你还可以获取成就的当前“图标 (icon)”,该图标将根据用户是否已达成此成就而显示锁定或解锁版本。
无代码方式
你可以使用成就模块化组件 (Achievement modular component) 来处理游戏中的成就。

使用下拉菜单选择要操作的成就。然后你可以添加字段:

设置一个或多个图标、名称或描述的 UI 元素。

C#
// 解锁成就
bool isAchieved = SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.IsAchieved;
// 获取成就名称
string displayName = SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.Name;
// 获取成就描述
string descriptionText = SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.Description;
// 获取解锁时间
DateTime? unlockTime = SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.UnlockTime;
// 获取成就的图像
SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.GetIcon(texture =>
{
// texture 是此成就的 Texture2D
});
5.3 清除成就
无代码方式
你可以使用成就模块化组件 (Achievement modular component) 来处理游戏中的成就。

你也可以使用它从 Unity 事件或类似功能清除成就。

C#
// 清除成就
SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.Clear();
5.4 存储更改
无代码方式
你可以使用成就模块化组件 (Achievement modular component) 来处理游戏中的成就。

你也可以使用它从 Unity 事件或类似功能存储成就。
这将存储所有已修改的统计数据和成就。

C#
// 存储所有已修改的统计数据和成就
SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.Store();
十、排行榜
Steam 排行榜是持久的、自动排序的表格,用于在全球范围内或朋友之间显示分数。它们可以出现在游戏中和游戏的 Steam 社区页面上。每个游戏最多可以创建 10,000 个独立的排行榜,分数在上传后可立即检索。
排行榜支持无限数量的玩家。每个条目存储一个必需的分数(作为 int),并可选择性地存储最多 64 个 int 值的详细数据。这些详细数据是一个简单的数组,用于存储特定会话的上下文,例如使用的角色、花费的时间或其他游戏统计数据。Steam 不会解析或排序这些数据,并且当提交新分数时会被覆盖。
你还可以将排行榜条目与 Steam UGC(用户生成内容)关联,从而允许你将回放、装备配置或屏幕截图与分数关联。
排行榜可以配置为仅通过受信任的服务器端 API 调用接受分数提交。强烈建议这样做以防止篡改。使用此设置时,只有有权访问 Steam Web API 和你的发布者令牌的服务器才能提交分数。如果保持非受信任状态,任何客户端都可以提交任何分数,这为滥用打开了大门。
使用 Steam Web API 设置受信任的排行榜分数
https://partner.steamgames.com/doc/webapi/ISteamLeaderboards#SetLeaderboardScore
1、快速入门
首先,你需要在 Steam 开发者门户上创建你的排行榜。https://partner.steamgames.com/
登录你的 Steam 开发者门户,进入你应用的管理页面。找到“Technical Tools”部分,选择“Edit Steamworks Settings”选项。

然后,选择 Stats & Achievements > Leaderboards 选项并创建你的新排行榜。
记下你在 API 名称 (API Name) 字段中使用的值。你将在代码中使用它来处理排行榜。\
发布
你 **必须 **在 Steam 开发者门户中发布你的更改,然后才能通过 Steam API 访问它们。在 Steam 开发者门户中,当你有待处理的更改时,你会看到屏幕顶部有一个红色横幅… 点击它并按照说明操作。

2、故障排除
上传忽略了我的值
刚开始使用时的一个常见问题是,排行榜似乎忽略了你上传的分数,或者只接受与你预期方向相反的分数。
例如,如果你上传 10,然后上传 11,它可能会忽略 11,但如果你上传 9,它会接受 9。
为什么?
Steam 排行榜被配置为按特定方向对分数进行排序,并且当你上传分数时,你通常是以“保留最佳分数 (Keep Best)”的方式进行的。
board.UploadScore(42,
ELeaderboardUploadScoreMethod.k_ELeaderboardUploadScoreMethodKeepBest,
(result, error) =>
{
if(!error)
Debug.Log("Recorded: " + result.m_nScore +
"New Rank: " + result.m_nGlobalRankNew);
});
“保留最佳分数 (Keep Best)”选项告诉 Steam,只有在你提交的新值 ** 如果 ** 优于之前记录的值时才记录新值。这是由你在 Steam 门户中为排行榜配置的“排序顺序 (sort order)”决定的。
如何修复?
假设你的排行榜配置错误,只需在 Steam 门户中更新其配置,然后发布更改。
在 Steam 门户中进行编辑时,你 必须始终 发布更改。它不会在你更改门户的瞬间就应用,这是一个基于 Perforce 的源代码控制系统,需要你发布你的更改。
如果出于某种原因,你发现你的排行榜仍然表现得像是反向排序,这很可能是由于 Steam 后端服务偶尔出现的问题。提交一个支持案例,让 Valve 知道你的排行榜似乎存在错误,没有按照应有的方式改变排序方向。
要解决此问题,请创建一个新的排行榜(使用新名称)并按你希望的排序方向设置;请不要删除损坏的排行榜…以便 Valve 可以查看它。
3、高级资料
丰富的公开用户资料对于社交游戏来说非常重要。

为了有效,资料需要在玩家名字可见的任何时候都可以访问。这包括离线的朋友、排行榜条目、队友或击败你的对手。任何看到它的用户都必须能够查看该资料,而无需成为好友或共享游戏大厅。
Steam 的丰富状态 (Rich Presence) 在某些情况下可以提供帮助。例如,你可能会在朋友的名字下看到“主菜单 (Main Menu)”。这来自丰富状态,但它只对你自己和你的朋友有效。
对于其他所有情况,数据都存储在资料对象中。以 DOTA 为例,它可能直接存储在账户上,因为 DOTA 可以访问比大多数开发者更多的 Steam 功能。也就是说,我们可以通过使用排行榜存储资料数据来接近同样的效果。
这是什么?
目标是创建一个可公开访问的玩家资料,类似于你在 DOTA 等游戏中看到的。这些资料让玩家可以展示游戏内成就、最喜欢的装备配置、顶级统计数据等——所有内容都可以被其他用户查看,即使不在活跃的游戏大厅或好友关系中。
Steam 排行榜用于此目的,因为它们是全局可读的,并且可以存储的不仅仅是分数。它们包括一个 details 数组(最多 64 个整数),并支持每个条目附加一个 UGC,两者结合可以实现丰富、灵活的数据存储。
它是如何工作的?
首先决定你希望玩家在他们的资料中显示什么数据,例如最喜欢的角色、达到的最高等级、所属公会或其他游戏特定的亮点。
这些信息应存储在可以序列化的结构化格式中。然后,将相关值打包到排行榜的 details 字段的 int[] 中。由于这仅限于 64 个整数,因此高效打包是关键。例如,布尔值可以使用位标志存储,而枚举或 ID 已经适合整数形式。
虽然 details 数组适用于紧凑、高速的查找;但更具描述性或扩展性的资料数据(如完整的装备配置或角色布局)应放在 UGC 附件中。这可以是通过 Steam 远程存储存储的文件,序列化为字节数组。
设置完成后,这些资料数据将使用标准分数提交上传到排行榜。你可以交替分数或递增它来指示新版本。上传后,UGC 文件将附加到排行榜条目,允许其他人在获取条目详细信息的同时获取该文件。
为什么两者都用?
排行榜详情 (int[]):快速、轻量,可即时访问。适用于统计数据、标志和 ID。
排行榜附件 (UGC):更灵活,适合更丰富或嵌套的数据。使用它来存储完整的资料对象,如 JSON、二进制数据或其他格式。
Steam 会保留附件,即使原始文件已从远程存储中删除,这使得关联附加数据与排行榜条目成为一种稳定的方式。
读取资料
要读取用户的资料,请使用他们的 Steam ID 或其他标识符查询排行榜条目。返回的数据将包括分数、details 以及附件(如果存在)。然后,你可以在游戏中将附件反序列化为可用的资料结构。
该系统实现了持久的、可共享的资料,这些资料在直接的 Steam 关系之外工作,并且在显示玩家名字的任何地方都可见——在排行榜、比赛历史记录或 UI 元素中。
4、示例
4.1 获取排行榜
无代码方式
在你的设置中声明你的排行榜,我们将在初始化时为你“获取”该排行榜。这也意味着排行榜将在 SteamTools.Game.Leaderboards 静态类中“生成”,使其可供其他工具访问,并更容易在代码中处理。

可以在运行时创建排行榜,如果它们尚不存在的话。

或者你可以直接在组件脚本上执行此操作,但是,如果它不在你的设置中,它将不会在你的 SteamTools.Game.Leaderboards 中可用。
C#
你不需要“获取 (Get)”在设置中定义并已生成封装器的排行榜。我们将在初始化时、触发“就绪 (Ready)”事件之前为你获取它们,这样你就可以直接访问它们。
SteamTools.Game.Leaderboards.Feet_Traveled
对于未在设置中定义的排行榜,你仍然可以在 C# 中获取或创建它们。
// 获取之前在 Steamworks 开发者门户中创建的排行榜
LeaderboardData.Get(apiName, (data, ioError) =>
{
if (!ioError)
{
targetBoard = data;
Debug.Log($"Found {apiName} with ID {targetBoard.id.m_SteamLeaderboard}");
// 此时,你已拥有该排行榜,可以对其进行操作了...
// 请参阅下面的函数,这些函数提供了操作排行榜的示例,例如读取和写入数据
}
else
{
Debug.LogError($"An IO error occurred while attempting to read {apiName}");
}
});
// 或者“获取或创建 (Get or Create)”,这意味着如果存在则获取现有排行榜,
// 如果不存在则创建新排行榜。
LeaderboardData.GetOrCreate(apiName
, ELeaderboardDisplayType.k_ELeaderboardDisplayTypeNumeric
, ELeaderboardSortMethod.k_ELeaderboardSortMethodDescending
, (data, ioError) =>
{
if (!ioError)
{
targetBoard = data;
Debug.Log($"Found {apiName} with ID {targetBoard.id.m_SteamLeaderboard}");
// 此时你已拥有该排行榜,可以对其进行操作了...
// 请参阅下面的函数,这些函数提供了操作排行榜的示例,例如读取和写入数据
}
else
{
Debug.LogError($"An IO error occurred while attempting to read {apiName}");
}
});
4.2 上传分数
无代码方式
你可以使用排行榜模块化组件 (Leaderboard modular component) 来处理游戏中的排行榜。

使用下拉菜单选择要操作的排行榜。然后你可以添加设置:

然后添加上传设置。

这些设置允许你设置分数和详情数据,并为你提供了一个易于使用的上传函数,可以选择性地用于附加一个 UGC 项目。该函数可以从 Unity 动作 (Unity Action) 或从代码中调用。

C#
你可以通过几种方式上传分数。
// 上传一个简单分数,要求 Steam 仅在分数优于当前分数时才记录。
SteamTools.Game.Leaderboards.Feet_Traveled.UploadScoreKeepBest(42);
// 做同样的事情,但带有 int[] 形式的附加详情数据。
SteamTools.Game.Leaderboards.Feet_Traveled.UploadScoreKeepBest(42, details);
// 上传一个分数,要求 Steam 无论当前分数如何都记录。
SteamTools.Game.Leaderboards.Feet_Traveled.UploadScoreForceUpdate(42);
// 做同样的事情,但带有 int[] 形式的附加详情数据。
SteamTools.Game.Leaderboards.Feet_Traveled.UploadScoreForceUpdate(42, details);
4.3 获取用户的条目
无代码方式
你可以使用排行榜模块化组件 (Leaderboard modular component) 来处理游戏中的排行榜。

使用下拉菜单选择要操作的排行榜。然后你可以添加字段:

然后添加“用户条目 (User Entries)”。

这会公开一个对排行榜条目 UI 组件的引用,并添加通用事件,这些事件将用于监视用户记录的状态变化。
Steam 排行榜条目 UI 利用了 用户 (User) 组件,并扩展它以同时显示排行榜条目的分数和排名。
你不需要设置 用户 (User) 组件的“本地用户 (Local User)”功能;传递进来的排行榜条目会携带本地用户信息。

C#
SteamTools.Game.Leaderboards.Feet_Traveled.GetUserEntry(detailEntriesCount, (foundEntry, ioError) =>
{
// 如果 ioError 为 false,则处理找到的条目
});
4.4 获取条目
无代码方式
你可以使用排行榜模块化组件 (Leaderboard modular component) 来处理游戏中的排行榜。

使用下拉菜单选择要操作的排行榜。然后你可以添加设置:

这为你提供了可用于定义显示列表的字段,它的工作原理是为每个找到的记录生成“条目模板 (Entry Template)”的实例。

条目模板是一个排行榜条目 UI 组件,它与 用户 (User) 组件配合使用,以显示相关用户和记录的数据。

你现在可以使用排行榜对象来获取记录并自动显示它们。

C#
//你有几种方便的方法来以不同方式读取记录
//在所有情况下,最后一个参数都是一个委托,该委托将在
//进程完成时被调用,并包含找到的结果(如果有的话)。
//前 X 条记录
int howMany = 42;
int detailsCount = 0;
SteamTools.Game.Leaderboards.Feet_Traveled.GetTopEntries(howMany,
detailsCount,
(entriesFound, ioError) =>
{
//如果 ioError 为 false,则处理找到的条目
});
//获取特定用户的条目
UserData[] users; //将其设置为你想读取的用户
int detailsCount = 0;
SteamTools.Game.Leaderboards.Feet_Traveled.GetEntries(users,
detailsCount,
(entriesFound, ioError) =>
{
//如果 ioError 为 false,则处理找到的条目
});
//获取用户条目周围的条目(包括用户自己的条目)
int beforeUser = -5;
int afterUser = 5;
int detailsCount = 0;
SteamTools.Game.Leaderboards.Feet_Traveled.GetEntries(ELeaderboardDataRequest.k_ELeaderboardDataRequestGlobalAroundUser,
beforeUser,
afterUser,
detailsCount,
(entriesFound, ioError) =>
{
//如果 ioError 为 false,则处理找到的条目
});
//获取所有好友的条目
int detailsCount = 0;
SteamTools.Game.Leaderboards.Feet_Traveled.GetEntries(ELeaderboardDataRequest.k_ELeaderboardDataRequestFriends,
0,
0,
detailsCount,
(entriesFound, ioError) =>
{
//如果 ioError 为 false,则处理找到的条目
});
//获取所有条目... 真的不推荐,但它确实存在
int detailsCount = 0;
SteamTools.Game.Leaderboards.Feet_Traveled.GetAllEntries(detailsCount,
(entriesFound, ioError =>
{
//如果 ioError 为 false,则处理找到的条目
});
4.5 附加文件
无代码方式
按照“上传分数 (Upload Score)”示例中的说明进行操作,并使用 Upload(T attachment) 函数同时上传分数和附加文件。你需要在 C# 中调用 Upload(T attachment)。
C#
// SteamLeaderboardUpload 组件将使用 "attachment" 作为
//临时文件名,一旦文件被附加,上传后覆盖/清除此文件是安全的。
//注意 fileData 可以是任何类型的对象,只要它是 [Serializable]
leaderboardUpload.Upload(fileData);
//注意文件需要一个名称,但这只是临时上传的文件名
//所以这将是用户远程存储中的文件名,一旦附加,我们可以删除它以节省空间。
//我们喜欢一直使用相同的名称 "tempFile",我们不会删除它,而是每次让它
//覆盖,作为为未来附件保留空间的一种方式。
SteamTools.Game.Leaderboards.Feet_Traveled.AttachUGC("tempFile", fileData, (ugcResult, ugcIoError) =>
{
if (!ugcIoError)
{
if (ugcResult.Result == Steamworks.EResult.k_EResultOK)
Debug.Log($"Attached file data to user entry on board {apiName}");
else
Debug.LogError($"Failed to attach file data to user entry on board {apiName}, Response Result = {ugcResult.Result}");
}
else
{
Debug.LogError($"Failed to attach file data to user entry on board {apiName}, IO Error!");
}
});
4.6 获取附加文件
无代码方式
不适用
C#
这假设你已经有一个想要读取其附件的 LeaderboardEntry,并且它假设你正在读取的数据是从名为“SomeObject”的可序列化对象序列化而来的。
// 假设
LeaderboardEntry entry;
entry.GetAttachedUgc<ExampleSerializableDataForAttachment>((dataFound, ioError) =>
{
if(!ioError)
{
//Data Found 是你的附件(如果有的话)。由你来检查这是默认值还是实际设置的数据
//如何做到这一点取决于它是哪种对象... 对于结构体,你应该始终实现 IEquitable、IComparable 并重载 == 和 != 操作符
}
});
十一、游戏大厅
Steam 游戏大厅经常被误解为多人游戏或网络连接功能。实际上,它们是带有元数据的聊天室,以至于 Valve 内部将其称为“聊天室 (chats)”而不是“游戏大厅 (lobbies)”。
核心来说,Steam 游戏大厅提供了一种将玩家分组并将元数据与组(游戏大厅)及其成员关联的方法。 这些元数据使得大厅搜索、匹配和特定于玩家的状态共享成为可能,所有这些都无需玩家之间建立直接的网络连接。
Steam 的匹配系统就是建立在这个游戏大厅结构之上的。一个游戏大厅充当一个共享空间,其中关于会话的数据(例如游戏模式、地图、状态)以键值对的形式存储。每个玩家也存储个人元数据(例如装备配置、准备状态),这些数据仅对同一游戏大厅的其他成员可见。
总结一下:
- 游戏大厅元数据 (Lobby metadata):传达整个会话的信息。任何能够看到或搜索到该游戏大厅的人都可以公开访问。
- 成员元数据 (Member metadata):传达每个玩家的信息。仅对同一游戏大厅内的其他成员可访问。
如果你要实现基于队伍的玩法、会话寻找或类似功能,Steam 游戏大厅很可能就是你需要的工具。
有关更多技术细节,请参阅 Valve 的 Steamworks 匹配文档。
1、什么是游戏大厅?
最重要的一点是:Steam 游戏大厅不是网络系统。
- 你可以在没有活动网络会话的情况下加入和使用游戏大厅。
- 你可以在完全不使用游戏大厅的情况下建立网络连接。
- 这两个系统是完全独立的。
可以把 Steam 游戏大厅看作是一个带有元数据的结构化聊天室。它帮助玩家在启动实际的多人游戏会话之前聚集在一起并交换轻量级信息,例如会话意图、玩家装备配置或状态。
Steam 游戏大厅通常用作组建队伍的第一步,随后作为匹配数据的容器。但游戏大厅本身并不促进多人游戏。这是你的网络层的任务。
你的用户可以成为多个游戏大厅的成员。Steam 允许用户成为 1 个(普通)游戏大厅和最多 2 个(不可见)游戏大厅的成员。一个游戏通常会为一个玩家管理至少 2 个游戏大厅。
游戏大厅类型
| 类型 | Steam 类 | 可见性 | 加入方式 | 常见用例 |
|---|---|---|---|---|
| 私密 (Private) | 普通 (Normal) | 对朋友和搜索隐藏 | 仅通过直接邀请 | 与特定玩家的封闭会话(例如,仅限邀请的合作游戏)。 |
| 仅限好友 (Friends Only) | 普通 (Normal) | 仅对好友可见 | 由好友或通过邀请 | 好友的临时加入游戏,不对外公开。 |
| 公开 (Public) | 普通 (Normal) | 通过搜索和朋友列表对所有用户可见 | 开放或邀请 | 公开匹配会话。 |
| 不可见 (Invisible) | 不可见 (Invisible) | 可搜索,但对朋友列表隐藏 | 开放或邀请 | 可以搜索但不会显示为“正在与朋友玩游戏”的隐藏游戏大厅。适用于不希望暴露在好友列表中的匹配场景。 |
会话游戏大厅 (Session Lobby)

这只是以特定方式使用的常规 Steam 游戏大厅。
会话游戏大厅 (Session Lobby) 是用于进行匹配的游戏大厅的术语,通常用于准备游戏会话。在像 DOTA、Halo 或类似游戏中,这是点击“开始游戏 (Play)”后进入的游戏大厅。它是玩家与他人随机或基于特定条件匹配的空间,并作为进入游戏会话的入口。
在会话游戏大厅中,玩家通常为匹配目的而分组。游戏大厅使随机或预设的玩家能够找到彼此并组织会话,例如在实际游戏开始前确定游戏模式、地图或其他偏好。这就是匹配过程发生的地方,促进了可能互不相识但希望一起玩的玩家之间的连接。
会话游戏大厅的主要功能包括:
- 匹配 (Matchmaking): 寻找并加入具有相似条件或技能水平的玩家。
- 会话准备: 玩家可以查看游戏设置,例如地图和模式,并在进入比赛前确认。
- 随机或有组织的分组: 玩家可以随机匹配,也可以作为更受控、结构化的系统的一部分,具体取决于游戏的匹配规则。
会话游戏大厅对于实现玩家事先互不相识的多人游戏至关重要,为匹配和游戏会话准备提供了基础设施。
队伍游戏大厅 (Party Lobby)

这只是以特定方式使用的常规 Steam 游戏大厅。
队伍游戏大厅 (Party Lobby) 是一个常用术语,用于描述朋友或玩家在进入会话前可以聚集的游戏大厅。它作为一种方式,让玩家协调和分享关于要一起加入哪个会话游戏大厅的信息,但它不用于直接创建或托管游戏。
在像 DOTA 或 Halo 这样的游戏中,队伍游戏大厅的功能类似于“火力小组 (Fire Team)”或“派对 (Party)”。队伍游戏大厅中的玩家不直接发起游戏。相反,他们使用游戏大厅聚集、共享信息并协调他们要加入哪个游戏会话。一旦队伍决定了会话,他们就会过渡到适当的会话游戏大厅进行实际游戏。
队伍游戏大厅通常用于:
- 组织朋友或队友一起玩。
- 分享他们打算加入的会话详情,例如游戏大厅 ID 或游戏服务器 ID 等。
- 确保所有队伍成员在加入会话前达成一致。
虽然队伍游戏大厅不促进游戏的创建,但它对于想要管理匹配的社交方面并确保平稳过渡到游戏体验的游戏来说是必不可少的。
通用游戏大厅 (General Lobby)
这只是一个既不是“会话 (Session)”游戏大厅也不是“队伍 (Party)”游戏大厅的游戏大厅。Steam 游戏大厅可以用于任何数量的用途;它本质上只是一个带有元数据的聊天室。
成员 (Members)
游戏大厅中的每个用户都表示为一个游戏大厅成员 (Lobby Member)。每个成员都可以为自己附加一组元数据,即键值对,对同一游戏大厅的其他成员可见。此元数据只能由所属成员自己设置。
- 你可以读取每个其他成员的元数据。
- 你只能写入自己的元数据。
此数据在游戏大厅外不可见。如果你不是成员,则无法访问成员元数据。它通常用于传达玩家装备配置、准备状态或会话内的角色选择等信息。
元数据 (Metadata)
游戏大厅元数据 (Lobby metadata) 和 成员元数据 (member metadata) 是 Steam 游戏大厅用于匹配和会话设置的基础。
所有元数据都以键值对的形式存储,键和值都是字符串:
Dictionary<string, string>
游戏大厅元数据 (Lobby Metadata)
- 由游戏大厅所有者(房主) 设置。
- 任何能看到游戏大厅的人都可以读取,包括搜索结果。
- 用于描述会话:地图名称、游戏模式、规则等。
- 被匹配系统用于过滤和排序搜索结果。
成员元数据 (Member Metadata)
- 由单个成员设置。
- 仅对同一游戏大厅的其他成员可读。
- 用于描述玩家特定信息:装备配置、准备状态、队伍选择等。
聊天 (Chat)
尽管在面向公众的 Steam API 中被称为游戏大厅,但在 Valve 的后端,它被称为聊天室——因为这就是它的底层本质。
核心概念
- Steam 游戏大厅是一个聊天室,具有内置的元数据和结构化的成员关系。
- 你可以在没有网络连接的情况下在游戏大厅成员之间发送和接收消息。
- 这个系统独立于你游戏的网络连接——它是 Steam 到 Steam 的通信。
消息格式
游戏大厅聊天消息以原始 byte[] 数据形式发送。由你的游戏定义这些数据代表什么——例如纯文本、JSON、二进制指令等。
- 消息仅发送给游戏大厅的当前成员。
- 没有内置的顺序、送达或可靠性保证——将其视为轻量级消息传递。
- 非常适合会话协调,如准备检查、地图投票或开始比赛前的状态更新。
用例
- 在 UI 中显示游戏大厅聊天文本。
- 交换准备/签到状态。
- 同步小的赛前数据(例如角色选择)。
要结构化处理此功能,请考虑在你的游戏中实现一个游戏大厅聊天指导 (Lobby Chat Director) 系统来路由和解释消息。
2、示例
2.1 所属游戏大厅 (Member of)
你经常需要获取玩家当前所在的通用、会话或队伍游戏大厅。我们提供了方便的工具来执行此操作。
无代码方式
你可以使用游戏大厅模块化组件 (Lobby modular component) 来处理游戏中的游戏大厅。

加载 (Load)
加载字段允许你自动将玩家所属的游戏大厅加载到此组件中。
- 任意 (Any)
简单地获取玩家所属的第一个游戏大厅,无论其类型。 - 通用 (General)
获取玩家所属的第一个“通用 (general)”类型游戏大厅。 - 会话 (Session)
获取玩家所属的第一个“会话 (session)”类型游戏大厅。 - 队伍 (Party)
获取玩家所属的第一个“队伍 (party)”类型游戏大厅。
C#
// 你可以遍历玩家所属的所有游戏大厅
foreach (var lobby in LobbyData.MemberOfLobbies)
{
}
// 我们还为会话游戏大厅创建了快捷方式
if(LobbyData.SessionLobby(out var lobby))
{
}
// 以及为队伍游戏大厅
if (LobbyData.PartyLobby(out var lobby))
{
}
2.2 创建
无代码方式
你可以使用游戏大厅模块化组件 (Lobby modular component) 来处理游戏中的游戏大厅。

然后你可以添加设置:

创建设置可以按队伍逻辑 (Party Wise) 进行配置,并配置为通用、会话或队伍类型游戏大厅。

队伍逻辑 (Party Wise)
如果为 true,系统将智能地更新队伍和会话游戏大厅。例如,如果你在队伍中并尝试创建会话,系统将首先检查你是否是队伍领导者;如果是,它将创建游戏大厅,然后通知队伍成员该游戏大厅,以便你的队伍成员可以加入。
使用提示 (Usage Hint)
这告诉我们的系统你打算如何使用此游戏大厅:是“通用 (General)”(意味着队伍逻辑不适用),还是会话或队伍类型游戏大厅(其中队伍逻辑将适用)。
席位 (Slots)
这是游戏大厅可以容纳的最大人数(包括你自己)。
类型 (Type)
Steam 游戏大厅类型
- 私密 (Private)
玩家只能通过邀请加入。注意,如果你在队伍中并且这是一个会话游戏大厅,系统将在创建时邀请每个队伍成员,但不会在队伍游戏大厅上设置 z_heathenSessionLobby 元数据。 - 仅限好友 (Friends Only)
只有好友将被允许查询此游戏大厅;非好友仍然可以直接被邀请。 - 公开 (Public)
此游戏大厅将出现在游戏大厅搜索中,并且通常可以被任何人加入,假设有可用席位且没有 Steam 社交屏蔽。 - 不可见 (Invisible)
此游戏大厅可供任何人加入,但不会出现在搜索中;这通常仅用于队伍用途的游戏大厅。
C#
// 通用创建函数,传入枚举器来设置类型
ELobbyType type; //= 设置你想要的游戏大厅类型
int slots; //= 设置此游戏大厅可以容纳的成员数量
LobbyData.Create(type, slots, HandleLobbyCreate);
// 假设你想创建一个不可见的游戏大厅用作队伍游戏大厅
LobbyData.CreateParty(slots, HandleLobbyCreate)
// 假设你想要一个公开类型的游戏大厅用作会话游戏大厅
LobbyData.CreatePublicSession(slots, HandleLobbyCreate)
// 假设你想要一个私密类型的游戏大厅用作会话游戏大厅
LobbyData.CreatePrivateSession(slots, HandleLobbyCreate)
// 假设你想要一个仅限好友类型的游戏大厅用作会话游戏大厅
LobbyData.CreateFriendOnlySession(slots, HandleLobbyCreate)
所有选项都将一个函数作为最后一个参数,在进程完成时被调用。该函数的形式如下。
void HandleLobbyCreate(EResult result, LobbyData lobby, bool ioError)
{
// 创建完成时调用此函数
}
2.3 搜索
无代码方式
使用游戏大厅搜索组件 (Lobby Search component) 来搜索游戏大厅并将找到的结果显示在你的 UI 中。

队伍逻辑 (Party Wise)
队伍逻辑标志 (Party-wise flag) 通知系统在搜索和加入游戏大厅时尊重队伍逻辑。例如,如果你是队伍领导者,系统将自动搜索有足够空位容纳你和你的队伍的游戏大厅。
席位 (Slots)
所需的空位数量,如果值为 0,则将其从搜索中排除,任何找到的非满员游戏大厅都将被返回。如果你在队伍中并使用队伍逻辑,则在搜索时这将设置为你的游戏大厅中的人数。
距离 (Distance)
使用 Steam 距离过滤器选项,我们应该在多大范围内搜索游戏大厅?
- 近 (Close)
仅限与玩家同一区域的游戏大厅。 - 默认 (Default)
允许同一区域或相邻区域的游戏大厅。 - 远 (Far)
允许较远的游戏大厅,但非“全球”。 - 全球 (Worldwide)
我们不会根据距离进行过滤,会考虑所有可用的游戏大厅。
近似值 (Near Values)

搜索时用于对结果进行排序。这将检查游戏大厅的元数据中是否存在指定的键,如果找到,它将尝试将值与在此处输入的值进行比较。为了获得最佳效果,这应该是一个数值。例如,你可以搜索所有 matchRank 接近 10 的游戏大厅。结果将是 matchRank 为 9 和 11 的游戏大厅将出现在结果列表的较高位置,而 matchRank 为 5 或 15 的游戏大厅位置较低。
数值过滤器 (Numeric Filters)
与近似值类似,但这将用于完全从列表中包含/排除结果。

字符串过滤器 (String Filters)
根据字符串值元数据包含或排除记录,例如地图名称是否匹配。

最大结果数 (Max Results)
Steam 永远不会返回超过 50 个条目。
Steam 游戏大厅不是游戏服务器,Steam不能用于列出每个可用的游戏大厅。
Steam 游戏大厅是一个匹配系统,最适合根据指定的过滤器返回 1 个玩家应该加入的最佳游戏大厅。
搜索结果总是按优先级过滤,因此最接近、最旧、最满且满足自定义过滤器的游戏大厅将首先列出。
最多应返回多少个条目。
模板 (Template)
为每个找到的游戏大厅生成的对象。它使用游戏大厅组件来显示结果。

内容 (Content)
生成条目时的父对象。
C#
public void SearchForLobbies()
{
// 首先,定义你的搜索参数
SearchArguments args = new();
args.distance = ELobbyDistanceFilter.k_ELobbyDistanceFilterDefault;
args.stringFilters.Add(new() { key = "SomeKey", value = "SomeValue", comparison = ELobbyComparison.k_ELobbyComparisonEqual });
// 接下来使用它们... 在这种情况下,我们使用表达式
LobbyData.Request(args, 1, (Lobbies, IOError) =>
{
// Lobbies 是一个找到的游戏大厅数组
// IOError 表示是否存在错误
});
// 或者你可以使用命名函数
LobbyData.Request(args, 1, HandleResults);
}
private void HandleResults(LobbyData[] Lobbies, bool IOError)
{
// Lobbies 是一个找到的游戏大厅数组
// IOError 表示是否存在错误
}
2.4 加入
无代码方式
你可以使用游戏大厅模块化组件 (Lobby modular component) 来处理游戏中的游戏大厅。

然后你可以添加设置:

添加“加入 (Join)”后,你现在可以轻松加入目标游戏大厅,并且可以选择让操作为“队伍逻辑 (Party Wise)”。

添加加入功能后,你可以通过其 ID 加入游戏大厅,要么通过输入你想要加入的代码(在开发测试中很有用)。

或者通过从输入字段中读取。

你还可以将游戏大厅组件连接到游戏大厅列表条目,以便在特定组件中加入和更新给定的游戏大厅。

这在演示场景中进行了演示,其中游戏大厅搜索中的每个游戏大厅条目都有一个“加入按钮 (join Button)”,点击时它会调用 Steam 游戏大厅 UI 的“请求加入 (Request join)”,以便用户加入列出的游戏大厅,并在加入时更新该游戏大厅的 UI。
C#
public void JoinLobby(LobbyData lobby)
{
// 你可以简单地调用 Join 并以内联表达式 (expression) 形式提供回调
lobby.Join((Result, IOError) =>
{
// Result.Lobby 是加入的游戏大厅(如果有)
// Result.Response 是响应消息(如果有)
// Result.Locked 表示此游戏大厅是否已锁定?
});
// 或者传入一个命名函数
lobby.Join(HandleJoined);
}
private void HandleJoined(LobbyEnter Result, bool IOError)
{
// Result.Lobby 是加入的游戏大厅(如果有)
// Result.Response 是响应消息(如果有)
// Result.Locked 表示此游戏大厅是否已锁定?
}
2.5 离开
无代码方式
你可以使用游戏大厅模块化组件 (Lobby modular component) 来处理游戏中的游戏大厅。

然后你可以添加设置:

离开功能不会暴露任何新的检查器字段,但提供了易于使用的功能,因此你可以从游戏大厅调用离开操作。
离开功能不暴露任何新的检查器字段,但提供了易于使用的功能,因此你可以从诸如按钮点击的 Unity 事件中访问离开调用。

C#
public void LeaveLobby(LobbyData lobby)
{
lobby.Leave();
}
2.6 邀请加入游戏大厅
邀请好友加入特定的游戏大厅。重要的是要理解,当邀请好友时,他们可能不会立即接受。你可以邀请当前没有玩游戏甚至可能不拥有该游戏的好友。有关如何处理接受用户可能面临的所有用例的详细信息,请参阅接受游戏大厅邀请。
无代码方式
你可以使用游戏大厅模块化组件 (Lobby modular component) 来处理游戏中的游戏大厅。

然后你可以添加设置:

与离开一样,没有新的检查器字段,但你现在可以从 Unity 动作(例如按钮点击)访问多个邀请选项。
从字符串 (From String)
通过传入十六进制 ID 邀请用户,这在开发测试中很方便。

从输入 (From Input)
或者你可以从输入字段读取该 ID,以便玩家可以输入好友的 Steam 十六进制 ID 来直接邀请他们。

从用户 (From User)
当然,你也可以邀请给定的用户 (User) 组件,例如在你好友列表组件 (friend’s list component) 中列出的用户。

C#
public void InviteToLobby(UserData User, LobbyData Lobby)
{
// 从用户出发
User.InviteToLobby(Lobby);
// 或者,从游戏大厅出发
Lobby.InviteUserToLobby(User);
}
或者,你可以打开 Steam 覆盖层到游戏大厅邀请对话框,并从那里选择要邀请的用户。
Overlay.Client.ActivateInviteDialog(Lobby);
2.7 接受游戏大厅邀请
当向用户发送游戏大厅邀请时,会发生一些事情:
- 用户将在 Steam 好友聊天中收到一个带有接受按钮的通知,他们可以点击。
- 如果用户正在游戏中,他们将收到“游戏大厅邀请 (Lobby Invite)”事件。
如果用户从 Steam 好友聊天中点击“接受 (Accept)”按钮,可能会发生几种不同的事情。
要理解,当用户点击“接受”时,该用户并未加入游戏大厅。它只是通知游戏用户已接受邀请。然后由游戏逻辑来决定是否加入所述游戏大厅。
- 如果用户当前没有玩游戏,Steam 将通过命令行启动游戏,并附带游戏大厅 ID。
- 如果用户当前正在玩游戏,则将引发“游戏大厅加入请求 (Game Lobby Join Requested)”事件。
无代码方式
你可以使用游戏大厅模块化组件 (Lobby modular component) 来处理游戏中的游戏大厅。

然后你可以添加设置:
通用事件 (General Events)
推荐的方法是添加一个通用事件设置 (General Events setting),并使用游戏大厅邀请收到 (Lobby Invite Received) 或游戏大厅加入请求 (Lobby Join Requested) 事件。

- 游戏大厅邀请收到 (Lobby Invite Received)
当发送邀请时引发此事件,你可以处理此事件以显示一个 UI 元素,玩家可以在其中查看谁发送了邀请,并选择性地接受邀请。 - 游戏大厅加入请求 (Lobby Join Requested)
当在 Steam 好友聊天 UI 中点击“接受 (Accept)”按钮时引发此事件。Steam 将在用户的聊天中显示游戏大厅邀请以及一个接受按钮。请注意,此按钮可能在邀请发送很久之后才被按下,因此你应该准备好处理邀请或缺失的游戏大厅。
收到邀请时加入 (Join On Invite)
这可用于在收到邀请时自动加入。通常这不是一个好做法,但在某些情况下很有用。

添加“收到邀请时加入 (Join On Invite)”设置后,也会添加“加入 (Join)”设置,这将允许你配置系统应如何响应游戏大厅邀请。

队伍逻辑 (Party Wise)
这会使系统在接受邀请时遵循队伍逻辑,并由加入 (Join) 设置驱动。
模式 (Mode)
Steam 游戏大厅邀请是一个 2 步过程,我们的系统可以在任一步骤加入。
- 随初始邀请 (With Inital Invite)
当你首次发送邀请后,在玩家在好友聊天 UI 中看到之前,Steam 会通知 Steamworks SDK。我们可以响应此邀请并立即加入。通常不推荐这样做,但在某些情况下可能有用。 - 在好友聊天中接受后 (After Accept In Friend Chat)
Steam 将在接收用户的好友聊天中显示邀请以及一个“接受 (Accept)”按钮。当点击该按钮时,我们可以响应。
过滤器 (Filter)
这将忽略邀请,如果用户当前是队伍、会话或任何游戏大厅的成员,具体取决于配置。
预处理 (Preprocess)
这将在加入目标游戏大厅之前发生,可用于首先离开任何现有的游戏大厅、会话游戏大厅或队伍游戏大厅。
C#
如果你想完全在游戏内处理邀请过程,那么你需要对游戏大厅邀请事件做出反应。
Matchmaking.Client.EventLobbyInvite.AddListener(HandleLobbyInvite);
此事件的处理程序形式如下:
private void HandleLobbyInvite(LobbyInvite Response)
{
// Response.ForGame 邀请针对的游戏
// Response.FromUser 邀请你的用户
// Response.ToLobby 你被邀请加入的游戏大厅
}
注册一个处理程序来监听游戏大厅加入请求事件。当用户收到加入游戏大厅的邀请后,在 Steam 好友聊天中点击“接受 (Accept)”按钮时,会引发此事件。
Overlay.Client.EventGameLobbyJoinRequested.AddListener(HandleLobbyJoinRequest);
此事件的处理程序将如下所示:
private void HandleLobbyJoinRequest(LobbyData Lobby, UserData User)
{
// Lobby 是你被邀请加入的游戏大厅
// User 是邀请你的用户
}
2.8 检测游戏启动时的游戏大厅
当用户接受游戏大厅邀请但当时游戏未运行时,Steam 将启动游戏,并通过命令行传递游戏大厅 ID。
无代码方式
你可以使用游戏大厅模块化组件 (Lobby modular component) 来处理游戏中的游戏大厅。

然后你可以添加设置:

命令行选项 (Command Line option) 将测试来自命令行的启动参数,并在满足规则时调用游戏大厅加入请求事件。你可以使用它来调用加入 (Join) 或执行类似操作。

C#
LobbyData targetLobby = Matchmaking.Client.GetCommandLineConnectLobby();
if(targetLobby.IsValid)
{
// 在命令行上找到了游戏大厅。你应该在游戏处于适当状态时加入它。
}
2.9 检测加入/离开
无代码方式
你可以使用游戏大厅模块化组件 (Lobby modular component) 来处理游戏中的游戏大厅。

然后你可以添加设置:
使用通用事件 (General Events) 以及用户离开 (On User Left) 和用户加入 (On User Joined) 事件来了解其他用户的进出情况。

C#
Matchmaking.Client.EventLobbyChatUpdate.AddListener(HandleChatUpdate);
处理程序的形式如下:
private void HandleChatUpdate(LobbyChatUpdate_t callback)
{
UserData who = callback.m_ulSteamIDUserChanged;
LobbyData whatLobby = callback.m_ulSteamIDLobby;
if((EChatMemberStateChange)callback.m_rgfChatMemberStateChange == EChatMemberStateChange.k_EChatMemberStateChangeLeft)
{
// 用户离开
}
else if ((EChatMemberStateChange)callback.m_rgfChatMemberStateChange == EChatMemberStateChange.k_EChatMemberStateChangeEntered)
{
// 用户加入
}
else if ((EChatMemberStateChange)callback.m_rgfChatMemberStateChange == EChatMemberStateChange.k_EChatMemberStateChangeDisconnected)
{
// 用户失去连接
}
}
2.10 元数据
无代码方式
你可以使用游戏大厅模块化组件 (Lobby modular component) 来处理游戏中的游戏大厅。
然后你可以添加设置:

添加元数据设置 (Metadata settings),这将允许你定义可以通过调用 Set 函数设置的元数据。你可以从按钮按下或游戏大厅上的通用事件(例如创建时)调用此函数。

更改时 (On Changed) 部分允许你跟踪特定元数据键的更改,每当指示的键的数据发生更改时,这些事件将被调用。
C#
public void MetadataUse(LobbyData Lobby)
{
// 让我们将一个简单字段设置为简单值
Lobby["a simple field"] = "a simple value";
// 好的,这很容易,现在让我们在我们的成员上设置它
LobbyMemberData Me = Lobby.Me;
Me["a simple field"] = "a simple value";
// 让我们读取所有者的元数据
LobbyMemberData Owner = Lobby.Owner;
Debug.Log($"Owner's field = {Owner["a simple field"]}");
}
2.11 成员
无代码方式
你可以使用游戏大厅模块化组件 (Lobby modular component) 来处理游戏中的游戏大厅。

然后你可以添加字段:

当你添加成员字段时。

你可以访问成员设置 (Members Settings)。
显示自身 (Show Self)
是否应显示本地用户(即玩家),还是系统应跳过他们只显示其他用户?
模板 (Template)
这是将为游戏大厅中的每个成员生成的对象。

它应该实现 Steam 游戏大厅成员数据组件 (Steam Lobby Member Data component),该组件将为你添加一个用户 (User) 组件。
内容 (Content)
这仅仅是记录生成时将放置在其下的游戏对象,通常是一个 uGUI 布局组件。
C#
public void LoopingLobbyMembers(LobbyData Lobby)
{
foreach(LobbyMemberData Member in Lobby.Members)
{
string MemberName = Member.user.Name;
Member.user.LoadAvatar(AvatarTexture =>
{
// AvatarTexture 现在是 Texture2D,请好好利用它
});
string ReadMetadataValues = Member["SomeKey"];
if(Member.IsReady)
{
// 成员已准备就绪!
}
else
{
// 成员未准备就绪!
}
}
}
2.12 通知准备连接
Steam 游戏大厅有一个称为“游戏服务器 (Game Server)”的功能,用于在游戏会话准备就绪时通知游戏大厅成员进行连接。
是的,即使你正在使用监听服务器 (Listen Server)(P2P)网络,你也会使用游戏服务器。
游戏服务器信息只是谁或什么正在提供游戏,可以是专用服务器或玩家。
无代码方式
你可以使用游戏大厅模块化组件 (Lobby modular component) 来处理游戏中的游戏大厅。

然后你可以添加字段:
添加游戏服务器设置 (Game Server setting);这不会添加任何检查器字段,但会提供易于使用的功能,以便你可以在游戏大厅上设置游戏服务器。
你可以使用“设置监听服务器 (Set Listen Server)”功能来指示本地用户/玩家是连接点。
监听服务器 (Listen Server) 是所谓的“主机 (Host)”的恰当术语,即它是一个正在充当或“监听”游戏服务器的玩家。

C#
// 将游戏服务器设置为游戏大厅的所有者
Lobby.SetGameServer();
// 让我们将游戏服务器设置为特定的 ID
CSteamID fakeServerID =new();
Lobby.SetGameServer(fakeServerID);
// 现在让我们通过 IP:端口 设置服务器
Lobby.SetGameServer("0.0.0.0", 7777);
// 最后,也许我们努力尝试,使用所有信息
Lobby.SetGameServer("0.0.0.0", 7777, fakeServerID);
然后我们可以检查游戏服务器。
// 你可以读取当前的游戏服务器
if(Lobby.HasServer)
{
LobbyGameServer server = Lobby.GameServer;
CSteamID serverId = server.id;
string ipAddress = server.IpAddress;
ushort port = server.port;
}
你也可以并且应该使用事件系统,例如,你可以监听你所属的任何游戏大厅上设置的游戏服务器。
// 如何知道何时完成此操作?
Matchmaking.Client.EventLobbyGameCreated.AddListener(HandleGameServerSet);
此事件的处理程序将如下所示:
private void HandleGameServerSet(LobbyGameCreated_t callback)
{
// 检查此信息与你的游戏大厅,以确定是哪个游戏大厅需要
LobbyData theLobbyThatWasSet = callback.m_ulSteamIDLobby;
// 你可以读取当前的游戏服务器
if (theLobbyThatWasSet.HasServer)
{
LobbyGameServer server = theLobbyThatWasSet.GameServer;
CSteamID serverId = server.id;
string ipAddress = server.IpAddress;
ushort port = server.port;
}
}
2.13 聊天(发送/接收)
无代码方式
你可以使用游戏大厅模块化组件 (Lobby modular component) 来处理游戏中的游戏大厅。
然后你可以添加设置:

聊天 UI 设置 (Chat UI settings) 可帮助你为游戏大厅设置聊天 UI。
最大消息数 (Max Messages)
这是滚动视图中同时显示的消息数量。当收到第 201 条消息时,第一条消息将被销毁。
聊天面板 (Chat Panel)
这应该是整个聊天的父对象。当有游戏大厅设置时,系统将启用它,当没有时则禁用它。
输入字段 (Input Field)
这将受到监视,当按下 Enter 或 Return 键时,如果有聊天消息,将发送。你可以选择调用 SendMessage() 函数,例如从按钮的点击事件,以触发发送文本。
滚动视图 (Scroll View)
这将被管理,以便在添加新消息时滚动到底部。
消息根 (Message Root)
这是新消息将生成的位置。你应该应用垂直布局控件或类似组件。
我的聊天模板 (My Chat Template)

这将是玩家发送的每条消息生成的模板。它应该实现 Steam 游戏大厅成员聊天消息组件 (Steam Lobby Member Chat Message component)。
他们的聊天模板 (Their Chat Template)

这将是为游戏大厅成员(玩家除外)发送的每条消息生成的模板。它应该实现 Steam 游戏大厅成员聊天消息组件 (Steam Lobby Member Chat Message component)。
C#
// 首先,你需要知道要发送到的游戏大厅
LobbyData Lobby = 123456798; // 代表一个真实的游戏大厅 ID
// 然后你需要决定发送什么
// 这可以是一个字符串消息
Lobby.SendChatMessage("Hello World");
// 或者
// 它可以是可序列化的结构体或类
Lobby.SendChatMessage(SomeObjectIHave);
// 为了“监听”聊天消息,你应该使用
Matchmaking.Client.EventLobbyChatMsg.AddListener(HandleChatMessage);
处理程序的形式如下:
private void HandleChatMessage(LobbyChatMsg ChatMsg)
{
// ChatMsg.Message:如果发送的是字符串,则为字符串消息
// ChatMsg.FromJson<YourDataType>():如果发送的是对象,则获取该对象
// ChatMsg.sender:谁发送的消息
// ChatMsg.lobby:消息发送到哪个游戏大厅
// ChatMsg.receivedTime:你首次看到消息的时间
// ChatMsg.type:消息的 EChatEntryType,应始终为 EChatEntryType.k_EChatEntryTypeChatMsg
}
专栏推荐
完结
好了,我是向宇,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!
这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!
更多推荐



所有评论(0)