367 lines
10 KiB
JavaScript
367 lines
10 KiB
JavaScript
"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
|