API更新解析:存档、排行榜、昵称相关-新项目无需关注

02/11949 浏览更新日志
我们已于2月10日更新了最新的存档、排行榜、昵称相关API,新建的项目均会更新到新版本文档,但是对于旧项目,则可以参考文档内容
(所谓参考,其实就是复制给嗒啦啦)
horizontal linehorizontal line
概念
clientScore
是全局对象,无需 require。提供客户端直接读写云端变量的能力。
数据类型: values (任意类型) 和 iscores (整数)
无需服务端: 客户端直接调用,异步回调返回结果
核心用途: 存档、积分、排行榜
仅限客户端: Standalone / Client 模式可用,Server 模式暂不支持
属性
lua
clientScore.userId   -- number: 当前用户 ID
clientScore.mapName  -- string: 当前地图名称
数据类型说明
| 存储表 | 写入方法 | 读取位置 | 用途 |
|--------|----------|----------|------|
| values | Set()
/ BatchSet():Set()
| 回调第 1 参数 values.key
| 任意类型(字符串、表、数值等) |
| iscores | SetInt()
/ Add()
/ BatchSet():SetInt()
/ BatchSet():Add()
| 回调第 2 参数 iscores.key
| 整数,可参与排行榜排序 |
  ~~sscores~~ 已废弃,不再使用。所有非整数数据统一使用 values。
API 速查
1. 单值写入
```lua
-- 写入任意类型 → values (支持 string/table/number/boolean)
clientScore:Set("game_config", { difficulty = "hard", music = true }, {
    ok = function() print("保存成功") end,
    error = function(code, reason) print("失败:", reason) end,
})
-- 写入复杂嵌套结构 → values (引擎自动 JSON 序列化/反序列化)
clientScore:Set("inventory", {
    { id = "sword01", level = 5, enchant = "fire" },
    { id = "plate
02", level = 3 },
}, { ok = function() end })
-- 写入整数 → iscores(可排行榜)
clientScore:SetInt("high_score", 9999, {
    ok = function() end,
})
-- 整数增量 → iscores(delta 可为负数)
clientScore:Add("gold", 100, { ok = function() end })
clientScore:Add("gold", -50, { ok = function() end })
```
2. 单值读取
lua
clientScore:Get("gold", {
    ok = function(values, iscores)
        local gold = iscores.gold or 0          -- 整数从 iscores 读
        local config = values.game_config       -- table 从 values 读 (已自动反序列化)
    end,
    error = function(code, reason) end,
})
3. 批量读写
```lua
-- 批量读取 (推荐: 比多次 Get 更高效)
clientScore:BatchGet()
    :Key("gold"):Key("exp"):Key("level")
    :Key("inventory")
    :Fetch({
        ok = function(values, iscores)
            local gold = iscores.gold or 0
            local inv = values.inventory or {}  -- table 自动反序列化
        end,
        error = function(code, reason) end,
    })
-- 批量写入
clientScore:BatchSet()
    :Set("inventory", { "铁剑", "木盾" })  -- 任意类型 → values
    :SetInt("gold", 100)                     -- 整数 → iscores
    :Add("playcount", 1)                    -- 增量 → iscores
    :Delete("temp
data")                     -- 删除云变量
    :Save("操作描述", {
        ok = function() end,
        error = function(code, reason) end,
    })
```
BatchSet 方法:
| 方法 | 目标表 | 说明 |
|------|--------|------|
| :Set(key, value)
| values | 设置任意类型 |
| :SetInt(key, value)
| iscores | 设置整数(可排行榜) |
| :Add(key, delta)
| iscores | 增量(delta 可为负数) |
| :Delete(key)
| 两者 | 删除云变量 |
| :Save(desc, events)
| - | 提交保存 |
4. 排行榜
```lua
-- 获取排行榜(前 10 名,按 iscores.key 值降序)
-- events 之后可传附加 key 名,同时获取额外字段
clientScore:GetRankList("gold", 0, 10, {
    ok = function(rankList)
        for i, item in ipairs(rankList) do
            local uid = item.userId                            -- 用户 ID
            local gold = item.iscore.gold or 0                 -- iscores 数据
            local playCount = item.iscore.playcount or 0      -- 附加字段数据
            local isMe = uid == clientScore.userId
        end
    end,
}, "play
count")  -- ← 附加字段: 同时获取 play_count
-- 获取自己的排名(未上榜返回 nil)
clientScore:GetUserRank(clientScore.userId, "gold", {
    ok = function(rank, scoreValue)
        if rank then print("排名: #" .. rank) end
    end,
})
-- 获取排行榜总人数
clientScore:GetRankTotal("gold", {
    ok = function(total) print("总人数: " .. total) end,
})
```
5. 获取用户昵称
```lua
-- ⚠️ 昵称由 TapTap 账号系统管理,不存储在云变量中
-- 不要用 clientScore:Set("nickname", ...) 存昵称!
-- 获取当前用户 ID
---@diagnostic disable-next-line: undefined-global
local myUserId = lobby:GetMyUserId()
-- 全局函数,客户端/服务端通用(客户端异步回调,服务端同步返回)
GetUserNickname({
    userIds = { myUserId },
    onSuccess = function(nicknames)
        for _, info in ipairs(nicknames) do
            print(info.userId, info.nickname)
        end
    end,
    onError = function(errorCode)
        print("查询失败, errorCode=" .. tostring(errorCode))
    end,
})
```
6. 排行榜 + 昵称 (完整示例)
```lua
function ShowLeaderboard(topN, callback)
    clientScore:GetRankList("highscore", 0, topN or 10, {
        ok = function(rankList)
            local leaderboard = {}
            local userIds = {}
            for i, item in ipairs(rankList) do
                table.insert(leaderboard, {
                    rank = i,
                    userId = item.userId,
                    score = item.iscore.high
score or 0,
                    playCount = item.iscore.play_count or 0,
                    isMe = item.userId == clientScore.userId,
                })
                table.insert(userIds, item.userId)
            end
        if #userIds == 0 then
            if callback then callback(leaderboard) end
            return
        end
        -- 昵称通过 GetUserNickname() 查询
        GetUserNickname({
            userIds = userIds,
            onSuccess = function(nicknames)
                local map = {}
                for _, info in ipairs(nicknames) do
                    map[info.userId] = info.nickname or ""
                end
                for _, entry in ipairs(leaderboard) do
                    entry.nickname = map[entry.userId] or "未知"
                end
                if callback then callback(leaderboard) end
            end,
            onError = function(errorCode)
                if callback then callback(leaderboard) end
            end,
        })
    end,
}, "play_count")  -- ← 附加字段
end
```
回调格式
所有异步操作使用统一回调表:
lua
{
    ok = function(...)              -- 成功(参数因方法不同)
    error = function(code, reason)  -- 失败(code: integer, reason: string)
    timeout = function()            -- 超时(可选)
}
各方法回调参数
| 方法 | ok 回调参数 |
|------|-----------|
| Get(key)
| (values, iscores)
|
| Set(key, value)
| (无参数) |
| SetInt(key, value)
| (无参数) |
| Add(key, delta)
| (无参数) |
| BatchGet():Fetch()
| (values, iscores)
|
| BatchSet():Save()
| (无参数) |
| GetRankList()
| (rankList)
— 每个 item: { userId, player, iscore, score }
|
| GetUserRank()
| (rank, scoreValue)
— rank 为 nil 表示未上榜 |
| GetRankTotal()
| (total)
|
排行榜 item 字段详解
| 字段 | 类型 | 说明 |
|------|------|------|
| item.player
| number | 用户 ID |
| item.userId
| number | 用户 ID(与 item.player
等价) |
| item.iscore
| table | { key = intValue, ... }
— 整数云变量 (SetInt/Add 写入) |
| item.score
| table | { key = value, ... }
— 任意类型云变量 (Set 写入)。table 自动 JSON 编解码 |
| item.sscore
| table | 已废弃,通常为空 {}
,不要使用 |
陷阱
1. userId/player 字段是 number
lua
-- 排行榜 item.userId 是 number,拼接字符串时必须 tostring()
local name = "玩家" .. tostring(item.userId)
2. 读取不存在的 key 返回 nil
lua
-- 始终提供默认值
local gold = iscores.gold or 0
local inv = values.inventory or {}
3. Add 是增量,不是赋值
lua
:Add("gold", 10)      -- 云端 gold += 10
:SetInt("gold", 100)   -- 云端 gold = 100(赋绝对值用 SetInt)
4. 异步操作不阻塞
```lua
-- 错误: 回调还没执行,myGold 是 nil
clientScore:Get("gold", { ok = function(v, i) myGold = i.gold end })
print(myGold)  -- nil!
-- 正确: 在回调中处理后续逻辑
clientScore:Get("gold", {
    ok = function(values, iscores)
        myGold = iscores.gold or 0
        onGoldLoaded(myGold)
    end,
})
```
5. Delete 后读取返回 nil,不是 0
Delete 某个 key 后再 Get 回来是 nil,本地状态需要手动归零。
6. 先读后写无事务保证
更新最高分等 Get → Set 场景,多端同时操作可能互相覆盖。竞争场景建议用 Add
增量操作。
7. Set() 支持任意类型,table 自动序列化
```lua
-- 直接存 table,引擎自动 JSON 序列化/反序列化
clientScore:Set("inventory", { "铁剑", "木盾", "药水" })
-- 读回来直接是 table,无需手动 cjson.decode
clientScore:Get("inventory", {
    ok = function(values, iscores)
        local inv = values.inventory  -- 直接是 table
        for _, item in ipairs(inv) do print(item) end
    end,
})
```
8. 不要使用 sscores (已废弃)
```lua
-- 错误: sscores 参数不存在
ok = function(scores, iscores, sscores)  -- 第三个参数不存在
    local nick = sscores.nickname         -- 报错!
end
-- 正确: 只有两个参数
ok = function(values, iscores)
    local config = values.game_config or {}
end
```
9. 昵称不要存云变量
```lua
-- ❌ 错误: 不要用云变量存昵称
clientScore:Set("player_name", "张三")
-- ✅ 正确: 用 GetUserNickname() 查询
GetUserNickname({
    userIds = { clientScore.userId },
    onSuccess = function(nicknames)
        local nick = nicknames[1].nickname
    end,
})
```
10. cjson 是全局对象,不能 require
```lua
-- 错误: require 会报 Module not found
local cjson = require("cjson")
-- 正确: cjson 是全局对象,直接使用 (但 Set() 已自动处理序列化,大多数场景不需要手动用 cjson)
local encoded = cjson.encode({ key = "value" })
local decoded = cjson.decode('{"key":"value"}')
-- pcall 包裹 decode 防止异常
local ok, result = pcall(cjson.decode, jsonStr)
```
11. 排行榜 item 结构
```lua
-- ❌ item.sscore 已废弃
local nick = item.sscore.nickname   -- 不要使用! sscore 通常为空 {}
-- ✅ item.player 和 item.userId 均可用(等价)
local uid = item.player
local uid = item.userId
-- ✅ 读取数据
local gold = item.iscore.gold or 0                         -- iscores
local config = (item.score and item.score.game_config)     -- values
```
12. GetRankList 附加字段
lua
-- events 参数之后可传入额外 key 名,减少额外请求
clientScore:GetRankList("gold", 0, 10, {
    ok = function(rankList)
        for _, item in ipairs(rankList) do
            -- 附加字段在 item.iscore 中
            local playCount = item.iscore.play_count or 0
        end
    end,
}, "play_count", "level")  -- ← 可传多个附加字段
存档结构管理
存档结构定义文件
首次为项目实现存档功能时,必须创建 docs/save-schema.md
,记录所有云变量字段。后续每次变更存档结构时同步更新此文件。
变更检查清单
当存档字段新增、删除或改名时,必须逐项检查:
[ ] docs/save-schema.md
— 更新字段定义表
[ ] BatchGet
的 :Key()
链 — 是否加了新 key / 去掉了旧 key
[ ] 读取回调 — 是否从 values/iscores 正确读取并赋默认值
[ ] BatchSet
/ Save
— 写入逻辑是否覆盖新字段
[ ] 本地状态表 — 是否包含新字段和默认值
[ ] 重置/清档逻辑 — 是否 Delete 了新字段、清空了本地状态
[ ] UI 展示 — 新字段是否需要显示
[ ] 排行榜 — 如果新字段参与排行,读取和展示是否更新
推荐架构
本地状态先行
```lua
-- 本地状态表(内存缓存)
local player = { gold = 0, exp = 0, level = 1, inventory = {} }
-- 启动时加载
function Start()
    clientScore:BatchGet()
        :Key("gold"):Key("exp"):Key("level"):Key("inventory")
        :Fetch({
            ok = function(values, iscores)
                player.gold      = iscores.gold or 0
                player.exp       = iscores.exp or 0
                player.level     = iscores.level or 1
                player.inventory = values.inventory or {}  -- table 自动反序列化
            end,
        })
end
-- 游戏中修改:本地先行,云端异步追赶
function OnGoldEarned(amount)
    player.gold = player.gold + amount  -- 立即更新本地
    clientScore:BatchSet()
        :Add("gold", amount)
        :Save("获得金币", { ok = function() end })
end
```
saveGeneration 防过期回调
重置存档时递增 generation,异步回调检查是否匹配:
```lua
local saveGeneration = 0
function resetAll()
    saveGeneration = saveGeneration + 1
    local gen = saveGeneration
    -- 重置本地...
    clientScore:BatchSet()
        :Delete("gold"):Delete("exp")
        :Save("重置", {
            ok = function()
                if gen ~= saveGeneration then return end  -- 过期则丢弃
                -- 二次覆盖归零...
                clientScore:BatchSet()
                    :SetInt("gold", 0):SetInt("exp", 0)
                    :Save("重置-归零")
            end,
        })
end
```
两种存档策略
| 模式 | 存储方式 | 优点 | 缺点 |
|------|---------|------|------|
| 逐 key | 每个字段一个云变量 (iscores) | 可排行、可增量、原子操作 | 字段多时 Key 链长 |
| 结构化 table | 单个 values 字段存整个 table | 灵活嵌套(背包等),自动序列化 | 不可排行、不可增量、覆盖式写入 |
建议: 核心数值用逐 key (iscores),复杂嵌套结构用 table (values)。
猜你想搜
taptap 制造 存档教程
46
14
59