核心系统: - combatSystem: 战斗逻辑、伤害计算、战斗状态管理 - skillSystem: 技能系统、技能解锁、经验值、里程碑 - taskSystem: 任务系统、任务类型、任务执行和完成 - eventSystem: 事件系统、随机事件处理 - environmentSystem: 环境系统、时间流逝、区域效果 - levelingSystem: 升级系统、属性成长 - soundSystem: 音效系统 配置文件: - enemies: 敌人配置、掉落表 - events: 事件配置、事件效果 - items: 物品配置、装备属性 - locations: 地点配置、探索事件 - skills: 技能配置、技能树 UI组件: - CraftingDrawer: 制造界面 - InventoryDrawer: 背包界面 - 其他UI优化和动画 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
335 lines
9.4 KiB
JavaScript
335 lines
9.4 KiB
JavaScript
/**
|
||
* 音效系统
|
||
* Phase 3 UI美化 - 音效支持
|
||
*
|
||
* 注意:uni-app的音频功能有限,不同平台支持情况不同
|
||
* H5端:使用Web Audio API
|
||
* 小程序端:使用 wx.createInnerAudioContext()
|
||
* App端:使用 plus.audio
|
||
*/
|
||
|
||
/**
|
||
* 音效类型枚举
|
||
*/
|
||
export const SOUND_TYPES = {
|
||
// UI音效
|
||
CLICK: 'click',
|
||
OPEN: 'open',
|
||
CLOSE: 'close',
|
||
TOGGLE: 'toggle',
|
||
|
||
// 游戏音效
|
||
COMBAT_START: 'combat_start',
|
||
COMBAT_HIT: 'combat_hit',
|
||
COMBAT_HIT_PLAYER: 'combat_hit_player',
|
||
COMBAT_VICTORY: 'combat_victory',
|
||
COMBAT_DEFEAT: 'combat_defeat',
|
||
|
||
// 奖励音效
|
||
REWARD: 'reward',
|
||
LEVEL_UP: 'level_up',
|
||
SKILL_UP: 'skill_up',
|
||
|
||
// 错误音效
|
||
ERROR: 'error',
|
||
WARNING: 'warning',
|
||
|
||
// 系统音效
|
||
NOTIFICATION: 'notification',
|
||
MESSAGE: 'message'
|
||
}
|
||
|
||
/**
|
||
* 音效配置
|
||
*/
|
||
const SOUND_CONFIG = {
|
||
// 是否启用音效
|
||
enabled: true,
|
||
// 音量 (0-1)
|
||
volume: 0.5,
|
||
// 音效文件路径配置 (需要实际文件)
|
||
soundPaths: {
|
||
[SOUND_TYPES.CLICK]: '/static/sounds/click.mp3',
|
||
[SOUND_TYPES.OPEN]: '/static/sounds/open.mp3',
|
||
[SOUND_TYPES.CLOSE]: '/static/sounds/close.mp3',
|
||
[SOUND_TYPES.TOGGLE]: '/static/sounds/toggle.mp3',
|
||
[SOUND_TYPES.COMBAT_START]: '/static/sounds/combat_start.mp3',
|
||
[SOUND_TYPES.COMBAT_HIT]: '/static/sounds/combat_hit.mp3',
|
||
[SOUND_TYPES.COMBAT_HIT_PLAYER]: '/static/sounds/combat_hit_player.mp3',
|
||
[SOUND_TYPES.COMBAT_VICTORY]: '/static/sounds/combat_victory.mp3',
|
||
[SOUND_TYPES.COMBAT_DEFEAT]: '/static/sounds/combat_defeat.mp3',
|
||
[SOUND_TYPES.REWARD]: '/static/sounds/reward.mp3',
|
||
[SOUND_TYPES.LEVEL_UP]: '/static/sounds/level_up.mp3',
|
||
[SOUND_TYPES.SKILL_UP]: '/static/sounds/skill_up.mp3',
|
||
[SOUND_TYPES.ERROR]: '/static/sounds/error.mp3',
|
||
[SOUND_TYPES.WARNING]: '/static/sounds/warning.mp3',
|
||
[SOUND_TYPES.NOTIFICATION]: '/static/sounds/notification.mp3',
|
||
[SOUND_TYPES.MESSAGE]: '/static/sounds/message.mp3'
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 音频上下文缓存
|
||
*/
|
||
let audioContext = null
|
||
let audioCache = {}
|
||
|
||
/**
|
||
* 初始化音频系统
|
||
*/
|
||
export function initSoundSystem() {
|
||
// #ifdef H5
|
||
try {
|
||
audioContext = new (window.AudioContext || window.webkitAudioContext)()
|
||
} catch (e) {
|
||
console.warn('Web Audio API not supported:', e)
|
||
}
|
||
// #endif
|
||
|
||
// #ifdef MP-WEIXIN
|
||
// 小程序端使用微信音频API
|
||
// #endif
|
||
|
||
// 从本地存储加载音效设置
|
||
loadSoundSettings()
|
||
}
|
||
|
||
/**
|
||
* 播放音效
|
||
* @param {String} soundType - 音效类型
|
||
* @param {Object} options - 选项 { volume, speed }
|
||
*/
|
||
export function playSound(soundType, options = {}) {
|
||
if (!SOUND_CONFIG.enabled) {
|
||
return { success: false, message: '音效已禁用' }
|
||
}
|
||
|
||
const volume = options.volume !== undefined ? options.volume : SOUND_CONFIG.volume
|
||
|
||
// #ifdef H5
|
||
return playSoundH5(soundType, volume)
|
||
// #endif
|
||
|
||
// #ifdef MP-WEIXIN
|
||
return playSoundWeixin(soundType, volume)
|
||
// #endif
|
||
|
||
// #ifdef APP-PLUS
|
||
return playSoundApp(soundType, volume)
|
||
// #endif
|
||
|
||
return { success: false, message: '当前平台不支持音效' }
|
||
}
|
||
|
||
/**
|
||
* H5端播放音效 (使用Web Audio API合成简单音效)
|
||
*/
|
||
function playSoundH5(soundType, volume) {
|
||
if (!audioContext) {
|
||
initSoundSystem()
|
||
if (!audioContext) {
|
||
return { success: false, message: '音频上下文初始化失败' }
|
||
}
|
||
}
|
||
|
||
// 恢复音频上下文(某些浏览器需要用户交互后才能恢复)
|
||
if (audioContext.state === 'suspended') {
|
||
audioContext.resume()
|
||
}
|
||
|
||
try {
|
||
// 创建振荡器生成简单音效
|
||
const oscillator = audioContext.createOscillator()
|
||
const gainNode = audioContext.createGain()
|
||
|
||
oscillator.connect(gainNode)
|
||
gainNode.connect(audioContext.destination)
|
||
|
||
// 根据音效类型设置不同的频率和包络
|
||
const soundParams = getSoundParameters(soundType)
|
||
oscillator.type = soundParams.type || 'sine'
|
||
oscillator.frequency.setValueAtTime(soundParams.frequency, audioContext.currentTime)
|
||
|
||
// 设置音量包络
|
||
const now = audioContext.currentTime
|
||
gainNode.gain.setValueAtTime(0, now)
|
||
gainNode.gain.linearRampToValueAtTime(volume * 0.3, now + 0.01)
|
||
gainNode.gain.exponentialRampToValueAtTime(0.001, now + soundParams.duration)
|
||
|
||
oscillator.start(now)
|
||
oscillator.stop(now + soundParams.duration)
|
||
|
||
return { success: true }
|
||
} catch (e) {
|
||
console.warn('播放音效失败:', e)
|
||
return { success: false, message: e.message }
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取音效参数 (用于合成音效)
|
||
*/
|
||
function getSoundParameters(soundType) {
|
||
const params = {
|
||
// 点击音效 - 短促的高音
|
||
[SOUND_TYPES.CLICK]: { type: 'sine', frequency: 800, duration: 0.05 },
|
||
// 打开音效 - 上升音调
|
||
[SOUND_TYPES.OPEN]: { type: 'sine', frequency: 400, duration: 0.15 },
|
||
// 关闭音效 - 下降音调
|
||
[SOUND_TYPES.CLOSE]: { type: 'sine', frequency: 300, duration: 0.1 },
|
||
// 切换音效
|
||
[SOUND_TYPES.TOGGLE]: { type: 'square', frequency: 500, duration: 0.08 },
|
||
// 战斗开始 - 低沉
|
||
[SOUND_TYPES.COMBAT_START]: { type: 'sawtooth', frequency: 150, duration: 0.3 },
|
||
// 命中敌人 - 尖锐
|
||
[SOUND_TYPES.COMBAT_HIT]: { type: 'square', frequency: 1200, duration: 0.08 },
|
||
// 被命中 - 低频
|
||
[SOUND_TYPES.COMBAT_HIT_PLAYER]: { type: 'sawtooth', frequency: 200, duration: 0.15 },
|
||
// 胜利 - 上升音阶
|
||
[SOUND_TYPES.COMBAT_VICTORY]: { type: 'sine', frequency: 523, duration: 0.4 },
|
||
// 失败 - 下降音
|
||
[SOUND_TYPES.COMBAT_DEFEAT]: { type: 'sawtooth', frequency: 100, duration: 0.4 },
|
||
// 奖励 - 悦耳的高音
|
||
[SOUND_TYPES.REWARD]: { type: 'sine', frequency: 880, duration: 0.2 },
|
||
// 升级 - 双音
|
||
[SOUND_TYPES.LEVEL_UP]: { type: 'sine', frequency: 659, duration: 0.3 },
|
||
// 技能升级
|
||
[SOUND_TYPES.SKILL_UP]: { type: 'sine', frequency: 784, duration: 0.25 },
|
||
// 错误 - 低频嗡鸣
|
||
[SOUND_TYPES.ERROR]: { type: 'sawtooth', frequency: 100, duration: 0.2 },
|
||
// 警告
|
||
[SOUND_TYPES.WARNING]: { type: 'square', frequency: 440, duration: 0.15 },
|
||
// 通知
|
||
[SOUND_TYPES.NOTIFICATION]: { type: 'sine', frequency: 660, duration: 0.2 },
|
||
// 消息
|
||
[SOUND_TYPES.MESSAGE]: { type: 'sine', frequency: 587, duration: 0.15 }
|
||
}
|
||
|
||
return params[soundType] || { type: 'sine', frequency: 440, duration: 0.1 }
|
||
}
|
||
|
||
/**
|
||
* 微信小程序端播放音效
|
||
*/
|
||
function playSoundWeixin(soundType, volume) {
|
||
// #ifdef MP-WEIXIN
|
||
const soundPath = SOUND_CONFIG.soundPaths[soundType]
|
||
if (!soundPath) {
|
||
return { success: false, message: '音效文件未配置' }
|
||
}
|
||
|
||
try {
|
||
const audio = uni.createInnerAudioContext()
|
||
audio.src = soundPath
|
||
audio.volume = volume
|
||
audio.play()
|
||
return { success: true }
|
||
} catch (e) {
|
||
return { success: false, message: e.message }
|
||
}
|
||
// #endif
|
||
|
||
return { success: false, message: '非微信小程序环境' }
|
||
}
|
||
|
||
/**
|
||
* App端播放音效
|
||
*/
|
||
function playSoundApp(soundType, volume) {
|
||
// #ifdef APP-PLUS
|
||
const soundPath = SOUND_CONFIG.soundPaths[soundType]
|
||
if (!soundPath) {
|
||
return { success: false, message: '音效文件未配置' }
|
||
}
|
||
|
||
try {
|
||
const player = plus.audio.createPlayer(soundPath)
|
||
player.setVolume(volume * 100)
|
||
player.play()
|
||
return { success: true }
|
||
} catch (e) {
|
||
return { success: false, message: e.message }
|
||
}
|
||
// #endif
|
||
|
||
return { success: false, message: '非App环境' }
|
||
}
|
||
|
||
/**
|
||
* 设置音效开关
|
||
* @param {Boolean} enabled - 是否启用音效
|
||
*/
|
||
export function setSoundEnabled(enabled) {
|
||
SOUND_CONFIG.enabled = enabled
|
||
saveSoundSettings()
|
||
}
|
||
|
||
/**
|
||
* 设置音量
|
||
* @param {Number} volume - 音量 (0-1)
|
||
*/
|
||
export function setSoundVolume(volume) {
|
||
SOUND_CONFIG.volume = Math.max(0, Math.min(1, volume))
|
||
saveSoundSettings()
|
||
}
|
||
|
||
/**
|
||
* 获取音效设置
|
||
* @returns {Object} { enabled, volume }
|
||
*/
|
||
export function getSoundSettings() {
|
||
return {
|
||
enabled: SOUND_CONFIG.enabled,
|
||
volume: SOUND_CONFIG.volume
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存音效设置到本地存储
|
||
*/
|
||
function saveSoundSettings() {
|
||
try {
|
||
uni.setStorageSync('soundSettings', JSON.stringify({
|
||
enabled: SOUND_CONFIG.enabled,
|
||
volume: SOUND_CONFIG.volume
|
||
}))
|
||
} catch (e) {
|
||
console.warn('保存音效设置失败:', e)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从本地存储加载音效设置
|
||
*/
|
||
function loadSoundSettings() {
|
||
try {
|
||
const settings = uni.getStorageSync('soundSettings')
|
||
if (settings) {
|
||
const parsed = JSON.parse(settings)
|
||
SOUND_CONFIG.enabled = parsed.enabled !== undefined ? parsed.enabled : true
|
||
SOUND_CONFIG.volume = parsed.volume !== undefined ? parsed.volume : 0.5
|
||
}
|
||
} catch (e) {
|
||
console.warn('加载音效设置失败:', e)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 快捷音效播放函数
|
||
*/
|
||
export const playClick = () => playSound(SOUND_TYPES.CLICK)
|
||
export const playOpen = () => playSound(SOUND_TYPES.OPEN)
|
||
export const playClose = () => playSound(SOUND_TYPES.CLOSE)
|
||
export const playCombatStart = () => playSound(SOUND_TYPES.COMBAT_START)
|
||
export const playCombatHit = () => playSound(SOUND_TYPES.COMBAT_HIT)
|
||
export const playCombatVictory = () => playSound(SOUND_TYPES.COMBAT_VICTORY)
|
||
export const playCombatDefeat = () => playSound(SOUND_TYPES.COMBAT_DEFEAT)
|
||
export const playReward = () => playSound(SOUND_TYPES.REWARD)
|
||
export const playLevelUp = () => playSound(SOUND_TYPES.LEVEL_UP)
|
||
export const playSkillUp = () => playSound(SOUND_TYPES.SKILL_UP)
|
||
export const playError = () => playSound(SOUND_TYPES.ERROR)
|
||
export const playWarning = () => playSound(SOUND_TYPES.WARNING)
|
||
|
||
// 初始化音效系统
|
||
initSoundSystem()
|