HTML5 重写:6 款游戏 + 用户系统 + 全局设置 + 5 轮迭代重构

- 新增 web/ 纯音频 HTML5 平台:共享引擎(手势/WebAudio 三层/设置/震动)、
  Node 零依赖服务器(静态 + 注册登录 + 进度存档 API)
- 游戏:剑与声(RPG 新作)、声命线、暗室(重设计)、音序回响(重设计)、
  节拍行者(跟随领奏重构)、盲航(同向闪避重构);声东击西删除
- 全局功能:男声/女声双套 TTS 资产、三档语速、BGM 音量三档、
  震动反馈语言(10 种震型)、服务器进度同步
- 资产:899 ogg + 899 m4a(Qwen3-TTS 双性别语音、Stable Audio 3 音效、
  Mureka BGM 4+3 首),终检零缺失
- 文档:docs/design/ 13 篇设计审查文档、docs/audio/ 资产管线、
  web/server/README.md API 文档
This commit is contained in:
SoundGame Dev
2026-08-29 21:55:44 +08:00
parent f0f0a9b50e
commit cc0f8c008d
1885 changed files with 11246 additions and 5 deletions
+106
View File
@@ -0,0 +1,106 @@
/**
* game.js — 游戏侧契约:Game 基类 + StateMachineGame 状态机基类。
* 对齐 Android 的 Game 接口 / GameEngine<S>(见 docs/architecture.md)。
*
* 每个游戏的接线方式(见 web/template/index.html 完整示例):
* const audio = new WebAudioEngine('audio/');
* const game = new MyGame(audio, document.getElementById('game-root'));
* await audio.showStartOverlay({ cover: 'cover.png' }); // 解锁音频
* game.start();
* attachGestures(document.getElementById('game-root'), (g) => game.onGesture(g));
*/
/**
* Game 基类(对齐 Android Game 接口)。
* 直接继承它,覆写三个方法即可;适合状态复杂的游戏
* (Android 端 storyline/reflex/escape 实际都是直接实现 Game 的)。
*/
export class Game {
/**
* @param {WebAudioEngine} audio 音频引擎(已解锁后才会调用 start)
* @param {HTMLElement} root 全屏根元素(#game-root),可用于改封面/DOM 提示
*/
constructor(audio, root) {
this.audio = audio;
this.root = root;
}
/**
* 手势入口。g ∈ 'up'|'down'|'left'|'right'|'tap'|'doubletap'
* 注意 'tap' 回调有约 300ms 延迟(双击判定窗口),即时反馈请用滑动/双击。
* @param {string} g
*/
onGesture(g) { /* 子类实现 */ }
/** 音频解锁后由页面调用一次。游戏从这里开始(播 BGM、开场白等)。 */
start() { /* 子类实现 */ }
/** 页面隐藏/退出时调用,应停止计时器与音频。可选实现。 */
stop() {
this.audio.stopAll();
}
}
/**
* 状态机基类(对齐 Android GameEngine<S>),线性剧情类游戏的推荐起点。
*
* 子类实现:
* initialState() -> 初始状态
* transition(state, g) -> 纯函数,返回新状态;返回原状态表示无转移
* onEnterState(state, prev) -> 副作用(播音频等),prev 初始为 null
*
* onGesture 是 final 逻辑:算 transition,状态变了才更新并回调 onEnterState。
*
* 示例:
* class MyGame extends StateMachineGame {
* initialState() { return 'INTRO'; }
* transition(s, g) {
* if (s === 'INTRO' && g === 'tap') return 'PLAY';
* return s;
* }
* onEnterState(s) {
* if (s === 'INTRO') this.audio.play('voice/intro', { onComplete: () => ... });
* }
* }
*/
export class StateMachineGame extends Game {
constructor(audio, root) {
super(audio, root);
this.state = null;
}
/** @returns {*} 初始状态 */
initialState() { throw new Error('implement initialState()'); }
/**
* 纯函数:由 (当前状态, 手势) 算新状态。返回原状态表示无转移。
* @param {*} state @param {string} g @returns {*}
*/
transition(state, g) { throw new Error('implement transition()'); }
/** 进入新状态的副作用(播音频)。prev 初始为 null。 */
onEnterState(state, prev) { /* 子类实现 */ }
onGesture(g) {
const next = this.transition(this.state, g);
if (next !== this.state) {
const prev = this.state;
this.state = next;
this.onEnterState(next, prev);
}
}
/** 强制切换状态(如计时器驱动、语音 onComplete 驱动)。 */
setState(next) {
if (next !== this.state) {
const prev = this.state;
this.state = next;
this.onEnterState(next, prev);
}
}
start() {
this.state = this.initialState();
this.onEnterState(this.state, null);
}
}