round2: 看门狗礼仪F1(不重播/单句升级/idle-skip) + 分档归因F2 + 交接count-in/answer F3 + 教学看表现F4-F5 + 警报去叠F6 + 回绕F7 + 开场瘦身F8 + 10句新语音 + 测试全绿(6playtest零改动+guide52断言+资产终检)
This commit is contained in:
+28
-7
@@ -10,6 +10,9 @@
|
||||
* full = earcon + 语音 + 震动;standard = earcon + 震动(无语音);off = 全静。
|
||||
* **off 时全部 API 静默 no-op,包括不挂任何定时器**——保证既有测试/玩法零干扰。
|
||||
* - 向导绝不打断内容:插播语音一律 queue:true(等当前对白播完,绝不顶掉)。
|
||||
* **error / watchdog 语音例外,为 idle-only(round2 F1.4)**:对白在播
|
||||
* (audio.isVoicePlaying())时跳过语音只发事件(事件带 skipped:true)——
|
||||
* 纠错/看门狗句排到长链尾部会迟到误导,宁可不说;turn 语音保持 queue 不变。
|
||||
*
|
||||
* 游戏侧用法(构造时创建一次):
|
||||
* import { Guide } from '../engine/guide.js';
|
||||
@@ -149,6 +152,8 @@ export class Guide {
|
||||
|
||||
/**
|
||||
* A4 纠错:error 震(除非 skipHaptic——游戏层已震过时传 true)+ full 档语音。
|
||||
* 语音为 idle-only(F1.4):对白在播时跳过只发 skipped 事件,且不占 onceKey
|
||||
* (玩家没听到不算说过,下次空闲再纠错仍可播一次)。
|
||||
* @param {{voice?: string|null, onceKey?: string|null, skipHaptic?: boolean}} [opts]
|
||||
*/
|
||||
error(opts = {}) {
|
||||
@@ -156,18 +161,23 @@ export class Guide {
|
||||
this._touch();
|
||||
if (!opts.skipHaptic) vibrate('error');
|
||||
if (!opts.voice) return;
|
||||
if (this.level() !== 'full') return;
|
||||
if (this.audio.isVoicePlaying()) {
|
||||
this._emit('error', 'error', opts.voice, true);
|
||||
return;
|
||||
}
|
||||
if (opts.onceKey) {
|
||||
if (this._once.has(opts.onceKey)) return;
|
||||
if (this.level() === 'full') this._once.add(opts.onceKey); // 只在真的播了才计数
|
||||
this._once.add(opts.onceKey); // 只在真的播了才计数
|
||||
}
|
||||
if (this.level() !== 'full') return;
|
||||
this._say('error', opts.voice);
|
||||
}
|
||||
|
||||
/**
|
||||
* L4 静默看门狗:arm 后按 at 逐级计时,每级播 nudge 轻 earcon(+ say[n-1] 语音,
|
||||
* full 档),再调 onEscalate(n);级数用尽自动停止。cond() 在触发时刻返回 false
|
||||
* 则静默取消(游戏已离开该等待态)。任何 guide API 调用 / disarm() / 再次 arm 复位。
|
||||
* full 档,idle-only——对白在播时跳过只发事件),再调 onEscalate(n);级数用尽
|
||||
* 自动停止。cond() 在触发时刻返回 false 则静默取消(游戏已离开该等待态)。
|
||||
* 任何 guide API 调用 / disarm() / 再次 arm 复位。
|
||||
* @param {{arm?: boolean, at?: number[], say?: (string|null)[], onEscalate?: (n:number)=>void, cond?: ()=>boolean}} opts
|
||||
*/
|
||||
watchdog(opts = {}) {
|
||||
@@ -194,16 +204,27 @@ export class Guide {
|
||||
return this.level() === 'off';
|
||||
}
|
||||
|
||||
_emit(type, name, voiceId) {
|
||||
_emit(type, name, voiceId, skipped = false) {
|
||||
if (!this._hook) return;
|
||||
try {
|
||||
this._hook({ type, name, voiceId: voiceId ?? null });
|
||||
const evt = { type, name, voiceId: voiceId ?? null };
|
||||
if (skipped) evt.skipped = true;
|
||||
this._hook(evt);
|
||||
} catch (_) { /* 钩子异常不影响游戏 */ }
|
||||
}
|
||||
|
||||
/** full 档语音插播:queue:true 排到当前对白之后,绝不顶掉正在播的语音。 */
|
||||
/**
|
||||
* full 档语音插播:queue:true 排到当前对白之后,绝不顶掉正在播的语音。
|
||||
* F1.4 idle-only:其中 error/watchdog 两类在对白在播(audio.isVoicePlaying())
|
||||
* 时跳过语音只发事件(事件带 skipped:true)——排到链尾会迟到误导;turn 保持
|
||||
* queue 不变。
|
||||
*/
|
||||
_say(type, voiceId) {
|
||||
if (this.level() !== 'full') return;
|
||||
if ((type === 'error' || type === 'watchdog') && this.audio.isVoicePlaying()) {
|
||||
this._emit(type, type, voiceId, true);
|
||||
return;
|
||||
}
|
||||
this._emit(type, type, voiceId);
|
||||
this.audio.play(`voice/${voiceId}`, { queue: true });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user