diff --git a/CMakeLists.txt b/CMakeLists.txt index 56d177a..3ba0fae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ find_package(spdlog REQUIRED) # 添加可执行文件 add_executable(${TARGET} src/main.cpp + src/engine/audio/audio_player.cpp src/engine/core/game_app.cpp src/engine/core/time.cpp src/engine/core/config.cpp @@ -52,6 +53,7 @@ add_executable(${TARGET} src/engine/component/collider_component.cpp src/engine/component/animation_component.cpp src/engine/component/health_component.cpp + src/engine/component/audio_component.cpp src/engine/physics/physics_engine.cpp src/engine/physics/collision.cpp src/engine/scene/scene.cpp diff --git a/Tiled地图解析思路.md b/Tiled地图解析思路.md index 2868c32..0c4159e 100644 --- a/Tiled地图解析思路.md +++ b/Tiled地图解析思路.md @@ -53,6 +53,13 @@ { "动画名" : {"帧持续时间" : ms, "行" : (从0开始数), "帧序列(列)": [0,1,2...]} } ``` 要求每个动画序列帧必须在同一行。 + - sound (json_string) : 添加音频组件,json格式为: + ```json + { + "id1" : "file_path1", + "id2" : ... + } + ``` - 图块碰撞编辑器:识别为碰撞盒,限一个图形,只支持矩形和圆形 - 两层皆可 diff --git a/assets/maps/actor.tsj b/assets/maps/actor.tsj index 0a9006e..2d25696 100644 --- a/assets/maps/actor.tsj +++ b/assets/maps/actor.tsj @@ -21,7 +21,6 @@ "objectgroup": { "draworder":"index", - "id":2, "name":"", "objects":[ { @@ -105,6 +104,11 @@ "type":"int", "value":3 }, + { + "name":"sound", + "type":"string", + "value":"{\n \"jump\":\"assets\/audio\/cartoon-jump-6462.mp3\",\n \"hurt\":\"assets\/audio\/monster.mp3\",\n \"dead\":\"assets\/audio\/dead-8bit-41400.mp3\"\n}" + }, { "name":"tag", "type":"string", @@ -158,6 +162,11 @@ "type":"int", "value":1 }, + { + "name":"sound", + "type":"string", + "value":"{\n \"cry\":\"assets\/audio\/frog_quak-81741.mp3\"\n}" + }, { "name":"tag", "type":"string", diff --git a/assets/maps/level1.tmj b/assets/maps/level1.tmj index 7278ed5..be8eafe 100644 --- a/assets/maps/level1.tmj +++ b/assets/maps/level1.tmj @@ -126,8 +126,8 @@ "type":"", "visible":true, "width":32, - "x":636.507575757576, - "y":157.962121212121 + "x":113.174242424242, + "y":156.628787878788 }, { "gid":607, diff --git a/src/engine/audio/audio_player.cpp b/src/engine/audio/audio_player.cpp new file mode 100644 index 0000000..c26963d --- /dev/null +++ b/src/engine/audio/audio_player.cpp @@ -0,0 +1,102 @@ +#include "audio_player.h" +#include "../resource/resource_manager.h" +#include +#include +#include + +namespace engine::audio { +AudioPlayer::~AudioPlayer() = default; + +AudioPlayer::AudioPlayer(engine::resource::ResourceManager* resource_manager) + : resource_manager_(resource_manager) { + if (!resource_manager_) { + throw std::runtime_error("AudioPlayer 构造失败: 提供的 ResourceManager 指针为空。"); + } +} + +int AudioPlayer::playSound(const std::string& sound_path, int channel) { + + Mix_Chunk* chunk = resource_manager_->getSound(sound_path); // 通过 ResourceManager 获取资源 + if (!chunk) { + spdlog::error("AudioPlayer: 无法获取音效 '{}' 播放。", sound_path); + return -1; + } + + int played_channel = Mix_PlayChannel(channel, chunk, 0); // 播放音效 + if (played_channel == -1) { + spdlog::error("AudioPlayer: 无法播放音效 '{}': {}", sound_path, SDL_GetError()); + } else { + spdlog::trace("AudioPlayer: 播放音效 '{}' 在通道 {}。", sound_path, played_channel); + } + return played_channel; +} + +bool AudioPlayer::playMusic(const std::string& music_path, int loops, int fade_in_ms) { + if (music_path == current_music_) return true; // 如果当前音乐已经在播放,则不重复播放 + current_music_ = music_path; + Mix_Music* music = resource_manager_->getMusic(music_path); // 通过 ResourceManager 获取资源 + if (!music) { + spdlog::error("AudioPlayer: 无法获取音乐 '{}' 播放。", music_path); + return false; + } + + Mix_HaltMusic(); // 停止之前的音乐 + + bool result = false; + if (fade_in_ms > 0) { + result = Mix_FadeInMusic(music, loops, fade_in_ms); // 淡入播放音乐 + } else { + result = Mix_PlayMusic(music, loops); + } + + if (!result) { + spdlog::error("AudioPlayer: 无法播放音乐 '{}': {}", music_path, SDL_GetError()); + } else { + spdlog::trace("AudioPlayer: 播放音乐 '{}'。", music_path); + } + return result; +} + +void AudioPlayer::stopMusic(int fade_out_ms) { + if (fade_out_ms > 0) { + Mix_FadeOutMusic(fade_out_ms); // 淡出音乐 + } else { + Mix_HaltMusic(); + } + spdlog::trace("AudioPlayer: 停止音乐。"); +} + +void AudioPlayer::pauseMusic() { + Mix_PauseMusic(); + spdlog::trace("AudioPlayer: 暂停音乐。"); +} + +void AudioPlayer::resumeMusic() { + Mix_ResumeMusic(); + spdlog::trace("AudioPlayer: 恢复音乐。"); +} + +void AudioPlayer::setSoundVolume(float volume, int channel) { + // 将浮点音量(0-1)转换为SDL_mixer的音量(0-128) + int sdl_volume = static_cast(glm::max(0.0f, glm::min(1.0f, volume)) * MIX_MAX_VOLUME); + Mix_Volume(channel, sdl_volume); + spdlog::trace("AudioPlayer: 设置通道 {} 的音量为 {:.2f}。", channel, volume); +} + +void AudioPlayer::setMusicVolume(float volume) { + int sdl_volume = static_cast(glm::max(0.0f, glm::min(1.0f, volume)) * MIX_MAX_VOLUME); + Mix_VolumeMusic(sdl_volume); + spdlog::trace("AudioPlayer: 设置音乐音量为 {:.2f}。", volume); +} + +float AudioPlayer::getMusicVolume() { + // SDL_mixer的音量(0-128)转为 0~1.0 的浮点数 + return static_cast(Mix_VolumeMusic(-1)) / static_cast(MIX_MAX_VOLUME); + /* 参数 -1 表示查询当前音量 */ +} + +float AudioPlayer::getSoundVolume(int channel) { + return static_cast(Mix_Volume(channel, -1)) / static_cast(MIX_MAX_VOLUME); +} + +} // namespace engine::audio diff --git a/src/engine/audio/audio_player.h b/src/engine/audio/audio_player.h new file mode 100644 index 0000000..c6bebc8 --- /dev/null +++ b/src/engine/audio/audio_player.h @@ -0,0 +1,101 @@ +#pragma once +#include + +namespace engine::resource { + class ResourceManager; +} + +struct Mix_Chunk; +struct Mix_Music; + +namespace engine::audio { + +/** + * @brief 用于控制音频播放的单例类。 + * + * 提供播放音效和音乐的方法,使用由 ResourceManager 管理的资源。 + * 必须使用有效的 ResourceManager 实例初始化。 + */ +class AudioPlayer final{ +private: + engine::resource::ResourceManager* resource_manager_; ///< @brief 指向 ResourceManager 的非拥有指针,用于加载和管理音频资源。 + std::string current_music_; ///< @brief 当前正在播放的音乐路径,用于避免重复播放同一音乐。 + +public: + /** + * @brief 构造函数,使用 ResourceManager 初始化。 + */ + explicit AudioPlayer(engine::resource::ResourceManager* resource_manager); + ~AudioPlayer(); + + // 删除复制/移动操作 + AudioPlayer(const AudioPlayer&) = delete; + AudioPlayer& operator=(const AudioPlayer&) = delete; + AudioPlayer(AudioPlayer&&) = delete; + AudioPlayer& operator=(AudioPlayer&&) = delete; + + // --- 播放控制方法 --- + /** + * @brief 播放音效(chunk)。 + * 如果尚未缓存,则通过 ResourceManager 加载音效。 + * @param sound_path 音效文件的路径。 + * @param channel 要播放的特定通道,或 -1 表示第一个可用通道。默认为 -1。 + * @return 音效正在播放的通道,出错时返回 -1。 + */ + int playSound(const std::string& sound_path, int channel = -1); + + /** + * @brief 播放背景音乐。如果正在播放,则淡出之前的音乐。 + * 如果尚未缓存,则通过 ResourceManager 加载音乐。 + * @param music_path 音乐文件的路径。 + * @param loops 循环次数(-1 无限循环,0 播放一次,1 播放两次,以此类推)。默认为 -1。 + * @param fade_in_ms 音乐淡入的时间(毫秒)(0 表示不淡入)。默认为 0。 + * @return 成功返回 true,出错返回 false。 + */ + bool playMusic(const std::string& music_path, int loops = -1, int fade_in_ms = 0); + + /** + * @brief 停止当前正在播放的背景音乐。 + * @param fade_out_ms 淡出时间(毫秒)(0 表示立即停止)。默认为 0。 + */ + void stopMusic(int fade_out_ms = 0); + + /** + * @brief 暂停当前正在播放的背景音乐。 + */ + void pauseMusic(); + + /** + * @brief 恢复已暂停的背景音乐。 + */ + void resumeMusic(); + + /** + * @brief 设置音效通道的音量。 + * @param volume 音量级别(0.0-1.0)。 + * @param channel 通道号(-1 表示所有通道)。默认为 -1。 + */ + void setSoundVolume(float volume, int channel = -1); + + /** + * @brief 设置音乐通道的音量。 + * @param volume 音量级别(0.0-1.0)。 + */ + void setMusicVolume(float volume); + + /** + * @brief 获取当前音乐音量。 + * @return 音量级别(0.0-1.0)。 + */ + float getMusicVolume(); + + /** + * @brief 获取当前音效音量。 + * @param channel 通道号(-1 表示所有通道)。默认为 -1。 + * @return 音量级别(0.0-1.0)。 + */ + float getSoundVolume(int channel = -1); + +}; + +} // namespace engine::audio diff --git a/src/engine/component/audio_component.cpp b/src/engine/component/audio_component.cpp new file mode 100644 index 0000000..dd79d6e --- /dev/null +++ b/src/engine/component/audio_component.cpp @@ -0,0 +1,60 @@ +#include "audio_component.h" +#include "transform_component.h" +#include "../object/game_object.h" +#include "../audio/audio_player.h" +#include "../render/camera.h" +#include + +namespace engine::component { + +AudioComponent::AudioComponent(engine::audio::AudioPlayer *audio_player, engine::render::Camera *camera) + : audio_player_(audio_player), camera_(camera) +{ + if (!audio_player_ || !camera_) { + spdlog::error("AudioComponent 初始化失败: 音频播放器或相机为空"); + } +} + +void AudioComponent::init() +{ + if (!owner_) { + spdlog::error("AudioComponent 没有所有者 GameObject!"); + return; + } + transform_ = owner_->getComponent(); + if (!transform_) { + spdlog::warn("AudioComponent 所在的 GameObject 上没有 TransformComponent!,无法进行空间定位"); + } +} + +void AudioComponent::playSound(const std::string &sound_id, int channel, bool use_spatial) +{ + // 如果 sound_id 是音效 ID,则在查找在map中查找对应的路径; 没找到的话则把 sound_id 当作路径直接使用 + auto sound_path = sound_id_to_path_.find(sound_id) != sound_id_to_path_.end() ? sound_id_to_path_[sound_id] : sound_id; + + if (use_spatial && transform_) { // 使用空间定位 + // TODO: (SDL_Mixer 不支持空间定位,未来更换音频库时可以方便地实现) + // 这里给一个简单的功能:150像素范围内播放,否则不播放 + auto camera_center = camera_->getPosition() + camera_->getViewportSize() / 2.0f; // 相机中心 + auto object_pos = transform_->getPosition(); + float distance = glm::length(camera_center - object_pos); + if (distance > 150.0f) { + spdlog::debug("AudioComponent::playSound: 音效 '{}' 超出范围,不播放。", sound_id); + return; // 超出范围,不播放 + } + audio_player_->playSound(sound_path, channel); + } else { // 不使用空间定位 + audio_player_->playSound(sound_path, channel); + } +} + +void AudioComponent::addSound(const std::string &sound_id, const std::string &sound_path) +{ + if (sound_id_to_path_.find(sound_id) != sound_id_to_path_.end()) { + spdlog::warn("AudioComponent::addSound: 音效 ID '{}' 已存在,覆盖旧路径。", sound_id); + } + sound_id_to_path_[sound_id] = sound_path; + spdlog::debug("AudioComponent::addSound: 添加音效 ID '{}' 路径 '{}'", sound_id, sound_path); +} + +} // namespace engine::component \ No newline at end of file diff --git a/src/engine/component/audio_component.h b/src/engine/component/audio_component.h new file mode 100644 index 0000000..3610024 --- /dev/null +++ b/src/engine/component/audio_component.h @@ -0,0 +1,60 @@ +#pragma once +#include "component.h" +#include +#include + +namespace engine::audio { + class AudioPlayer; +} + +namespace engine::render { + class Camera; +} + +namespace engine::component { + class TransformComponent; + +/** + * @brief 音频组件,用于处理音频播放和管理。 + */ +class AudioComponent final: public Component { + friend class engine::object::GameObject; +private: + engine::audio::AudioPlayer* audio_player_; ///< @brief 音频播放器的非拥有指针 + engine::render::Camera* camera_; ///< @brief 相机的非拥有指针,用于音频空间定位 + engine::component::TransformComponent* transform_ = nullptr; ///< @brief 缓存变换组件 + + std::unordered_map sound_id_to_path_; ///< @brief 音效id 到路径的映射表 + +public: + AudioComponent(engine::audio::AudioPlayer* audio_player, engine::render::Camera* camera); + ~AudioComponent() override = default; + + // 禁止拷贝和移动 + AudioComponent(const AudioComponent&) = delete; + AudioComponent& operator=(const AudioComponent&) = delete; + AudioComponent(AudioComponent&&) = delete; + AudioComponent& operator=(AudioComponent&&) = delete; + + /** + * @brief 播放音效。 + * @param sound_path 音效文件的id (或路径)。 + * @param channel 要播放的特定通道,或 -1 表示第一个可用通道。 + * @param use_spatial 是否使用空间定位。 + */ + void playSound(const std::string& sound_id, int channel = -1, bool use_spatial = false); + + /** + * @brief 添加音效到映射表。 + * @param sound_id 音效的标识符(针对本组件唯一即可)。 + * @param sound_path 音效文件的路径。 + */ + void addSound(const std::string& sound_id, const std::string& sound_path); + +private: + // 核心循环方法 + void init() override; + void update(float, engine::core::Context&) override {} +}; + +} // namespace engine::component \ No newline at end of file diff --git a/src/engine/core/context.cpp b/src/engine/core/context.cpp index efe409c..17fdf1e 100644 --- a/src/engine/core/context.cpp +++ b/src/engine/core/context.cpp @@ -4,6 +4,7 @@ #include "../render/camera.h" #include "../resource/resource_manager.h" #include "../physics/physics_engine.h" +#include "../audio/audio_player.h" #include namespace engine::core { @@ -12,12 +13,14 @@ Context::Context(engine::input::InputManager& input_manager, engine::render::Renderer& renderer, engine::render::Camera& camera, engine::resource::ResourceManager& resource_manager, - engine::physics::PhysicsEngine& physics_engine) + engine::physics::PhysicsEngine& physics_engine, + engine::audio::AudioPlayer& audio_player) : input_manager_(input_manager), renderer_(renderer), camera_(camera), resource_manager_(resource_manager), - physics_engine_(physics_engine) + physics_engine_(physics_engine), + audio_player_(audio_player) { spdlog::trace("上下文已创建并初始化,包含输入管理器、渲染器、相机和资源管理器。"); } diff --git a/src/engine/core/context.h b/src/engine/core/context.h index 709d9c2..16efbe8 100644 --- a/src/engine/core/context.h +++ b/src/engine/core/context.h @@ -17,6 +17,10 @@ namespace engine::physics { class PhysicsEngine; } +namespace engine::audio { + class AudioPlayer; +} + namespace engine::core { /** @@ -32,6 +36,7 @@ private: engine::render::Camera& camera_; ///< @brief 相机 engine::resource::ResourceManager& resource_manager_; ///< @brief 资源管理器 engine::physics::PhysicsEngine& physics_engine_; ///< @brief 物理引擎 + engine::audio::AudioPlayer& audio_player_; ///< @brief 音频播放器 public: /** @@ -46,7 +51,8 @@ public: engine::render::Renderer& renderer, engine::render::Camera& camera, engine::resource::ResourceManager& resource_manager, - engine::physics::PhysicsEngine& physics_engine); + engine::physics::PhysicsEngine& physics_engine, + engine::audio::AudioPlayer& audio_player); // 禁止拷贝和移动,Context 对象通常是唯一的或按需创建/传递 Context(const Context&) = delete; @@ -60,6 +66,7 @@ public: engine::render::Camera& getCamera() const { return camera_; } ///< @brief 获取相机 engine::resource::ResourceManager& getResourceManager() const { return resource_manager_; } ///< @brief 获取资源管理器 engine::physics::PhysicsEngine& getPhysicsEngine() const { return physics_engine_; } ///< @brief 获取物理引擎 + engine::audio::AudioPlayer& getAudioPlayer() const { return audio_player_; } ///< @brief 获取音频播放器 }; } // namespace engine::core \ No newline at end of file diff --git a/src/engine/core/game_app.cpp b/src/engine/core/game_app.cpp index 7b34b51..544376b 100644 --- a/src/engine/core/game_app.cpp +++ b/src/engine/core/game_app.cpp @@ -3,6 +3,7 @@ #include "context.h" #include "config.h" #include "../resource/resource_manager.h" +#include "../audio/audio_player.h" #include "../render/renderer.h" #include "../render/camera.h" #include "../input/input_manager.h" @@ -50,6 +51,7 @@ bool GameApp::init() { if (!initSDL()) return false; if (!initTime()) return false; if (!initResourceManager()) return false; + if (!initAudioPlayer()) return false; if (!initRenderer()) return false; if (!initCamera()) return false; if (!initInputManager()) return false; @@ -178,6 +180,18 @@ bool GameApp::initResourceManager() { return true; } +bool GameApp::initAudioPlayer() +{ + try { + audio_player_ = std::make_unique(resource_manager_.get()); + } catch (const std::exception& e) { + spdlog::error("初始化音频播放器失败: {}", e.what()); + return false; + } + spdlog::trace("音频播放器初始化成功。"); + return true; +} + bool GameApp::initRenderer() { try { renderer_ = std::make_unique(sdl_renderer_, resource_manager_.get()); @@ -228,7 +242,12 @@ bool GameApp::initPhysicsEngine() bool GameApp::initContext() { try { - context_ = std::make_unique(*input_manager_, *renderer_, *camera_, *resource_manager_, *physics_engine_); + context_ = std::make_unique(*input_manager_, + *renderer_, + *camera_, + *resource_manager_, + *physics_engine_, + *audio_player_); } catch (const std::exception& e) { spdlog::error("初始化上下文失败: {}", e.what()); return false; diff --git a/src/engine/core/game_app.h b/src/engine/core/game_app.h index 17f12cb..521b1c1 100644 --- a/src/engine/core/game_app.h +++ b/src/engine/core/game_app.h @@ -26,6 +26,10 @@ namespace engine::scene { class SceneManager; } +namespace engine::audio { +class AudioPlayer; +} + namespace engine::core { // 命名空间的最佳实践:与文件路径一致 class Time; class Config; @@ -50,6 +54,7 @@ private: std::unique_ptr context_; std::unique_ptr scene_manager_; std::unique_ptr physics_engine_; + std::unique_ptr audio_player_; public: GameApp(); @@ -78,6 +83,7 @@ private: [[nodiscard]] bool initSDL(); [[nodiscard]] bool initTime(); [[nodiscard]] bool initResourceManager(); + [[nodiscard]] bool initAudioPlayer(); [[nodiscard]] bool initRenderer(); [[nodiscard]] bool initCamera(); [[nodiscard]] bool initInputManager(); diff --git a/src/engine/scene/level_loader.cpp b/src/engine/scene/level_loader.cpp index a109922..a29276f 100644 --- a/src/engine/scene/level_loader.cpp +++ b/src/engine/scene/level_loader.cpp @@ -7,6 +7,7 @@ #include "../component/physics_component.h" #include "../component/animation_component.h" #include "../component/health_component.h" +#include "../component/audio_component.h" #include "../object/game_object.h" #include "../scene/scene.h" #include "../core/context.h" @@ -253,6 +254,24 @@ void LevelLoader::loadObjectLayer(const nlohmann::json& layer_json, Scene& scene addAnimation(anim_json, ac, src_size); } + // 获取音效信息并设置 + auto sound_string = getTileProperty(tile_json, "sound"); + if (sound_string) { + // 解析string为JSON对象 + nlohmann::json sound_json; + try { + sound_json = nlohmann::json::parse(sound_string.value()); + } catch (const nlohmann::json::parse_error& e) { + spdlog::error("解析音效 JSON 字符串失败: {}", e.what()); + continue; // 跳过此对象 + } + // 添加AudioComponent + auto* audio_component = game_object->addComponent(&scene.getContext().getAudioPlayer(), + &scene.getContext().getCamera()); + // 添加音效到 AudioComponent + addSound(sound_json, audio_component); + } + // 获取生命值信息并设置 auto health = getTileProperty(tile_json, "health"); if (health) { @@ -316,6 +335,25 @@ void LevelLoader::addAnimation(const nlohmann::json& anim_json, engine::componen } } +void LevelLoader::addSound(const nlohmann::json &sound_json, engine::component::AudioComponent *audio_component) +{ + if (!sound_json.is_object() || !audio_component) { + spdlog::error("无效的音效 JSON 或 AudioComponent 指针。"); + return; + } + // 遍历音效 JSON 对象中的每个键值对(音效id : 音效路径) + for (const auto& sound : sound_json.items()) { + const std::string& sound_id = sound.key(); + const std::string& sound_path = sound.value(); + if (sound_id.empty() || sound_path.empty() ) { + spdlog::warn("音效 '{}' 缺少必要信息。", sound_id); + continue; + } + // 添加音效到 AudioComponent + audio_component->addSound(sound_id, sound_path); + } +} + std::optional LevelLoader::getColliderRect(const nlohmann::json &tile_json) { if (!tile_json.contains("objectgroup")) return std::nullopt; diff --git a/src/engine/scene/level_loader.h b/src/engine/scene/level_loader.h index 7d6b894..5afae46 100644 --- a/src/engine/scene/level_loader.h +++ b/src/engine/scene/level_loader.h @@ -8,6 +8,7 @@ namespace engine::component { class AnimationComponent; +class AudioComponent; struct TileInfo; enum class TileType; } @@ -48,6 +49,13 @@ private: */ void addAnimation(const nlohmann::json& anim_json, engine::component::AnimationComponent* ac, const glm::vec2& sprite_size); + /** + * @brief 添加音效到指定的 AudioComponent。 + * @param sound_json 音效json数据(自定义) + * @param audio_component AudioComponent 指针(音效添加到此组件) + */ + void addSound(const nlohmann::json& sound_json, engine::component::AudioComponent* audio_component); + /** * @brief 获取瓦片属性 * @tparam T 属性类型 diff --git a/src/game/component/ai/jump_behavior.cpp b/src/game/component/ai/jump_behavior.cpp index d91349e..e1d3f1f 100644 --- a/src/game/component/ai/jump_behavior.cpp +++ b/src/game/component/ai/jump_behavior.cpp @@ -4,6 +4,7 @@ #include "../../../engine/component/transform_component.h" #include "../../../engine/component/sprite_component.h" #include "../../../engine/component/animation_component.h" +#include "../../../engine/component/audio_component.h" #include "../../../engine/object/game_object.h" #include @@ -35,6 +36,7 @@ void JumpBehavior::update(float delta_time, AIComponent& ai_component) { auto* transform_component = ai_component.getTransformComponent(); auto* sprite_component = ai_component.getSpriteComponent(); auto* animation_component = ai_component.getAnimationComponent(); + auto* audio_component = ai_component.getAudioComponent(); if (!physics_component || !transform_component || !sprite_component || !animation_component) { spdlog::error("JumpBehavior:缺少必要的组件,无法执行跳跃行为。"); return; @@ -42,6 +44,9 @@ void JumpBehavior::update(float delta_time, AIComponent& ai_component) { auto is_on_ground = physics_component->hasCollidedBelow(); // 着地标志 if (is_on_ground) { // 如果在地面上 + if (audio_component && jump_timer_ < 0.001f) { // 刚刚落地时(进入idle状态),如果有音频组件,播放音效 + audio_component->playSound("cry", -1, true); // 使用空间音频 + } jump_timer_ += delta_time; // 增加跳跃计时器 physics_component->velocity_.x = 0.0f; // 停止水平移动(否则会有惯性) diff --git a/src/game/component/ai_component.cpp b/src/game/component/ai_component.cpp index e1f1c9c..9d782db 100644 --- a/src/game/component/ai_component.cpp +++ b/src/game/component/ai_component.cpp @@ -6,6 +6,7 @@ #include "../../engine/component/sprite_component.h" #include "../../engine/component/animation_component.h" #include "../../engine/component/health_component.h" +#include "../../engine/component/audio_component.h" #include namespace game::component { @@ -21,8 +22,9 @@ void AIComponent::init() { physics_component_ = owner_->getComponent(); sprite_component_ = owner_->getComponent(); animation_component_ = owner_->getComponent(); + audio_component_ = owner_->getComponent(); - // 检查是否所有必需的组件都存在 + // 检查是否所有必需的组件都存在(音频组件并非必须存在) if (!transform_component_ || !physics_component_ || !sprite_component_ || !animation_component_) { spdlog::error("GameObject '{}' 上的 AIComponent 缺少必需的组件", owner_->getName()); } diff --git a/src/game/component/ai_component.h b/src/game/component/ai_component.h index cad6a6c..556fe2d 100644 --- a/src/game/component/ai_component.h +++ b/src/game/component/ai_component.h @@ -9,6 +9,7 @@ namespace engine::component { class PhysicsComponent; class SpriteComponent; class AnimationComponent; + class AudioComponent; } namespace game::component { @@ -30,6 +31,7 @@ private: engine::component::PhysicsComponent* physics_component_ = nullptr; engine::component::SpriteComponent* sprite_component_ = nullptr; engine::component::AnimationComponent* animation_component_ = nullptr; + engine::component::AudioComponent* audio_component_ = nullptr; public: AIComponent() = default; @@ -50,6 +52,7 @@ public: engine::component::PhysicsComponent* getPhysicsComponent() const { return physics_component_; } engine::component::SpriteComponent* getSpriteComponent() const { return sprite_component_; } engine::component::AnimationComponent* getAnimationComponent() const { return animation_component_; } + engine::component::AudioComponent* getAudioComponent() const { return audio_component_; } private: // 核心循环方法 diff --git a/src/game/component/player_component.cpp b/src/game/component/player_component.cpp index a7da2bc..4f5378d 100644 --- a/src/game/component/player_component.cpp +++ b/src/game/component/player_component.cpp @@ -7,11 +7,13 @@ #include "../../engine/component/sprite_component.h" #include "../../engine/component/animation_component.h" #include "../../engine/component/health_component.h" +#include "../../engine/component/audio_component.h" #include "../../engine/object/game_object.h" #include "../../engine/input/input_manager.h" #include #include #include +#include namespace game::component { @@ -27,9 +29,11 @@ void PlayerComponent::init() { sprite_component_ = owner_->getComponent(); animation_component_ = owner_->getComponent(); health_component_ = owner_->getComponent(); + audio_component_ = owner_->getComponent(); // 检查必要组件是否存在 - if (!transform_component_ || !physics_component_ || !sprite_component_ || !animation_component_ || !health_component_) { + if (!transform_component_ || !physics_component_ || !sprite_component_ || + !animation_component_ || !health_component_ || !audio_component_) { spdlog::error("Player 对象缺少必要组件!"); } diff --git a/src/game/component/player_component.h b/src/game/component/player_component.h index e260231..3fc028f 100644 --- a/src/game/component/player_component.h +++ b/src/game/component/player_component.h @@ -12,6 +12,7 @@ namespace engine::component { class SpriteComponent; class AnimationComponent; class HealthComponent; + class AudioComponent; } namespace game::component::state { @@ -32,6 +33,7 @@ private: engine::component::PhysicsComponent* physics_component_ = nullptr; engine::component::AnimationComponent* animation_component_ = nullptr; engine::component::HealthComponent* health_component_ = nullptr; + engine::component::AudioComponent* audio_component_ = nullptr; std::unique_ptr current_state_; bool is_dead_ = false; @@ -72,6 +74,7 @@ public: engine::component::PhysicsComponent* getPhysicsComponent() const { return physics_component_; } engine::component::AnimationComponent* getAnimationComponent() const { return animation_component_; } engine::component::HealthComponent* getHealthComponent() const { return health_component_; } + engine::component::AudioComponent* getAudioComponent() const { return audio_component_; } void setIsDead(bool is_dead) { is_dead_ = is_dead; } ///< @brief 设置玩家是否死亡 bool isDead() const { return is_dead_; } ///< @brief 获取玩家是否死亡 diff --git a/src/game/component/state/dead_state.cpp b/src/game/component/state/dead_state.cpp index 8e62546..d62eb11 100644 --- a/src/game/component/state/dead_state.cpp +++ b/src/game/component/state/dead_state.cpp @@ -4,6 +4,7 @@ #include "../../../engine/component/animation_component.h" #include "../../../engine/component/physics_component.h" #include "../../../engine/component/collider_component.h" +#include "../../../engine/component/audio_component.h" namespace game::component::state { @@ -20,6 +21,10 @@ void DeadState::enter() { if (collider_component) { collider_component->setActive(false); } + + if (auto* audio_component = player_component_->getAudioComponent(); audio_component) { + audio_component->playSound("dead"); // 播放死亡音效 + } } void DeadState::exit() { diff --git a/src/game/component/state/hurt_state.cpp b/src/game/component/state/hurt_state.cpp index 5f1de96..084f558 100644 --- a/src/game/component/state/hurt_state.cpp +++ b/src/game/component/state/hurt_state.cpp @@ -6,6 +6,7 @@ #include "../../../engine/core/context.h" #include "../../../engine/component/physics_component.h" #include "../../../engine/component/sprite_component.h" +#include "../../../engine/component/audio_component.h" #include namespace game::component::state { @@ -22,6 +23,9 @@ void HurtState::enter() { } physics_component->velocity_ = knockback_velocity; // 设置击退速度 + if (auto* audio_component = player_component_->getAudioComponent(); audio_component) { + audio_component->playSound("hurt"); // 播放受伤音效 + } } void HurtState::exit() { diff --git a/src/game/component/state/jump_state.cpp b/src/game/component/state/jump_state.cpp index 9d3079b..8519c57 100644 --- a/src/game/component/state/jump_state.cpp +++ b/src/game/component/state/jump_state.cpp @@ -8,6 +8,7 @@ #include "../../../engine/input/input_manager.h" #include "../../../engine/component/physics_component.h" #include "../../../engine/component/sprite_component.h" +#include "../../../engine/component/audio_component.h" #include #include @@ -17,6 +18,10 @@ void JumpState::enter() { playAnimation("jump"); // 播放跳跃动画 auto physics_component = player_component_->getPhysicsComponent(); physics_component->velocity_.y = -player_component_->getJumpVelocity(); // 向上跳跃 + + if (auto* audio_component = player_component_->getAudioComponent(); audio_component) { + audio_component->playSound("jump"); // 播放跳跃音效 + } spdlog::debug("PlayerComponent 进入 JumpState,设置初始垂直速度为: {}", physics_component->velocity_.y); } diff --git a/src/game/scene/game_scene.cpp b/src/game/scene/game_scene.cpp index b3e088c..b548bbc 100644 --- a/src/game/scene/game_scene.cpp +++ b/src/game/scene/game_scene.cpp @@ -14,6 +14,7 @@ #include "../../engine/input/input_manager.h" #include "../../engine/render/camera.h" #include "../../engine/render/animation.h" +#include "../../engine/audio/audio_player.h" #include "../component/ai_component.h" #include "../component/ai/patrol_behavior.h" #include "../component/ai/updown_behavior.h" @@ -52,6 +53,12 @@ void GameScene::init() { return; } + // 设置音量 + context_.getAudioPlayer().setMusicVolume(0.2f); // 设置背景音乐音量为20% + context_.getAudioPlayer().setSoundVolume(0.5f); // 设置音效音量为50% + // 播放背景音乐 (循环,淡入1秒) + context_.getAudioPlayer().playMusic("assets/audio/hurry_up_and_run.ogg", true, 1000); + Scene::init(); spdlog::trace("GameScene 初始化完成。"); } @@ -245,6 +252,8 @@ void GameScene::PlayerVSEnemyCollision(engine::object::GameObject *player, engin } // 玩家跳起效果 player->getComponent()->velocity_.y = -300.0f; // 向上跳起 + // 播放音效 (此音效完全可以放在玩家的音频组件中,这里示例另一种用法:直接用AudioPlayer播放,传入文件路径) + context_.getAudioPlayer().playSound("assets/audio/punch2a.mp3"); } // 踩踏判断失败,玩家受伤 else { @@ -264,6 +273,7 @@ void GameScene::PlayerVSItemCollision(engine::object::GameObject * player, engin item->setNeedRemove(true); // 标记道具为待删除状态 auto item_aabb = item->getComponent()->getWorldAABB(); createEffect(item_aabb.position + item_aabb.size / 2.0f, item->getTag()); // 创建特效 + context_.getAudioPlayer().playSound("assets/audio/poka01.mp3"); // 播放音效 } void GameScene::createEffect(const glm::vec2& center_pos, const std::string &tag)