hud.toast可以在屏幕上方的胶囊提示条里显示自定义文本(最长 80 字,可设置显示时长)。只能显示文字,不能插入 HTML、图片或按钮。
作者声明:只影响本地表现
《dsv4.1f 鹈鹕自行车赛》创意工坊
收藏用于稍后查找;“开启”决定下次启动时加载哪些 Mod。所有已开启 Mod 会一起进入游戏。
推荐方式 · 不需要懂编程
Prompt 只注入作品、能力、依赖和你的 Mod ID,并要求 Agent 实时读取唯一官方 Skill;固定命令与规则不再复制到任务文本中。
发布 Mod 时必须声明使用哪些能力。以下内容是作者约定,平台不会扫描或限制 Mod 代码;只安装你信任的内容。
联机策略:游戏进入联机前应禁用全部 Mod
hud.toast可以在屏幕上方的胶囊提示条里显示自定义文本(最长 80 字,可设置显示时长)。只能显示文字,不能插入 HTML、图片或按钮。
作者声明:只影响本地表现
hud.badge可以在左侧 HUD 增加一块文字徽章(自定义颜色与文本)。同一 id 重复调用是更新同一块徽章而不是新增,不能移动或覆盖游戏自带的计时、速度、耐久等面板。
作者声明:只影响本地表现
event.lifecycle可以监听游戏启动就绪、每场开跑、每完成一圈、自己完赛、返回主菜单这些流程事件,拿到圈数、用时、是否联机等数据。只能读取,不能修改比赛流程或结果。
作者声明:只影响本地表现
event.pickup可以在自己吃到道具时收到通知,拿到道具类型与拾取时的耐久、氮气数值。不能替玩家拾取道具,也不能修改道具布局或效果。
作者声明:只影响本地表现
event.frame可以注册每帧执行的回调并拿到帧间隔时间,用于自绘界面或统计。回调必须足够轻,不能阻塞主循环,也不允许自行开启额外的动画循环或定时器风暴。
作者声明:只影响本地表现
state.read可以只读地拿到当前场景、圈数、速度、耐久、氮气、房间号、玩家列表,以及本机玩家和对手的位置对象。只能读取快照,不能写入或伪造位置、圈数与完赛时间。
作者声明:只影响本地表现
scene.read可以拿到赛道曲线、赛道长度、路面半宽、总圈数,以及游戏使用的 Three.js 命名空间,用于自绘轨迹或可视化。不能修改赛道几何、围栏或拾取物布局。
作者声明:只影响本地表现
tuning.speed可以在单机模式下调整最高速与加速度倍率(0.25~4 倍),用于做轻松或硬核难度。联机对局中该调用会被直接拒绝并返回失败,避免不同玩家物理参数不一致。
作者声明:联机时不应使用
storage.local可以在按 Mod 命名空间隔离的浏览器 localStorage 里读写自己的数据,用于纪录、成就和设置。数据只存在本机浏览器,不能用来存储或传递账号、Token 等凭据。
作者声明:只影响本地表现
debug.log可以向控制台输出带 [mod] 前缀的调试日志,方便开发排查。仅用于调试,不应输出玩家隐私数据。
作者声明:只影响本地表现
Agent 会同时收到下面这份本游戏专属规范。
项目 slug:low-poly 基础 Mod API 版本:1.0.0 文档版本:1.0.0
本文档是作者(游戏)与 Mod 创作者之间的开发约定,不是平台扫描规则。平台不会扫描或限制 Mod 代码, 所以请按本文档的边界来写:超出约定的用法不会被拦截,但会在后续版本里直接失效。
index.html(界面/样式)+ game.js(ES Module,全部游戏逻辑)+ Three.js 0.150.1。index.html 里固定):加载官方 Loader → await import("./game.js")(此时 Mod API 已建好并挂到
window.MyGame)→ await beforeStart(注入启动前 Mod)→ start()(建场景、开主循环)→
markGameReady() → await afterStart(注入启动后 Mod)。before-start 阶段完成。只开放与上面这套架构真实匹配的能力:
| 方向 | 可以做什么 | 典型玩法 |
|---|---|---|
| 界面扩展 | 顶部胶囊提示、HUD 徽章、圈速/排名信息面板 | 圈速计时器、连击提示、完赛播报 |
| 观赛与记录 | 监听开赛/每圈/完赛/拾取,读取只读状态 | 本地战绩统计、路线复盘点 |
| 单机数值 | 调整单机最高速与加速度倍率(0.25×~4×) | 轻松模式、硬核模式 |
| 本地存档 | 按 Mod 命名空间隔离的 localStorage | 个人纪录、成就、设置 |
不开放:赛道几何与拾取物布局、车辆模型与贴图、联机协议与远端玩家状态、服务端代码、账号数据。
Mod 清单里的 loadPhase 只有两个值:
before-start(默认,推荐):在游戏 start() 之前注入。适合所有注册类调用:
on()/once()、addHudBadge()、storage()、setSpeedMultiplier()、读取 api.version 做兼容判断。
此时游戏对象已存在,但主循环还没跑,不要读位置/速度,也不要依赖别的 Mod 已经注册。after-start:在 start() 且 markGameReady() 之后注入。适合需要"游戏已经在跑"的 Mod,
例如依赖其它 Mod 已经注册好的徽章位、或需要立即读一帧状态做初始化。禁止的跨阶段依赖:before-start 的 Mod 不得依赖任何 after-start 的 Mod(后者注入更晚,
那时前者已经执行完)。反过来可以。
游戏暴露唯一入口:
window.MyGame // = modApi
modApi 是唯一公共对象。它不是安全沙箱,但没有它就无法与游戏交互。
| 字段 | 类型 | 说明 |
|---|---|---|
version | string | Mod API 版本,如 "1.0.0" |
slug | string | 作品 slug,恒为 "low-poly" |
game | string | 游戏名 "鹈鹕自行车赛" |
multiplayer | boolean | 本作自带联机,恒为 true |
| 方法 | 参数 | 返回 |
|---|---|---|
on(event, fn) | 事件名、回调 | 退订函数 off() |
once(event, fn) | 同上,只触发一次 | 退订函数 |
off(event, fn) | 事件名、原回调 | — |
事件表:
| 事件名 | 触发时机 | 回调参数 |
|---|---|---|
game:beforeStart | start() 被调用 | {} |
game:ready | 主循环已启动 | {} |
race:start | 每场开跑(单机/联机都有) | { online:boolean, laps:number, room:string|null } |
lap | 自己完成一圈 | { lap:number, total:number, time:number, online:boolean }(time 是从起跑累计的总用时,不是本圈分段;要分段自己跟上一圈相减) |
race:end | 自己完赛 | { time:number, lap:number, online:boolean, hp:number, pickups:number } |
pickup | 自己吃到道具 | { type:"fix"|"nitro", x, y, z, lap, hp, nitro } |
ui:menu | 从大厅/赛道返回主菜单 | {} |
tick | 每帧 | dt:number(秒) |
mod:changed | 数值被改动 | { speedMul:number } |
回调抛出的异常会被捕获并 console.warn,不会中断游戏或其它 Mod。
| 方法 | 返回 |
|---|---|
getState() | { scene, online, raceT, lap, totalLaps, frac, speed, hp, nitro, finished, room, players } |
getPlayer() | 本机玩家对象(位置 x/z/y、speed、hp、nitro、lap、frac …) |
getRivals() | 单机 AI 数组(联机时为空数组) |
getPeers() | 联机远端玩家数组(单机时为空数组) |
findPeer(id) | 按玩家号查远端玩家,找不到返回 null |
getTrack() | { curve, length, halfWidth, laps }(curve 是 Three.js 曲线,可用于取点) |
THREE | Three.js 命名空间(0.150.1) |
| 方法 | 参数 | 说明 |
|---|---|---|
setSpeedMultiplier(v) | 0.25 ~ 4 | 乘 MAXV / NITRO_MAX / ACCEL。联机时返回 false 且不生效 |
getSpeedMultiplier() | — | 当前倍率 |
| 方法 | 参数 | 说明 |
|---|---|---|
toast(text, ms?) | 文本、毫秒(默认 2200) | 顶部胶囊提示,最长 80 字 |
addHudBadge({id, text, color?}) | id 必填且唯一 | 左侧 HUD 徽章;同一 id 重复调用是更新而非叠加 |
const st = api.storage('my-mod'); // 命名空间隔离
st.get(key, default) // 读
st.set(key, value) // 写
st.all() // 取该命名空间全部
底层 localStorage,实际键名 pelican-mod:<命名空间>。写失败(隐私模式/配额)会静默忽略。
api.log(...) → 带 [mod] 前缀的 console.log。
vibehub.mod.json:
{
"title": "圈速提示",
"summary": "每完成一圈在顶部提示用时",
"description": "监听 lap 事件,把当前圈数与本圈用时显示在顶部。",
"version": "1.0.0",
"releaseNotes": "首个版本",
"entry": "mod.js",
"entryType": "classic",
"styles": [],
"loadPhase": "before-start",
"dependencyIds": [],
"capabilities": ["event.lifecycle", "hud.toast"]
}
mod.js:
(function () {
const api = window.MyGame;
if (!api) throw new Error('需要《鹈鹕自行车赛》Mod API');
if (api.version.split('.')[0] !== '1') {
api.log('Mod API 主版本不是 1,可能不兼容');
}
api.on('lap', (e) => {
api.toast(`第 ${e.lap}/${e.total} 圈 · ${e.time.toFixed(1)}s`);
});
api.on('race:end', (e) => {
api.toast(`完赛 ${e.time.toFixed(2)}s · 拾取 ${e.pickups} 次`, 4000);
});
})();
每个例子都对应真实可用的 API,capabilities 直接抄进清单即可。
vibehub.mod.json:
{
"title": "节奏档位",
"summary": "在单机模式下切换轻松/普通/硬核,每次回主菜单换下一档",
"description": "调整最高速与加速度倍率,并在 HUD 显示当前档位。联机时自动失效并提示。",
"version": "1.0.0",
"releaseNotes": "首个版本",
"entry": "mod.js",
"entryType": "classic",
"styles": [],
"loadPhase": "before-start",
"dependencyIds": [],
"capabilities": ["tuning.speed", "hud.badge", "hud.toast", "event.lifecycle", "storage.local"]
}
mod.js:
(function () {
var api = window.MyGame;
var MUL = { 轻松: 0.7, 普通: 1, 硬核: 1.6 };
var ORDER = ['轻松', '普通', '硬核'];
var st = api.storage('pace-mode');
var mode = st.get('mode', '轻松');
if (ORDER.indexOf(mode) < 0) mode = '轻松';
function apply(m) {
mode = m;
var ok = api.setSpeedMultiplier(MUL[m]); // 联机时返回 false 且不生效
api.addHudBadge({ id: 'pace-mode', text: '档位 ' + m, color: ok ? '#7dffa8' : '#ffb35c' });
if (!ok) api.toast('节奏档位只在单机生效,联机中已自动关闭', 3200);
st.set('mode', m);
}
apply(mode);
api.on('ui:menu', function () { // 每次回主菜单换下一档
apply(ORDER[(ORDER.indexOf(mode) + 1) % ORDER.length]);
});
})();
loadPhase 用 after-start,因为它要靠游戏已经跑起来才能拿到完整的一局数据。
{
"title": "完赛战报",
"summary": "每局结束汇总用时、拾取与耐久,并记录最快单圈",
"description": "监听 race:end,把本局结果播报在顶部,同时把最快成绩存在本地。",
"version": "1.0.0",
"releaseNotes": "首个版本",
"entry": "mod.js",
"entryType": "classic",
"styles": [],
"loadPhase": "after-start",
"dependencyIds": [],
"capabilities": ["event.lifecycle", "hud.toast", "storage.local", "hud.badge"]
}
(function () {
var api = window.MyGame;
var st = api.storage('recap');
api.on('race:end', function (e) {
var best = st.get('best', 0);
var isPB = !best || e.time < best;
if (isPB) st.set('best', e.time);
var line = (e.online ? '联机' : '单机') + ' 完赛 ' +
e.time.toFixed(2) + 's · 拾取 ' + e.pickups + ' 次 · 剩余耐久 ' + Math.round(e.hp);
api.toast(isPB ? '★ 新纪录!' + line : line, 4200);
});
var runs = (st.get('runs', 0) || 0) + 1;
st.set('runs', runs);
api.addHudBadge({ id: 'recap', text: '第 ' + runs + ' 局' });
})();
{
"title": "连击计数器",
"summary": "连续吃到道具就累加连击,4 秒没吃到清零",
"description": "统计连续拾取,在 HUD 上显示连击数,5 连以上变金色。",
"version": "1.0.0",
"releaseNotes": "首个版本",
"entry": "mod.js",
"entryType": "classic",
"styles": [],
"loadPhase": "before-start",
"dependencyIds": [],
"capabilities": ["event.pickup", "event.frame", "hud.badge"]
}
(function () {
var api = window.MyGame;
var combo = 0, timer = 0, shown = -1;
api.on('pickup', function () { combo++; timer = 4; });
api.addHudBadge({ id: 'combo', text: '连击 0' });
api.on('tick', function (dt) {
if (timer > 0) { timer -= dt; if (timer <= 0) combo = 0; }
if (combo === shown) return; // 只在数值变化时写 DOM
shown = combo;
api.addHudBadge({ id: 'combo', text: '连击 ' + combo,
color: combo >= 5 ? '#ffe14d' : '#7dffa8' });
});
})();
lap 事件给的是累计总用时,所以要自己跟上一圈相减才是本圈成绩。
(function () {
var api = window.MyGame;
var prev = 0, splits = [];
api.on('race:start', function () { prev = 0; splits = []; });
api.on('lap', function (e) {
var split = e.time - prev;
prev = e.time;
splits.push(split);
api.toast('第 ' + e.lap + '/' + e.total + ' 圈 · ' + split.toFixed(2) + 's');
});
api.on('race:end', function () {
if (splits.length) api.toast('最快一圈 ' + Math.min.apply(null, splits).toFixed(2) + 's', 4000);
});
})();
addHudBadge 的 id 全局唯一。两个 Mod 用同一个 id 会互相覆盖(后注册的生效)。
请用 你的modid.功能 形式,例如 speedrun.split。api.storage(ns) 的命名空间请用你的 Mod ID,不同 Mod 之间不会串数据。off 只退订你自己拿到的那个函数)。window.MyGame、不要改写 game.js 内部变量、不要给共享对象挂同名属性。setSpeedMultiplier 是覆盖式的,最后调用的生效。
需要组合请自己先读 getSpeedMultiplier() 再写回,并在 releaseNotes 里说明。window 挂全局变量;必须暴露时用 window.__你的modid__ 这类唯一名。localStorage,换设备/清缓存即丢失。
不要在里面存账号、Token 或任何凭据。mods-disabled。本作联机是实时位置同步对战,任何 Mod 都可能造成不同步。
因此游戏在进入联机大厅前会拒绝所有已启用的 Mod,并提示玩家先去关闭;
同时 setSpeedMultiplier 在联机中直接返回 false。
想让 Mod 与联机共存请等作者切换到 author-managed 策略。before-start → start() → markGameReady() → after-start。见第 2 节。api.version 是语义化版本。主版本号变化代表破坏性变更;Mod 应在初始化时检查
主版本并在不匹配时降级或提示,而不是直接抛错让游戏白屏。game.js 内部函数或变量、改写 window.MyGame。tick 回调里做重活;不要自己起 requestAnimationFrame 或定时器风暴。after-start Worker 请用 VibeHubWorkshop.createWorker(modId, path) 创建单文件 Worker。登录后可以上传完整 Mod 包或单脚本。
登录反馈渠道
有任何问题、建议或反馈,请加 VibeHub 官方支持群:
QQ 752883513
进群后请直接在群里提反馈,我们会及时关注并处理。