Skip to content

Unity GameKit 接入指南

This content is not available in your language yet.

本页由包内 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 当前用户资料、Friends 好友快照、Leaderboard 排行榜、Share 分享及 Host 通信消息能力。它们走主包 IGP.UnitySDK 的 Host Session 路径;游戏侧不持有用户 JWT,用户上下文、权限、审核、路由、API 转发或 Host 自主流程由 Desktop/Mobile 处理。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。

  1. IGPConfig.appId 已配置。
  2. 场景里有且只有一个长期有效的 IGPRuntimeManager
  3. 游戏启动流程已调用 IGPRuntimeManager.InitializeAsync()
  4. desktop session attach 成功。
  5. 读取 Profile 时,desktop 当前用户已登录且返回的 capabilities.userContexttrue
  6. 查询 Players 时,desktop 当前用户已登录且返回的 capabilities.userContexttrue
  7. 使用排行榜时,desktop 返回的 capabilities.leaderboardtrue
  8. 使用分享时,desktop 返回的 capabilities.sharetrue
  9. 接收通信消息时,把 IGPGameKitRuntimeIGPRuntimeManager 挂在同一个 GameObject;该推送能力不要求游戏发起请求。

如果 capability 不可用,调用会失败,错误码为 DESKTOP_SESSION_CAPABILITY_MISSING

IGPGameKitRuntime 是 Host 推送通信消息的事件入口。它作为 Core runtime extension 初始化,事件由 IGPRuntimeManager.Update() 按接收顺序在 Unity 主线程交付。组件必须 与 IGPRuntimeManager 位于同一个 GameObject。

从场景配置到 UI 展示的完整代码案例见 INTEGRATION-EXAMPLE.zh-CN.md

using IGP.UnitySDK.GameKit;
using UnityEngine;
public sealed class GameCommunicationPresenter : MonoBehaviour
{
[SerializeField] private IGPGameKitRuntime gameKitRuntime;
private void OnEnable()
{
gameKitRuntime.CommunicationMessageReceived += OnMessage;
gameKitRuntime.VoiceSpeakingStarted += OnSpeakingStarted;
gameKitRuntime.VoiceSpeakingStopped += OnSpeakingStopped;
}
private void OnDisable()
{
gameKitRuntime.CommunicationMessageReceived -= OnMessage;
gameKitRuntime.VoiceSpeakingStarted -= OnSpeakingStarted;
gameKitRuntime.VoiceSpeakingStopped -= OnSpeakingStopped;
}
private void OnMessage(IGPCommunicationMessageEvent message)
{
foreach (IGPCommunicationSegment segment in message.Segments)
{
switch (segment)
{
case IGPCommunicationTextSegment text:
RenderText(text.Text);
break;
case IGPCommunicationEmotionSegment emotion:
RenderEmotion(emotion.EmotionId);
break;
}
}
}
private void OnSpeakingStarted(IGPVoiceSpeakingEvent value) { }
private void OnSpeakingStopped(IGPVoiceSpeakingEvent value) { }
private void RenderText(string text) { }
private void RenderEmotion(string emotionId) { }
}

CommunicationMessageReceivedMessageId 来自 Host envelope;Segments 是不可变、 有序列表,支持纯文本、纯表情和任意位置混合。Text 保留 Host 原值,不 trim、不合并; EmotionId 是不透明字符串,游戏用自己的资源表渲染。SDK 不做文本审核、表情权限检查 或表情目录查询。任一 segment 非法会使整条消息忽略,不展示部分内容。

语音事件的 IGPVoiceSpeakingEvent 包含 RoomIdPlayerIdVoiceActivityIdOccurredAtUnixMs。重复 start、过期 stop 被忽略;Host Session reset 或 GameKit shutdown 清除状态,不合成 stop。消息没有 replay;GameKit 未安装或未初始化时 Core 可安全忽略。

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

字段类型说明
avatarIGPProfileAvatarSelection?当前头像选择,包含 assetKeydisplayNameimageUrl
avatarUrlstring?当前头像 URL。
avatarFrameIGPProfileAvatarFrameSelection?当前头像框及 renderConfig
backgroundIGPProfileBackgroundSelection?当前主页背景选择。
backgroundUrlstring?当前主页背景 URL。
biostring?用户简介。
nameChangesint?已使用的改名次数。
lastChangedAtstring?上次改名时间,ISO 时间字符串。
lastActiveAtstring?最近活跃时间,ISO 时间字符串。
subscriptionBenefitsActivebool?服务端计算的会员权益状态:生效为 true,未生效为 false,缺失或未知为 null;不等同于是否付费。
pendingNicknameReviewIGPPendingNicknameReview?当前待处理昵称审核。

avatarFrame.renderConfig 包含画布、头像区域、遮罩和前后景/动态效果图层。profile 中的头像是选择信息和 URL;需要统一的 256x256 PNG 字节时,调用主包已有的 runtimeManager.GetDesktopUserAvatarAsync()

失败统一抛出 IGPProfileException

Code说明
DESKTOP_SESSION_REQUIREDdesktop session 尚未 attach。
DESKTOP_USER_CONTEXT_REQUIREDdesktop 未登录或没有可用用户上下文。
PROFILE_UNAVAILABLE当前认证 session 未返回 profile。
PROFILE_INVALID_RESPONSEdesktop 返回的 profile JSON 为空或无法解析。

IGPPlayers.GetProfilesAsync(...) 是唯一的玩家资料查询入口,一次接受 1 到 50 个用户 ID。查询单个玩家时传入单元素数组。

IGPPlayerProfile[] profiles = await IGPPlayers.GetProfilesAsync(
runtimeManager,
new[] { "user-2", "user-3" });

重复 ID 按第一次出现的位置去重,不存在的用户从结果中省略。每个 IGPPlayerProfile 包含 idnicknameavatarUrl、静态 avatarFrameUrlbool? subscriptionBenefitsActive。会员权益字段与当前用户资料采用相同三态语义;旧宿主缺失字段时保持 null。这是查询时的快照,需要最新结果时显式重新查询。Friends 入口未提供该字段时也保持 null

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
}
}
}
方法HTTPRoute请求响应
ListLeaderboardsByGameAsyncGETgames/:gameIdIGPListLeaderboardsByGameOptionsIGPListLeaderboardsByGameResult
GetLeaderboardAsyncGET:leaderboardIdIGPGetLeaderboardQueryIGPGetLeaderboardResult
SubmitLeaderboardScoreAsyncPOST:leaderboardId/scoreIGPSubmitLeaderboardScoreRequestIGPSubmitLeaderboardScoreResult
QueryLeaderboardScoresAsyncPOST:leaderboardId/scores/queryIGPQueryLeaderboardScoresRequestIGPQueryLeaderboardScoresResult

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

类型:IGPListLeaderboardsByGameOptions

字段类型说明
leaderboardIdstring?可选过滤条件;不传时返回该游戏下全部排行榜元数据。
periodstring?当前只支持 PERMANENT。为空时 SDK 默认填 PERMANENT

类型:IGPGetLeaderboardQuery

字段类型说明
periodstring?当前只支持 PERMANENT。为空时 SDK 默认填 PERMANENT
startint?查询起始位置,0 表示第一名。为空时 SDK 默认填 0
countint?返回条目数量,范围 1 到 100。为空时 SDK 默认填 20
includeSelfbool?true 时请求 API 返回当前用户自己的排名;无成绩时 selfEntrynull

类型:IGPSubmitLeaderboardScoreRequest

字段类型说明
gameIdint游戏 AppID,SDK 会从 IGPConfig.appId 写入请求 body。
valuelong当前玩家提交的整数分数,分数越高排名越靠前。
eventIdstring?可选幂等键。同一次结算重试时应复用同一个值。
sessionIdstring?可选游戏会话 id,透传给后端。

类型:IGPQueryLeaderboardScoresRequest

字段类型说明
gameIdint游戏 AppID,SDK 会从 IGPConfig.appId 写入请求 body。
userIdsstring[]要查询的用户 id,长度 1 到 50。
periodstring?当前只支持 PERMANENT。为空时 SDK 默认填 PERMANENT

类型:IGPListLeaderboardsByGameResult

字段类型说明
gameIdint游戏 id。
leaderboardsIGPLeaderboardMetadataItem[]排行榜元数据列表。

类型:IGPLeaderboardMetadataItem

字段类型说明
gameIdint游戏 id。
leaderboardIdstring排行榜 id。
periodstring当前为 PERMANENT
statusstring排行榜状态,例如 ACTIVE
displayNamestring?展示名。
descriptionstring?描述。
configobject?服务端配置摘要。
createdAtstringISO 时间字符串。
updatedAtstringISO 时间字符串。

类型:IGPGetLeaderboardResult

字段类型说明
gameIdint游戏 id。
leaderboardIdstring排行榜 id。
periodstring当前为 PERMANENT
entriesIGPLeaderboardEntry[]榜单条目。返回条目数量不保证一定等于请求的 count
selfEntryIGPLeaderboardEntry?请求 includeSelf=true 时返回当前用户排名;未上榜时为 null
generatedAtstringISO 时间字符串。

类型:IGPLeaderboardEntry

字段类型说明
rankint排名。
userIGPLeaderboardUser用户摘要。
valuelong分数。

类型:IGPSubmitLeaderboardScoreResult

字段类型说明
successbool是否成功。
duplicatedbooleventId 命中已有提交时为 true
valuelong本次提交分数。

类型:IGPQueryLeaderboardScoresResult

字段类型说明
gameIdint游戏 id。
leaderboardIdstring排行榜 id。
periodstring当前为 PERMANENT
scoresIGPLeaderboardScoreItem[]查询结果。未提交过成绩的用户 valuenull
generatedAtstringISO 时间字符串。

调用前 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_MISSINGdesktop 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 调用通过下面路径完成:

Unity game
-> IGPShare
-> IGPRuntimeManager.RequestDesktopShareAsync
-> desktop session command RequestDesktopShare
-> Curio desktop
-> desktop share handler / platform share API

SDK 不直接发布内容,只提交版本化 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

字段类型说明
schemaVersionint固定为 1
contentTypestring?游戏自定义内容类型,例如 score-cardlevel-share
titlestring?分享标题。
textstring?分享文本。
imagesIGPShareImage[]多张图片,图片字节不进入 JSON。
templateIdstring?游戏和 desktop 约定的模板 id。
dataobject?游戏自定义结构化数据。
metadataobject?游戏自定义元数据,例如 matchId、levelId、traceId。

类型:IGPShareImage

字段类型说明
mimeTypestringimage/pngimage/jpeg
bytesbyte[]图片原始字节;SDK 会写入 contentBytes
widthint?图片宽度。
heightint?图片高度。
fileNamestring?可选文件名。
altTextstring?可选替代文本。

IGPShareImage 只定义数据字段;调用侧按图片实际格式填入 mimeTypebytes

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 用 byteOffsetbyteLength 读取每张图片。

类型:IGPShareResult

字段类型说明
successbool分享流程结果。
codestringdesktop 分享结果码,例如 SHARE_COMPLETEDSHARE_CANCELLEDSHARE_LOGIN_REQUIRED
shareIdstringdesktop 或平台返回的分享 id。
statusstring分享状态,例如 completedcancelledloginRequired
messagestring可展示或记录的结果说明。
dataobject?desktop 返回的扩展结果。
contentJsonstringdesktop 原始结果 JSON,便于排查问题。

用户取消是正常结果,例如 success=falsestatus=cancelledcode=SHARE_CANCELLED,不会抛异常。

  • IGPConfig.appId 必填。
  • schemaVersion 必须是 1
  • 请求至少包含文本、图片、模板 id、datametadata 之一。
  • 图片 MIME 只支持 image/pngimage/jpeg
  • 所有图片原始字节合计最大 10 MiB
  • 超过 10 MiB 时抛出 IGPShareExceptionCode = SHARE_IMAGES_TOO_LARGE,不会发送 desktop command。

desktop 分享流程的业务结果通过 IGPShareResult 返回。桥协议或 desktop 命令失败才会抛出 IGPShareException

  • Code:desktop 返回的稳定错误码。
  • UpstreamJson:desktop 原始错误 body(如果有)。
using IGP.UnitySDK.GameKit;
var friends = await IGPFriends.GetFriendsAsync(runtimeManager);
foreach (var friend in friends)
{
var playerId = friend.profile.id;
var nickname = friend.profile.nickname;
var isOnline = friend.onlineStatus == IGPFriendOnlineStatus.Online;
}

好友申请的三个入口、返回字段和错误语义见 FriendsSendFriendRequestAsyncGetIncomingFriendRequestsAsyncAcceptFriendRequestAsync

需要 Desktop Host Session 中存在已登录用户。每次调用返回独立快照,包含玩家 ID、 昵称、头像 URL、头像框 URL 以及 Offline / Online;展示字段可以为 null。 Offline 表示当前未确认在线,包括 presence 未就绪、断线或快照属于其他账号。 再次调用即可刷新;不会自动轮询或订阅变化。空数组表示没有好友。

通过 IGPFriendsException.Code 区分命令或查询失败,FRIENDS_INVALID_RESPONSE 表示响应不符合合同。Desktop 不可用、未登录、不支持命令或查询失败会报错,不返回 伪造的空列表。取消仍抛出 OperationCanceledException。 不返回好友关系 ID、好友备注或当前游戏信息。

共享 JSON 合同唯一来源是 igp-proto 子模块中的 jsonschema/desktop-session/game-kit-friends-response.schema.json