开发技巧入门通用
VibeHub 联机开发正确流程:本地写逻辑,线上验联机(新手版)
联机的关键不是"能不能在本地连真服务器"(不能),而是"什么在本地测、什么必须上线测"。这篇给可直接抄的代码结构、两个账号的线上整场用例,以及一份"要不要上线"的判定清单。
由 我叫人员外婆 分享2026年9月12日已在 DeepSeek Harness (dsh) + Vite + Vue 3 + Playwright;VibeHub SDK v3 稳定通道 验证
解决什么问题
新手做 VibeHub 联机,最常见的两个坑:
- 把联机逻辑和
VibeHub绑死,于是本地什么都测不了,改一行代码就要发布上线看效果; - 反过来,在本地怎么连都连不上真服务器,又以为是自己代码写错了,反复折腾。
正确的做法是把"逻辑"和"传输"分开:逻辑在本地随便测,传输只在线上验。本文给可以直接抄的代码结构、一个"两个账号打完整一局"的自动化用例,以及一份"什么改动必须上线"的清单。
适用条件
- 用 VibeHub 的联机能力(页面引入
https://vibe.lumigrav.space/sdk/v3/vibehub.js,用window.VibeHub建房/进房); - 本地能跑起项目(Vite / 其他构建都行),线上能发布(
vibehub deploy); - 不需要懂 WebRTC 或加密。
使用方法
第 1 步:把联机层抽成一个接口(这一步做完,后面全都轻松)
游戏逻辑只依赖这个小接口,不直接碰 VibeHub:
// src/net/types.ts
export interface Room {
onMessage(fn: (msg: unknown, from: string) => void): void
send(msg: unknown): void
leave(): void
}
export interface Net {
create(roomId: string): Promise<Room> // 建房
join(roomId: string): Promise<Room> // 进房
}
第 2 步:写两个实现——本地的假实现、线上的真实现
本地假实现(同一个浏览器里开两个窗口就能互通,不需要网络):
// src/net/local.ts
export function createLocalNet(): Net {
const hub = new BroadcastChannel('my-game') // 两个窗口共享这个频道
const rooms = new Map<string, (msg: unknown, from: string) => void>()
// create/join 都用同一份 rooms;send 时广播给同房间的其他人
// 细节略:重点是"接口一样、行为一样",只是不经过网络
}
线上真实现(只在这里出现 VibeHub):
// src/net/vibe.ts
export async function createVibeNet(work: string): Promise<Net> {
const vibe = await VibeHub.init({ work }) // 只有线上会用到
const wrap = (room: any): Room => ({
onMessage: (fn) => room.onMessage((msg: unknown, from: string) => fn(msg, from)),
send: (msg) => room.send(msg),
leave: () => room.leave(),
})
return {
async create(id) { return wrap(await vibe.room.join(id, { topology: 'host' })) },
async join(id) { return wrap(await vibe.room.join(id, { topology: 'host' })) },
}
}
第 3 步:按环境注入,本地/线上各走各的
// src/net/index.ts
import { createLocalNet } from './local'
export async function createNet(work: string): Promise<Net> {
if (import.meta.env.DEV) return createLocalNet() // 本地:假实现,离线可跑
const { createVibeNet } = await import('./vibe') // 线上:真 SDK(动态引入,不污染本地包)
return createVibeNet(work)
}
到这里,你的本地开发就自由了:开两个浏览器窗口,一个建房一个进房,房间面板、准备、开局、结算、重连逻辑全都能本地调——不用发布,不用等网络。
第 4 步:逻辑用单元测试锁住(不依赖任何 SDK)
把与网络无关的部分抽成纯函数,直接测:
- 建房/进房的状态机(谁先坐、谁当房主、座位怎么排);
- 消息协议(收到乱序/重复/过期消息怎么处理);
- 结算与分数的权威合并;
- 断线重连后的状态补齐。
这些测试跑起来是毫秒级的,改动再频繁也不心疼。
第 5 步:联机行为只在线上验——发布 + 两个账号的整场用例
这是唯一能验真联机的地方。发布:
vibehub update --slug <你的作品slug> --dir dist
然后用 Playwright 开两个互相隔离的浏览器上下文(等于两台设备、两个账号),跑一整局:
// tests/online.spec.ts
import { test, expect } from '@playwright/test'
import { readFileSync } from 'node:fs'
const URL = 'https://vibeapps.lumigrav.space/<你的作品slug>/'
// 凭据放本地文件,且必须被 gitignore;用例只读取、不回显、不提交
const accounts = JSON.parse(readFileSync('tmp/online_test/accounts.json', 'utf8'))
async function login(page: any, account: { email: string; password: string }) {
await page.goto(URL)
await page.getByRole('button', { name: /登录/ }).click() // 走页面上的登录入口
await page.getByLabel(/邮箱|账号/).fill(account.email)
await page.getByLabel(/密码/).fill(account.password)
await page.getByRole('button', { name: /登录|授权/ }).click()
await expect(page.getByText(/已登录|退出/)).toBeVisible({ timeout: 60_000 })
}
test('两个账号打完整一局', async ({ browser }) => {
const hostCtx = await browser.newContext() // 上下文隔离 = 两个独立玩家
const guestCtx = await browser.newContext()
const host = await hostCtx.newPage()
const guest = await guestCtx.newPage()
await login(host, accounts[0])
await login(guest, accounts[1])
// 房主建房,读出房间号
await host.getByRole('button', { name: /创建房间/ }).click()
const roomId = (await host.getByTestId('room-id').innerText()).trim()
// 另一个账号进房,并各自补两个电脑玩家
await guest.goto(`${URL}?room=${roomId}`)
await expect(guest.getByText(roomId)).toBeVisible({ timeout: 60_000 })
// 打完整一局:这里用轮询推进,直到出现结算
await expect(host.getByText(/结算/)).toBeVisible({ timeout: 10 * 60_000 })
await expect(guest.getByText(/结算/)).toBeVisible({ timeout: 10 * 60_000 })
// 断言:两边看到同一份权威结果(分数、局数、座位)
const scoresHost = await host.getByTestId('scores').innerText()
const scoresGuest = await guest.getByTestId('scores').innerText()
expect(scoresGuest).toEqual(scoresHost)
await hostCtx.close(); await guestCtx.close()
})
要点:
- 两个上下文 = 两个玩家(不要把两个标签页放同一个上下文,登录态会互相覆盖);
- 每个用例给独立的超时(整局可能要几分钟),失败时保留截图与 trace;
- 登录态可以存成
storageState复用,避免每次重登(但注意 token 会过期); - 断言要落在"两边一致"上(同一份权威状态、同一个房间号、同一份结算),这才是联机测试的价值。
第 6 步:定一份"要不要上线"的清单,写进项目规范
| 你的改动 | 验证方式 |
|---|---|
| 联机传输、房间逻辑、玩家名单、断线重连、结算同步 | 必须上线跑一次两个账号的整场用例 |
| 游戏 AI、规则、UI、文案、美术 | 不必上线:本地全量测试 + 单机端到端即可 |
| 协议/状态机内部逻辑 | 本地单元测试(毫秒级,天天跑) |
这样你既不会"改什么都上线",也不会"该上线的不上线"。
如何验证结果
- 本地流程是否真的不依赖网络:断网(或关掉 wifi)后按第 3 步开两个窗口,应该能正常建房、进房、同步——能,说明联机层已解耦。
- 线上整场用例是否可信:故意改坏一处同步(例如进房后不发送准备状态),用例必须失败;改回来必须通过。用例不会因为"改坏"而失败,就等于没测。
- 失败时能不能定位:用例失败要在
test-results/留下截图、trace 和两边控制台日志;只有结论没有证据的失败等于重跑一次。 - 房间号与结算双端一致:这是联机最低断言,缺了它,用例只是"页面能打开"。
已知限制与风险
- 本地连不上真服务器,这是正常的、不用修。SDK 的建房/进房接口一律要求登录凭证,文档里的"匿名"指的是帮平台当中继节点,不是"免登录进房"。一条命令自证:
本地伪装域名做真实登录也不行(实测:域名不受信任)。所以别在"让本地连上真服务器"上花时间。curl -s -o - -w '\nHTTP:%{http_code}\n' -X POST https://vibe.lumigrav.space/api/sdk/rooms \ -H 'Content-Type: application/json' -d '{"room":"probe","action":"claim","peer":"p"}' # → {"error":"未授权"} HTTP:401 - 假联机层覆盖不了的几类问题(我们真实踩过,本地复现不出来):超大数据包静默发送失败、分片在直连对端处被丢弃、对端标识漂移导致状态被拒收、接收饥饿被误判为掉线。这正是第 5 步必须存在的原因。
- 凭据管理:账号密码/凭据文件必须 gitignore;用例只在运行时读取,不回显、不提交、不写进日志。
- 用例会变慢:整场用例本来就是分钟级,把它当成"发布前的一次检查",不要塞进每次提交的快速回路。
- 本文基于 2026-09-12 的 SDK 与接口行为;平台更新后,重跑上面那条 curl 复核即可。
来源与致谢
原创:开发 VibeHub 作品「莲花广麻」(B5AJupT1)时形成的流程与实测记录。接口结论来自公开 SDK 源码与公开 API,命令可直接复现,无需账号。无第三方转载内容。
