Bläddra i källkod

Add AudioEventHandler and placeholder audio files

Co-authored-by: djeada <[email protected]>
copilot-swe-agent[bot] 1 månad sedan
förälder
incheckning
2e6d9c7062

+ 15 - 0
CMakeLists.txt

@@ -145,6 +145,21 @@ endif()
 # Copy assets next to the binary for dev runs
 file(COPY assets/ DESTINATION ${CMAKE_BINARY_DIR}/assets/)
 
+# ---- Test executables ----
+if(QT_VERSION_MAJOR EQUAL 6)
+    qt6_add_executable(test_audio_event_handler test_audio_event_handler.cpp)
+else()
+    add_executable(test_audio_event_handler test_audio_event_handler.cpp)
+endif()
+
+target_link_libraries(test_audio_event_handler
+    PRIVATE
+        Qt${QT_VERSION_MAJOR}::Core
+        Qt${QT_VERSION_MAJOR}::Multimedia
+        engine_core
+        audio_system
+)
+
 # ---- clang-format helpers (optional but convenient) ----
 # Provides:
 #   - clang-format        : formats all C/C++ sources using .clang-format

BIN
assets/audio/music/combat.wav


BIN
assets/audio/music/defeat.wav


BIN
assets/audio/music/peaceful.wav


BIN
assets/audio/music/tense.wav


BIN
assets/audio/music/victory.wav


BIN
assets/audio/voices/archer_voice.wav


BIN
assets/audio/voices/knight_voice.wav


BIN
assets/audio/voices/spearman_voice.wav


+ 117 - 0
game/audio/AudioEventHandler.cpp

@@ -0,0 +1,117 @@
+#include "AudioEventHandler.h"
+#include "AudioSystem.h"
+#include "../core/component.h"
+#include "../core/entity.h"
+#include "../core/world.h"
+
+namespace Game {
+namespace Audio {
+
+AudioEventHandler::AudioEventHandler(Engine::Core::World *world)
+    : m_world(world), m_initialized(false) {}
+
+AudioEventHandler::~AudioEventHandler() { shutdown(); }
+
+bool AudioEventHandler::initialize() {
+  if (m_initialized) {
+    return true;
+  }
+
+  m_unitSelectedSub = Engine::Core::ScopedEventSubscription<
+      Engine::Core::UnitSelectedEvent>(
+      [this](const Engine::Core::UnitSelectedEvent &event) {
+        onUnitSelected(event);
+      });
+
+  m_ambientChangedSub = Engine::Core::ScopedEventSubscription<
+      Engine::Core::AmbientStateChangedEvent>(
+      [this](const Engine::Core::AmbientStateChangedEvent &event) {
+        onAmbientStateChanged(event);
+      });
+
+  m_audioTriggerSub =
+      Engine::Core::ScopedEventSubscription<Engine::Core::AudioTriggerEvent>(
+          [this](const Engine::Core::AudioTriggerEvent &event) {
+            onAudioTrigger(event);
+          });
+
+  m_musicTriggerSub =
+      Engine::Core::ScopedEventSubscription<Engine::Core::MusicTriggerEvent>(
+          [this](const Engine::Core::MusicTriggerEvent &event) {
+            onMusicTrigger(event);
+          });
+
+  m_initialized = true;
+  return true;
+}
+
+void AudioEventHandler::shutdown() {
+  if (!m_initialized) {
+    return;
+  }
+
+  m_unitSelectedSub.unsubscribe();
+  m_ambientChangedSub.unsubscribe();
+  m_audioTriggerSub.unsubscribe();
+  m_musicTriggerSub.unsubscribe();
+
+  m_unitVoiceMap.clear();
+  m_ambientMusicMap.clear();
+
+  m_initialized = false;
+}
+
+void AudioEventHandler::loadUnitVoiceMapping(const std::string &unitType,
+                                             const std::string &soundId) {
+  m_unitVoiceMap[unitType] = soundId;
+}
+
+void AudioEventHandler::loadAmbientMusic(Engine::Core::AmbientState state,
+                                         const std::string &musicId) {
+  m_ambientMusicMap[state] = musicId;
+}
+
+void AudioEventHandler::onUnitSelected(
+    const Engine::Core::UnitSelectedEvent &event) {
+  if (!m_world) {
+    return;
+  }
+
+  auto *entity = m_world->getEntity(event.unitId);
+  if (!entity) {
+    return;
+  }
+
+  auto *unitComponent = entity->getComponent<Engine::Core::UnitComponent>();
+  if (!unitComponent) {
+    return;
+  }
+
+  auto it = m_unitVoiceMap.find(unitComponent->unitType);
+  if (it != m_unitVoiceMap.end()) {
+    AudioSystem::getInstance().playSound(it->second);
+  }
+}
+
+void AudioEventHandler::onAmbientStateChanged(
+    const Engine::Core::AmbientStateChangedEvent &event) {
+  auto it = m_ambientMusicMap.find(event.newState);
+  if (it != m_ambientMusicMap.end()) {
+    AudioSystem::getInstance().playMusic(it->second);
+  }
+}
+
+void AudioEventHandler::onAudioTrigger(
+    const Engine::Core::AudioTriggerEvent &event) {
+  AudioSystem::getInstance().playSound(event.soundId, event.volume, event.loop,
+                                       event.priority);
+}
+
+void AudioEventHandler::onMusicTrigger(
+    const Engine::Core::MusicTriggerEvent &event) {
+  AudioSystem::getInstance().playMusic(event.musicId, event.volume,
+                                       event.crossfade);
+}
+
+} // namespace Audio
+} // namespace Game

+ 54 - 0
game/audio/AudioEventHandler.h

@@ -0,0 +1,54 @@
+#pragma once
+
+#include "../core/event_manager.h"
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+class AudioSystem;
+
+namespace Engine::Core {
+class World;
+}
+
+namespace Game {
+namespace Audio {
+
+class AudioEventHandler {
+public:
+  AudioEventHandler(Engine::Core::World *world);
+  ~AudioEventHandler();
+
+  bool initialize();
+  void shutdown();
+
+  void loadUnitVoiceMapping(const std::string &unitType,
+                            const std::string &soundId);
+  void loadAmbientMusic(Engine::Core::AmbientState state,
+                        const std::string &musicId);
+
+private:
+  void onUnitSelected(const Engine::Core::UnitSelectedEvent &event);
+  void onAmbientStateChanged(
+      const Engine::Core::AmbientStateChangedEvent &event);
+  void onAudioTrigger(const Engine::Core::AudioTriggerEvent &event);
+  void onMusicTrigger(const Engine::Core::MusicTriggerEvent &event);
+
+  Engine::Core::World *m_world;
+  std::unordered_map<std::string, std::string> m_unitVoiceMap;
+  std::unordered_map<Engine::Core::AmbientState, std::string> m_ambientMusicMap;
+
+  Engine::Core::ScopedEventSubscription<Engine::Core::UnitSelectedEvent>
+      m_unitSelectedSub;
+  Engine::Core::ScopedEventSubscription<Engine::Core::AmbientStateChangedEvent>
+      m_ambientChangedSub;
+  Engine::Core::ScopedEventSubscription<Engine::Core::AudioTriggerEvent>
+      m_audioTriggerSub;
+  Engine::Core::ScopedEventSubscription<Engine::Core::MusicTriggerEvent>
+      m_musicTriggerSub;
+
+  bool m_initialized;
+};
+
+} // namespace Audio
+} // namespace Game

+ 2 - 1
game/audio/CMakeLists.txt

@@ -2,7 +2,8 @@ add_library(audio_system STATIC
     AudioSystem.cpp
     Sound.cpp
     Music.cpp
+    AudioEventHandler.cpp
 )
 
 target_include_directories(audio_system PUBLIC .)
-target_link_libraries(audio_system PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Multimedia)
+target_link_libraries(audio_system PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Multimedia engine_core)

+ 100 - 0
test_audio_event_handler.cpp

@@ -0,0 +1,100 @@
+#include "game/audio/AudioEventHandler.h"
+#include "game/audio/AudioSystem.h"
+#include "game/core/event_manager.h"
+#include "game/core/world.h"
+#include <QCoreApplication>
+#include <QTimer>
+#include <iostream>
+
+int main(int argc, char *argv[]) {
+  QCoreApplication app(argc, argv);
+
+  std::cout << "=== Audio Event Handler Test ===" << std::endl;
+
+  std::cout << "\n1. Initializing Audio System..." << std::endl;
+  auto &audioSystem = AudioSystem::getInstance();
+  if (!audioSystem.initialize()) {
+    std::cerr << "Failed to initialize audio system!" << std::endl;
+    return 1;
+  }
+  std::cout << "   ✓ Audio System initialized" << std::endl;
+
+  std::cout << "\n2. Creating World instance..." << std::endl;
+  Engine::Core::World world;
+  std::cout << "   ✓ World created" << std::endl;
+
+  std::cout << "\n3. Initializing Audio Event Handler..." << std::endl;
+  Game::Audio::AudioEventHandler handler(&world);
+  if (!handler.initialize()) {
+    std::cerr << "Failed to initialize audio event handler!" << std::endl;
+    return 1;
+  }
+  std::cout << "   ✓ Audio Event Handler initialized" << std::endl;
+
+  std::cout << "\n4. Loading placeholder audio resources..." << std::endl;
+  audioSystem.loadSound("archer_voice",
+                        "assets/audio/voices/archer_voice.wav");
+  audioSystem.loadSound("knight_voice",
+                        "assets/audio/voices/knight_voice.wav");
+  audioSystem.loadSound("spearman_voice",
+                        "assets/audio/voices/spearman_voice.wav");
+  std::cout << "   ✓ Loaded unit voice sounds" << std::endl;
+
+  audioSystem.loadMusic("peaceful", "assets/audio/music/peaceful.wav");
+  audioSystem.loadMusic("tense", "assets/audio/music/tense.wav");
+  audioSystem.loadMusic("combat", "assets/audio/music/combat.wav");
+  audioSystem.loadMusic("victory", "assets/audio/music/victory.wav");
+  audioSystem.loadMusic("defeat", "assets/audio/music/defeat.wav");
+  std::cout << "   ✓ Loaded ambient music" << std::endl;
+
+  std::cout << "\n5. Configuring unit type mappings..." << std::endl;
+  handler.loadUnitVoiceMapping("archer", "archer_voice");
+  handler.loadUnitVoiceMapping("knight", "knight_voice");
+  handler.loadUnitVoiceMapping("spearman", "spearman_voice");
+  std::cout << "   ✓ Unit voice mappings configured" << std::endl;
+
+  std::cout << "\n6. Configuring ambient state mappings..." << std::endl;
+  handler.loadAmbientMusic(Engine::Core::AmbientState::PEACEFUL, "peaceful");
+  handler.loadAmbientMusic(Engine::Core::AmbientState::TENSE, "tense");
+  handler.loadAmbientMusic(Engine::Core::AmbientState::COMBAT, "combat");
+  handler.loadAmbientMusic(Engine::Core::AmbientState::VICTORY, "victory");
+  handler.loadAmbientMusic(Engine::Core::AmbientState::DEFEAT, "defeat");
+  std::cout << "   ✓ Ambient music mappings configured" << std::endl;
+
+  std::cout << "\n7. Testing AudioTriggerEvent..." << std::endl;
+  Engine::Core::EventManager::instance().publish(
+      Engine::Core::AudioTriggerEvent("archer_voice", 0.8f));
+  std::cout << "   ✓ Published AudioTriggerEvent" << std::endl;
+
+  std::cout << "\n8. Testing MusicTriggerEvent..." << std::endl;
+  Engine::Core::EventManager::instance().publish(
+      Engine::Core::MusicTriggerEvent("peaceful", 0.6f));
+  std::cout << "   ✓ Published MusicTriggerEvent" << std::endl;
+
+  std::cout << "\n9. Testing AmbientStateChangedEvent..." << std::endl;
+  Engine::Core::EventManager::instance().publish(
+      Engine::Core::AmbientStateChangedEvent(
+          Engine::Core::AmbientState::COMBAT,
+          Engine::Core::AmbientState::PEACEFUL));
+  std::cout << "   ✓ Published AmbientStateChangedEvent (PEACEFUL -> COMBAT)"
+            << std::endl;
+
+  std::cout << "\n10. Testing event handler registration..." << std::endl;
+  auto stats = Engine::Core::EventManager::instance().getStats(
+      std::type_index(typeid(Engine::Core::AudioTriggerEvent)));
+  std::cout << "   ✓ AudioTriggerEvent subscribers: " << stats.subscriberCount
+            << std::endl;
+  std::cout << "   ✓ AudioTriggerEvent publish count: " << stats.publishCount
+            << std::endl;
+
+  std::cout << "\n11. Shutting down..." << std::endl;
+  handler.shutdown();
+  std::cout << "   ✓ Audio Event Handler shutdown" << std::endl;
+  audioSystem.shutdown();
+  std::cout << "   ✓ Audio System shutdown" << std::endl;
+
+  std::cout << "\n=== All tests passed! ===" << std::endl;
+
+  QTimer::singleShot(0, &app, &QCoreApplication::quit);
+  return app.exec();
+}