refactor: 将数学工具函数移至GameMath类 feat(音频): 实现地图音频控制器 feat(调试): 添加游戏调试UI组件 feat(地图): 增加移动区域边界获取方法 fix(角色): 修复角色移动区域抑制逻辑 refactor(世界): 重构游戏世界场景初始化 docs(音频): 完善音频数据库注释
354 lines
9.8 KiB
C++
354 lines
9.8 KiB
C++
#pragma once
|
||
|
||
#include <frostbite2D/types/type_alias.h>
|
||
#include <optional>
|
||
#include <random>
|
||
#include <string>
|
||
#include <unordered_map>
|
||
#include <vector>
|
||
|
||
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<RandomItem> 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<AudioEntry> get(const std::string& id);
|
||
|
||
/**
|
||
* @brief 获取音效 ID(自动处理 RANDOM 类型)
|
||
*
|
||
* 如果 id 是 RANDOM 类型,会按概率随机选择一个音效;
|
||
* 如果 id 是 EFFECT 类型,直接返回该 ID。
|
||
*
|
||
* @param id 音效 ID 或随机组 ID
|
||
* @return 音效 ID,未找到返回 std::nullopt
|
||
*/
|
||
std::optional<std::string> getSound(const std::string& id);
|
||
|
||
/**
|
||
* @brief 获取文件路径
|
||
*
|
||
* 对于 EFFECT 和 MUSIC 类型,返回对应的文件路径;
|
||
* 对于 RANDOM 类型,先随机选择一个音效再返回其文件路径。
|
||
*
|
||
* @param id 音频 ID
|
||
* @return 文件路径,未找到返回 std::nullopt
|
||
*/
|
||
std::optional<std::string> getFilePath(const std::string& id);
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 类型特定查询(高级用法)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/**
|
||
* @brief 获取音效配置
|
||
* @param id 音效 ID
|
||
* @return 音效配置指针,未找到返回 std::nullopt
|
||
*/
|
||
std::optional<const AudioEffect*> getEffect(const std::string& id);
|
||
|
||
/**
|
||
* @brief ????????????
|
||
* @param id ?????ID
|
||
* @return ????????????????????? std::nullopt
|
||
*/
|
||
std::optional<const AudioAmbient*> getAmbient(const std::string& id);
|
||
|
||
/**
|
||
* @brief 获取音乐配置
|
||
* @param id 音乐 ID
|
||
* @return 音乐配置指针,未找到返回 std::nullopt
|
||
*/
|
||
std::optional<const AudioMusic*> getMusic(const std::string& id);
|
||
|
||
/**
|
||
* @brief 获取随机音效组配置
|
||
* @param id 随机组 ID
|
||
* @return 随机组配置指针,未找到返回 std::nullopt
|
||
*/
|
||
std::optional<const AudioRandom*> 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<std::string, AudioEffect> effectMap_; ///< 音效映射
|
||
std::unordered_map<std::string, AudioAmbient> ambientMap_; ///< ????????
|
||
std::unordered_map<std::string, AudioMusic> musicMap_; ///< 音乐映射
|
||
std::unordered_map<std::string, AudioRandom> randomMap_; ///< 随机组映射
|
||
|
||
std::mt19937 rng_; ///< 随机数生成器
|
||
bool rngInitialized_ = false; ///< 随机数生成器是否已初始化
|
||
bool loaded_ = false;
|
||
};
|
||
|
||
} // namespace frostbite2D
|