Builda SDK
SDK 0.12.3 · 更新于 2026-08-25 English
文档目录

Builda SDK

面向把游戏发布到 Builda 平台的开发者(Godot Web / HTML5 / Unity WebGL):Builda SDK 提供哪些能力、 如何接入使用,以及必须遵守的注意事项。打包与上传规范见 h5_bundle_spec.md,本文只讲 SDK 本身。

复制给 AI

Install Builda game tools from /agent/setup.md

Builda SDK 是什么

Builda SDK 是游戏与 Builda 平台(Builda App 宿主)之间的唯一接口层,以全局对象 window.Builda 暴露给游戏。三个引擎共用同一套 JS API;Godot 与 Unity 各有一层官方包装 (GDScript addon / C# 静态类),语义与 JS 完全一致。

模块能力
runtime就绪通知、退出、安全区/胶囊按钮避让、当前语言
whoami / requestToken当前玩家身份、game-scoped 短期服务 token
getRoleInfo / getServerTime其他玩家的公开昵称与头像、Builda 服务端权威时间
privateKV玩家私域云存档(当前游戏 × 当前玩家)
assets平台资源包(assets.zip)URL 解析
audioBGM / 音效 / 静音 / 震动 / 麦克风权限
mic麦克风 PCM 流原语(声控玩法)
pay内购支付面板
ad激励视频广告
input平台原生文本输入面板
rank排行榜提交与查询
events订阅式回调:App 切后台/回前台、暂停面板弹出/关闭

双版本机制:mock 开发版与 runtime 正式版

SDK 有两个实现,API 表面完全一致,游戏代码不需要区分环境:

关键契约:游戏 zip 不打包 SDK 文件本身。index.html 的 <head> 用根相对路径引用 <script src="builda-sdk.js"></script>。本地 builda dev 自动兜底伺服 mock 版;正式运行时由 Builda App 按 release manifest 的 sdk 契约从 CDN 下载正式版注入到同一路径。上传校验会硬查这条引用; 构建工具(vite/webpack)不要把它当模块打包,必须保持 <script> 外链形态。

统一返回格式

所有异步 API 返回 Promise,结果是统一的 Result 结构,不抛异常:

{ "ok": true,  "data": { } }
{ "ok": false, "error": { "code": "BAD_KEY", "message": "..." } }

少数纯本地数据接口是同步裸返回(不包 Result、不需要 await):runtime.safeArea()、 runtime.capsuleMenuRect()、runtime.language()、assets.url() / assets.baseUrl, 以及 mic.read()(实时数据要当帧拿到)。多数异步 API 支持 opts.timeoutMs(默认 120 秒), 超时返回 code: "PENDING"。

除请求式 API 外还有一组订阅式回调 Builda.events.on/off(宿主主动推给游戏的事件:App 切后台/回前台、暂停面板弹出/关闭),见下方「events:订阅式回调」。

接入方式(按引擎)

三个引擎共同的前提:项目已按首页 / upload 页的安装任务装好项目级工具链(./.builda-agent/builda)。 没装的话,点右上角「复制给 AI」把安装任务发给你项目里的 AI 即可。

HTML5(pixi / phaser / Cocos / 原生 canvas)

  1. 项目根运行 ./.builda-agent/builda sdk install —— mock SDK、builda-sdk.d.ts、测试外壳装到 .builda-agent/sdk/web/(可再生,不入库)。
  2. 游戏 HTML <head> 加 <script src="builda-sdk.js"></script>(根相对路径,构建产物必须保留)。
  3. 直接调用 window.Builda.*。游戏最开始必须调用 Builda.runtime.ready()(入口脚本第一件事)。
  4. TypeScript 项目可把 builda-sdk.d.ts 加进 tsconfig include 获得类型提示。
  5. 本地验证:./.builda-agent/builda dev --web dist,在输出的 dev-url 测试外壳里验收。
Builda.runtime.ready();                                 // 游戏最开始必调(30 秒内没收到会被强制关闭)

const who = await Builda.whoami();
if (who.ok) console.log(who.data.name);

await Builda.privateKV.set("save_slot1", bytes);        // bytes: Uint8Array
const r = await Builda.privateKV.get("save_slot1");     // r.data === null 表示 key 不存在

Godot(Web 导出)

  1. 在 Godot 项目根(含 project.godot)运行 ./.builda-agent/builda sdk install 与 ./.builda-agent/builda sdk check。会安装 addons/builda/(SDK 包装 builda.gd、mock JS、示例), 把推荐封装 BuildaClient.gd 拷到 scripts/ 并注册 Autoload,同时把 SDK 引用写进 Web 导出 preset 的 html/head_include。
  2. 结果模型是异步 request/signal:每次调用返回 request_id,结果经 sdk_result(request_id, result) 信号回来(result 即统一 Result 的 Dictionary 形态):
    var builda = BuildaClient.builda
    var rid := builda.whoami()
    # 在 sdk_result 回调里按 request_id 匹配结果
    
    builda.private_kv_set("save", var_to_bytes(save_dict))
    # 读取侧:bytes_to_var(builda.private_kv_bytes(result.data))
    字节类值(privateKV、mic 样本)过桥统一 base64,用 private_kv_bytes() / mic_samples() 还原; 存档格式统一用 var_to_bytes() / bytes_to_var()。
  3. 游戏最开始必须调用 builda.runtime_ready()(自动装好的 BuildaClient Autoload 已在 _ready() 第一件事调用)。
  4. 编辑器 / 非 Web 导出下所有调用返回 UNSUPPORTED(不崩溃);行为验证必须导出 Web 后用 builda dev --web build/web 在浏览器里做。
  5. 不要手动混拷不同版本的 addon / JS / d.ts;版本不一致时重跑 sdk install。 scripts/BuildaClient.gd 归你的项目所有,可按需修改,升级不覆盖。

Unity(WebGL 导出)

  1. Unity 项目根运行 ./.builda-agent/builda sdk install —— C# 包装层与 jslib 桥装到 Assets/Builda/,Builda WebGL 模板装到 Assets/WebGLTemplates/Builda/。
  2. Player Settings → Resolution and Presentation → WebGL Template 选 Builda (模板已含 SDK 引用;自定义模板需自己在 <head> 加)。
  3. 调用 Builda 命名空间下的静态类 BuildaSDK,异步 API 传回调:
    using Builda;
    
    BuildaSDK.RuntimeReady(_ => {});                   // 游戏最开始必调(30 秒内没收到会被强制关闭)
    BuildaSDK.Whoami(result => {
        if (result.Ok) Debug.Log(result.DataMap["name"]);
    });
    BuildaSDK.KvSet("save_slot1", bytes);              // byte[],SDK 内部 base64 过桥
    BuildaSDK.KvGet("save_slot1", r => {
        var data = BuildaSDK.KvBytes(r.Data);          // null = key 不存在
    });
    var lang = BuildaSDK.RuntimeLanguage();            // 同步 API 直接返回
  4. 编辑器 / 非 WebGL 平台所有调用返回 UNSUPPORTED 失败态(不抛异常)。验证同样走 WebGL 导出 + builda dev。

能力详解与使用规范

runtime:就绪、退出、避让、语言

whoami 与 requestToken

红线:token 只在内存持有,严禁写入 privateKV、本地文件、URL、日志或存档。

getRoleInfo 与 getServerTime:他人公开资料、权威时间

const profiles = await Builda.getRoleInfo(["11299825", "11299826"]);
if (profiles.ok) {
  const p = profiles.data["11299825"];           // 可能为 undefined:本次没查到
  showOpponent(p ? p.userName : "玩家", p ? p.userImg : "");
}

const now = await Builda.getServerTime();
if (now.ok && now.data.serverTimeMs >= dailyResetAtMs) refreshDailyTasks();

规范:公开资料只用于当前会话内展示,不要写入存档或拼成自己的玩家库;时间判定以 serverTimeMs 为准,但涉及奖励、排名、支付的最终结果仍由平台服务端校验。

playerData:本人存储观察

Builda.playerData.getMany(["coins"]) 要求非空 keys;listKeys() 返回目录 data.meta.keys,values={}。两者 data 都是 {meta,values,source}。已发布游戏联网登录时不进房也可读取本人数据,草稿试玩不支持。Godot:player_data_get_many / player_data_list_keys;Unity:PlayerDataGetMany / PlayerDataListKeys。

这是可能滞后的非权威观察,revision/source 不保证最新。发奖、扣减、偷菜和支付由 Online 游戏服务器动作裁决;不能从观察整档覆盖存档,后续旧读不能推翻正式动作结果或触发重放。失败不是空档或零余额;保留旧展示时标明“可能滞后/本次刷新失败”。

缺 key 返回 null,tombstone 是 value=null 的非 null 项。value 保留 {type,data} 类型树:int64/revision 是十进制字符串,bytes 是 Base64,object 递归包含 typed 字段,schemaVersion 为 uint32 数值或 null;不把 int64 转 Number。它与 privateKV 独立,服务端秘密不能放在可读字段。本地 mock 为独立 coins=0 fixture,不发送真实 HTTP。

privateKV:玩家云存档

当前游戏 × 当前玩家的私域存档,value 是不透明字节流(JS 侧 Uint8Array,Godot 侧 PackedByteArray + var_to_bytes(),Unity 侧 byte[])。接口: get / set / remove / getMany / setMany / removeMany。

项规则
key 格式^[A-Za-z0-9_:-]{1,64}$(新代码建议只用字母、数字、_、-)
单 value≤ 32KB(按解码后字节数)
批量≤ 32 key / 次(SDK 不自动拆批,超过直接返回 BATCH_TOO_LARGE)
总配额每玩家每游戏总活 key ≤ 100(无聚合字节配额)
写频率服务端管控约 60 次/分钟,batch 算 1 次(mock 不模拟)
并发last write wins,无版本号/CAS
get 返回 nullkey 不存在(空字节是合法值,删除必须显式 remove)
setMany整批校验,任一违规整批拒绝;写入尽力、不承诺事务
removeMany对不存在的 key 幂等

使用规范

assets:平台资源包

大体积资源(音频、3D 模型等)不打进游戏主包,单独打成 assets.zip 经 builda assets upload 上传,内部两个顶层子树:

限额:单文件 ≤ 32MB、解压总量 ≤ 320MB、文件数 ≤ 10000;禁止脚本/可执行内容 (.js / .wasm / .html)与字体。

audio:BGM / 音效 / 震动

await Builda.audio.playBGM("audio/bgm/main.mp3", { loop: true, volume: 0.8 });
await Builda.audio.stopBGM();
await Builda.audio.playSFX("audio/sfx/hit.ogg", { sessionId: "hit", volume: 1 });
await Builda.audio.setMuted(true, "bgm");     // group: "bgm" | "effect"
await Builda.audio.vibrate(2);                // 1 轻 / 2 中 / 3 重

mic:麦克风声控

声控玩法(音量/吹气/音高)优先用官方内置件 BuildaMic(随 SDK 分发,Godot / Unity 同构), 不要直连底层原语:

直连底层(audio.requestMicrophone + mic.start/read/stop,HTML5 无内置件时用)的核心约束是两次手势:

  1. 第 1 次点击(「开启声控」按钮)回调里调 audio.requestMicrophone() 申请权限。available: false = 用户拒绝或无麦:给出明确的阻断提示并允许重试(不能黑屏/卡死/静默);无麦替代玩法可选、不是必须。
  2. 权限结果回来后不要直接调 mic.start()——权限回调不在手势上下文(Godot 信号经逐帧轮询派发必然脱离手势),AudioContext 会被自动播放策略拦下且不可恢复。停在「再点一次开始」的引导,第 2 次点击回调里调 mic.start()。
  3. 之后每帧 mic.read() 同步拉单声道 float32 PCM([-1,1]),不用时 mic.stop()。

红线:PCM 只许本地实时分析,禁止录音留存/上传,违反视为审核事故。

iap:内购支付

// grantBy 声明预期发货路径;不覆盖订单冻结配置,实际发货按回包 grantBy 分流
const r = await Builda.iap.showPayPanel("coin_pack_1", { grantBy: "client" });
// r.data = { success, orderId, grantBy }   success 只表示付钱成功,订单停在 Paid
// r.data.grantBy === "client":按 orderId 幂等发货,然后
await Builda.iap.claim(r.data.orderId);
// r.data.grantBy === "room":把 orderId 交 Room doOrder,客户端不发货不 claim
// { orderId, status: "delivered" }(幂等)
const pending = await Builda.iap.getPendingOrderIds();
// pending.data = { orderIds: string[] }
const r = await Builda.ad.showRewardAd("revive");
// r.data = { success, transId }

input:文本输入

手机 WebView 里游戏自绘输入框拿不到系统 IME(拼音候选、光标、选区全没有)。起名、输房间号等文本输入一律调:

const r = await Builda.input.showInputPanel({ placeholder: "输入昵称", defaultValue: "", maxLength: 16 });
// 确认: { confirmed: true, text }   取消: { confirmed: false, text: "" }

rank:排行榜

榜单必须先在 builda.publish.json 的 rankBoards 声明并随发布审核生效 (每榜 rankId/displayName/sortType/cycleType/minScore/maxScore,最多 100 个榜; sortType: asc|desc,cycleType: day|week|month|forever)。

await Builda.rank.submitScore("weekly_score", 1234);
const r = await Builda.rank.getRankList("weekly_score", { limit: 50 });
// r.data = { rankId, list: [{rank, score, player}], self: {rank, score} | null }

events:订阅式回调(App 前后台、暂停面板)

请求式 API 是「游戏问、平台答」;events 是「平台推、游戏收」。当前有两个事件。

const off = Builda.events.on("appVisibility", ({ visible, source }) => {
  if (!visible) pauseGame();   // App 最小化 / 切后台 / 锁屏
  else resumeGame();           // 回到前台
});
// off();                       // 需要时退订;Builda.events.off("appVisibility") 清空全部订阅

Builda.events.on("pausePanel", ({ visible }) => {
  if (visible) pauseGameplay();   // 宿主「已暂停」面板弹出(胶囊 ✕ 或 runtime.quit() 弹的同一面板)
  else resumeGameplay();          // 面板关闭且没有退出
});

online:联机(Builda Online)

需要多人对战、房间或权威服务器时,联机能力由 Builda Online 提供,与本 SDK 同版本发布: Builda Online 用户文档。在完成本页的 Builda 接入之后,让项目 AI 运行 ./.builda-agent/builda sdk install --with-online(CLI 0.4.56 起):联机 Skill、客户端 SDK、Room SDK 与本地 Mock 都跟随本 SDK 版本安装,之后由 sdk upgrade 一起升级。AI 可阅读的安装说明:https://ai.builda.game/online/releases/0.12.3/setup.md。

本地开发与调试

builda dev 测试外壳

./.builda-agent/builda dev --web <构建目录>          # 如 dist、build/web
./.builda-agent/builda dev --web build/web --safearea 44,0,34,0   # 模拟刘海+底部横条

打开输出的 dev-url,测试外壳提供:横竖屏切换、刘海/安全区模拟、胶囊按钮模拟、支付/广告 mock 模态框 (模拟宿主面板盖在 WebView 上)、「拒绝麦克风」/「麦克风中断」开关、「切后台」按钮(模拟宿主前后台推送); 胶囊按钮弹出的暂停面板同时模拟 events.pausePanel。

mock 环境 URL 参数

参数作用
builda_mock_player / builda_mock_name切换 mock 玩家身份(KV 按玩家隔离)
builda_mock_lang=es-419覆盖 runtime.language() 返回值,测语言回退
builda_mock_safearea=44,0,34,0即 --safearea,注入安全区(上,右,下,左)
builda_mock_mic=deny模拟用户拒绝麦克风

mock 与真机的差异

本地测过 ≠ 真机没问题。mock 验证接口形态与 UI 流程;真实身份、KV、支付、广告、排行榜语义只能走草稿流程 在 Builda App 待发布区试玩验证。

能力mock(builda dev)真机(Builda App)
身份URL 参数假玩家真实登录玩家
privateKVlocalStorage(配额校验一致;写频率不管控)宿主落服务端/本地,写频率 60/min
支付/广告模态框手工点成功/失败宿主真实面板/广告
音频浏览器 Audio(受 autoplay 限制)宿主原生,无 autoplay 限制
排行榜不校验榜单配置,固定假对手按已发布 rankBoards 权威校验
requestToken伪造 token,连不上真实服务宿主向平台换发真 token
events.appVisibility外壳「切后台」按钮模拟宿主推送;切标签页走浏览器兜底宿主 onAppEvent 推送 + WebView 兜底
events.pausePanel外壳胶囊按钮弹出的暂停面板 / 「继续游戏」宿主「已暂停」面板弹出/关闭推送
SDK 文件dev 兜底伺服 mock 版宿主按 manifest 从 CDN 注入正式版

发布前自查清单

常用的 AI 指令

装好工具链后,以下任务直接把这句话说给你的 agent(Claude Code / Cursor 等)即可:

指令作用
打开本地预览启动本地 mock 页面,可在 PC 网页端预览 App 内的效果
上传草稿将游戏上传到 Builda App,随后可在 App 内发布
部署服务器(联机游戏)上传并激活 Room 服务端源码,由 Builda Online Skill 执行,见 Builda Online 用户文档

注意事项与红线汇总

红线(违反会被拒审或视为事故):

  1. 不要绕过 SDK 直调宿主桥——游戏代码禁止接触 window.__cysjHost、flutter_inappwebview.callHandler 或任何底层 App 桥方法。
  2. token 只在内存——requestToken 的 token 禁写 privateKV、本地文件、URL、日志、存档。
  3. 麦克风 PCM 只许本地实时分析——禁止录音留存/上传。
  4. 无宿主不伪造成功——浏览器环境支付/广告返回 HOST_UNAVAILABLE 是正确行为,不要在游戏侧兜底成「购买成功」。
  5. 不要修改 SDK 分发文件——builda-sdk.js、addons/builda/(mic/web/ 除外)、Assets/Builda/ 由 sdk install / builda check 升级时整体覆盖;归你所有的只有 scripts/BuildaClient.gd、mic 分析 worker 目录和你自己的游戏代码。

持久化标识符(改动前必须想清楚)

平台代劳、游戏别做的事

常见错误码

code含义
HOST_UNAVAILABLE无 Builda App 宿主(浏览器直开 runtime 版时的正常失败态)
PENDING等待宿主响应超时(默认 120s,可传 timeoutMs)
BUSY同刻单例的面板已打开(如 input 面板未关重复调)
UNSUPPORTED非 Web/WebGL 环境调用引擎包装层
BAD_KEY / BAD_VALUE / BAD_PAY_ID / BAD_RANK_ID / BAD_SCORE入参格式非法(SDK 本地前置校验)
VALUE_TOO_LARGE / BATCH_TOO_LARGE / QUOTA_EXCEEDEDprivateKV 超限
RANK_NOT_FOUND / SCORE_OUT_OF_RANGE榜单未发布 / 分数越界(宿主权威校验)
PERMISSION_DENIED / MIC_UNAVAILABLE / MIC_NOT_STARTED麦克风权限拒绝 / 不可用 / 未 start 就 read
WORKER_LOAD_FAILEDBuildaMic 分析 worker 未随包分发
JS_ERROR / BAD_RESULT / BAD_RESPONSE桥层异常(多为接入方式错误,检查 SDK 版本与调用姿势)

进一步阅读

文档内容
h5_bundle_spec.md游戏 zip 打包规范(入口、排除清单、分引擎校验规则)
md_persistent_ids.md持久化标识符(KV key / payId / rankId)完整契约
md_rankboards.md排行榜 manifest 配置完整契约
md_bundle_size.md包体瘦身与 res/** 资源分包实战
md_godot_i18n.md / md_unity_webgl.md多语言与字体、Unity WebGL 导出专题
md_mobile_perf.md移动端性能 / 动态 DPR
Builda Online 用户文档联机能力:玩家体验选择、匹配与房间、Builda App 自带的房间聊天与邀请、使用限制;AI 安装:builda sdk install --with-online,说明见 https://ai.builda.game/online/releases/0.12.3/setup.md

md_* 专题文档随 SDK 0.12.3 一起发布在 https://ai.builda.game/game-sdk/releases/0.12.3/,也随三个引擎包的 guidance/ 安装到项目;h5_bundle_spec.md 随 Agent 工具链分发。