第二批反馈:引擎改造(限幅/SoundTouch不变调变速/长按快进/跳句/iOS闪光降级) + 6游戏状态BGM与试玩测试全绿

This commit is contained in:
SoundGame Dev
2026-08-30 16:45:42 +08:00
parent cc0f8c008d
commit 9a915d62c0
1163 changed files with 6346 additions and 208 deletions
+60 -1
View File
@@ -2,6 +2,7 @@
* gesture.js — 6 手势识别(触摸 + 鼠标双支持),对齐 Android GestureEngine 行为。
*
* 手势集合:'up' | 'down' | 'left' | 'right' | 'tap' | 'doubletap'
* | 'longpress' | 'longpressend'
*
* 判定规则(与 Android 版 GestureConfig 一致):
* - 滑动:累积位移 >= swipeMinDistance(默认 64px ≈ 64dp@160dpi)
@@ -10,6 +11,12 @@
* - 单击/双击:位移 < tapSlop 视为点按;双击窗口 doubleTapWindow(默认 300ms)。
* **单击回调有约 300ms 延迟**(要等双击窗口关闭才能确定不是双击),
* 即时反馈类操作请绑定滑动或双击,单击只用于非即时操作。
* - **长按**:按住不动 >= longpressMs(默认 450ms)触发 'longpress',
* 松开触发 'longpressend';长按期间不触发 tap;位移超 tapSlop 取消
* (已激活的长按会先补发 'longpressend',之后滑动照常判定)。
* 长按触发/结束同时派发 window 事件 'soundgame:longpress' /
* 'soundgame:longpressend'——audio.js 内置监听:voice 播放中长按 =
* 旁白临时 1.7 倍速(不变调),松开恢复设置语速,游戏代码零改动。
*
* 用法:
* import { attachGestures } from '../engine/gesture.js';
@@ -32,8 +39,17 @@ export const DEFAULT_GESTURE_CONFIG = {
swipeMinVelocity: 400, // px/s,平均速度阈值(位移 / 拖动时长)
doubleTapWindow: 300, // ms,双击判定窗口(单击因此延迟 ~300ms 回调)
tapSlop: 24, // px,位移小于此值才算点按
longpressMs: 450, // ms,按住不动超过此值触发 longpress
};
/** 长按引擎事件:audio.js 监听它实现"长按=旁白临时倍速"。 */
function _dispatchLongpress(type) {
if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function'
&& typeof CustomEvent === 'function') {
window.dispatchEvent(new CustomEvent(`soundgame:${type}`));
}
}
export function attachGestures(element, onGesture, config = {}) {
const cfg = { ...DEFAULT_GESTURE_CONFIG, ...config };
@@ -42,6 +58,10 @@ export function attachGestures(element, onGesture, config = {}) {
let startX = 0, startY = 0, startTime = 0;
let moved = false; // 位移是否已超 tapSlop(超过则本次不可能是点按)
// 长按状态
let longPressTimer = null;
let longPressActive = false;
// 点按状态
let lastTapTime = 0;
let pendingTapTimer = null;
@@ -56,22 +76,57 @@ export function attachGestures(element, onGesture, config = {}) {
}
}
function cancelLongPress() {
if (longPressTimer !== null) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
}
/** 长按已激活后收尾(松开/滑动取消):补发 longpressend。 */
function endLongPress() {
if (!longPressActive) return;
longPressActive = false;
_dispatchLongpress('longpressend');
onGesture('longpressend');
}
function handleDown(x, y) {
tracking = true;
moved = false;
startX = x;
startY = y;
startTime = performance.now();
cancelLongPress();
longPressTimer = setTimeout(() => {
longPressTimer = null;
if (!tracking || moved) return;
// 触发长按:作废等待中的单击
cancelPendingTap();
longPressActive = true;
_dispatchLongpress('longpress');
onGesture('longpress');
}, cfg.longpressMs);
}
function handleMove(x, y) {
if (!tracking) return;
if (Math.hypot(x - startX, y - startY) > cfg.tapSlop) moved = true;
if (Math.hypot(x - startX, y - startY) > cfg.tapSlop) {
moved = true;
cancelLongPress(); // 滑动取消长按判定
endLongPress(); // 已激活的长按被滑动终止(之后滑动照常判定)
}
}
function handleUp(x, y) {
if (!tracking) return;
tracking = false;
cancelLongPress();
if (longPressActive) {
endLongPress();
lastTapTime = 0;
return; // 长按后不触发 tap/doubletap
}
const dx = x - startX;
const dy = y - startY;
@@ -115,6 +170,8 @@ export function attachGestures(element, onGesture, config = {}) {
function handleCancel() {
tracking = false;
cancelLongPress();
endLongPress();
}
// ---- 触摸 ----
@@ -178,6 +235,8 @@ export function attachGestures(element, onGesture, config = {}) {
return function detach() {
cancelPendingTap();
cancelLongPress();
endLongPress();
element.removeEventListener('touchstart', onTouchStart, opts);
element.removeEventListener('touchmove', onTouchMove, opts);
element.removeEventListener('touchend', onTouchEnd, opts);