更新
This commit is contained in:
+366
@@ -0,0 +1,366 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../../common/vendor.js");
|
||||
const STORAGE_KEY = "tongji_game_sfx_enabled";
|
||||
const COS = "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file";
|
||||
const SRC = {
|
||||
tap: `${COS}/20260526/20260526105128557ef8669.mp3`,
|
||||
swoosh: `${COS}/20260527/202605271539371ebe13672.mp3`,
|
||||
pop: `${COS}/20260526/202605261051282a0a94508.mp3`,
|
||||
buzz: `${COS}/20260527/202605271539376ff628472.mp3`,
|
||||
chime: `${COS}/20260528/202605280937077bad76716.mp3`,
|
||||
fanfare: `${COS}/20260528/20260528093707b021f5794.mp3`,
|
||||
blast: `${COS}/20260528/20260528093707ebd4d5733.mp3`,
|
||||
sparkle: `${COS}/20260528/20260528093707016f07427.mp3`,
|
||||
stinger: `${COS}/20260528/20260528093709c669f4659.mp3`
|
||||
};
|
||||
const SFX = {
|
||||
// —— 基础素材别名(供分层音效直接调用)——
|
||||
tap: { src: SRC.tap, volume: 0.32, rate: 1, pool: 3 },
|
||||
pop: { src: SRC.pop, volume: 0.3, rate: 1, pool: 4 },
|
||||
fanfare: { src: SRC.fanfare, volume: 0.56, rate: 1, pool: 2 },
|
||||
blast: { src: SRC.blast, volume: 0.62, rate: 1, pool: 2 },
|
||||
sparkle: { src: SRC.sparkle, volume: 0.5, rate: 1, pool: 2 },
|
||||
// —— 交互 ——
|
||||
select: { src: SRC.tap, volume: 0.32, rate: 1.05, pool: 3 },
|
||||
deselect: { src: SRC.tap, volume: 0.22, rate: 0.82, pool: 2 },
|
||||
hint: { src: SRC.tap, volume: 0.26, rate: 1.28, pool: 2 },
|
||||
swap: { src: SRC.swoosh, volume: 0.38, rate: 1, pool: 3 },
|
||||
swapFail: { src: SRC.buzz, volume: 0.36, rate: 1.18, pool: 2 },
|
||||
drop: { src: SRC.pop, volume: 0.3, rate: 0.88, pool: 4 },
|
||||
dropLight: { src: SRC.pop, volume: 0.22, rate: 1.22, pool: 2 },
|
||||
// —— 消除(按连数)——
|
||||
match3: { src: SRC.chime, volume: 0.48, rate: 1, pool: 4 },
|
||||
match4: { src: SRC.fanfare, volume: 0.54, rate: 1.06, pool: 3 },
|
||||
match5: { src: SRC.blast, volume: 0.62, rate: 1, pool: 3 },
|
||||
// —— 消除(按 GI 主色)——
|
||||
matchLowGi: { src: SRC.chime, volume: 0.42, rate: 0.86, pool: 2 },
|
||||
matchMidGi: { src: SRC.chime, volume: 0.46, rate: 1.02, pool: 2 },
|
||||
matchHighGi: { src: SRC.buzz, volume: 0.44, rate: 0.92, pool: 2 },
|
||||
// —— 连消 ——
|
||||
combo2: { src: SRC.fanfare, volume: 0.56, rate: 1.12, pool: 2 },
|
||||
combo3: { src: SRC.blast, volume: 0.64, rate: 1.08, pool: 2 },
|
||||
comboMega: { src: SRC.blast, volume: 0.72, rate: 1.22, pool: 2 },
|
||||
// —— 道具 ——
|
||||
insulin: { src: SRC.sparkle, volume: 0.5, rate: 1.15, pool: 2 },
|
||||
fiber: { src: SRC.swoosh, volume: 0.48, rate: 1.32, pool: 2 },
|
||||
meal: { src: SRC.stinger, volume: 0.42, rate: 1.35, pool: 1 },
|
||||
reshuffle: { src: SRC.swoosh, volume: 0.46, rate: 0.72, pool: 2 },
|
||||
// —— 反馈 ——
|
||||
score: { src: SRC.tap, volume: 0.28, rate: 1.38, pool: 2 },
|
||||
goalTick: { src: SRC.chime, volume: 0.36, rate: 1.45, pool: 2 },
|
||||
meterWarn: { src: SRC.buzz, volume: 0.5, rate: 1, pool: 2 },
|
||||
meterDanger: { src: SRC.buzz, volume: 0.58, rate: 0.78, pool: 2 },
|
||||
meterStable: { src: SRC.pop, volume: 0.24, rate: 1.05, pool: 1 },
|
||||
win: { src: SRC.fanfare, volume: 0.66, rate: 1, pool: 1 },
|
||||
winStinger: { src: SRC.stinger, volume: 0.38, rate: 1.5, pool: 1 },
|
||||
lose: { src: SRC.buzz, volume: 0.52, rate: 0.68, pool: 1 },
|
||||
// 兼容旧名
|
||||
match: { src: SRC.chime, volume: 0.48, rate: 1, pool: 4 },
|
||||
combo: { src: SRC.fanfare, volume: 0.56, rate: 1.1, pool: 3 },
|
||||
heal: { src: SRC.pop, volume: 0.44, rate: 1.18, pool: 2 },
|
||||
boost: { src: SRC.sparkle, volume: 0.52, rate: 1, pool: 2 },
|
||||
invalid: { src: SRC.buzz, volume: 0.4, rate: 1.15, pool: 2 },
|
||||
danger: { src: SRC.blast, volume: 0.58, rate: 0.95, pool: 2 }
|
||||
};
|
||||
const WARMUP_SFX = [
|
||||
"select",
|
||||
"swap",
|
||||
"swapFail",
|
||||
"drop",
|
||||
"dropLight",
|
||||
"match3",
|
||||
"match4",
|
||||
"match5",
|
||||
"combo2",
|
||||
"combo3",
|
||||
"comboMega",
|
||||
"insulin",
|
||||
"sparkle",
|
||||
"reshuffle",
|
||||
"lose"
|
||||
];
|
||||
function createAudioContext() {
|
||||
if (typeof Audio !== "undefined") {
|
||||
const audio = new Audio();
|
||||
audio.preload = "auto";
|
||||
return {
|
||||
get src() {
|
||||
return audio.src;
|
||||
},
|
||||
set src(value) {
|
||||
audio.src = value;
|
||||
},
|
||||
get volume() {
|
||||
return audio.volume;
|
||||
},
|
||||
set volume(value) {
|
||||
audio.volume = value;
|
||||
},
|
||||
get playbackRate() {
|
||||
return audio.playbackRate;
|
||||
},
|
||||
set playbackRate(value) {
|
||||
audio.playbackRate = value;
|
||||
},
|
||||
play() {
|
||||
const promise = audio.play();
|
||||
if (promise == null ? void 0 : promise.catch)
|
||||
promise.catch(() => {
|
||||
});
|
||||
},
|
||||
stop() {
|
||||
audio.pause();
|
||||
try {
|
||||
audio.currentTime = 0;
|
||||
} catch (_) {
|
||||
}
|
||||
},
|
||||
seek(time) {
|
||||
try {
|
||||
audio.currentTime = time;
|
||||
} catch (_) {
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
audio.pause();
|
||||
audio.removeAttribute("src");
|
||||
}
|
||||
};
|
||||
}
|
||||
return common_vendor.index.createInnerAudioContext();
|
||||
}
|
||||
class SfxPool {
|
||||
constructor(src, size, volume, rate = 1) {
|
||||
this.src = src;
|
||||
this.size = size;
|
||||
this.volume = volume;
|
||||
this.rate = rate;
|
||||
this.list = [];
|
||||
this.cursor = 0;
|
||||
this.warmedUp = false;
|
||||
}
|
||||
createOne() {
|
||||
const ctx = createAudioContext();
|
||||
ctx.src = this.src;
|
||||
ctx.obeyMuteSwitch = false;
|
||||
ctx.autoplay = false;
|
||||
ctx.volume = this.volume;
|
||||
try {
|
||||
ctx.playbackRate = this.rate;
|
||||
} catch (_) {
|
||||
}
|
||||
this.list.push(ctx);
|
||||
return ctx;
|
||||
}
|
||||
create() {
|
||||
while (this.list.length < this.size)
|
||||
this.createOne();
|
||||
}
|
||||
warmUp() {
|
||||
if (this.warmedUp)
|
||||
return;
|
||||
const ctx = this.list[0] || this.createOne();
|
||||
try {
|
||||
ctx.volume = 0;
|
||||
ctx.play();
|
||||
setTimeout(() => {
|
||||
try {
|
||||
ctx.stop();
|
||||
ctx.volume = this.volume;
|
||||
} catch (_) {
|
||||
}
|
||||
}, 60);
|
||||
} catch (_) {
|
||||
}
|
||||
if (this.size > 1)
|
||||
this.cursor = 1;
|
||||
this.warmedUp = true;
|
||||
}
|
||||
play(scale = 1, rateMul = 1) {
|
||||
if (this.list.length < this.size)
|
||||
this.create();
|
||||
const ctx = this.list[this.cursor % this.list.length];
|
||||
this.cursor += 1;
|
||||
const vol = Math.min(1, this.volume * scale);
|
||||
const rate = Math.min(2, Math.max(0.5, this.rate * rateMul));
|
||||
try {
|
||||
ctx.volume = vol;
|
||||
try {
|
||||
ctx.playbackRate = rate;
|
||||
} catch (_) {
|
||||
}
|
||||
ctx.stop();
|
||||
ctx.seek(0);
|
||||
ctx.play();
|
||||
} catch (_) {
|
||||
try {
|
||||
ctx.play();
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
destroy() {
|
||||
this.list.forEach((ctx) => {
|
||||
try {
|
||||
ctx.stop();
|
||||
ctx.destroy();
|
||||
} catch (_) {
|
||||
}
|
||||
});
|
||||
this.list = [];
|
||||
this.warmedUp = false;
|
||||
}
|
||||
}
|
||||
function dominantGi(tiles = []) {
|
||||
const cnt = { low: 0, mid: 0, high: 0 };
|
||||
tiles.forEach((t) => {
|
||||
var _a;
|
||||
const g = (_a = t == null ? void 0 : t.type) == null ? void 0 : _a.gi;
|
||||
if (g && cnt[g] !== void 0)
|
||||
cnt[g] += 1;
|
||||
});
|
||||
let best = "mid";
|
||||
let max = 0;
|
||||
Object.entries(cnt).forEach(([k, v]) => {
|
||||
if (v > max) {
|
||||
max = v;
|
||||
best = k;
|
||||
}
|
||||
});
|
||||
return best;
|
||||
}
|
||||
function useGameSfx() {
|
||||
const enabled = common_vendor.ref(true);
|
||||
const pools = {};
|
||||
try {
|
||||
const stored = common_vendor.index.getStorageSync(STORAGE_KEY);
|
||||
if (stored === false || stored === "0" || stored === 0) {
|
||||
enabled.value = false;
|
||||
}
|
||||
} catch (_) {
|
||||
}
|
||||
function ensureAudioOption() {
|
||||
try {
|
||||
common_vendor.index.setInnerAudioOption({
|
||||
obeyMuteSwitch: false,
|
||||
mixWithOther: true
|
||||
});
|
||||
} catch (_) {
|
||||
}
|
||||
}
|
||||
function getPool(name) {
|
||||
const cfg = SFX[name];
|
||||
if (!cfg)
|
||||
return null;
|
||||
if (!pools[name]) {
|
||||
pools[name] = new SfxPool(cfg.src, cfg.pool, cfg.volume, cfg.rate ?? 1);
|
||||
}
|
||||
return pools[name];
|
||||
}
|
||||
function warmUp() {
|
||||
if (!enabled.value)
|
||||
return;
|
||||
ensureAudioOption();
|
||||
const names = typeof Audio !== "undefined" ? ["select"] : WARMUP_SFX;
|
||||
names.forEach((name) => {
|
||||
var _a;
|
||||
(_a = getPool(name)) == null ? void 0 : _a.warmUp();
|
||||
});
|
||||
}
|
||||
function play(name, scale = 1, rateMul = 1) {
|
||||
var _a;
|
||||
if (!enabled.value)
|
||||
return;
|
||||
(_a = getPool(name)) == null ? void 0 : _a.play(scale, rateMul);
|
||||
}
|
||||
function playLayer(primary, secondary, delayMs = 70, secScale = 0.6) {
|
||||
play(primary);
|
||||
if (secondary) {
|
||||
setTimeout(() => play(secondary, secScale), delayMs);
|
||||
}
|
||||
}
|
||||
function playMatch({ matchCount = 3, chain = 0, tiles = [] } = {}) {
|
||||
if (!enabled.value)
|
||||
return;
|
||||
const giTrack = (() => {
|
||||
const gi = dominantGi(tiles);
|
||||
if (gi === "low")
|
||||
return "matchLowGi";
|
||||
if (gi === "high")
|
||||
return "matchHighGi";
|
||||
return "matchMidGi";
|
||||
})();
|
||||
if (chain >= 3) {
|
||||
playLayer("comboMega", giTrack, 90, 0.55);
|
||||
return;
|
||||
}
|
||||
if (chain === 2) {
|
||||
playLayer("combo3", giTrack, 80, 0.5);
|
||||
return;
|
||||
}
|
||||
if (chain === 1) {
|
||||
playLayer("combo2", giTrack, 70, 0.45);
|
||||
return;
|
||||
}
|
||||
if (matchCount >= 5) {
|
||||
playLayer("match5", "matchHighGi", 85, 0.5);
|
||||
} else if (matchCount >= 4) {
|
||||
playLayer("match4", giTrack, 65, 0.48);
|
||||
} else {
|
||||
play(giTrack, 1, 1);
|
||||
setTimeout(() => play("match3", 0.85), 40);
|
||||
}
|
||||
}
|
||||
function playDrop(count = 1) {
|
||||
if (!enabled.value || count <= 0)
|
||||
return;
|
||||
const name = count >= 4 ? "drop" : "dropLight";
|
||||
play(name, Math.min(1.2, 0.85 + count * 0.04), count >= 6 ? 0.9 : 1);
|
||||
}
|
||||
function playMeter(delta, level) {
|
||||
if (!enabled.value)
|
||||
return;
|
||||
if (level > 70)
|
||||
play("meterDanger", 1 + (level - 70) * 0.02);
|
||||
else if (level < 30)
|
||||
play("meterWarn", 0.9, 0.95);
|
||||
else if (delta > 8)
|
||||
play("meterWarn", 0.75);
|
||||
else if (delta < -5)
|
||||
play("meterStable", 1.1);
|
||||
}
|
||||
function toggleEnabled() {
|
||||
enabled.value = !enabled.value;
|
||||
try {
|
||||
common_vendor.index.setStorageSync(STORAGE_KEY, enabled.value ? "1" : "0");
|
||||
} catch (_) {
|
||||
}
|
||||
if (enabled.value) {
|
||||
warmUp();
|
||||
play("select");
|
||||
}
|
||||
}
|
||||
function destroyAll() {
|
||||
Object.keys(pools).forEach((key) => {
|
||||
var _a;
|
||||
(_a = pools[key]) == null ? void 0 : _a.destroy();
|
||||
delete pools[key];
|
||||
});
|
||||
}
|
||||
common_vendor.onUnmounted(() => {
|
||||
destroyAll();
|
||||
});
|
||||
return {
|
||||
enabled,
|
||||
play,
|
||||
playLayer,
|
||||
playMatch,
|
||||
playDrop,
|
||||
playMeter,
|
||||
warmUp,
|
||||
toggleEnabled,
|
||||
destroyAll
|
||||
};
|
||||
}
|
||||
exports.useGameSfx = useGameSfx;
|
||||
//# sourceMappingURL=../../../../.sourcemap/mp-weixin/tongji/endless-game/composables/useGameSfx.js.map
|
||||
Reference in New Issue
Block a user