Unity GameKit 接入指南
本页由包内
Documentation~/DEVELOPER-GUIDE.zh-CN.md自动同步生成;源文件:adapters/unity/Runtime/IGP.UnitySDK.GameKit/Documentation~/DEVELOPER-GUIDE.zh-CN.md。
这份文档面向 Unity 游戏开发者,说明如何在 Unity 工程中接入和调用 IGP.UnitySDK.GameKit。
当前 GameKit 包包含 Profile 当前用户资料、Leaderboard 排行榜和 Share 分享能力。它们走主包 IGP.UnitySDK 的 desktop session 路径;游戏侧不持有用户 JWT,用户上下文、API 转发或桌面自主流程由 desktop 处理。SDK 会从 IGPConfig.appId 读取当前游戏 AppID,并按 Runtime API 契约写入需要的 appId/gameId。
先安装主包:
adapters/unity/Runtime/IGP.UnitySDK/package.json再安装 GameKit 可选包:
adapters/unity/Runtime/IGP.UnitySDK.GameKit/package.json正式发布包对应:
cn.indiegp.sdk.unity.game-kit-<version>.unitypackage代码命名空间:
using IGP.UnitySDK.GameKit;Leaderboard 调用通过下面路径完成:
Unity game -> IGPLeaderboard -> IGPRuntimeManager.LeaderboardForwardAsync -> desktop session command LeaderboardForward -> Curio desktop -> Runtime Leaderboard API游戏不需要直接访问后端 API,不需要持有用户 JWT,也不需要自己拼认证 headers。
IGPConfig.appId已配置。- 场景里有且只有一个长期有效的
IGPRuntimeManager。 - 游戏启动流程已调用
IGPRuntimeManager.InitializeAsync()。 - desktop session attach 成功。
- 读取 Profile 时,desktop 当前用户已登录且返回的
capabilities.userContext为true。 - 查询 Players 时,desktop 当前用户已登录且返回的
capabilities.userContext为true。 - 使用排行榜时,desktop 返回的
capabilities.leaderboard为true。 - 使用分享时,desktop 返回的
capabilities.share为true。
如果 capability 不可用,调用会失败,错误码为 DESKTOP_SESSION_CAPABILITY_MISSING。
Profile 当前用户资料
Section titled “Profile 当前用户资料”IGPProfile.GetProfileAsync(...) 通过 desktop session command GetDesktopUserProfile 主动获取当前登录用户最新的完整 SessionResponse.profile。desktop 负责认证和 session 刷新,游戏不持有平台 JWT。
using IGP.UnitySDK;using IGP.UnitySDK.GameKit;
public sealed class ProfileDriver{ private IGPRuntimeManager runtimeManager;
public async System.Threading.Tasks.Task LoadProfileAsync() { IGPUserProfile profile = await IGPProfile.GetProfileAsync(runtimeManager);
UnityEngine.Debug.Log($"Avatar: {profile.avatarUrl}"); }}类型:IGPUserProfile
| 字段 | 类型 | 说明 |
|---|---|---|
avatar | IGPProfileAvatarSelection? | 当前头像选择,包含 assetKey、displayName、imageUrl。 |
avatarUrl | string? | 当前头像 URL。 |
avatarFrame | IGPProfileAvatarFrameSelection? | 当前头像框及 renderConfig。 |
background | IGPProfileBackgroundSelection? | 当前主页背景选择。 |
backgroundUrl | string? | 当前主页背景 URL。 |
bio | string? | 用户简介。 |
nameChanges | int? | 已使用的改名次数。 |
lastChangedAt | string? | 上次改名时间,ISO 时间字符串。 |
lastActiveAt | string? | 最近活跃时间,ISO 时间字符串。 |
pendingNicknameReview | IGPPendingNicknameReview? | 当前待处理昵称审核。 |
avatarFrame.renderConfig 包含画布、头像区域、遮罩和前后景/动态效果图层。profile 中的头像是选择信息和 URL;需要统一的 256x256 PNG 字节时,调用主包已有的 runtimeManager.GetDesktopUserAvatarAsync()。
失败统一抛出 IGPProfileException:
| Code | 说明 |
|---|---|
DESKTOP_SESSION_REQUIRED | desktop session 尚未 attach。 |
DESKTOP_USER_CONTEXT_REQUIRED | desktop 未登录或没有可用用户上下文。 |
PROFILE_UNAVAILABLE | 当前认证 session 未返回 profile。 |
PROFILE_INVALID_RESPONSE | desktop 返回的 profile JSON 为空或无法解析。 |
Players 玩家简单资料
Section titled “Players 玩家简单资料”IGPPlayers.GetProfilesAsync(...) 是唯一的玩家资料查询入口,一次接受 1 到 50 个用户 ID。查询单个玩家时传入单元素数组。
IGPPlayerProfile[] profiles = await IGPPlayers.GetProfilesAsync( runtimeManager, new[] { "user-2", "user-3" });重复 ID 按第一次出现的位置去重,不存在的用户从结果中省略。每个 IGPPlayerProfile 严格只包含 id、nickname、avatarUrl 和静态 avatarFrameUrl。
Leaderboard 最小接入
Section titled “Leaderboard 最小接入”using IGP.UnitySDK;using IGP.UnitySDK.GameKit;
public sealed class LeaderboardDriver{ private IGPRuntimeManager runtimeManager;
public async void ListLeaderboards() { IGPListLeaderboardsByGameResult result = await IGPLeaderboard.ListLeaderboardsByGameAsync( runtimeManager, new IGPListLeaderboardsByGameOptions { leaderboardId = "global-score", });
foreach (IGPLeaderboardMetadataItem item in result.leaderboards) { // item.leaderboardId // item.status // item.displayName } }
public async void ShowLeaderboard(string leaderboardId) { IGPGetLeaderboardResult result = await IGPLeaderboard.GetLeaderboardAsync( runtimeManager, leaderboardId, new IGPGetLeaderboardQuery { start = 0, count = 20, includeSelf = true, });
foreach (IGPLeaderboardEntry entry in result.entries) { // entry.rank // entry.user.id // entry.user.nickname // entry.value } }
public async void SubmitScore(string leaderboardId, long score) { IGPSubmitLeaderboardScoreResult result = await IGPLeaderboard.SubmitLeaderboardScoreAsync( runtimeManager, leaderboardId, score, new IGPSubmitLeaderboardScoreOptions { eventId = "match-20260626-0001", sessionId = "session-abc", });
// result.success // result.duplicated // result.value }
public async void QueryFriends(string leaderboardId, string[] userIds) { IGPQueryLeaderboardScoresResult result = await IGPLeaderboard.QueryLeaderboardScoresAsync( runtimeManager, leaderboardId, userIds);
foreach (IGPLeaderboardScoreItem score in result.scores) { // score.userId // score.value: null means the user has never submitted a score } }}API 与路由
Section titled “API 与路由”| 方法 | HTTP | Route | 请求 | 响应 |
|---|---|---|---|---|
ListLeaderboardsByGameAsync | GET | games/:gameId | IGPListLeaderboardsByGameOptions | IGPListLeaderboardsByGameResult |
GetLeaderboardAsync | GET | :leaderboardId | IGPGetLeaderboardQuery | IGPGetLeaderboardResult |
SubmitLeaderboardScoreAsync | POST | :leaderboardId/score | IGPSubmitLeaderboardScoreRequest | IGPSubmitLeaderboardScoreResult |
QueryLeaderboardScoresAsync | POST | :leaderboardId/scores/query | IGPQueryLeaderboardScoresRequest | IGPQueryLeaderboardScoresResult |
Leaderboard 转发 payload 由 SDK 生成,游戏侧只传排行榜业务参数。下面的 appId/gameId 由 SDK 从 IGPConfig.appId 写入,不需要游戏业务层传入:
{ "method": "POST", "appId": 10010, "route": ":leaderboardId/score", "pathParams": { "leaderboardId": "global-score" }, "body": { "gameId": 10010, "value": 100, "eventId": "match-20260626-0001" }}列出游戏排行榜请求选项
Section titled “列出游戏排行榜请求选项”类型:IGPListLeaderboardsByGameOptions
| 字段 | 类型 | 说明 |
|---|---|---|
leaderboardId | string? | 可选过滤条件;不传时返回该游戏下全部排行榜元数据。 |
period | string? | 当前只支持 PERMANENT。为空时 SDK 默认填 PERMANENT。 |
获取排行榜查询参数
Section titled “获取排行榜查询参数”类型:IGPGetLeaderboardQuery
| 字段 | 类型 | 说明 |
|---|---|---|
period | string? | 当前只支持 PERMANENT。为空时 SDK 默认填 PERMANENT。 |
start | int? | 查询起始位置,0 表示第一名。为空时 SDK 默认填 0。 |
count | int? | 返回条目数量,范围 1 到 100。为空时 SDK 默认填 20。 |
includeSelf | bool? | 为 true 时请求 API 返回当前用户自己的排名;无成绩时 selfEntry 为 null。 |
提交排行榜分数请求
Section titled “提交排行榜分数请求”类型:IGPSubmitLeaderboardScoreRequest
| 字段 | 类型 | 说明 |
|---|---|---|
gameId | int | 游戏 AppID,SDK 会从 IGPConfig.appId 写入请求 body。 |
value | long | 当前玩家提交的整数分数,分数越高排名越靠前。 |
eventId | string? | 可选幂等键。同一次结算重试时应复用同一个值。 |
sessionId | string? | 可选游戏会话 id,透传给后端。 |
查询用户排行榜分数请求
Section titled “查询用户排行榜分数请求”类型:IGPQueryLeaderboardScoresRequest
| 字段 | 类型 | 说明 |
|---|---|---|
gameId | int | 游戏 AppID,SDK 会从 IGPConfig.appId 写入请求 body。 |
userIds | string[] | 要查询的用户 id,长度 1 到 50。 |
period | string? | 当前只支持 PERMANENT。为空时 SDK 默认填 PERMANENT。 |
列出游戏排行榜响应
Section titled “列出游戏排行榜响应”类型:IGPListLeaderboardsByGameResult
| 字段 | 类型 | 说明 |
|---|---|---|
gameId | int | 游戏 id。 |
leaderboards | IGPLeaderboardMetadataItem[] | 排行榜元数据列表。 |
排行榜元数据
Section titled “排行榜元数据”类型:IGPLeaderboardMetadataItem
| 字段 | 类型 | 说明 |
|---|---|---|
gameId | int | 游戏 id。 |
leaderboardId | string | 排行榜 id。 |
period | string | 当前为 PERMANENT。 |
status | string | 排行榜状态,例如 ACTIVE。 |
displayName | string? | 展示名。 |
description | string? | 描述。 |
config | object? | 服务端配置摘要。 |
createdAt | string | ISO 时间字符串。 |
updatedAt | string | ISO 时间字符串。 |
获取排行榜响应
Section titled “获取排行榜响应”类型:IGPGetLeaderboardResult
| 字段 | 类型 | 说明 |
|---|---|---|
gameId | int | 游戏 id。 |
leaderboardId | string | 排行榜 id。 |
period | string | 当前为 PERMANENT。 |
entries | IGPLeaderboardEntry[] | 榜单条目。返回条目数量不保证一定等于请求的 count。 |
selfEntry | IGPLeaderboardEntry? | 请求 includeSelf=true 时返回当前用户排名;未上榜时为 null。 |
generatedAt | string | ISO 时间字符串。 |
类型:IGPLeaderboardEntry
| 字段 | 类型 | 说明 |
|---|---|---|
rank | int | 排名。 |
user | IGPLeaderboardUser | 用户摘要。 |
value | long | 分数。 |
提交排行榜分数响应
Section titled “提交排行榜分数响应”类型:IGPSubmitLeaderboardScoreResult
| 字段 | 类型 | 说明 |
|---|---|---|
success | bool | 是否成功。 |
duplicated | bool | eventId 命中已有提交时为 true。 |
value | long | 本次提交分数。 |
查询用户排行榜分数响应
Section titled “查询用户排行榜分数响应”类型:IGPQueryLeaderboardScoresResult
| 字段 | 类型 | 说明 |
|---|---|---|
gameId | int | 游戏 id。 |
leaderboardId | string | 排行榜 id。 |
period | string | 当前为 PERMANENT。 |
scores | IGPLeaderboardScoreItem[] | 查询结果。未提交过成绩的用户 value 为 null。 |
generatedAt | string | ISO 时间字符串。 |
调用前 SDK 会做基础校验,失败时不会发送 desktop command:
leaderboardId:非空,trim 后最大 96 字符。IGPConfig.appId:必填,SDK 会用它填充排行榜转发 payload 的appId/gameId。start:大于等于 0 的整数。count:1 到 100 的整数。eventId/sessionId:如果传入,trim 后非空。userIds:长度 1 到 50,每项非空。period:只能是PERMANENT。
后端仍然是最终规则来源;SDK 本地校验只负责提前拦截明显错误。
失败统一抛出 IGPLeaderboardException:
try{ await IGPLeaderboard.GetLeaderboardAsync(runtimeManager, "global-score");}catch (IGPLeaderboardException ex){ UnityEngine.Debug.LogError( $"Leaderboard failed: code={ex.Code}, status={ex.HttpStatus()}, upstream={ex.UpstreamJson}");}常见错误码:
| 错误码 | 说明 |
|---|---|
DESKTOP_SESSION_CAPABILITY_MISSING | desktop session 没有启用 leaderboard capability。 |
LEADERBOARD_HTTP_<status> | Runtime Leaderboard API 返回非 2xx。 |
APP_ID_MISMATCH | 当前 appId 与 desktop/session 上下文不一致。 |
UpstreamJson 会保留 API 返回的原始错误 body,便于游戏侧日志和排查。
- 只支持四个 Runtime Leaderboard 接口:查询游戏排行榜元数据、读取排行榜区间、批量查询用户分数、提交当前玩家成绩。
- 不支持创建、审核、启用排行榜。
- 不支持 cursor、includeMe。
period当前只支持PERMANENT。- 不保证同分顺序。
- 不保证读取榜单时返回条目数量一定等于请求的
count。
Share 分享
Section titled “Share 分享”Share 调用通过下面路径完成:
Unity game -> IGPShare -> IGPRuntimeManager.RequestDesktopShareAsync -> desktop session command RequestDesktopShare -> Curio desktop -> desktop share handler / platform share APISDK 不直接发布内容,只提交版本化 payload。文本和扩展数据放在 contentJson;图片是二进制字节,放在同一个 desktop command 的 contentBytes。登录、确认面板、发布、取消和失败流程由 desktop 自主控制,并通过 IGPShareResult 返回。
using IGP.UnitySDK;using IGP.UnitySDK.GameKit;
public sealed class ShareDriver{ private IGPRuntimeManager runtimeManager;
public async void ShareScore(byte[] posterPngBytes) { IGPShareResult result = await IGPShare.ShareAsync( runtimeManager, new IGPShareRequest { contentType = "score-card", title = "New record", text = "I scored 12345", templateId = "score", images = new[] { new IGPShareImage { mimeType = "image/png", bytes = posterPngBytes, width = 1280, height = 720, fileName = "score.png", }, }, data = new { score = 12345, }, metadata = new { matchId = "match-20260709-0001", }, });
UnityEngine.Debug.Log($"Share status: {result.status}, id={result.shareId}"); }}类型:IGPShareRequest
| 字段 | 类型 | 说明 |
|---|---|---|
schemaVersion | int | 固定为 1。 |
contentType | string? | 游戏自定义内容类型,例如 score-card、level-share。 |
title | string? | 分享标题。 |
text | string? | 分享文本。 |
images | IGPShareImage[] | 多张图片,图片字节不进入 JSON。 |
templateId | string? | 游戏和 desktop 约定的模板 id。 |
data | object? | 游戏自定义结构化数据。 |
metadata | object? | 游戏自定义元数据,例如 matchId、levelId、traceId。 |
类型:IGPShareImage
| 字段 | 类型 | 说明 |
|---|---|---|
mimeType | string | image/png 或 image/jpeg。 |
bytes | byte[] | 图片原始字节;SDK 会写入 contentBytes。 |
width | int? | 图片宽度。 |
height | int? | 图片高度。 |
fileName | string? | 可选文件名。 |
altText | string? | 可选替代文本。 |
IGPShareImage 只定义数据字段;调用侧按图片实际格式填入 mimeType 和 bytes。
SDK 发送给 desktop 的 contentJson 中,图片只保留描述符:
{ "schemaVersion": 1, "appId": 10010, "contentType": "score-card", "text": "I scored 12345", "images": [ { "mimeType": "image/png", "byteOffset": 0, "byteLength": 1024, "width": 1280, "height": 720, "fileName": "score.png" } ], "data": { "score": 12345 }}图片原始字节拼接在 contentBytes 中。desktop 用 byteOffset 和 byteLength 读取每张图片。
类型:IGPShareResult
| 字段 | 类型 | 说明 |
|---|---|---|
success | bool | 分享流程结果。 |
code | string | desktop 分享结果码,例如 SHARE_COMPLETED、SHARE_CANCELLED、SHARE_LOGIN_REQUIRED。 |
shareId | string | desktop 或平台返回的分享 id。 |
status | string | 分享状态,例如 completed、cancelled、loginRequired。 |
message | string | 可展示或记录的结果说明。 |
data | object? | desktop 返回的扩展结果。 |
contentJson | string | desktop 原始结果 JSON,便于排查问题。 |
用户取消是正常结果,例如 success=false、status=cancelled、code=SHARE_CANCELLED,不会抛异常。
IGPConfig.appId必填。schemaVersion必须是1。- 请求至少包含文本、图片、模板 id、
data或metadata之一。 - 图片 MIME 只支持
image/png和image/jpeg。 - 所有图片原始字节合计最大
10 MiB。 - 超过 10 MiB 时抛出
IGPShareException,Code = SHARE_IMAGES_TOO_LARGE,不会发送 desktop command。
desktop 分享流程的业务结果通过 IGPShareResult 返回。桥协议或 desktop 命令失败才会抛出 IGPShareException:
Code:desktop 返回的稳定错误码。UpstreamJson:desktop 原始错误 body(如果有)。