下载 App

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

02/111342 浏览官方信息
我们已于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 做了大量无效/重
精华
66 赞
20 回复
03:06
为了不被斩杀,我只能做到这些了截图
为了不被斩杀,我只能做到这些了
之前一直申请不到计划3,之前说太花哨,后面说体量完成度不足,在我一番小作文下,编辑终于给了我解答,什么原生质感,玩家开局体验啥的。 本来计划2也够用,准备以后再优化了,结果计划2赠送的积分要砍半了,学生党实在消费不起,于是只能再冲一波计划3了 从周六中午到现在一天半时间,优化了一遍角色卡,战斗卡,还有抽卡界面,优化了新手教程,该说不说确实比之前要舒服些。 前半部分为新版,后半部分为旧版。 明天再次
28 赞
6 回复
01:27
《大二暑假,我用AI做了一款属于自己的游戏》截图
《大二暑假,我用AI做了一款属于自己的游戏》
本人自制游戏现已加入taptap封闭测试(仅适配安卓机型)欢迎大家前来尝试(八月十号可以正式开始游玩) 如果有建议或者bug反馈可以加入qq群928501443 #发现好游戏 #游戏日常 #浅评一下 #今天游戏圈发生了啥
3 赞
01:13
【游戏自荐】《王朝崛起》——一个人做的2D骑马砍杀,但真的上头截图
【游戏自荐】《王朝崛起》——一个人做的2D骑马砍杀,但真的上头
略沙盒,玩法硬核,还在修bug,但真的上头 先声明:我不是托,是开发者自己来安利的。如果你喜欢《骑马与砍杀》那种从零拉起部队、跑商、招降将领、攻城叛变的玩法,那这款游戏大概率会对你的胃口。 一、这游戏玩什么? · 超大的2D沙盒地图:64座城市 + 128个村庄,四大势力割据,满地图的蛮族部落。· 动态世界 & AI:AI势力之间会自己宣战、结盟、叛变;武将忠诚度低了会自立为王,最多能冒出8个新势
9 赞
7 回复
感谢官方大大真的让我通过了截图
感谢官方大大真的让我通过了
我抱着试一试的心态,用了这个聚光灯活动,活动免费白嫖的10万积分,做我不擅长的叙事游戏,我跟官方审核说清楚原因,升级开发者计划二用来做新游戏,没想到真的通过,牛牛牛( ఠൠఠ )ノ点赞点赞👍,效率真高,不过我记得开发者计划二以前不是有每日积分的吗,怎么只剩下周期积分了[表情_疑惑][表情_疑惑]
16 赞
16 回复
AI 生成的 NPC 太假?那是你没给它注入“灵魂” | TapTap 制造截图
AI 生成的 NPC 太假?那是你没给它注入“灵魂” | TapTap 制造
在多人或竞技游戏中,如何让 AI 队友不呆板?如何在高延迟下实现丝滑的竞技体验?本期我们采访了《猛攻兄弟》与《飞驰登山拉力赛车》的作者,分享他们的经验之谈~ 2026/03/30 - 2026/4/6 本期核心看点: 数据概览:该周上架游戏中动作策略模拟类持续领跑优质分发榜,占比均达 43.08%。 重点关注:让 AI 像活人,让同步“零延迟” —— 多人竞技开发实战 避坑指南:人工测评准入与分
精华
26 赞
6 回复
TapTap 制造本地开发模式上线:无需测试资格,即刻开启游戏制作截图
TapTap 制造本地开发模式上线:无需测试资格,即刻开启游戏制作
今天,TapTap 制造上线了一个新的使用方式:本地开发模式。 如果你已经获得了TapTap 制造的测试资格,你可以按需选择云端或本地开发模式。 如果你暂时还没有云端 TapTap 制造的测试资格,也可以先用自己熟悉的 AI 工具开始创作。项目开发、调试在本地完成,完成后再回到 TapTap 制造选择项目进行发布。 本地开发模式能做什么? 你可以使用 Codex、Cursor、Claude Cod
官方
142 赞
155 回复
如何提升你游戏中的UI美术水平截图
如何提升你游戏中的UI美术水平
一、请为你的游戏挑选一款合适的字体 很多开发者没有注意到其实UI界面中最重要的元素是字体,一款合适的字体能给整个游戏的界面感官带来蜕变。 请看案例 那么如何挑选适合自己的字体呢? 我总结出了一些小经验可供参考: 黑体:通用 宋体:古风、武侠、修仙 楷体:古风、武侠、修仙 圆体:卡通、动漫、Q版 科技:科幻、未来、电子 顺手分享一个免费可商用字体网站: 当然 如果你不知道自己的游戏适合什么风格
精华
122 赞
44 回复
【游戏上新】——宅男打丧尸截图
【游戏上新】——宅男打丧尸
这是一款横向俯视角射击生存游戏,主打"轻松上手、越玩越爽"的爽快体验。游戏目前已包含: 四种核心模式:挑战、防守、生存、无尽,满足不同玩家口味 多种枪械,从手枪到重型火力,打出自己的风格 地雷,手雷,枪架多种不同军火自由选择,给敌人造成最大伤害 完整的成长体系:角色等级、军火系统、被动技能,越肝越强 丰富的敌人设计:普通丧尸到多种 Boss,战斗层次感明显 未来会制作更多不同的角色,天赋,军火,玩
4 赞
12 回复
00:33
[游戏上新]史诗级大乱斗 多人动作联机截图
[游戏上新]史诗级大乱斗 多人动作联机
《史诗级大乱斗》 是一款多人联机大乱斗游戏,现已免费上线 TapTap,并支持 安卓 / iOS / PC 跨平台联机。 🎮 核心玩法 选择拥有独特技能的多位角色,在快节奏对局中配合多种主动道具,打出属于自己的大乱斗风格。 🎯 游戏模式 · 占领模式 · 团队模式 · 个人模式 🚀 持续更新 更多英雄、地图与玩法已在计划中,游戏将持续迭代。 今天带来部分基础玩法展示,游戏玩法请看视频,点击下方链接
4 赞
7 回复