docs: add bark battle 2d runtime plan
This commit is contained in:
@@ -0,0 +1,662 @@
|
||||
# bark-battle 2D Runtime 前端技术方案(2026-05-11)
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
本方案基于 `.hermes/plans/2026-05-11_144229-bark-battle-2d-game-bdd-ddd-tdd-plan.md`,为“汪汪声浪大作战 / bark-battle”细化前端与浏览器游戏 runtime 的技术实现路线。
|
||||
|
||||
本任务只产出技术方案,不直接实现代码。后续编码应以本文作为 runtime 层设计约束,再按 TDD 小步落地。
|
||||
|
||||
### 1.1 玩法定位
|
||||
|
||||
`bark-battle` 是一个声控拔河式 2D 浏览器小游戏:玩家对麦克风发出狗叫声,系统根据音量峰值、有效叫声次数和节奏计算本方声浪推动力,在限时 30 秒内推动顶部红蓝能量条,时间结束后按能量条偏向判定胜负。
|
||||
|
||||
### 1.2 本文范围
|
||||
|
||||
本文覆盖:
|
||||
|
||||
- Phaser + TypeScript + Vite 栈选择
|
||||
- simulation / render / HUD 边界
|
||||
- 建议目录结构
|
||||
- 核心 domain 类型
|
||||
- Web Audio 输入适配
|
||||
- Phaser Scene 切分
|
||||
- DOM HUD 设计
|
||||
- 移动端与权限降级
|
||||
- 测试与验证命令
|
||||
|
||||
本文不覆盖:
|
||||
|
||||
- 后端表结构、持久化成绩、作品发布、广场接入
|
||||
- 实时多人对战协议
|
||||
- 复杂 AI 狗叫识别模型
|
||||
- 美术素材正式生产流程
|
||||
|
||||
## 2. 技术栈选择
|
||||
|
||||
### 2.1 推荐栈
|
||||
|
||||
```text
|
||||
Runtime Renderer: Phaser 3
|
||||
Language: TypeScript
|
||||
Build: Vite
|
||||
Host UI: React / DOM overlay
|
||||
Audio Input: Web Audio API + MediaDevices.getUserMedia
|
||||
Test: Vitest + Testing Library + 浏览器 smoke / Playwright 可选
|
||||
```
|
||||
|
||||
### 2.2 选择理由
|
||||
|
||||
1. 玩法是横版 2D 舞台,核心表现是狗狗 sprite、声浪、拟声词、粒子、屏幕震动与能量条反馈,Phaser 对 2D 渲染、时间循环、sprite animation、camera、粒子和 Scene 生命周期支持成熟。
|
||||
2. 当前项目主前端已使用 TypeScript + Vite,继续复用现有构建和测试体系,避免引入独立构建链。
|
||||
3. 文字密集、权限提示、结算、设置和移动端响应式布局适合 DOM HUD;Canvas 保持负责 playfield 和动态特效。
|
||||
4. Web Audio API 可以在浏览器端完成 MVP 所需音量采样、RMS/peak 计算、环境噪音校准和输入归一化,不需要首版接入后端音频处理。
|
||||
|
||||
### 2.3 不选择其它路线的原因
|
||||
|
||||
- 不使用 Three.js / 3D:当前玩法画面是 2D 横版舞台,不需要 3D 相机、模型和材质管线。
|
||||
- 不把 HUD 全部塞入 Phaser Canvas:权限说明、重试、结算、移动端布局和可访问性更适合 DOM。
|
||||
- 不在前端实现正式业务真相:浏览器 runtime 可承载单局即时 simulation,但若后续涉及成绩、作品、排行榜、发布和奖励,必须交给后端投影/API 裁决。
|
||||
|
||||
## 3. 总体架构
|
||||
|
||||
### 3.1 分层总览
|
||||
|
||||
```text
|
||||
React Runtime Shell
|
||||
├─ DOM HUD / Panels
|
||||
│ ├─ PermissionPanel
|
||||
│ ├─ TopEnergyBar
|
||||
│ ├─ TimerChip
|
||||
│ └─ ResultPanel
|
||||
│
|
||||
├─ Application Controller
|
||||
│ ├─ permission / calibration orchestration
|
||||
│ ├─ simulation tick
|
||||
│ ├─ audio sample submission
|
||||
│ └─ snapshot publish
|
||||
│
|
||||
├─ Pure Domain / Simulation
|
||||
│ ├─ BarkBattleSession
|
||||
│ ├─ BarkDetector
|
||||
│ ├─ EnergyTugOfWar
|
||||
│ ├─ BarkBattleScoring
|
||||
│ └─ OpponentStrategy
|
||||
│
|
||||
├─ Infrastructure Adapters
|
||||
│ ├─ BrowserMicrophoneInput
|
||||
│ ├─ AudioAnalyserSampler
|
||||
│ └─ PhaserGameHost
|
||||
│
|
||||
└─ Phaser Renderer
|
||||
├─ BootScene
|
||||
├─ PreloadScene
|
||||
├─ BattleScene
|
||||
├─ FxScene / DebugScene(可选)
|
||||
└─ Asset manifest
|
||||
```
|
||||
|
||||
### 3.2 强制边界
|
||||
|
||||
1. `domain/` 不依赖 Phaser、Web Audio、DOM、React、浏览器全局对象或后端 API。
|
||||
2. `domain/` 只接收 plain data,例如时间增量、归一化音量样本、对手 power、配置参数。
|
||||
3. `application/` 负责编排权限、校准、音频输入、AI 对手、tick 和 snapshot 分发。
|
||||
4. Phaser Scene 只消费 `BarkBattleSnapshot`,把 snapshot 映射成 sprite、动画、粒子、camera 和 sound effect;不得持有核心胜负、计数和能量条规则。
|
||||
5. DOM HUD 只消费 snapshot 和少量 runtime UI 状态,负责展示、按钮和弹层;不得重复实现核心胜负规则。
|
||||
6. 若后续接入平台作品/成绩/排行榜,前端只调用后端 API 和展示投影,不在本地绕过后端生成正式结论。
|
||||
|
||||
## 4. 建议目录结构
|
||||
|
||||
首版建议以独立 runtime 原型落在 `src/games/bark-battle/`,避免提前侵入平台创作链路。
|
||||
|
||||
```text
|
||||
src/games/bark-battle/
|
||||
domain/
|
||||
BarkBattleTypes.ts
|
||||
BarkBattleSession.ts
|
||||
BarkDetector.ts
|
||||
EnergyTugOfWar.ts
|
||||
BarkBattleScoring.ts
|
||||
OpponentStrategy.ts
|
||||
__tests__/
|
||||
BarkDetector.test.ts
|
||||
EnergyTugOfWar.test.ts
|
||||
BarkBattleSession.test.ts
|
||||
BarkBattleScoring.test.ts
|
||||
|
||||
application/
|
||||
BarkBattleController.ts
|
||||
BarkBattleConfig.ts
|
||||
BarkBattleSnapshotStore.ts
|
||||
__tests__/
|
||||
BarkBattleController.test.ts
|
||||
|
||||
infrastructure/
|
||||
BrowserMicrophoneInput.ts
|
||||
AudioAnalyserSampler.ts
|
||||
MicrophonePermission.ts
|
||||
__tests__/
|
||||
AudioAnalyserSampler.test.ts
|
||||
|
||||
phaser/
|
||||
BarkBattleGameHost.ts
|
||||
scenes/
|
||||
BarkBattleBootScene.ts
|
||||
BarkBattlePreloadScene.ts
|
||||
BarkBattleScene.ts
|
||||
BarkBattleFxScene.ts
|
||||
assets/
|
||||
barkBattleAssetManifest.ts
|
||||
|
||||
ui/
|
||||
BarkBattleRuntimeShell.tsx
|
||||
BarkBattleHud.tsx
|
||||
BarkBattlePermissionPanel.tsx
|
||||
BarkBattleResultPanel.tsx
|
||||
BarkBattleMobileControls.tsx
|
||||
BarkBattleHud.css
|
||||
__tests__/
|
||||
BarkBattleHud.test.tsx
|
||||
BarkBattlePermissionPanel.test.tsx
|
||||
BarkBattleResultPanel.test.tsx
|
||||
```
|
||||
|
||||
若后续进入 Genarrative 正式玩法类型闭环,再按 `genarrative-play-type-integration` 扩展到:
|
||||
|
||||
```text
|
||||
src/components/bark-battle-runtime/BarkBattleRuntimeShell.tsx
|
||||
src/components/bark-battle-result/BarkBattleResultView.tsx
|
||||
src/services/barkBattleRuntimeClient.ts
|
||||
packages/shared/src/contracts/barkBattle.ts
|
||||
server-rs/crates/shared-contracts/src/bark_battle.rs
|
||||
```
|
||||
|
||||
首版不建议直接新增后端表或正式作品链路,除非产品明确要求成绩、发布和广场能力。
|
||||
|
||||
## 5. 核心 Domain 类型
|
||||
|
||||
### 5.1 基础枚举与数值约定
|
||||
|
||||
```ts
|
||||
export type BarkBattlePhase =
|
||||
| 'permission'
|
||||
| 'calibration'
|
||||
| 'countdown'
|
||||
| 'playing'
|
||||
| 'finished'
|
||||
| 'unsupported'
|
||||
| 'permission-denied'
|
||||
|
||||
export type BarkBattleSide = 'player' | 'opponent'
|
||||
|
||||
export type BarkBattleWinner = BarkBattleSide | 'draw' | null
|
||||
|
||||
export type BarkBattleDifficulty = 'easy' | 'normal' | 'hard'
|
||||
```
|
||||
|
||||
关键数值:
|
||||
|
||||
- `energy`: `-100..100`,正数偏玩家侧,负数偏对手侧。
|
||||
- `currentVolume`: `0..1`,音频采样归一化后的瞬时音量。
|
||||
- `recentPeak`: `0..1`,短窗口内峰值。
|
||||
- `power`: `0..1` 或 `0..100` 二选一,建议 domain 内统一 `0..1`,HUD 显示再转百分比。
|
||||
- `remainingMs`: 单局剩余毫秒。
|
||||
|
||||
### 5.2 Snapshot
|
||||
|
||||
```ts
|
||||
export type BarkBattleSnapshot = {
|
||||
phase: BarkBattlePhase
|
||||
elapsedMs: number
|
||||
remainingMs: number
|
||||
countdownMs: number
|
||||
energy: number
|
||||
player: BarkSideState
|
||||
opponent: BarkSideState
|
||||
winner: BarkBattleWinner
|
||||
result: BarkBattleResult | null
|
||||
lastEvents: BarkBattleVisualEvent[]
|
||||
}
|
||||
|
||||
export type BarkSideState = {
|
||||
side: BarkBattleSide
|
||||
barkCount: number
|
||||
currentVolume: number
|
||||
recentPeak: number
|
||||
combo: number
|
||||
power: number
|
||||
isBarking: boolean
|
||||
lastBarkAtMs: number | null
|
||||
maxVolume: number
|
||||
}
|
||||
|
||||
export type BarkBattleResult = {
|
||||
winner: BarkBattleWinner
|
||||
finalEnergy: number
|
||||
playerBarkCount: number
|
||||
playerMaxVolume: number
|
||||
playerAveragePower: number
|
||||
score: number
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 输入样本与叫声事件
|
||||
|
||||
```ts
|
||||
export type BarkAudioSample = {
|
||||
atMs: number
|
||||
volume: number
|
||||
peak: number
|
||||
rms: number
|
||||
}
|
||||
|
||||
export type BarkDetectedEvent = {
|
||||
id: string
|
||||
atMs: number
|
||||
side: BarkBattleSide
|
||||
volume: number
|
||||
strength: number
|
||||
combo: number
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 视觉事件
|
||||
|
||||
视觉事件由 domain 或 application 生成,但只包含 plain data,不包含 Phaser 对象:
|
||||
|
||||
```ts
|
||||
export type BarkBattleVisualEvent =
|
||||
| {
|
||||
type: 'bark-word'
|
||||
id: string
|
||||
side: BarkBattleSide
|
||||
atMs: number
|
||||
strength: number
|
||||
text: 'BARK' | 'WOOF' | 'WAN' | 'WANGOOF'
|
||||
}
|
||||
| {
|
||||
type: 'shockwave'
|
||||
id: string
|
||||
side: BarkBattleSide
|
||||
atMs: number
|
||||
strength: number
|
||||
}
|
||||
| {
|
||||
type: 'combo-burst'
|
||||
id: string
|
||||
side: BarkBattleSide
|
||||
atMs: number
|
||||
combo: number
|
||||
}
|
||||
```
|
||||
|
||||
Phaser 只根据这些事件播放一次性特效,并维护已消费事件 ID,避免重复播放。
|
||||
|
||||
## 6. Domain 模块职责
|
||||
|
||||
### 6.1 BarkDetector
|
||||
|
||||
职责:把连续音频样本转换为有效叫声事件。
|
||||
|
||||
输入:
|
||||
|
||||
- `BarkAudioSample`
|
||||
- 校准后的 `ambientNoiseFloor`
|
||||
- `barkThreshold`
|
||||
- `minBarkGapMs`
|
||||
- `minBarkDurationMs`
|
||||
- `maxBarkDurationMs`
|
||||
|
||||
规则建议:
|
||||
|
||||
1. 音量超过动态阈值进入 candidate 状态。
|
||||
2. 峰值回落到阈值以下或持续时长达到上限时结束 candidate。
|
||||
3. candidate 持续时间在 `80ms..1200ms` 且与上一次有效叫声间隔足够时,记为一次叫声。
|
||||
4. 长时间持续噪音不应无限计数,只能按冷却和峰值回落形成新事件。
|
||||
5. MVP 不要求识别“是否真狗叫”,先基于音量峰值、时长和间隔判断。
|
||||
|
||||
### 6.2 EnergyTugOfWar
|
||||
|
||||
职责:更新红蓝拉锯条。
|
||||
|
||||
建议公式:
|
||||
|
||||
```text
|
||||
playerPower = volumeScore * 0.65 + barkRateScore * 0.35 + comboBonus
|
||||
opponentPower = opponentStrategy.tick(...)
|
||||
energyDelta = (playerPower - opponentPower) * deltaSeconds * balanceFactor
|
||||
energy = clamp(energy + energyDelta, -100, 100)
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- `EnergyTugOfWar` 不知道玩家来自麦克风还是 mock input。
|
||||
- `EnergyTugOfWar` 不知道 Phaser 能量条宽度。
|
||||
- 平衡参数集中在 `BarkBattleConfig`,不要散落在 Scene 或 HUD 中。
|
||||
|
||||
### 6.3 BarkBattleSession
|
||||
|
||||
职责:管理局内 phase、计时、胜负和 snapshot。
|
||||
|
||||
状态机建议:
|
||||
|
||||
```text
|
||||
permission → calibration → countdown → playing → finished
|
||||
↘ permission-denied
|
||||
↘ unsupported
|
||||
```
|
||||
|
||||
关键规则:
|
||||
|
||||
- `countdown` 结束才进入 `playing`。
|
||||
- `playing` 时 `remainingMs` 随 tick 递减。
|
||||
- `remainingMs <= 0` 后进入 `finished`。
|
||||
- `energy > drawThreshold` 判定玩家胜利。
|
||||
- `energy < -drawThreshold` 判定对手胜利。
|
||||
- `abs(energy) <= drawThreshold` 判定平局。
|
||||
|
||||
### 6.4 OpponentStrategy
|
||||
|
||||
职责:为单机 MVP 提供对手推动力。
|
||||
|
||||
```ts
|
||||
export interface OpponentStrategy {
|
||||
tick(input: OpponentTickInput): OpponentTickOutput
|
||||
}
|
||||
```
|
||||
|
||||
普通难度建议:
|
||||
|
||||
- 周期性小叫声提供基础压力。
|
||||
- 每 3~6 秒一次短爆发。
|
||||
- 玩家大幅领先时可轻微增强,但不能追到不可赢。
|
||||
|
||||
## 7. Web Audio 输入适配
|
||||
|
||||
### 7.1 BrowserMicrophoneInput
|
||||
|
||||
职责:封装浏览器麦克风权限与音频流生命周期。
|
||||
|
||||
建议 API:
|
||||
|
||||
```ts
|
||||
export interface MicrophoneInputPort {
|
||||
isSupported(): boolean
|
||||
requestPermission(): Promise<MicrophoneSession>
|
||||
stop(): void
|
||||
}
|
||||
|
||||
export interface MicrophoneSession {
|
||||
sample(atMs: number): BarkAudioSample
|
||||
stop(): void
|
||||
}
|
||||
```
|
||||
|
||||
实现要点:
|
||||
|
||||
1. 使用 `navigator.mediaDevices?.getUserMedia({ audio: true })`。
|
||||
2. 在用户点击“开始”后创建或 resume `AudioContext`,避免移动端自动播放策略拦截。
|
||||
3. 使用 `AnalyserNode` 读取时域数据,计算 RMS 与 peak。
|
||||
4. 输出归一化样本,不把 `MediaStream`、`AudioContext`、`AnalyserNode` 泄漏到 domain。
|
||||
5. 退出、重开、页面卸载时停止 track,避免麦克风占用残留。
|
||||
|
||||
### 7.2 校准流程
|
||||
|
||||
`calibration` 阶段建议持续 `800ms..1500ms`:
|
||||
|
||||
1. 收集静默环境样本。
|
||||
2. 计算 `ambientNoiseFloor`,例如 `p75` 或均值 + 标准差。
|
||||
3. 设置动态阈值:
|
||||
|
||||
```text
|
||||
barkThreshold = clamp(ambientNoiseFloor + 0.12, 0.18, 0.55)
|
||||
```
|
||||
|
||||
4. 若环境噪音过高,HUD 给出简短提示和“继续 / 重新校准”入口,但不要把长说明常驻在画面上。
|
||||
|
||||
### 7.3 权限与错误分类
|
||||
|
||||
```ts
|
||||
export type MicrophoneFailureReason =
|
||||
| 'unsupported'
|
||||
| 'permission-denied'
|
||||
| 'not-found'
|
||||
| 'not-readable'
|
||||
| 'audio-context-blocked'
|
||||
| 'unknown'
|
||||
```
|
||||
|
||||
前端只根据错误分类展示可操作状态:重试授权、返回、或使用调试备用输入。不要把浏览器原始错误堆栈展示给玩家。
|
||||
|
||||
## 8. Phaser Scene 切分
|
||||
|
||||
### 8.1 BarkBattleBootScene
|
||||
|
||||
职责:
|
||||
|
||||
- 初始化 Phaser 全局配置。
|
||||
- 注册 scale、background color、全局事件桥。
|
||||
- 不加载重资源,不处理玩法规则。
|
||||
|
||||
### 8.2 BarkBattlePreloadScene
|
||||
|
||||
职责:
|
||||
|
||||
- 根据 `barkBattleAssetManifest` 加载背景、狗狗 sprite、声浪 FX、拟声词 bitmap / atlas、轻量音效。
|
||||
- 使用稳定 manifest key,不在 gameplay 代码中散写文件路径。
|
||||
- 加载完成后进入 `BarkBattleScene`。
|
||||
|
||||
### 8.3 BarkBattleScene
|
||||
|
||||
职责:
|
||||
|
||||
- 创建横版舞台、左右狗狗、背景层、声浪层、拟声词层。
|
||||
- 每帧读取最新 `BarkBattleSnapshot`。
|
||||
- 根据 snapshot 更新:
|
||||
- 狗狗 idle / bark / win / lose 动画
|
||||
- 声浪强度
|
||||
- camera shake
|
||||
- transient bark words
|
||||
- shockwave
|
||||
- 把可选的调试输入 action 传给 controller,但不处理麦克风和规则。
|
||||
|
||||
不得在 Scene 中实现:
|
||||
|
||||
- 叫声计数
|
||||
- 胜负判定
|
||||
- 能量条规则
|
||||
- 权限流程
|
||||
- 结算数据计算
|
||||
|
||||
### 8.4 BarkBattleFxScene(可选)
|
||||
|
||||
如果特效复杂,可拆出叠加 Scene:
|
||||
|
||||
- 专门处理拟声词、粒子、冲击波和 camera shake。
|
||||
- 通过视觉事件 ID 去重。
|
||||
- 对 `prefers-reduced-motion` 或低端设备降级。
|
||||
|
||||
首版也可以先把 FX 保持在 `BarkBattleScene` 内,但必须仍然只消费 snapshot / visual events。
|
||||
|
||||
## 9. DOM HUD 设计
|
||||
|
||||
### 9.1 HUD 层级
|
||||
|
||||
DOM HUD 建议覆盖在 Phaser Canvas 上方:
|
||||
|
||||
```text
|
||||
BarkBattleRuntimeShell
|
||||
├─ <div className="bark-battle-canvas-host" />
|
||||
└─ <BarkBattleHud snapshot={snapshot} uiState={uiState} />
|
||||
```
|
||||
|
||||
HUD 分区:
|
||||
|
||||
- 顶部:红蓝声浪能量条 + 小型剩余时间。
|
||||
- 中央:仅在倒计时、关键提示或结算时短暂展示大号状态。
|
||||
- 左右边缘:双方简洁状态,例如叫声次数 / combo chip。
|
||||
- 底部角落:麦克风状态、重试、小菜单。
|
||||
- 结算:独立居中面板,显示胜负、叫声次数、最大音量、评分、再来一局、返回。
|
||||
|
||||
### 9.2 Playfield 保护
|
||||
|
||||
遵循 game UI 约束:
|
||||
|
||||
1. 正常 playing 阶段保持中心和下中部 playfield 清爽,不常驻长文案。
|
||||
2. 不把规则说明、长控制说明、多段提示默认铺在画面上。
|
||||
3. 权限、设置、结算使用独立面板或弹层,不在当前面板下面展开一大块内容。
|
||||
4. 移动端优先保证顶部能量条、倒计时、狗狗和重试入口可见可点。
|
||||
5. 大动效不能遮挡顶部能量条和倒计时。
|
||||
|
||||
### 9.3 CSS 设计建议
|
||||
|
||||
- 使用局部 CSS class 或 CSS module,避免污染全站。
|
||||
- 使用 CSS 变量定义主题:
|
||||
- `--bark-player-color`
|
||||
- `--bark-opponent-color`
|
||||
- `--bark-panel-bg`
|
||||
- `--bark-safe-bottom`
|
||||
- 使用 `dvh` / `svh` 和 safe-area inset 处理移动端地址栏与刘海。
|
||||
- `pointer-events` 分层:HUD 容器默认 `pointer-events: none`,按钮和面板恢复 `pointer-events: auto`。
|
||||
|
||||
## 10. 移动端与权限降级
|
||||
|
||||
### 10.1 移动端输入约束
|
||||
|
||||
移动端浏览器通常要求用户手势才能启动 AudioContext。开局流程必须是:
|
||||
|
||||
```text
|
||||
玩家点击“开始” → requestPermission → 创建/恢复 AudioContext → calibration → countdown → playing
|
||||
```
|
||||
|
||||
不要在页面加载时自动请求或自动启动 AudioContext。
|
||||
|
||||
### 10.2 响应式布局
|
||||
|
||||
移动端建议:
|
||||
|
||||
- 横屏优先呈现完整舞台;竖屏可保持舞台居中并缩小 HUD。
|
||||
- 顶部能量条高度保持可读,但不要占满大面积。
|
||||
- 结算面板宽度使用 `min(92vw, 420px)`。
|
||||
- 底部按钮避开 `env(safe-area-inset-bottom)`。
|
||||
- 非关键设置折叠进小菜单。
|
||||
|
||||
### 10.3 权限失败降级
|
||||
|
||||
权限失败时:
|
||||
|
||||
- `unsupported`:展示“当前浏览器不支持麦克风输入”,提供返回入口。
|
||||
- `permission-denied`:展示简短说明和“重新授权”入口。
|
||||
- `not-found`:提示未检测到麦克风,提供返回入口。
|
||||
- `audio-context-blocked`:提示点击重试。
|
||||
|
||||
可选开发调试降级:
|
||||
|
||||
- 本地 dev 可启用键盘 mock input,例如按住空格模拟音量峰值。
|
||||
- mock input 必须标记为开发/调试能力,不作为正式竞技能力。
|
||||
|
||||
## 11. 测试策略
|
||||
|
||||
### 11.1 Domain 单元测试(优先)
|
||||
|
||||
目标:不接 Phaser、不接 DOM、不接 Web Audio。
|
||||
|
||||
建议测试:
|
||||
|
||||
- `BarkDetector`:超过阈值且间隔足够时计为一次有效叫声。
|
||||
- `BarkDetector`:持续噪音不会无限计数。
|
||||
- `BarkDetector`:低于环境噪音阈值不计入叫声。
|
||||
- `EnergyTugOfWar`:玩家 power 高于对手时 energy 向玩家侧移动。
|
||||
- `EnergyTugOfWar`:energy clamp 在 `-100..100`。
|
||||
- `BarkBattleSession`:倒计时结束进入 playing。
|
||||
- `BarkBattleSession`:剩余时间归零进入 finished。
|
||||
- `BarkBattleSession`:按 energy 和 drawThreshold 判定胜 / 负 / 平。
|
||||
|
||||
### 11.2 Application 测试
|
||||
|
||||
目标:验证输入样本、AI、tick 和 snapshot 编排。
|
||||
|
||||
建议测试:
|
||||
|
||||
- 权限允许后进入校准,再进入倒计时。
|
||||
- 权限拒绝后 phase 为 `permission-denied`,不进入 playing。
|
||||
- 提交 mock audio sample 后 snapshot 中玩家状态更新。
|
||||
- AI 对手 power 参与能量条拉锯。
|
||||
- `lastEvents` 只发布新增视觉事件。
|
||||
|
||||
### 11.3 HUD 组件测试
|
||||
|
||||
目标:验证 snapshot 到 DOM 的展示映射。
|
||||
|
||||
建议测试:
|
||||
|
||||
- playing 阶段展示倒计时和能量条。
|
||||
- energy 正负值映射到玩家 / 对手侧比例。
|
||||
- permission-denied 展示重试入口。
|
||||
- finished 展示胜负、叫声次数和再来一局。
|
||||
- 移动端 class / 结构不依赖 Phaser Canvas 才能渲染。
|
||||
|
||||
### 11.4 Phaser 集成与 smoke
|
||||
|
||||
自动化层面不强行在 Vitest 中完整启动 Phaser。建议:
|
||||
|
||||
- 用 adapter mock 测试 Scene 消费 snapshot 的纯映射函数。
|
||||
- 浏览器 smoke 验证真实 Canvas、Web Audio 和动画。
|
||||
- 若后续引入 Playwright,再做最小视觉和交互 smoke。
|
||||
|
||||
## 12. 验证命令建议
|
||||
|
||||
文档阶段只需要编码检查和 diff 检查:
|
||||
|
||||
```bash
|
||||
npm run check:encoding
|
||||
git diff -- docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md
|
||||
```
|
||||
|
||||
后续实现 domain 后建议:
|
||||
|
||||
```bash
|
||||
npm run test -- --run src/games/bark-battle/domain/**/*.test.ts
|
||||
npm run typecheck
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
后续实现 HUD 后建议:
|
||||
|
||||
```bash
|
||||
npm run test -- --run src/games/bark-battle/ui/**/*.test.tsx
|
||||
npm run lint:eslint
|
||||
npm run typecheck
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
后续接入浏览器 runtime 后建议:
|
||||
|
||||
```bash
|
||||
npm run dev:web
|
||||
# 人工 smoke:授权麦克风 → 校准 → 发声 → 能量条变化 → 结算 → 再来一局
|
||||
```
|
||||
|
||||
若未来接入 Genarrative 正式玩法类型、后端持久化或发布链路,再追加对应契约、api-server 和 SpacetimeDB 验证;首版 runtime 原型不应提前新增这些命令作为门槛。
|
||||
|
||||
## 13. 后续落地顺序
|
||||
|
||||
建议后续实现按以下顺序推进:
|
||||
|
||||
1. 先建 `domain/` 和纯单元测试。
|
||||
2. 实现 `BarkDetector`、`EnergyTugOfWar`、`BarkBattleSession` 的最小规则。
|
||||
3. 建 `application/` controller,用 mock audio sample 跑通 snapshot。
|
||||
4. 实现 DOM HUD 的 permission / energy / timer / result 展示。
|
||||
5. 接入 `BrowserMicrophoneInput` 和校准流程。
|
||||
6. 接入 Phaser host、Scene 和 asset manifest,占位素材先跑通视觉反馈。
|
||||
7. 做移动端视口和权限失败 smoke。
|
||||
8. 产品确认后再决定是否进入正式玩法类型、作品发布和后端真相链。
|
||||
|
||||
## 14. 关键技术决策
|
||||
|
||||
1. 默认采用 Phaser 3 + TypeScript + Vite,符合 2D 浏览器游戏默认路线。
|
||||
2. 核心 simulation 放在纯 TypeScript domain,严格不依赖 Phaser / Web Audio / DOM。
|
||||
3. Web Audio 只作为输入 adapter,输出归一化 `BarkAudioSample`。
|
||||
4. Phaser Scene 是 renderer,只消费 snapshot 和 visual events,不承载规则真相。
|
||||
5. HUD 使用 DOM overlay,承载权限、能量条、倒计时、结算和移动端响应式布局。
|
||||
6. MVP 不做复杂狗叫语义识别,先用音量峰值、持续时长、冷却和环境噪音校准。
|
||||
7. MVP 建议玩家 vs AI 单机 runtime,正式成绩、排行榜、发布和奖励后续再交给后端链路。
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
## 文档列表
|
||||
|
||||
- [BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md](./BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md):冻结“汪汪声浪大作战 / bark-battle”2D 浏览器 runtime 技术方案,明确 Phaser + TypeScript + Vite 选型、纯 TS simulation 与 Phaser renderer/DOM HUD 边界、Web Audio 输入适配、移动端权限降级和后续测试验证命令。
|
||||
- [CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md](./CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md):冻结儿童动作识别互动玩法 Demo 固定热身关的开发落地规格,覆盖横屏展示、摄像头背景虚化、角色剪影、绿色圆环 2 秒保持、动作教学、当前会话内空间边界记录和后续关卡安全暂停规则。
|
||||
- [RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md](./RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md):记录运行态输入设备抽象层,明确鼠标、触控、mocap 等设备统一归一为通用拖拽语义,玩法组件只负责解释目标和落点。
|
||||
- [RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md](./RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md):记录 `server-rs` Cargo 依赖集中配置口径,第三方版本和 workspace 内部 crate path 统一维护在根 `server-rs/Cargo.toml`,成员 crate 只保留 feature/optional 差异。
|
||||
|
||||
Reference in New Issue
Block a user