Steam Unity SDK 对接文档
面向 Unity 游戏研发:接入 Steam SDK,完成登录、内购、事件上报、用户信息等能力。
版本
当前版本及SDK下载
客户端 Unity
最新版本下载: 1.0.2unitypackage
1. 接入前确认
1.1 必需信息
| 项 | 游戏侧用途 | 说明 | 提供者 |
|---|---|---|---|
| SDK对接包 | 游戏方接入时需要,下载后导入游戏项目 | 见【当前版本及SDK下载】 | SDK研发,见说明 |
| 正式 API 地址 | 游戏方接入时需要,SDK正式接口地址 | https://api-steam-sdk.miaorui1.com/api/v1 | SDK研发,见说明 |
| Steam AppId | 游戏方接入时需要,游戏在Steam后台的ID | 类似于 5052040 数字 | Steam 后台 |
| Web API密钥 | SDK技术需要,服务端校验需配置 | 在【用户与权限-管理组-对应游戏用户组】右侧创建 | Steam 后台 |
| 内购 product_id 列表 | SDK接入时需要,购买时需传入 | 内购商品字段:商品id(product_id)、商品名、商品描述、人民币价格(分) | 游戏方 |
| 发货回调 URL | SDK接入时需要,服务端支付发货接口 | 游戏研发按文档接入后并提供 | 游戏方 |
| 发货密钥 DELIVERY_SECRET | SDK接入时需要,服务端支付发货密钥 | 游戏研发按文档接入后并提供 | SDK研发 |
2. Unity工程操作
2.1 导入 SDK
| 步骤 | 操作 | 作用 |
|---|---|---|
| 1 | 在游戏工程 Assets → Import Package → Custom Package 导入MJGameSDK_v1.x.x.unitypackage | 将 SDK 代码、Steamworks.NET、Editor 脚本导入游戏 |
SDK 已内置 Steamworks.NET(v20.2.0),一般无需再单独下载。
2.2 创建并填写 MJGameConfig
| 步骤 | 操作 | 作用 |
|---|---|---|
| 1 | 菜单 MJGameSDK > Create Default Config | 生成 Assets/MJGameSDK/Resources/MJGameConfig.asset |
| 2 | 在 Inspector 填写字段(见下表) | MJGameConfig.LoadDefault() 从 Resources 加载,决定连哪套环境、是否 Mock |
| 字段 | 含义 | 正式 |
|---|---|---|
| appId | Steam AppId,同时作为 X-Steam-App-Id | 正式 Steam AppId |
| useMockSteam | true 时不调真 Steam,用 Mock ticket | false |
| serverBaseUrl | SDK 服务端 API 根地址(须含 /api/v1) | 正式 API 地址 |
| requestTimeout | HTTP 超时(秒) | 30 |
| privacyPolicyUrl | 目前用不到,可不动 | 正式 URL |
| logLevel | Console / 文件日志级别 | Info / Debug |
| enableFileLog | 是否写入本地 mjgame_sdk.log | 建议 true |
2.3 配置 steam_appid.txt
| 步骤 | 操作 | 作用 |
|---|---|---|
| 1 | 确认 Assets/StreamingAssets/steam_appid.txt 内容为一行 Steam AppId | 编辑器 / 本地调试时 Steamworks 识别 App |
| 2 | 构建后 exe 同目录会自动复制该文件(MJGameCopySteamAppId) | 本地双击 exe 调试可用;Steam 库启动的正式包通常不依赖此文件 |
2.4 设置编译符号
| 步骤 | 操作 | 作用 |
|---|---|---|
| 1 | 导入含 Steamworks.NET 的工程后,Editor 会自动添加符号;也可手动 MJGameSDK > Setup Steamworks.NET Defines | 定义 STEAMWORKS_NET + STEAMWORKS_WIN(Win)或 STEAMWORKS_LIN_OSX(Mac 编辑器) |
| 2 | 若未定义 STEAMWORKS_NET | 真 Steam 路径会静默回退 Mock 票,正式联调会失败 |
2.5 场景挂载 Runner
| 步骤 | 操作 | 作用 |
|---|---|---|
| 1 | 在首个常驻场景(如 Loading)创建空物体 | — |
| 2 | 挂载 MJGameSDKRunner | DontDestroyOnLoad;每帧 SteamAPI.RunCallbacks;承载 SDK 协程 |
| 备选 | Initialize(..., runner: this) 传入任意常驻 MonoBehaviour | 未挂 Runner 时 SDK 也会自动创建,但显式挂载更可控 |
2.6 在启动代码中调用 Initialize
在游戏启动入口(Loading)调用一次 MJGameSDK.Initialize,成功后再登录 / 上报。
2.7 构建与 Steam 启动(正式联调)
| 步骤 | 操作 | 作用 |
|---|---|---|
| 1 | Build Settings → Windows x64 | 正式包目标平台 |
| 2 | 将产物放到 Steam 安装目录,或通过 SteamPipe 上传 | Steam 才能以「库启动」方式拉起游戏 |
| 3 | Steam 设置中开启游戏内 Overlay;库内点「开始游戏」 | Overlay 是微交易授权必要条件 |
| 4 | 进游戏 Shift+Tab 确认 Overlay;日志应有 overlayEnabled=True | 避免内购卡在 2005 超时 |
3. 推荐接入流程
启动游戏
→ Initialize SDK
→ ReportGameActivate(启动激活,无需登录)
→ LoginWithSteam (静默登录Steam账号)
→ ReportLoadingUI(加载界面,上报日志)
→ ReportLoginUI(登录界面展示时,上报日志)
→ [游戏自管:选服、选角]
→ ReportRoleCreate(首次创建角色,上报日志)
→ ReportRoleLogin(每次进入角色,上报日志)
→ ReportLoadingGame(进服加载,上报日志)
→ ReportEnterGame(进入游戏,上报日志)
→ [内购 / GetOrders / 用户信息]
3.1 启动 → 登录 → 进角全流程

涉及:游戏客户端、游戏服务端、SDK、Steam 客户端、SDK 服务端、Steam 服务端。
顺序与 §3 一致:Initialize → ReportGameActivate → LoginWithSteam → ReportLoadingUI / ReportLoginUI → 选服选角 → ReportRoleCreate / ReportRoleLogin → ReportLoadingGame → ReportEnterGame。
3.2 内购(含 Overlay、验单、游戏服发货)

发货由 SDK 服务端回调游戏服务端;客户端 Purchase 成功仅表示订单 completed。
4. SDK API 接入
通用约定:
- 失败回调统一为
Action<MJGameResult>,字段见 §4.10。 - 标注「需登录」的接口须先
LoginWithSteam成功。 - 除特别说明外,成功回调在主线程 / 协程回调链上触发,可直接刷新 UI。
4.1 初始化 MJGameSDK.Initialize
MJGameSDK.Initialize(MJGameConfig config, Action<MJGameResult> onComplete, MonoBehaviour runner = null);
| 参数 | 类型 | 必填 | 含义 |
|---|---|---|---|
config | MJGameConfig | 建议 | 环境与 Steam 配置;null 时 LoadDefault() |
onComplete | Action<MJGameResult> | 是 | 初始化完成回调 |
runner | MonoBehaviour | 否 | 承载协程的 Runner;默认用场景中的 MJGameSDKRunner |
onComplete(MJGameResult)
| 字段 | 含义 |
|---|---|
Code == 0 / IsSuccess | Steam(或 Mock)初始化成功,可继续登录 |
Code != 0 | 失败,见错误码(常见 2000/2001) |
Message | 可读说明 |
调用时机:游戏启动、进入业务前调用 一次。已初始化时再次调用会直接 Success("SDK already initialized")。
var config = MJGameConfig.LoadDefault();
MJGameSDK.Initialize(config, result =>
{
if (!result.IsSuccess)
{
Debug.LogError($"SDK 初始化失败: [{result.Code}] {result.Message}");
return;
}
// 推荐:Activate → 静默 LoginWithSteam → LoadingUI / LoginUI → 选服选角 → 创角/登角 → LoadingGame / EnterGame
}, runner: this);
相关:
| 成员 | 含义 |
|---|---|
MJGameSDK.IsInitialized | 是否已初始化成功 |
4.2 登录 Auth.LoginWithSteam
MJGameSDK.Auth.LoginWithSteam(Action<MJGameUserInfo> onSuccess, Action<MJGameResult> onError);
| 参数 | 类型 | 含义 |
|---|---|---|
onSuccess | Action<MJGameUserInfo> | 验票成功,Token / request_key 已写入 SDK |
onError | Action<MJGameResult> | Steam 出票失败或服务端验票失败等 |
onSuccess — MJGameUserInfo
| 字段 | 类型 | 含义 |
|---|---|---|
id | int | 平台用户 ID |
app_id | int | 平台 apps 表主键 |
steam_app_id | ulong | Steam AppId |
steam_id | string | 64 位 SteamID,Steam用户id |
nickname | string | Steam 昵称(PersonaName) |
avatar_url | string | 头像 URL(可能为空) |
其它
| 成员 | 含义 |
|---|---|
Auth.IsLoggedIn | 是否有用户且本地有 access token |
Auth.CurrentUser | 当前用户;未登录为 null |
Auth.Logout() | 仅清本地 Token / 用户,不调服务端 |
调用时机:初始化成功后、进入需登录业务前。正式模式需 Steam 客户端已登录且对本 App 有权限。
4.3 事件上报 Analytics.*
激活 / UI 阶段的设备字段由 SDK 内部自动采集(device_id、机型、OS、版本等),游戏不用拼。创角/登角/进服阶段由游戏传入服/角色信息。
4.3.1 ReportGameActivate
MJGameSDK.Analytics.ReportGameActivate(Action onSuccess = null, Action<MJGameResult> onError = null);
| 参数 | 含义 |
|---|---|
onSuccess | 上报成功(无业务载荷);可省略 |
onError | 失败(如 5001 缺 device_id);可省略 |
无需传入 MJGameActivateInfo;SDK 内部调用与 CollectActivateInfo() 相同的采集逻辑。
时机:Initialize 成功后立刻调用;无需登录(推荐排在 LoginWithSteam 之前)。
MJGameSDK.Analytics.ReportGameActivate(
() => Debug.Log("激活上报成功"),
err => Debug.LogWarning($"激活上报失败: {err.Message}"));
4.3.2 ReportLoadingUI / ReportLoginUI
MJGameSDK.Analytics.ReportLoadingUI(Action onSuccess = null, Action<MJGameResult> onError = null);
MJGameSDK.Analytics.ReportLoginUI(Action onSuccess = null, Action<MJGameResult> onError = null);
| 方法 | 推荐时机 | 需登录 |
|---|---|---|
ReportLoadingUI | 静默登录成功后,进入/展示加载界面时 | 否(推荐流程在登录后) |
ReportLoginUI | 展示登录角色相关界面时(推荐紧接 LoadingUI) | 否(同上) |
内部自动带设备信息;回调可空(失败仅打日志时可省略 onError)。
4.3.3 ReportLoadingGame / ReportEnterGame
MJGameSDK.Analytics.ReportLoadingGame(MJGameRoleInfo roleInfo, Action onSuccess = null, Action<MJGameResult> onError = null);
MJGameSDK.Analytics.ReportEnterGame(MJGameRoleInfo roleInfo, Action onSuccess = null, Action<MJGameResult> onError = null);
| 参数 | 含义 |
|---|---|
roleInfo | 建议传入当前服/角色;可 null(仅设备信息) |
onSuccess / onError | 可选 |
需登录。推荐时机:ReportRoleCreate / ReportRoleLogin 之后,进服加载中 / 真正进入游戏世界时。
4.3.4 ReportRoleCreate / ReportRoleLogin
MJGameSDK.Analytics.ReportRoleCreate(MJGameRoleInfo roleInfo, Action onSuccess, Action<MJGameResult> onError);
MJGameSDK.Analytics.ReportRoleLogin(MJGameRoleInfo roleInfo, Action onSuccess, Action<MJGameResult> onError);
| 参数 | 含义 |
|---|---|
roleInfo | 必填;null → 5002 |
onSuccess | 上报成功 |
onError | 失败 |
MJGameRoleInfo
| 字段 | 建议 | 含义 |
|---|---|---|
server_id | 强建议 | 区服 ID |
server_name | 建议 | 区服名 |
role_id | 强建议 | 角色 ID |
role_name | 建议 | 角色名 |
role_level | 可选 | 等级(未传服务端可按 0) |
role_class | 可选 | 职业/兵种等 |
| 方法 | 时机 | 需登录 |
|---|---|---|
ReportRoleCreate | 该角色第一次创建;推荐在选角后、LoadingGame 之前 | 是 |
ReportRoleLogin | 每次进入该角色;推荐紧接 RoleCreate(若有),再 LoadingGame | 是 |
var role = new MJGameRoleInfo
{
server_id = "1",
server_name = "华东一区",
role_id = "101",
role_name = "剑士·阿尔法",
role_level = 1,
role_class = "Warrior"
};
MJGameSDK.Analytics.ReportRoleCreate(role, () => { }, err => Debug.LogError(err.Message));
MJGameSDK.Analytics.ReportRoleLogin(role, () => { }, err => Debug.LogError(err.Message));
4.4 商品列表 IAP.GetProducts (可选)
MJGameSDK.IAP.GetProducts(Action<MJGameProduct[]> onSuccess, Action<MJGameResult> onError);
| 参数 | 含义 |
|---|---|
onSuccess | 商品数组(可能为空数组) |
onError | 网络/鉴权失败等 |
MJGameProduct
| 字段 | 含义 |
|---|---|
product_id | 商品 ID,购买时必须传这个;须与 游戏、平台配置需一致 |
name | 显示名 |
description | 描述 |
price_cents | 价格(分) |
currency | 币种,如 USD / CNY |
需登录。时机:打开商城 UI 时拉取。
4.5 购买 IAP.Purchase
MJGameSDK.IAP.Purchase(string productId, Action<MJGameOrder> onSuccess, Action<MJGameResult> onError);
MJGameSDK.IAP.Purchase(string productId, MJGamePurchaseContext context, Action<MJGameOrder> onSuccess, Action<MJGameResult> onError);
| 参数 | 含义 |
|---|---|
productId | 平台商品 ID(同 MJGameProduct.product_id) |
context | 服/角色/自定义字段,原样进入发货回调 |
onSuccess | 仅当订单 status == "completed"(Steam 验单 + 游戏服发货成功) |
onError | 拒付、超时、验单失败、发货未完成(3008)等 |
MJGamePurchaseContext
| 字段 | 含义 |
|---|---|
server_id / server_name | 发货目标区服 |
role_id / role_name | 发货目标角色 |
extra | 游戏自定义,推荐传入游戏订单判定标识,最长 255,原样回传游戏发货接口 |
onSuccess — MJGameOrder
| 字段 | 含义 |
|---|---|
id | 平台订单表主键 |
order_id | 平台订单号(Steam orderid,幂等键) |
product_id | 商品 ID |
status | 成功回调时恒为 completed |
amount_cents | 金额(分) |
currency | 币种 |
created_at | 创建时间 |
内部步骤(游戏无需逐步调用)
POST /iap/purchase/start→ SDK 服务端 InitTxn- 等待 Steam Overlay 授权(默认 120s)
POST /iap/purchase/confirm→ FinalizeTxn → 回调游戏服发货- 仅
completed触发onSuccess
需登录。正式模式须 Steam 库启动且 Overlay 可用。
var ctx = new MJGamePurchaseContext
{
server_id = "1",
server_name = "华东一区",
role_id = "101",
role_name = "剑士·阿尔法",
extra = "game_order_id=xxx"
};
MJGameSDK.IAP.Purchase("1001", ctx, order =>
{
Debug.Log($"购买成功 order_id={order.order_id}");
// 道具以游戏服发货为准;此处可刷新商城 UI / 提示成功
}, err => Debug.LogError($"[{err.Code}] {err.Message}"));
4.6 订单列表 IAP.GetOrders
MJGameSDK.IAP.GetOrders(Action<MJGameOrder[]> onSuccess, Action<MJGameResult> onError);
| 参数 | 含义 |
|---|---|
onSuccess | 当前用户在本 App 下 最近 15 天 的订单列表(可能为空数组),按创建时间倒序 |
onError | 未登录、网络错误等 |
onSuccess 中每个 MJGameOrder
| 字段 | 含义 |
|---|---|
order_id | 平台订单号 |
product_id | 商品 ID |
status | 见下表 |
amount_cents / currency | 金额与币种 |
created_at | 创建时间 |
status 含义
| status | 含义 | 客户端建议 |
|---|---|---|
pending | 已创单,待 Overlay 授权或验单 | 可提示「支付中」;勿当已发货 |
steam_verified | Steam 已扣款验单,发货中或待补单 | 不要客户端再发货;等补单或查游戏服 |
completed | 游戏服发货成功 | 与 Purchase 成功一致 |
cancelled | 已取消(拒付/超时取消等) | 展示已取消 |
用途与边界
| 适合 | 不适合 |
|---|---|
| 商城「购买记录」展示 | 作为发货依据(发货只认游戏服回调) |
| 客服 / 联调核对订单状态 | 替代 Purchase 成功回调做入账 |
客户端异常退出后,查看是否有 steam_verified / completed | 在客户端根据查单结果再调一次发货 |
需登录。
MJGameSDK.IAP.GetOrders(orders =>
{
foreach (var o in orders)
Debug.Log($"{o.order_id} {o.product_id} {o.status}");
}, err => Debug.LogError($"[{err.Code}] {err.Message}"));
发货以游戏服务端收到的平台通知为准。
GetOrders是查询辅助,不是发货 API。详见 §5。
4.7 用户信息 User.GetProfile
MJGameSDK.User.GetProfile(Action<MJGameUserProfile> onSuccess, Action<MJGameResult> onError);
MJGameUserProfile
| 字段 | 含义 |
|---|---|
id | 平台用户 ID |
steam_id | SteamID |
nickname / avatar_url | 昵称 / 头像 |
privacy_accepted | 是否已记录同意隐私协议,暂时无作用 |
last_login_at | 最近登录时间 |
total_orders | 订单数量(平台统计) |
需登录。
4.9 静态入口一览
| 方法 | 需登录 | 说明 |
|---|---|---|
MJGameSDK.Initialize | 否 | 初始化 |
MJGameSDK.Shutdown | — | 重置(测试) |
Auth.LoginWithSteam | 否 | Steam 登录 |
Auth.Logout | — | 本地登出 |
Analytics.ReportGameActivate | 否 | 启动激活 |
Analytics.ReportLoadingUI / ReportLoginUI | 否 | UI 阶段 |
Analytics.ReportLoadingGame / ReportEnterGame | 是 | 游戏阶段 |
Analytics.ReportRoleCreate / ReportRoleLogin | 是 | 创角 / 登角 |
IAP.GetProducts | 是 | 商品列表 |
IAP.Purchase | 是 | 完整购买 |
IAP.GetOrders | 是 | 订单历史(查询辅助) |
User.GetProfile | 是 | 用户资料 |
4.10 通用类型
MJGameResult(失败 / 初始化回调)
| 字段 | 含义 |
|---|---|
Code | 0 成功;其它见 §6 |
Message | 说明文案 |
IsSuccess | Code == 0 |
5. 内购与发货约定
5.1 权威数据源
| 事项 | 权威方 |
|---|---|
| 是否扣款成功 | Steam + SDK 服务端 Finalize |
| 是否给玩家发道具 | 游戏服务端(收到平台回调并幂等处理后) |
客户端 Purchase success | 仅表示平台侧已 completed(验单+发货回调成功) |
GetOrders | 平台订单状态快照,不能替代游戏服发货逻辑 |
客户端 不要 在 Purchase 成功或 GetOrders 看到 completed 后再自己调游戏服发货;避免重复发货。游戏服必须以 order_id 幂等。
5.2 游戏发货接口(游戏服实现)
游戏按以下规则实现,实现后将URL发给SDK技术。 平台在 FinalizeTxn 成功后,向游戏提供的 URL POST JSON。
请求体字段
| 字段 | 说明 |
|---|---|
user_id | 平台用户 ID |
steam_id | Steam ID |
product_id | 商品 ID |
order_id | 平台订单号(幂等键) |
amount_cents | 金额(分) |
currency | 币种 |
extra | 客户端 MJGamePurchaseContext.extra |
role_id / server_id | 角色 / 区服 |
created_at | 订单创建时间(ISO8601) |
req_time | Unix 秒;建议允许 ±5 分钟 |
sign | 见下 |
签名
sign = md5(user_id + "#" + steam_id + "#" + order_id + "#" + product_id + "#" + req_time + "#" + delivery_secret)
成功响应:HTTP 200 且 {"code":0,"message":"OK"}。平台仅认 code 整型 0。
如果游戏返回不成功,平台会在1小时内进行多次补单;仍失败则 delivery_abandoned。
游戏服原生 PHP 验签示例
<?php
header('Content-Type: application/json; charset=utf-8');
$deliverySecret = 'YOUR_DELIVERY_SECRET';
$payload = json_decode(file_get_contents('php://input'), true);
$userId = (string) ($payload['user_id'] ?? '');
$steamId = (string) ($payload['steam_id'] ?? '');
$orderId = (string) ($payload['order_id'] ?? '');
$productId = (string) ($payload['product_id'] ?? '');
$reqTime = (int) ($payload['req_time'] ?? 0);
$sign = (string) ($payload['sign'] ?? '');
if (abs(time() - $reqTime) > 300) {
echo json_encode(['code' => 4003, 'message' => 'req_time expired']);
exit;
}
$expected = md5($userId . '#' . $steamId . '#' . $orderId . '#' . $productId . '#' . $reqTime . '#' . $deliverySecret);
if ($expected !== $sign) {
echo json_encode(['code' => 4004, 'message' => 'Invalid sign']);
exit;
}
// TODO: 按 order_id 幂等发货
echo json_encode(['code' => 0, 'message' => 'OK']);
6. 错误处理
void OnErr(MJGameResult err)
{
Debug.LogError($"[{err.Code}] {err.Message}");
}
| 码段 | 含义 |
|---|---|
| 0 | 成功 |
| 1xxx | 客户端网络 / 解析 |
| 2xxx | Steam / 签名相关 |
| 3xxx | 内购业务 |
| 5xxx | 上报参数 |
| Code | 说明 |
|---|---|
| 1001 | 网络错误 |
| 2000 | Steam 未初始化 |
| 2001 | SteamAPI.Init / 验票失败 |
| 2002 | Web API Ticket 获取失败 |
| 2004 | 用户 Overlay 拒付 |
| 2005 | Overlay 授权超时(120s) |
| 2010–2014 | 请求签名相关(缺头、时钟、nonce、key 过期、验签失败) |
| 3001 | 商品不存在 |
| 3002 | 验单失败 |
| 3003 | InitTxn / start 失败 |
| 3008 | 游戏发货未完成(Steam 已验单,平台补单) |
| 5001 | 缺 device_id |
| 5002 | 角色信息为空 |
10. 日志与联调
10.1 日志与联调
| 位置 | 说明 |
|---|---|
| Unity Console | 前缀 [MJGameSDK]、[Http]、[Steam]、[IAP] |
| 本地文件 | {persistentDataPath}/mjgame_sdk.log |
对接问题请提供:AppId、serverBaseUrl、复现步骤、客户端 mjgame_sdk.log、失败时的 X-Request-Id。