guide层v1: 全局向导交互层落地(状态签名earcon/turn cue/静默看门狗/纠错) + 6游戏接线 + 8句向导语音 + 测试全绿
- engine/guide.js 运行时合成8个签名earcon(方法论L2硬参数),off档零定时器,插播queue不顶对白 - settings: guide三档(full/standard/off默认full) + 语速2.2视障档;audio: isVoicePlaying()/setRateBoostEnabled() 清两个已知问题 - 6游戏按schemes§2接线: storyline看门狗+无效手势纠错、simon免扣重听看门狗、escape turn cue+提前卡关提示、rhythm gd_answer/gd_miss+结算看门狗+快进豁免、space首撞指令提示+结算看门狗、rpg格挡纠错+60s催促+状态签名 - 大厅设置面板+向导文档(14-guide-layer.md)+00-global-ux §6;R5台词表补4条一致性 - 试玩转写含向导事件;6 playtest(guide=off断言零改动)+guide.test(34断言)+资产终检全绿
This commit is contained in:
+38
-2
@@ -22,6 +22,7 @@
|
||||
* 按需解码 + AudioBuffer 缓存,见 docs/web-engine-api.md)。
|
||||
*/
|
||||
import { Game } from '../engine/game.js';
|
||||
import { Guide } from '../engine/guide.js';
|
||||
|
||||
/** 剧情节点。voices 顺序播放;prompt 非空表示这是一个选择节点。 */
|
||||
function node(voices, opts = {}) {
|
||||
@@ -81,9 +82,13 @@ const VOICE_WATCHDOG_MS = 30000;
|
||||
/** 抉择提示音 id(voice/p1~p8):播放中单击不可跳过(12 号文档 §1.2)。 */
|
||||
const PROMPT_RE = /^voice\/p\d/;
|
||||
|
||||
// 向导层(schemes.md §2):W1 静默看门狗阈值——8s 轻提醒+gd_continue,20s 升级重播帮助
|
||||
const GUIDE_WATCHDOG_AT = [8000, 20000];
|
||||
|
||||
export class StorylineGame extends Game {
|
||||
constructor(audio, root) {
|
||||
super(audio, root);
|
||||
this.guide = new Guide(audio); // 全局向导层(guide='off' 时全部静默 no-op)
|
||||
this.mode = MODE.STORY;
|
||||
this.nodeId = 'n00';
|
||||
this.voicePlaying = false;
|
||||
@@ -99,12 +104,17 @@ export class StorylineGame extends Game {
|
||||
this.companion = false; // 带上了小满
|
||||
this.knowTrap = false; // 装死时听到北区陷阱的情报
|
||||
this.betrayed = false; // 向猎狗交出过坐标
|
||||
|
||||
// 向导层:升级帮助播过后不再自动重挂看门狗(L4"之后停止,不轰炸"),
|
||||
// 玩家下一次手势即复位
|
||||
this._guideHelped = false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Game
|
||||
|
||||
start() {
|
||||
this.audio.play('bgm/dark_drone');
|
||||
this.guide.state('explore'); // 状态签名:剧情/探索(与状态 BGM 同点,10 号文档)
|
||||
// 预解码全部短音效(等价 Android 的 SoundPool 音量 0 预热)
|
||||
this.audio.prewarm(ALL_SFX);
|
||||
this.playNode('n00');
|
||||
@@ -112,6 +122,8 @@ export class StorylineGame extends Game {
|
||||
|
||||
onGesture(g) {
|
||||
console.log(`[StorylineGame] onGesture: ${g} mode=${this.mode} node=${this.nodeId} armed=${this.choiceArmed} playing=${this.voicePlaying}`);
|
||||
this.guide.disarm(); // 任何手势复位静默看门狗(guide=off 时 no-op)
|
||||
this._guideHelped = false; // 玩家有动作,恢复看门狗自动挂载
|
||||
if (this.mode === MODE.MENU) this.onMenuGesture(g);
|
||||
else this.onStoryGesture(g);
|
||||
}
|
||||
@@ -134,7 +146,12 @@ export class StorylineGame extends Game {
|
||||
break;
|
||||
case 'left':
|
||||
case 'right': {
|
||||
if (node.prompt == null) return;
|
||||
if (node.prompt == null) {
|
||||
// 无效手势零反馈是全 6 款最盲点(清点 §1.4):向导纠错——error 震 +
|
||||
// full 档 gd_invalid(onceKey 同局只播一次;off 档整段 no-op)
|
||||
this.guide.error({ voice: 'gd_invalid', onceKey: 'storyline_invalid' });
|
||||
return;
|
||||
}
|
||||
if (this.choiceArmed) this.choose(g);
|
||||
else if (this.voicePlaying) this.pendingChoice = g; // 提示音播放中,缓存抉择
|
||||
break;
|
||||
@@ -220,10 +237,26 @@ export class StorylineGame extends Game {
|
||||
this.pendingChoice = null;
|
||||
this.choose(g);
|
||||
}
|
||||
// 抉择提示播完即可选(W2 契约):此处不可插任何语音,也不挂看门狗
|
||||
} else if (node.ending) {
|
||||
this.awaitingRestart = true;
|
||||
}
|
||||
// 否则等待单击继续
|
||||
// W1/W3 静默等待看门狗(向导层,schemes §2):8s nudge+gd_continue;
|
||||
// 20s 升级 = 复用 playHelpAndResume(帮助+进度+重播当前节点),升级后不再
|
||||
// 自动重挂(防循环轰炸),玩家下一次手势恢复。抉择态与语音播放中不挂。
|
||||
if (node.prompt == null && !this._guideHelped) {
|
||||
this.guide.watchdog({
|
||||
arm: true,
|
||||
at: GUIDE_WATCHDOG_AT,
|
||||
say: ['gd_continue', null],
|
||||
onEscalate: (n) => {
|
||||
if (n !== 2 || this.mode !== MODE.STORY) return;
|
||||
this._guideHelped = true;
|
||||
this.playHelpAndResume();
|
||||
},
|
||||
cond: () => this.mode === MODE.STORY && !this.voicePlaying,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 顺序播放一组对白,全部自然播完后回调。被打断时回调作废(seqToken 机制)。 */
|
||||
@@ -302,6 +335,7 @@ export class StorylineGame extends Game {
|
||||
this.audio.stopVoice();
|
||||
this.audio.play('sfx/ui_menu');
|
||||
this.audio.play('bgm/menu', { fadeMs: 500 }); // 状态 BGM:菜单 pad(10 号文档 §2)
|
||||
this.guide.state('menu');
|
||||
this.playSeq(['voice/menu'], () => { this.voicePlaying = false; });
|
||||
}
|
||||
|
||||
@@ -313,6 +347,7 @@ export class StorylineGame extends Game {
|
||||
this.mode = MODE.STORY;
|
||||
this.audio.play('sfx/ui_confirm');
|
||||
this.audio.play('bgm/dark_drone', { fadeMs: 800 }); // 关菜单淡回主题 drone
|
||||
this.guide.state('explore');
|
||||
this.playNode(this.nodeId); // 回到故事,重播当前节点
|
||||
}
|
||||
|
||||
@@ -325,6 +360,7 @@ export class StorylineGame extends Game {
|
||||
this.mode = MODE.STORY;
|
||||
this.audio.play('sfx/ui_confirm');
|
||||
this.audio.play('bgm/dark_drone'); // 从菜单重开时切回主题(结局重开为同首不重启)
|
||||
this.guide.state('explore');
|
||||
this.playNode('n00');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user