v2收尾:手势升级 + 暂停退出 + 结局语音化 + 分支补全 + 12条新音频
gestures.js: 新增长按(1s)、两指长按(暂停)、三指点击(退出),支持多指检测 engine.js: REPLY_COUNT变量、长按重播/状态广播、暂停恢复退出机制、 选择提示音、结局台词预录音频替换TTS、内容预警+心理热线 story.js: MSG-10/11 follow-up补全down(沉默)选项 index.html: 操作指南添加长按/安全手势、内容预警文字 batch_tts_voxcpm.py: 新增12条台词(结局旁白+系统提示+分支NPC反应) audio/mp3/tts: 12个VoxCPM2生成的林夏声线MP3 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+150
-9
@@ -1,7 +1,8 @@
|
||||
// gestures.js — 触控手势检测器(简化版)
|
||||
// gestures.js — 触控手势检测器
|
||||
//
|
||||
// 仅支持 6 种手势:单击 · 双击 · 左滑 · 右滑 · 上滑 · 下滑
|
||||
// 无长按,无多指操作
|
||||
// 支持 9 种手势:
|
||||
// 单击 · 双击 · 长按(1s) · 左滑 · 右滑 · 上滑 · 下滑
|
||||
// 两指长按(1s) · 三指点击
|
||||
|
||||
class GestureDetector {
|
||||
constructor(el) {
|
||||
@@ -14,6 +15,15 @@ class GestureDetector {
|
||||
this._tapTimer = null;
|
||||
this._tapCount = 0;
|
||||
|
||||
// 长按检测
|
||||
this._longPressTimer = null;
|
||||
this._longPressTriggered = false;
|
||||
|
||||
// 多指检测
|
||||
this._maxTouches = 0; // 本次触摸中同时出现的最大手指数
|
||||
this._multiTouchLPTimer = null; // 两指长按计时器
|
||||
this._multiTouchHandled = false;
|
||||
|
||||
this._bindTouch();
|
||||
this._bindMouse();
|
||||
this._bindKeyboard();
|
||||
@@ -30,21 +40,105 @@ class GestureDetector {
|
||||
// ── 触摸事件 ──
|
||||
_bindTouch() {
|
||||
const el = this._el;
|
||||
|
||||
el.addEventListener('touchstart', e => {
|
||||
e.preventDefault();
|
||||
const t = e.touches[0];
|
||||
this._startX = t.clientX;
|
||||
this._startY = t.clientY;
|
||||
this._startTime = Date.now();
|
||||
const count = e.touches.length;
|
||||
|
||||
// 记录本次触摸出现过的最大手指数
|
||||
if (count > this._maxTouches) this._maxTouches = count;
|
||||
|
||||
if (count === 1) {
|
||||
// 单指:记录起点 + 启动长按计时
|
||||
const t = e.touches[0];
|
||||
this._startX = t.clientX;
|
||||
this._startY = t.clientY;
|
||||
this._startTime = Date.now();
|
||||
this._longPressTriggered = false;
|
||||
|
||||
this._longPressTimer = setTimeout(() => {
|
||||
this._longPressTriggered = true;
|
||||
this._longPressTimer = null;
|
||||
this._emit('longpress');
|
||||
}, 1000);
|
||||
|
||||
} else if (count === 2) {
|
||||
// 两指:取消单指长按,启动两指长按计时
|
||||
this._cancelLongPress();
|
||||
this._multiTouchLPTimer = setTimeout(() => {
|
||||
this._multiTouchHandled = true;
|
||||
this._multiTouchLPTimer = null;
|
||||
this._emit('twofinger_longpress');
|
||||
}, 1000);
|
||||
|
||||
} else if (count >= 3) {
|
||||
// 三指+:取消所有计时
|
||||
this._cancelLongPress();
|
||||
this._cancelMultiTouchLP();
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
el.addEventListener('touchmove', e => {
|
||||
e.preventDefault();
|
||||
// 移动超 15px 取消长按
|
||||
if (this._longPressTimer && e.touches.length === 1) {
|
||||
const t = e.touches[0];
|
||||
const dx = t.clientX - this._startX;
|
||||
const dy = t.clientY - this._startY;
|
||||
if (Math.sqrt(dx * dx + dy * dy) > 15) {
|
||||
this._cancelLongPress();
|
||||
}
|
||||
}
|
||||
// 两指移动也取消两指长按
|
||||
if (this._multiTouchLPTimer) {
|
||||
this._cancelMultiTouchLP();
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
el.addEventListener('touchend', e => {
|
||||
e.preventDefault();
|
||||
|
||||
// 还有手指在屏幕上,等全部抬起再处理
|
||||
if (e.touches.length > 0) return;
|
||||
|
||||
// 全部手指抬起 — 根据 maxTouches 判定手势类型
|
||||
const max = this._maxTouches;
|
||||
|
||||
// 清理计时器
|
||||
this._cancelLongPress();
|
||||
this._cancelMultiTouchLP();
|
||||
|
||||
// 已在计时器中处理过(长按 / 两指长按)
|
||||
if (this._longPressTriggered || this._multiTouchHandled) {
|
||||
this._resetMultiTouch();
|
||||
return;
|
||||
}
|
||||
|
||||
// 三指点击
|
||||
if (max >= 3) {
|
||||
this._resetMultiTouch();
|
||||
this._emit('threefinger_tap');
|
||||
return;
|
||||
}
|
||||
|
||||
// 两指快速触摸(< 1s)→ 忽略
|
||||
if (max === 2) {
|
||||
this._resetMultiTouch();
|
||||
return;
|
||||
}
|
||||
|
||||
// 单指:正常处理(滑动 / 点击)
|
||||
this._resetMultiTouch();
|
||||
const t = e.changedTouches[0];
|
||||
this._process(t.clientX, t.clientY);
|
||||
}, { passive: false });
|
||||
|
||||
el.addEventListener('touchcancel', () => this._reset(), { passive: false });
|
||||
el.addEventListener('touchcancel', () => {
|
||||
this._cancelLongPress();
|
||||
this._cancelMultiTouchLP();
|
||||
this._resetMultiTouch();
|
||||
this._reset();
|
||||
}, { passive: false });
|
||||
}
|
||||
|
||||
// ── 鼠标事件(桌面调试)──
|
||||
@@ -53,10 +147,31 @@ class GestureDetector {
|
||||
this._startX = e.clientX;
|
||||
this._startY = e.clientY;
|
||||
this._startTime = Date.now();
|
||||
this._longPressTriggered = false;
|
||||
|
||||
this._longPressTimer = setTimeout(() => {
|
||||
this._longPressTriggered = true;
|
||||
this._longPressTimer = null;
|
||||
this._emit('longpress');
|
||||
}, 1000);
|
||||
});
|
||||
this._el.addEventListener('mouseup', e => {
|
||||
this._cancelLongPress();
|
||||
if (this._longPressTriggered) {
|
||||
this._longPressTriggered = false;
|
||||
return;
|
||||
}
|
||||
this._process(e.clientX, e.clientY);
|
||||
});
|
||||
this._el.addEventListener('mousemove', e => {
|
||||
if (this._longPressTimer) {
|
||||
const dx = e.clientX - this._startX;
|
||||
const dy = e.clientY - this._startY;
|
||||
if (Math.sqrt(dx * dx + dy * dy) > 15) {
|
||||
this._cancelLongPress();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── 键盘事件(桌面调试)──
|
||||
@@ -69,11 +184,14 @@ class GestureDetector {
|
||||
case 'ArrowRight': e.preventDefault(); this._emit('swipe_right'); break;
|
||||
case 'ArrowUp': e.preventDefault(); this._emit('swipe_up'); break;
|
||||
case 'ArrowDown': e.preventDefault(); this._emit('swipe_down'); break;
|
||||
case 'KeyL': e.preventDefault(); this._emit('longpress'); break;
|
||||
case 'KeyP': e.preventDefault(); this._emit('twofinger_longpress'); break;
|
||||
case 'KeyQ': e.preventDefault(); this._emit('threefinger_tap'); break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── 手势判定 ──
|
||||
// ── 手势判定(单指) ──
|
||||
_process(endX, endY) {
|
||||
const dx = endX - this._startX;
|
||||
const dy = endY - this._startY;
|
||||
@@ -114,8 +232,31 @@ class GestureDetector {
|
||||
}
|
||||
}
|
||||
|
||||
// ── 工具方法 ──
|
||||
|
||||
_cancelLongPress() {
|
||||
if (this._longPressTimer) {
|
||||
clearTimeout(this._longPressTimer);
|
||||
this._longPressTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
_cancelMultiTouchLP() {
|
||||
if (this._multiTouchLPTimer) {
|
||||
clearTimeout(this._multiTouchLPTimer);
|
||||
this._multiTouchLPTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
_resetMultiTouch() {
|
||||
this._maxTouches = 0;
|
||||
this._multiTouchHandled = false;
|
||||
}
|
||||
|
||||
_reset() {
|
||||
clearTimeout(this._tapTimer);
|
||||
this._tapCount = 0;
|
||||
this._longPressTriggered = false;
|
||||
this._resetMultiTouch();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user