下载 App

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

02/111360 浏览官方信息
我们已于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 制造 存档教程
别再烧积分了!从每次 2000 到每次 50 的血泪经验截图
别再烧积分了!从每次 2000 到每次 50 的血泪经验
适用场景:所有在用 TapTap Code 开发游戏的小白 难度:★☆☆☆☆ 背景最近群里总有人"炫耀"一次操作花了几千积分,说实话这不是值得高兴的事——这是在告诉所有人你的开发方式有根本性问题。我也走过这条弯路,所以来说说怎么改。 积分消耗的本质是什么积分 = AI 处理的 token 数量 = 你发给 AI 的内容 + AI 回给你的内容所以烧积分的本质只有两种: 你让 AI 做了大量无效/重
精华
82 赞
23 回复
如何提升你游戏中的UI美术水平截图
如何提升你游戏中的UI美术水平
一、请为你的游戏挑选一款合适的字体 很多开发者没有注意到其实UI界面中最重要的元素是字体,一款合适的字体能给整个游戏的界面感官带来蜕变。 请看案例 那么如何挑选适合自己的字体呢? 我总结出了一些小经验可供参考: 黑体:通用 宋体:古风、武侠、修仙 楷体:古风、武侠、修仙 圆体:卡通、动漫、Q版 科技:科幻、未来、电子 顺手分享一个免费可商用字体网站: 当然 如果你不知道自己的游戏适合什么风格
精华
126 赞
44 回复
01:13
【游戏自荐】《王朝崛起》——一个人做的2D骑马砍杀,但真的上头截图
【游戏自荐】《王朝崛起》——一个人做的2D骑马砍杀,但真的上头
略沙盒,玩法硬核,还在修bug,但真的上头 先声明:我不是托,是开发者自己来安利的。如果你喜欢《骑马与砍杀》那种从零拉起部队、跑商、招降将领、攻城叛变的玩法,那这款游戏大概率会对你的胃口。 一、这游戏玩什么? · 超大的2D沙盒地图:64座城市 + 128个村庄,四大势力割据,满地图的蛮族部落。· 动态世界 & AI:AI势力之间会自己宣战、结盟、叛变;武将忠诚度低了会自立为王,最多能冒出8个新势
13 赞
7 回复
01:29
官方阅,申请积分计划2,希望官方继续支持我完成这款游戏,截图
官方阅,申请积分计划2,希望官方继续支持我完成这款游戏,
视频内容只是一部分, 本游戏耗时3个月,目前完成进度百分之80, 是生存游戏,进入修仙界,走剧情找到宗门加入, 这时游戏才是真正开始, 提升实力境界后就可以变强,获取功法法术法宝, @视频内是游戏里挑战妖族入侵一种主流玩法,游戏就是围绕着这个来进行, 还有很多地方可以战斗,获得资源修炼变强,比如去藏书阁学习功法, 去法宝楼抽法宝,每一个宗门数十个法宝抽取 去修仙界探索,获得各自的资源,可能遇见妖兽
1 赞
3 回复
01:27
《大二暑假,我用AI做了一款属于自己的游戏》截图
《大二暑假,我用AI做了一款属于自己的游戏》
本人自制游戏现已加入taptap封闭测试(仅适配安卓机型)欢迎大家前来尝试(八月十号可以正式开始游玩) 如果有建议或者bug反馈可以加入qq群928501443 #发现好游戏 #游戏日常 #浅评一下 #今天游戏圈发生了啥
4 赞
1 回复
00:33
[游戏上新]史诗级大乱斗 多人动作联机截图
[游戏上新]史诗级大乱斗 多人动作联机
《史诗级大乱斗》 是一款多人联机大乱斗游戏,现已免费上线 TapTap,并支持 安卓 / iOS / PC 跨平台联机。 🎮 核心玩法 选择拥有独特技能的多位角色,在快节奏对局中配合多种主动道具,打出属于自己的大乱斗风格。 🎯 游戏模式 · 占领模式 · 团队模式 · 个人模式 🚀 持续更新 更多英雄、地图与玩法已在计划中,游戏将持续迭代。 今天带来部分基础玩法展示,游戏玩法请看视频,点击下方链接
4 赞
7 回复
00:48
《大衡勘藏录》新版本:这次,队员终于"开口说话"了截图
《大衡勘藏录》新版本:这次,队员终于"开口说话"了
【版本 2.1.25 更新 · 可玩】 暴雨夜,一封司空府的勘牒递进营帐。 你带五个人下地宫——探查、破机关、搜刮、撤退。 蜡烛就是命:烧到最后一寸,人就得撤。 这一版主要动了"人味": ① 队友真的在说话 点探查,是两个人一接一答(不再是干巴巴一句); 点取用,一个人说话。 台词配了音,而且是按人物性格写的——毒医说话凉、老兵说话硬、机巧少年嘴碎。 ② 一开口就知道摊上什么事了 中毒、虚弱、困乏
2 赞
01:21
请都看过来~!!!截图
请都看过来~!!!
我是傻…… 很遗憾错的过了这次活动的报名,但刚好我的游戏也是在这个期间完成的,看了下大佬们的作品,感觉我做的还是太粗浅了。模型粗糙,UI也用的原始版本,很多地方都还需要打磨,可惜...这次赠送积分已经败光了。以后的积分大概只能先维护下游戏修复点bug,希望以后还能申请积分... 既然都这样了,我也想看看和大佬们的作品有多大的差距。 星三国/星三国纪元 (其实名字我都还没决定好) 游戏了一半灵感来自
2 赞
4 回复
开发日志(day2)坚持去做就是好的开始截图
开发日志(day2)坚持去做就是好的开始
今天主要是修复了一下昨天遇到的很多问题,闪帧;装备的等级没有随怪物等级参考;再次进入地下城,怪物状态竟然显示(已倒下);不会自动更换目标,导致会反复鞭尸;等很多的问题,不过通过昨天的经验,有效的向ai提出问题并且发送截图发给ai调试,也是把这些问题成功排除,甚至还有多余的free额度来生成新的内容。 新增的内容有返回城镇;返回主界面;中途退出地下城保留进入前的状态;完成地下城的面板结算;当然最有感
1 赞
【游戏上新】【游戏自荐】纹章战记截图
【游戏上新】【游戏自荐】纹章战记
以下纯手打: 游戏特色 1. 游戏为策略回合制,核心战斗玩法主打战前的排兵布阵、技能搭配、装备搭配,结合战斗中的少量大招释放环节,更符合手游玩家的碎片化时间特点; 2. 围绕核心战斗机制,有关卡推图、无限试炼塔、全服竞技场、肉鸽远征、以及高难BOSS挑战等多个玩法,堪称玩法丰富,而且有趣不肝。 游戏运营 1. 游戏已上线一段时间,对于初期的服务器连不上等恶性BUG已经得到了有效管控; 2. 作者持
8 赞
8 回复