# GameKit Quickstart

## 前置条件

1. 项目已安装 `cn.indiegp.sdk.unity`。
2. 项目已安装 `cn.indiegp.sdk.unity.game-kit`。
3. 场景中已有 `IGPRuntimeManager`。
4. `IGPConfig.appId` 已配置。
5. 游戏启动流程已经调用 `IGPRuntimeManager.InitializeAsync()`。
6. Curio desktop 已完成 desktop session attach。
7. 读取 Profile 时，desktop 当前用户已登录且返回 `capabilities.userContext == true`。
8. 使用排行榜时，desktop 返回 `capabilities.leaderboard == true`。
9. 使用分享时，desktop 返回 `capabilities.share == true`。

## 调用方式

### Profile

```csharp
using IGP.UnitySDK;
using IGP.UnitySDK.GameKit;

public sealed class ProfileQuickstart
{
    private IGPRuntimeManager runtimeManager;

    public async System.Threading.Tasks.Task<IGPUserProfile> GetProfileAsync()
    {
        return await IGPProfile.GetProfileAsync(runtimeManager);
    }
}
```

每次调用都会通过 desktop session 获取当前用户最新的完整 profile，其中包含 `avatar` 和 `avatarUrl`。头像 PNG 字节仍通过主包的 `runtimeManager.GetDesktopUserAvatarAsync()` 获取。

### Players

```csharp
using IGP.UnitySDK.GameKit;

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

接口一次接受 1 到 50 个用户 ID，并只返回 `id`、`nickname`、`avatarUrl` 和静态 `avatarFrameUrl`。查询一个玩家时传入单元素数组。

### Leaderboard

```csharp
using IGP.UnitySDK;
using IGP.UnitySDK.GameKit;

public sealed class LeaderboardQuickstart
{
    private IGPRuntimeManager runtimeManager;

    public async System.Threading.Tasks.Task<IGPListLeaderboardsByGameResult> ListLeaderboardsAsync()
    {
        return await IGPLeaderboard.ListLeaderboardsByGameAsync(
            runtimeManager);
    }

    public async System.Threading.Tasks.Task<IGPGetLeaderboardResult> LoadLeaderboardRangeAsync(
        string leaderboardId,
        int start = 0,
        int count = 20)
    {
        return await IGPLeaderboard.GetLeaderboardAsync(
            runtimeManager,
            leaderboardId,
            new IGPGetLeaderboardQuery
            {
                start = start,
                count = count,
                includeSelf = true,
            });
    }

    public async System.Threading.Tasks.Task<IGPSubmitLeaderboardScoreResult> SubmitScoreAsync(
        string leaderboardId,
        long score,
        string stableEventId)
    {
        return await IGPLeaderboard.SubmitLeaderboardScoreAsync(
            runtimeManager,
            leaderboardId,
            score,
            new IGPSubmitLeaderboardScoreOptions
            {
                eventId = stableEventId,
            });
    }

    public async System.Threading.Tasks.Task<IGPQueryLeaderboardScoresResult> QueryFriendsAsync(
        string leaderboardId,
        string[] userIds)
    {
        return await IGPLeaderboard.QueryLeaderboardScoresAsync(
            runtimeManager,
            leaderboardId,
            userIds);
    }
}
```

### 错误处理

`IGPLeaderboardException` 会保留 desktop 返回的错误码、消息和上游 JSON。

```csharp
try
{
    await IGPLeaderboard.SubmitLeaderboardScoreAsync(
        runtimeManager,
        "global-score",
        100,
        new IGPSubmitLeaderboardScoreOptions
        {
            eventId = "match-20260626-0001",
        });
}
catch (IGPLeaderboardException ex)
{
    var status = ex.HttpStatus();
    if (status.HasValue && status.Value >= 500)
    {
        // 可按业务策略重试。重试同一次分数提交时复用同一个 eventId。
    }
}
```

常见规则：

- `LEADERBOARD_HTTP_<status>`：API 返回非 2xx，`UpstreamJson` 里通常有 API error envelope。
- `LEADERBOARD_*`：bridge 或调用参数问题，通常不重试。
- `DESKTOP_*`：desktop session 问题，用户登录或能力可用后重新 attach。
- 提交分数重试时，建议复用同一个 `eventId`，避免同一次结算被重复写入。

### 路由

本模块当前只暴露 4 条排行榜接口。route 使用相对 `/runtime/leaderboards/` 的路径模板：

- `games/:gameId`
- `:leaderboardId`
- `:leaderboardId/scores/query`
- `:leaderboardId/score`

完整请求/响应字段见 `DEVELOPER-GUIDE.zh-CN.md`。

### Share

```csharp
using IGP.UnitySDK;
using IGP.UnitySDK.GameKit;

public sealed class ShareQuickstart
{
    private IGPRuntimeManager runtimeManager;

    public async System.Threading.Tasks.Task<IGPShareResult> ShareScoreAsync(byte[] posterPngBytes)
    {
        return await IGPShare.ShareAsync(
            runtimeManager,
            new IGPShareRequest
            {
                contentType = "score-card",
                text = "I scored 12345",
                images = new[]
                {
                    new IGPShareImage
                    {
                        mimeType = "image/png",
                        bytes = posterPngBytes,
                        fileName = "score.png",
                    },
                },
                data = new
                {
                    score = 12345,
                },
            });
    }
}
```

分享图片以 `byte[]` 传入，SDK 会把多张图片拼成 desktop command 的 `contentBytes`，并在 `contentJson` 中写入每张图片的 `byteOffset` / `byteLength` 描述符。desktop 自主控制登录、确认、发布和取消流程，并通过 `IGPShareResult` 返回结果。所有图片原始字节合计最大 `10 MiB`。
