#pragma once #include #include #include #include #include #include namespace frostbite2D { /** * @brief 音频项类型枚举 */ enum class AudioEntryType { None, ///< invalid Effect, ///< one-shot sound effect Ambient, ///< looping ambient track Music, ///< background music Random ///< random sound group }; /** * @brief 音效配置 */ struct AudioEffect { std::string id; ///< 音效 ID std::string file; ///< 文件路径 std::string subgroup; ///< 子组(可选) int32 loopDelay = -1; ///< 循环延迟(-1 表示未设置) int32 loopDelayRange = -1;///< 循环延迟范围(-1 表示未设置) }; /** * @brief 音乐配置 */ struct AudioMusic { std::string id; ///< 音乐 ID std::string file; ///< 文件路径 int32 loopDelay = -1; ///< 循环延迟(-1 表示未设置) }; /** * @brief ????????? */ struct AudioAmbient { std::string id; ///< ?????ID std::string file; ///< ?????? int32 loopDelay = -1; ///< ????????1 ????????? int32 loopDelayRange = -1;///< ???????????1 ????????? }; /** * @brief 随机音效项 */ struct RandomItem { std::string tag; ///< 音效标签(ID) int32 probability = 0; ///< 概率权重 }; /** * @brief 随机音效组 */ struct AudioRandom { std::string id; ///< 随机组 ID std::vector items; ///< 随机项列表 }; /** * @brief 统一的音频条目(包含类型和数据) */ struct AudioEntry { AudioEntryType type = AudioEntryType::None; ///< 条目类型 const AudioEffect* effect = nullptr; ///< 音效指针(type == Effect 时有效) const AudioAmbient* ambient = nullptr;///< ???????????ype == Ambient ?????? const AudioMusic* music = nullptr; ///< 音乐指针(type == Music 时有效) const AudioRandom* random = nullptr; ///< 随机组指针(type == Random 时有效) /// @brief 判断是否有效 bool isValid() const { return type != AudioEntryType::None; } /// @brief 判断是否为音效 bool isEffect() const { return type == AudioEntryType::Effect; } /// @brief ???????????? bool isAmbient() const { return type == AudioEntryType::Ambient; } /// @brief 判断是否为音乐 bool isMusic() const { return type == AudioEntryType::Music; } /// @brief 判断是否为随机组 bool isRandom() const { return type == AudioEntryType::Random; } /// @brief 安全获取文件路径 std::string getFilePath() const { if (type == AudioEntryType::Effect && effect) return effect->file; if (type == AudioEntryType::Ambient && ambient) return ambient->file; if (type == AudioEntryType::Music && music) return music->file; return ""; } /// @brief 安全获取子组 std::string getSubgroup() const { if (type == AudioEntryType::Effect && effect) return effect->subgroup; return ""; } }; /** * @brief 音频数据库(单例) * * 用于解析和访问 audio.xml 文件中的音频配置。 * 提供统一的查询接口,自动判断音频类型。 * * @example * auto& audioDB = AudioDatabase::get(); * if (audioDB.loadFromFile("audio.xml")) { * // 统一查询 * if (auto entry = audioDB.get("GN_FINAL_SHOT_01")) { * // 使用 entry * } * * // 获取文件路径 * if (auto filePath = audioDB.getFilePath("GN_FINAL_SHOT_01")) { * // 使用 *filePath * } * * // 获取音效(自动处理随机组) * if (auto soundId = audioDB.getSound("R_GN_FINAL_SHOT")) { * // 使用 *soundId * } * } */ class AudioDatabase { public: /** * @brief 获取单例实例 * @return 音频数据库实例引用 */ static AudioDatabase& get(); AudioDatabase(const AudioDatabase&) = delete; AudioDatabase& operator=(const AudioDatabase&) = delete; // --------------------------------------------------------------------------- // 加载和解析 // --------------------------------------------------------------------------- /** * @brief 从文件加载音频数据库 * @param path XML 文件路径 * @return 加载成功返回 true */ bool loadFromFile(const std::string& path); /** * @brief 从字符串加载音频数据库 * @param xmlContent XML 内容字符串 * @return 加载成功返回 true */ bool loadFromString(const std::string& xmlContent); /** * @brief 清空数据库 */ void clear(); /** * @brief 检查数据库是否已加载 * @return 已加载返回 true */ bool isLoaded() const; // --------------------------------------------------------------------------- // 统一查询接口 // --------------------------------------------------------------------------- /** * @brief 统一查询:输入任意 ID,自动判断类型 * @param id 音频 ID(可以是 EFFECT、MUSIC 或 RANDOM 的 ID) * @return 音频条目,未找到返回 std::nullopt */ std::optional get(const std::string& id); /** * @brief 获取音效 ID(自动处理 RANDOM 类型) * * 如果 id 是 RANDOM 类型,会按概率随机选择一个音效; * 如果 id 是 EFFECT 类型,直接返回该 ID。 * * @param id 音效 ID 或随机组 ID * @return 音效 ID,未找到返回 std::nullopt */ std::optional getSound(const std::string& id); /** * @brief 获取文件路径 * * 对于 EFFECT 和 MUSIC 类型,返回对应的文件路径; * 对于 RANDOM 类型,先随机选择一个音效再返回其文件路径。 * * @param id 音频 ID * @return 文件路径,未找到返回 std::nullopt */ std::optional getFilePath(const std::string& id); // --------------------------------------------------------------------------- // 类型特定查询(高级用法) // --------------------------------------------------------------------------- /** * @brief 获取音效配置 * @param id 音效 ID * @return 音效配置指针,未找到返回 std::nullopt */ std::optional getEffect(const std::string& id); /** * @brief ???????????? * @param id ?????ID * @return ????????????????????? std::nullopt */ std::optional getAmbient(const std::string& id); /** * @brief 获取音乐配置 * @param id 音乐 ID * @return 音乐配置指针,未找到返回 std::nullopt */ std::optional getMusic(const std::string& id); /** * @brief 获取随机音效组配置 * @param id 随机组 ID * @return 随机组配置指针,未找到返回 std::nullopt */ std::optional getRandom(const std::string& id); // --------------------------------------------------------------------------- // 统计信息 // --------------------------------------------------------------------------- /** * @brief 获取音效数量 */ size_t effectCount() const; /** * @brief ??????????? */ size_t ambientCount() const; /** * @brief 获取音乐数量 */ size_t musicCount() const; /** * @brief 获取随机组数量 */ size_t randomCount() const; // --------------------------------------------------------------------------- // 便捷访问方法(推荐使用) // --------------------------------------------------------------------------- /** * @brief 检查 ID 是否存在 * @param id 音频 ID * @return 存在返回 true */ bool has(const std::string& id); /** * @brief 获取文件路径(不存在返回空字符串) * @param id 音频 ID * @return 文件路径,未找到返回空字符串 */ std::string filePath(const std::string& id); /** * @brief 获取音效 ID(自动处理随机组,不存在返回空字符串) * @param idOrRandomId 音效 ID 或随机组 ID * @return 音效 ID,未找到返回空字符串 */ std::string soundId(const std::string& idOrRandomId); /** * @brief 获取子组(EFFECT 的 SUBGROUP 属性) * @param id 音频 ID * @return 子组名,未找到或不存在返回空字符串 */ std::string subgroup(const std::string& id); /** * @brief 获取条目类型 * @param id 音频 ID * @return 条目类型,未找到返回 AudioEntryType::None */ AudioEntryType typeOf(const std::string& id); // --------------------------------------------------------------------------- // 带检查的访问方法 // --------------------------------------------------------------------------- /** * @brief 尝试获取文件路径,带成功标志 * @param id 音频 ID * @param outPath 输出文件路径 * @return 成功返回 true */ bool tryGetFilePath(const std::string& id, std::string& outPath); /** * @brief 尝试获取音效 ID,带成功标志 * @param id 音效 ID 或随机组 ID * @param outSoundId 输出音效 ID * @return 成功返回 true */ bool tryGetSoundId(const std::string& id, std::string& outSoundId); private: AudioDatabase() = default; ~AudioDatabase() = default; /** * @brief 初始化随机数生成器 */ void initializeRNG(); /** * @brief 从随机组中按概率选择一个音效 * @param randomGroup 随机组配置 * @return 选中的音效 ID */ std::string selectFromRandom(const AudioRandom& randomGroup); std::unordered_map effectMap_; ///< 音效映射 std::unordered_map ambientMap_; ///< ???????? std::unordered_map musicMap_; ///< 音乐映射 std::unordered_map randomMap_; ///< 随机组映射 std::mt19937 rng_; ///< 随机数生成器 bool rngInitialized_ = false; ///< 随机数生成器是否已初始化 bool loaded_ = false; }; } // namespace frostbite2D