diff --git a/assets/common.js b/assets/common.js
index baf6b7a..2b03851 100644
--- a/assets/common.js
+++ b/assets/common.js
@@ -180,3 +180,12 @@ function thumbSVG(it){
``+
``;
}
+
+// ---------- 全局音效开关(与 world.html 设置面板共用) ----------
+function loadWorldSFXSetting() {
+ try { return localStorage.getItem('world_sfx_enabled') !== 'false'; }
+ catch (e) { return true; }
+}
+function saveWorldSFXSetting(enabled) {
+ try { localStorage.setItem('world_sfx_enabled', enabled ? 'true' : 'false'); } catch (e) {}
+}
diff --git a/index.html b/index.html
index f7f00c4..bf2d4c5 100644
--- a/index.html
+++ b/index.html
@@ -87,10 +87,100 @@
.sl-dots{right:16px;gap:10px;}
.hero .enter-btn{font-size:15px;padding:11px 30px;}
}
+
+ /* 进游戏开场动画(yusen 科技感)—— 总时长约 5.2s */
+ #introSplash {
+ position: fixed; inset: 0; z-index: 600; overflow: hidden;
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
+ background:
+ radial-gradient(ellipse at 50% 48%, rgba(20,44,78,.55), rgba(3,5,10,.97) 70%),
+ #03050a;
+ opacity: 0; visibility: hidden; transition: opacity .5s ease;
+ }
+ #introSplash.show { opacity: 1; visibility: visible; }
+ #introSplash.hide { opacity: 0; visibility: hidden; transition: opacity .45s ease; }
+ #introSplash .intro-grid {
+ position: absolute; inset: -50%;
+ background-image:
+ linear-gradient(rgba(74,175,255,.09) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(74,175,255,.09) 1px, transparent 1px);
+ background-size: 44px 44px;
+ transform: perspective(440px) rotateX(62deg);
+ animation: introGridMove 3.2s linear infinite;
+ -webkit-mask-image: radial-gradient(ellipse at 50% 38%, #000 8%, transparent 72%);
+ mask-image: radial-gradient(ellipse at 50% 38%, #000 8%, transparent 72%);
+ }
+ @keyframes introGridMove { from { background-position: 0 0; } to { background-position: 0 44px; } }
+ #introSplash .intro-sweep {
+ position: absolute; top: 0; bottom: 0; width: 42%; left: -42%;
+ transform: skewX(-18deg); z-index: 3; pointer-events: none;
+ background: linear-gradient(90deg, transparent, rgba(140,210,255,.20), transparent);
+ }
+ #introSplash.show .intro-sweep { animation: introSweep 1.8s ease-in-out .6s 1; }
+ @keyframes introSweep { 0% { left: -42%; } 100% { left: 110%; } }
+ #introSplash .intro-logo {
+ position: relative; z-index: 2; margin: 0;
+ font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif;
+ font-weight: 800; font-size: clamp(48px, 12vw, 132px);
+ letter-spacing: .22em; text-indent: .22em; text-transform: lowercase;
+ color: #eaf6ff; opacity: 0; transform: scale(.92); filter: blur(8px);
+ text-shadow: 0 0 18px rgba(74,175,255,.7), 0 0 42px rgba(74,175,255,.35);
+ }
+ #introSplash.show .intro-logo { animation: introLogoIn 2s cubic-bezier(.16,1,.3,1) .2s forwards, introHoldPulse 1.6s ease-in-out 3.6s 1 forwards; }
+ @keyframes introLogoIn {
+ 0% { opacity: 0; transform: scale(.92); filter: blur(8px); }
+ 30% { opacity: 1; transform: scale(1.04); filter: blur(0); text-shadow: 3px 0 rgba(255,46,86,.55), -3px 0 rgba(40,200,255,.55), 0 0 18px rgba(74,175,255,.7); }
+ 48% { text-shadow: 0 0 18px rgba(74,175,255,.7), 0 0 42px rgba(74,175,255,.35); }
+ 100% { opacity: 1; transform: scale(1); filter: blur(0); }
+ }
+ @keyframes introHoldPulse {
+ 0% { text-shadow: 0 0 18px rgba(74,175,255,.7), 0 0 42px rgba(74,175,255,.35); }
+ 50% { text-shadow: 0 0 28px rgba(74,175,255,.9), 0 0 72px rgba(74,175,255,.5), 0 0 120px rgba(138,92,246,.25); }
+ 100% { text-shadow: 0 0 18px rgba(74,175,255,.7), 0 0 42px rgba(74,175,255,.35); }
+ }
+ #introSplash .intro-line {
+ position: relative; z-index: 2; margin-top: 18px; height: 2px; width: 0;
+ background: linear-gradient(90deg, transparent, #4af, #a8e6cf, transparent);
+ box-shadow: 0 0 12px rgba(74,175,255,.7);
+ }
+ #introSplash.show .intro-line { animation: introLine 1.4s ease-out 1.4s forwards; }
+ @keyframes introLine { to { width: min(320px, 60vw); } }
+ #introSplash .intro-sub {
+ position: relative; z-index: 2; margin-top: 14px; font-size: 13px;
+ letter-spacing: .4em; text-indent: .4em; color: rgba(150,200,255,.55); opacity: 0;
+ }
+ #introSplash.show .intro-sub { animation: introFade 1.2s ease 2.6s forwards; }
+ #introSplash .intro-skip {
+ position: absolute; bottom: 26px; z-index: 4; font-size: 12px; letter-spacing: .15em;
+ color: rgba(180,210,255,.4); opacity: 0;
+ }
+ #introSplash.show .intro-skip { animation: introFade 1s ease 3.6s forwards; }
+ #introSplash .intro-audio-unlock {
+ position: absolute; bottom: 58px; z-index: 5; cursor: pointer;
+ padding: 9px 20px; border-radius: 999px; font-size: 13px; letter-spacing: .12em;
+ color: #eaf6ff; background: rgba(74,175,255,.12);
+ border: 1px solid rgba(74,175,255,.5); box-shadow: 0 0 18px rgba(74,175,255,.35);
+ -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
+ opacity: 0; transition: opacity .4s ease, transform .2s ease;
+ }
+ #introSplash.show .intro-audio-unlock { opacity: 1; }
+ #introSplash .intro-audio-unlock:hover { transform: scale(1.05); }
+ #introSplash.hide .intro-audio-unlock { opacity: 0; }
+ @keyframes introFade { to { opacity: 1; } }
+
+
+
+
+
yusen
+
+
宇森游戏
+
+
点击任意位置跳过
+
宇森
@@ -250,21 +340,172 @@ document.getElementById('enterGame').addEventListener('click', async ()=>{
diff --git a/world.html b/world.html
index dada74f..350fb9b 100644
--- a/world.html
+++ b/world.html
@@ -606,6 +606,86 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
#loadingScreen .loadFill { height: 100%; width: 0%; background: linear-gradient(90deg, #4af, #a8e6cf); border-radius: 6px; transition: width 0.3s; }
#loadingScreen .loadPct { font-size: 20px; color: #4af; font-variant-numeric: tabular-nums; letter-spacing: 1px; }
+/* 进游戏开场动画(yusen 科技感)—— 总时长约 5.2s */
+#introSplash {
+ position: fixed; inset: 0; z-index: 600; overflow: hidden;
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
+ background:
+ radial-gradient(ellipse at 50% 48%, rgba(20,44,78,.55), rgba(3,5,10,.97) 70%),
+ #03050a;
+ opacity: 0; visibility: hidden; transition: opacity .5s ease;
+}
+#introSplash.show { opacity: 1; visibility: visible; }
+#introSplash.hide { opacity: 0; visibility: hidden; transition: opacity .45s ease; }
+#introSplash .intro-grid {
+ position: absolute; inset: -50%;
+ background-image:
+ linear-gradient(rgba(74,175,255,.09) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(74,175,255,.09) 1px, transparent 1px);
+ background-size: 44px 44px;
+ transform: perspective(440px) rotateX(62deg);
+ animation: introGridMove 3.2s linear infinite;
+ -webkit-mask-image: radial-gradient(ellipse at 50% 38%, #000 8%, transparent 72%);
+ mask-image: radial-gradient(ellipse at 50% 38%, #000 8%, transparent 72%);
+}
+@keyframes introGridMove { from { background-position: 0 0; } to { background-position: 0 44px; } }
+#introSplash .intro-sweep {
+ position: absolute; top: 0; bottom: 0; width: 42%; left: -42%;
+ transform: skewX(-18deg); z-index: 3; pointer-events: none;
+ background: linear-gradient(90deg, transparent, rgba(140,210,255,.20), transparent);
+}
+#introSplash.show .intro-sweep { animation: introSweep 1.8s ease-in-out .6s 1; }
+@keyframes introSweep { 0% { left: -42%; } 100% { left: 110%; } }
+#introSplash .intro-logo {
+ position: relative; z-index: 2; margin: 0;
+ font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif;
+ font-weight: 800; font-size: clamp(48px, 12vw, 132px);
+ letter-spacing: .22em; text-indent: .22em; text-transform: lowercase;
+ color: #eaf6ff; opacity: 0; transform: scale(.92); filter: blur(8px);
+ text-shadow: 0 0 18px rgba(74,175,255,.7), 0 0 42px rgba(74,175,255,.35);
+}
+#introSplash.show .intro-logo { animation: introLogoIn 2s cubic-bezier(.16,1,.3,1) .2s forwards, introHoldPulse 1.6s ease-in-out 3.6s 1 forwards; }
+@keyframes introLogoIn {
+ 0% { opacity: 0; transform: scale(.92); filter: blur(8px); }
+ 30% { opacity: 1; transform: scale(1.04); filter: blur(0); text-shadow: 3px 0 rgba(255,46,86,.55), -3px 0 rgba(40,200,255,.55), 0 0 18px rgba(74,175,255,.7); }
+ 48% { text-shadow: 0 0 18px rgba(74,175,255,.7), 0 0 42px rgba(74,175,255,.35); }
+ 100% { opacity: 1; transform: scale(1); filter: blur(0); }
+}
+@keyframes introHoldPulse {
+ 0% { text-shadow: 0 0 18px rgba(74,175,255,.7), 0 0 42px rgba(74,175,255,.35); }
+ 50% { text-shadow: 0 0 28px rgba(74,175,255,.9), 0 0 72px rgba(74,175,255,.5), 0 0 120px rgba(138,92,246,.25); }
+ 100% { text-shadow: 0 0 18px rgba(74,175,255,.7), 0 0 42px rgba(74,175,255,.35); }
+}
+#introSplash .intro-line {
+ position: relative; z-index: 2; margin-top: 18px; height: 2px; width: 0;
+ background: linear-gradient(90deg, transparent, #4af, #a8e6cf, transparent);
+ box-shadow: 0 0 12px rgba(74,175,255,.7);
+}
+#introSplash.show .intro-line { animation: introLine 1.4s ease-out 1.4s forwards; }
+@keyframes introLine { to { width: min(320px, 60vw); } }
+#introSplash .intro-sub {
+ position: relative; z-index: 2; margin-top: 14px; font-size: 13px;
+ letter-spacing: .4em; text-indent: .4em; color: rgba(150,200,255,.55); opacity: 0;
+}
+#introSplash.show .intro-sub { animation: introFade 1.2s ease 2.6s forwards; }
+#introSplash .intro-skip {
+ position: absolute; bottom: 26px; z-index: 4; font-size: 12px; letter-spacing: .15em;
+ color: rgba(180,210,255,.4); opacity: 0;
+}
+#introSplash.show .intro-skip { animation: introFade 1s ease 3.6s forwards; }
+#introSplash .intro-audio-unlock {
+ position: absolute; bottom: 58px; z-index: 5; cursor: pointer;
+ padding: 9px 20px; border-radius: 999px; font-size: 13px; letter-spacing: .12em;
+ color: #eaf6ff; background: rgba(74,175,255,.12);
+ border: 1px solid rgba(74,175,255,.5); box-shadow: 0 0 18px rgba(74,175,255,.35);
+ -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
+ opacity: 0; transition: opacity .4s ease, transform .2s ease;
+}
+#introSplash.show .intro-audio-unlock { opacity: 1; }
+#introSplash .intro-audio-unlock:hover { transform: scale(1.05); }
+#introSplash.hide .intro-audio-unlock { opacity: 0; }
+@keyframes introFade { to { opacity: 1; } }
+
/* 单位详情弹窗 */
#unitModal {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
@@ -969,6 +1049,17 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
+
+
+
+
+
yusen
+
+
宇森游戏
+
+
点击任意位置跳过
+
+
@@ -7213,12 +7304,184 @@ function init() {
const MIN_LOAD_MS = 1500; // [PLACEHOLDER] 体感:1.5s 是否够?可下调至 1000
const hold = Math.max(0, MIN_LOAD_MS - (Date.now() - loadStart));
setTimeout(() => {
- document.getElementById('loadingScreen').style.display = 'none';
- requestAnimationFrame(render);
+ const ls = document.getElementById('loadingScreen');
+ if (ls) ls.style.display = 'none';
+ playIntro(() => {
+ maybePlayIntroStory().then(() => {
+ startGameGate();
+ requestAnimationFrame(render);
+ });
+ });
}, hold);
});
}
+// 进游戏开场动画(yusen 科技感):加载完成后覆盖播放,约 5.2s,可点击跳过,本会话只播一次
+let _introAudioCtx = null;
+function getIntroAudioCtx() {
+ try {
+ const AC = window.AudioContext || window.webkitAudioContext;
+ if (!AC) return null;
+ if (!_introAudioCtx) _introAudioCtx = new AC();
+ if (_introAudioCtx.state === 'suspended') _introAudioCtx.resume();
+ return _introAudioCtx;
+ } catch (e) { return null; }
+}
+function stopIntroSound(master) {
+ if (!master) return;
+ try {
+ const ctx = master.context;
+ const t = ctx.currentTime;
+ master.gain.cancelScheduledValues(t);
+ master.gain.setValueAtTime(Math.max(0.0001, master.gain.value), t);
+ master.gain.exponentialRampToValueAtTime(0.0001, t + 0.12);
+ } catch (e) {}
+}
+function playIntroSound() {
+ // 参考背景音乐设置:读取「音效」开关,关闭时不发声
+ if (typeof loadWorldSFXSetting === 'function' && !loadWorldSFXSetting()) return null;
+ const ctx = getIntroAudioCtx();
+ if (!ctx) return null;
+ const now = ctx.currentTime;
+ const master = ctx.createGain();
+ master.gain.setValueAtTime(0.0001, now);
+ master.gain.linearRampToValueAtTime(0.55, now + 0.06);
+ master.connect(ctx.destination);
+
+ // 1. 深空底噪:50Hz + 100Hz 正弦,贯穿全程
+ [50, 100].forEach(f => {
+ const o = ctx.createOscillator(); o.type = 'sine'; o.frequency.value = f;
+ const g = ctx.createGain();
+ g.gain.setValueAtTime(0.0001, now);
+ g.gain.linearRampToValueAtTime(0.05, now + 0.8);
+ g.gain.setValueAtTime(0.05, now + 4.2);
+ g.gain.linearRampToValueAtTime(0.0001, now + 5.2);
+ o.connect(g).connect(master); o.start(now); o.stop(now + 5.3);
+ });
+
+ // 2. 低频滤波噪声扫过(0.2s - 1.5s)
+ const len = Math.floor(ctx.sampleRate * 1.4);
+ const buf = ctx.createBuffer(1, len, ctx.sampleRate);
+ const data = buf.getChannelData(0);
+ for (let i = 0; i < len; i++) data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / len, 1.4);
+ const noise = ctx.createBufferSource(); noise.buffer = buf;
+ const nf = ctx.createBiquadFilter(); nf.type = 'lowpass';
+ nf.frequency.setValueAtTime(800, now + 0.2); nf.frequency.linearRampToValueAtTime(2200, now + 0.9); nf.frequency.linearRampToValueAtTime(600, now + 1.5);
+ const ng = ctx.createGain();
+ ng.gain.setValueAtTime(0.0001, now + 0.2); ng.gain.linearRampToValueAtTime(0.16, now + 0.7); ng.gain.linearRampToValueAtTime(0.0001, now + 1.5);
+ noise.connect(nf).connect(ng).connect(master); noise.start(now + 0.2); noise.stop(now + 1.6);
+
+ // 3. 数字 glitch 短鸣(0.5 / 0.9 / 1.3s)
+ [0.5, 0.9, 1.3].forEach((t, i) => {
+ const o = ctx.createOscillator(); o.type = 'square';
+ o.frequency.setValueAtTime(1200 + i * 600, now + t); o.frequency.exponentialRampToValueAtTime(1800 + i * 400, now + t + 0.04);
+ const g = ctx.createGain();
+ g.gain.setValueAtTime(0.0001, now + t); g.gain.exponentialRampToValueAtTime(0.05, now + t + 0.01); g.gain.exponentialRampToValueAtTime(0.0001, now + t + 0.08);
+ o.connect(g).connect(master); o.start(now + t); o.stop(now + t + 0.1);
+ });
+
+ // 4. 上扬合成器扫频(1.2s - 2.7s)
+ const sweep = ctx.createOscillator(); sweep.type = 'sawtooth';
+ sweep.frequency.setValueAtTime(180, now + 1.2); sweep.frequency.exponentialRampToValueAtTime(1100, now + 2.5);
+ const sg = ctx.createGain();
+ sg.gain.setValueAtTime(0.0001, now + 1.2); sg.gain.linearRampToValueAtTime(0.11, now + 2.0); sg.gain.linearRampToValueAtTime(0.0001, now + 2.7);
+ const sf = ctx.createBiquadFilter(); sf.type = 'lowpass';
+ sf.frequency.setValueAtTime(600, now + 1.2); sf.frequency.linearRampToValueAtTime(3200, now + 2.5);
+ sweep.connect(sf).connect(sg).connect(master); sweep.start(now + 1.2); sweep.stop(now + 2.8);
+
+ // 5. 冲击点(2.4s):噪声爆发 + 低频重击
+ const impactLen = Math.floor(ctx.sampleRate * 0.35);
+ const impactBuf = ctx.createBuffer(1, impactLen, ctx.sampleRate);
+ const id = impactBuf.getChannelData(0);
+ for (let i = 0; i < impactLen; i++) id[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / impactLen, 1.2);
+ const impactNoise = ctx.createBufferSource(); impactNoise.buffer = impactBuf;
+ const impactF = ctx.createBiquadFilter(); impactF.type = 'bandpass'; impactF.frequency.value = 450; impactF.Q.value = 1.2;
+ const impactG = ctx.createGain();
+ impactG.gain.setValueAtTime(0.0001, now + 2.4); impactG.gain.exponentialRampToValueAtTime(0.24, now + 2.42); impactG.gain.exponentialRampToValueAtTime(0.0001, now + 2.85);
+ impactNoise.connect(impactF).connect(impactG).connect(master); impactNoise.start(now + 2.4); impactNoise.stop(now + 2.9);
+ const thump = ctx.createOscillator(); thump.type = 'sine';
+ thump.frequency.setValueAtTime(90, now + 2.4); thump.frequency.exponentialRampToValueAtTime(35, now + 2.8);
+ const thumpG = ctx.createGain();
+ thumpG.gain.setValueAtTime(0.0001, now + 2.4); thumpG.gain.exponentialRampToValueAtTime(0.28, now + 2.43); thumpG.gain.exponentialRampToValueAtTime(0.0001, now + 2.9);
+ thump.connect(thumpG).connect(master); thump.start(now + 2.4); thump.stop(now + 2.95);
+
+ // 6. 科技琶音(2.9s - 4.3s)
+ [523.25, 659.25, 783.99, 1046.50, 783.99, 659.25].forEach((f, i) => {
+ const t = now + 2.9 + i * 0.24;
+ const o = ctx.createOscillator(); o.type = 'triangle'; o.frequency.value = f;
+ const g = ctx.createGain();
+ g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(0.08, t + 0.02); g.gain.exponentialRampToValueAtTime(0.0001, t + 0.55);
+ o.connect(g).connect(master); o.start(t); o.stop(t + 0.6);
+ });
+
+ // 7. 最终揭示和弦(4.4s - 5.2s)
+ [392.00, 523.25, 659.25, 783.99].forEach((f, i) => {
+ const t = now + 4.4 + i * 0.04;
+ const o = ctx.createOscillator(); o.type = 'sine'; o.frequency.value = f;
+ const g = ctx.createGain();
+ g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(0.12, t + 0.05); g.gain.exponentialRampToValueAtTime(0.0001, t + 1.0);
+ o.connect(g).connect(master); o.start(t); o.stop(t + 1.1);
+ });
+
+ return master;
+}
+function playIntro(done) {
+ const el = document.getElementById('introSplash');
+ if (!el) { done && done(); return; }
+ try {
+ if (sessionStorage.getItem('introPlayed')) { el.remove(); done && done(); return; }
+ } catch (e) {}
+ el.classList.add('show');
+ let soundMaster = null;
+ try { soundMaster = playIntroSound(); } catch (e) { console.warn('intro音效异常', e); }
+ // 浏览器自动播放策略:无用户手势时 AudioContext 被挂起,音效静音 → 显示解锁按钮
+ const unlockBtn = document.getElementById('introAudioUnlock');
+ let needsUnlock = false;
+ try { const ctx = getIntroAudioCtx(); needsUnlock = !!ctx && ctx.state !== 'running'; } catch (e) {}
+ if (needsUnlock && unlockBtn) unlockBtn.style.display = '';
+ const unlockAudio = async () => {
+ try {
+ const ctx = getIntroAudioCtx();
+ if (ctx && ctx.state === 'suspended') await ctx.resume();
+ if (ctx && ctx.state === 'running') {
+ try { stopIntroSound(soundMaster); } catch (e) {}
+ try { soundMaster = playIntroSound(); } catch (e) {}
+ }
+ } catch (e) {}
+ if (unlockBtn) unlockBtn.style.display = 'none';
+ };
+ if (unlockBtn) unlockBtn.addEventListener('click', (e) => { e.stopPropagation(); unlockAudio(); });
+ let finished = false;
+ const finish = () => {
+ if (finished) return; finished = true;
+ try { stopIntroSound(soundMaster); } catch (e) {}
+ try { sessionStorage.setItem('introPlayed', '1'); } catch (e) {}
+ el.classList.remove('show'); el.classList.add('hide');
+ setTimeout(() => { el.remove(); done && done(); }, 480);
+ };
+ const timer = setTimeout(finish, 5200);
+ const onSkip = () => { clearTimeout(timer); finish(); };
+ el.addEventListener('click', onSkip);
+ window.addEventListener('keydown', onSkip, { once: true });
+}
+
+// 首次进入剧情:在 yusen 开场动画之后播放,再进入登录/游戏
+async function maybePlayIntroStory() {
+ if (localStorage.getItem('seen_intro_part1')) return;
+ try {
+ await playIntroStory([
+ 'assets/story/story_01_transmigration.jpg',
+ 'assets/story/story_02_awakening.jpg'
+ ], {
+ onStart: () => {
+ const authSplash = document.getElementById('authSplash');
+ if (authSplash) authSplash.style.display = 'none';
+ }
+ });
+ } catch (e) { console.warn('剧情播放异常', e); }
+ localStorage.setItem('seen_intro_part1', '1');
+}
+
window.addEventListener('resize', () => {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight - BOTTOM_BAR_H;
@@ -7363,6 +7626,7 @@ window.addEventListener('keydown', e => {
if (typeof fitWorld === 'function') fitWorld();
+
@@ -7572,8 +7836,8 @@ if (typeof fitWorld === 'function') fitWorld();
}catch(e){ return null; }
}
function playTalentSfx(type){
- const tg = document.getElementById('sfxToggle');
- if(tg && !tg.classList.contains('active')) return; // 音效关闭则不发声
+ // 音效开关统一走 localStorage(设置面板可能未打开)
+ if (typeof loadWorldSFXSetting === 'function' && !loadWorldSFXSetting()) return;
const ctx = _getSfxCtx(); if(!ctx) return;
const now = ctx.currentTime;
if(type === 'whoosh'){
@@ -7792,29 +8056,12 @@ if (typeof fitWorld === 'function') fitWorld();