diff --git a/CMakeLists.txt b/CMakeLists.txt index a313ce0..116b7f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -243,6 +243,9 @@ add_library(Engine STATIC engine/src/core/inputsOutputs/context/InputContext.h engine/src/layer/SceneManager.cpp engine/src/layer/SceneManager.h + engine/src/core/events/EventBus.cpp + engine/src/core/events/EventBus.h + engine/src/core/audio/ecs/PlaySoundComponent.h ) target_include_directories(Engine PUBLIC engine/src ${dr_libs_SOURCE_DIR}) target_link_libraries(Engine PUBLIC OpenGL::GL glfw glad glm::glm imgui spdlog::spdlog tinyobjloader stb_image OpenAL::OpenAL) @@ -280,6 +283,9 @@ target_link_libraries(GameLib PUBLIC Engine) add_executable(ColorRace game/src/main.cpp engine/src/core/animation/components/BlockingAnimationComponent.h + engine/src/core/audio/ecs/AudioSystem.cpp + engine/src/core/audio/ecs/AudioSystem.h + engine/src/core/audio/ecs/BlockingSoundComponent.h ) target_link_libraries(ColorRace PRIVATE diff --git a/assets/sounds/rolling_dice.wav b/assets/sounds/rolling_dice.wav index 81b87f5..f512a99 100644 Binary files a/assets/sounds/rolling_dice.wav and b/assets/sounds/rolling_dice.wav differ diff --git a/engine/src/core/audio/ecs/AudioSystem.cpp b/engine/src/core/audio/ecs/AudioSystem.cpp new file mode 100644 index 0000000..19ac5b0 --- /dev/null +++ b/engine/src/core/audio/ecs/AudioSystem.cpp @@ -0,0 +1,35 @@ +// +// Created by sebastian on 05.08.26. +// + +#include "AudioSystem.h" + +#include "BlockingSoundComponent.h" +#include "PlaySoundComponent.h" +#include "core/audio/SoundSource.h" + +void engine::audio::AudioSystem::update(float deltaTime) const { + std::vector toRemove; + + for (auto& [entity, playSound] : m_registry.getPool()) { + auto& source = m_registry.getComponent(entity); + if (!playSound.started) { + source.setBuffer(*playSound.soundBuffer); + source.setGain(playSound.gain); + source.play(); + playSound.started = true; + } else if (!source.isPlaying()) { + if (playSound.onFinished) { + playSound.onFinished(); + } + toRemove.push_back(entity); + } + } + + for (auto entity : toRemove) { + m_registry.removeComponent(entity); + if (m_registry.hasComponent(entity)) { + m_registry.removeComponent(entity); + } + } +} diff --git a/engine/src/core/audio/ecs/AudioSystem.h b/engine/src/core/audio/ecs/AudioSystem.h new file mode 100644 index 0000000..1bf3a03 --- /dev/null +++ b/engine/src/core/audio/ecs/AudioSystem.h @@ -0,0 +1,20 @@ +// +// Created by sebastian on 05.08.26. +// + +#ifndef COLORRACE_AUDIOSYSTEM_H +#define COLORRACE_AUDIOSYSTEM_H +#include "ecs/EntityRegistry.h" + + +namespace engine::audio { + class AudioSystem { + public: + explicit AudioSystem(EntityRegistry& registry) : m_registry(registry) {} + void update(float deltaTime) const; + private: + EntityRegistry& m_registry; + }; +} + +#endif //COLORRACE_AUDIOSYSTEM_H diff --git a/engine/src/core/audio/ecs/BlockingSoundComponent.h b/engine/src/core/audio/ecs/BlockingSoundComponent.h new file mode 100644 index 0000000..663d95a --- /dev/null +++ b/engine/src/core/audio/ecs/BlockingSoundComponent.h @@ -0,0 +1,10 @@ +// +// Created by sebastian on 05.08.26. +// + +#ifndef COLORRACE_BLOCKINGSOUNDCOMPONENT_H +#define COLORRACE_BLOCKINGSOUNDCOMPONENT_H +namespace engine::audio { + struct BlockingSoundComponent {}; +} +#endif //COLORRACE_BLOCKINGSOUNDCOMPONENT_H diff --git a/engine/src/core/audio/ecs/PlaySoundComponent.h b/engine/src/core/audio/ecs/PlaySoundComponent.h new file mode 100644 index 0000000..8445d58 --- /dev/null +++ b/engine/src/core/audio/ecs/PlaySoundComponent.h @@ -0,0 +1,22 @@ +// +// Created by sebastian on 05.08.26. +// + +#ifndef COLORRACE_PLAYSOUNDCOMPONENT_H +#define COLORRACE_PLAYSOUNDCOMPONENT_H +#include +#include + +#include "../SoundBuffer.h" + +namespace engine::audio { + struct PlaySoundComponent { + std::shared_ptr soundBuffer; + float gain = 1.f; + bool started = false; + bool blocking = false; + std::function onFinished; + }; +} + +#endif //COLORRACE_PLAYSOUNDCOMPONENT_H diff --git a/engine/src/core/events/EventBus.cpp b/engine/src/core/events/EventBus.cpp new file mode 100644 index 0000000..e104ea1 --- /dev/null +++ b/engine/src/core/events/EventBus.cpp @@ -0,0 +1,8 @@ +// +// Created by sebastian on 05.08.26. +// + +#include "EventBus.h" + +namespace engine { +} // engine \ No newline at end of file diff --git a/engine/src/core/events/EventBus.h b/engine/src/core/events/EventBus.h new file mode 100644 index 0000000..0e00444 --- /dev/null +++ b/engine/src/core/events/EventBus.h @@ -0,0 +1,29 @@ +// +// Created by sebastian on 05.08.26. +// + +#ifndef COLORRACE_EVENTBUS_H +#define COLORRACE_EVENTBUS_H +#include + +namespace engine { + template + class EventBus { + public: + using Handler = std::function; + + void subscribe(Handler handler) { + m_handlers.push_back(handler); + } + + void publish(Event& event) const { + for (const auto& handler : m_handlers) { + handler(event); + } + } + private: + std::vector m_handlers; + }; +} // engine + +#endif //COLORRACE_EVENTBUS_H diff --git a/engine/src/layer/Scene.h b/engine/src/layer/Scene.h index 64f5727..1cfcea3 100644 --- a/engine/src/layer/Scene.h +++ b/engine/src/layer/Scene.h @@ -8,6 +8,7 @@ #include #include "Layer.h" +#include "core/events/EventBus.h" #include "loader/assets/AssetManager.h" #include "loader/assets/AssetRequests.h" #include "loader/assets/LoadedAssets.h" diff --git a/game/src/AudioLayer.cpp b/game/src/AudioLayer.cpp index 71a59fb..0134dee 100644 --- a/game/src/AudioLayer.cpp +++ b/game/src/AudioLayer.cpp @@ -4,25 +4,34 @@ #include "AudioLayer.h" +#include "../../engine/src/core/audio/ecs/PlaySoundComponent.h" +#include "core/audio/ecs/BlockingSoundComponent.h" #include "GLFW/glfw3.h" void ludo::AudioLayer::onUpdate(float deltaTime) { Layer::onUpdate(deltaTime); - if (getKeyboard().keyPressEvent(GLFW_KEY_P)) { - for (auto& soundSource : soundSources) { - soundSource.play(); - } - } + + m_audioSystem.update(deltaTime); + } void ludo::AudioLayer::onAttachImpl() { Layer::onAttachImpl(); - auto rollingDiceSoundBufffer = m_assetManager.getSound("rolling_dice"); - auto rollingDiceSoundSource = engine::audio::SoundSource(); - rollingDiceSoundSource.setBuffer(*rollingDiceSoundBufffer); - rollingDiceSoundSource.setPosition(0.0f, 0.0f, 0.0f); - rollingDiceSoundSource.setGain(1.0f); + auto rollingDiceSoundBuffer = m_assetManager.getSound("rolling_dice"); + m_diceSoundEntity = m_registry.createEntity(); + m_registry.addComponent(m_diceSoundEntity, engine::audio::SoundSource()); - soundSources.push_back(std::move(rollingDiceSoundSource)); + m_eventBus.subscribe([this, rollingDiceSoundBuffer](const ludo::LudoEvent& event) { + std::visit([this, rollingDiceSoundBuffer](const T& e) { + if constexpr (std::is_same_v) { + engine::audio::PlaySoundComponent playSound; + playSound.soundBuffer = rollingDiceSoundBuffer; + playSound.gain = 1.0f; + playSound.blocking = true; + m_registry.addComponent(m_diceSoundEntity, playSound); + m_registry.addComponent(m_diceSoundEntity, engine::audio::BlockingSoundComponent()); + } + }, event); + }); } diff --git a/game/src/AudioLayer.h b/game/src/AudioLayer.h index 75b7c8c..a02a56d 100644 --- a/game/src/AudioLayer.h +++ b/game/src/AudioLayer.h @@ -4,15 +4,24 @@ #ifndef COLORRACE_AUDIOLAYER_H #define COLORRACE_AUDIOLAYER_H +#include + #include "core/audio/SoundSource.h" +#include "core/audio/ecs/AudioSystem.h" +#include "core/events/EventBus.h" #include "layer/Layer.h" #include "loader/assets/AssetManager.h" +#include "ludo/LudoGameMode.h" + namespace ludo { class AudioLayer :public engine::Layer { public: - AudioLayer(engine::AssetManager& assetManager, engine::Keyboard &keyboard, engine::Mouse &mouse): Layer(keyboard, mouse), m_assetManager(assetManager) { + AudioLayer(engine::AssetManager& assetManager, engine::Keyboard &keyboard, engine::Mouse &mouse, std::shared_ptr gameMode, + engine::EntityRegistry& registry, engine::EventBus& eventBus) + : Layer(keyboard, mouse), m_assetManager(assetManager), m_gameMode(std::move(gameMode)), m_registry(registry), + m_eventBus(eventBus), m_audioSystem(m_registry) { } void onUpdate(float deltaTime) override; @@ -21,7 +30,11 @@ namespace ludo { void onAttachImpl() override; private: engine::AssetManager& m_assetManager; - std::vector soundSources; + std::shared_ptr m_gameMode; + engine::EntityRegistry& m_registry; + engine::Entity m_diceSoundEntity; + engine::EventBus& m_eventBus; + engine::audio::AudioSystem m_audioSystem; }; } diff --git a/game/src/GameLayer.cpp b/game/src/GameLayer.cpp index a3dd5d7..a885b82 100644 --- a/game/src/GameLayer.cpp +++ b/game/src/GameLayer.cpp @@ -17,6 +17,8 @@ #include "core/animation/components/BlockingAnimationComponent.h" #include "core/audio/SoundSource.h" +#include "core/audio/ecs/BlockingSoundComponent.h" +#include "core/audio/ecs/PlaySoundComponent.h" #include "GLFW/glfw3.h" using namespace engine; void GameLayer::onAttachImpl() { @@ -56,15 +58,19 @@ void GameLayer::renderHud() { } -bool GameLayer::isAnimationBlocking() { +bool GameLayer::isAnimationBlocking() const { return m_entityRegistry.getPool().size() > 0; } +bool GameLayer::isSoundBlocking() const { + return m_entityRegistry.getPool().size() > 0; +} + void GameLayer::onUpdate(float deltaTime) { Layer::onUpdate(deltaTime); m_cameraController->update(getKeyboard(), *m_camera, deltaTime); m_animationSystem.update(deltaTime); - if (isAnimationBlocking()) return; + if (isAnimationBlocking() || isSoundBlocking()) return; for (auto& ai : aiControllers) { ai.update(deltaTime); diff --git a/game/src/GameLayer.h b/game/src/GameLayer.h index d60079e..5dd6984 100644 --- a/game/src/GameLayer.h +++ b/game/src/GameLayer.h @@ -66,8 +66,9 @@ private: void renderHud(); - bool isAnimationBlocking(); + bool isAnimationBlocking() const; + bool isSoundBlocking() const; }; #endif //COLORRACE_GAMELAYER_H diff --git a/game/src/GameScene.cpp b/game/src/GameScene.cpp index f3b1d19..a783b65 100644 --- a/game/src/GameScene.cpp +++ b/game/src/GameScene.cpp @@ -15,7 +15,7 @@ GameScene::GameScene(engine::InputContextStack& inputStack, engine::Keyboard& keyboard, engine::Mouse& mouse) : Scene(inputStack, keyboard, mouse), m_camera(std::make_unique()) { m_pointLight = std::make_shared(glm::vec3(.0f, 10.f, -5.0f), glm::vec3(1.0f, 1.0f, 1.0f)); const std::vector players = {0,1,2,3}; - m_gameMode = std::make_shared(players); + m_gameMode = std::make_shared(m_eventBus, players); } void GameScene::onEnter() { @@ -39,7 +39,7 @@ void GameScene::onEnter() { pieceModels["Green"] = assetManager->getModel("pawn_green"); pieceModels["Yellow"] = assetManager->getModel("pawn_yellow"); - addLayer(std::make_unique(*assetManager, m_keyboard, m_mouse)); + addLayer(std::make_unique(*assetManager, m_keyboard, m_mouse, m_gameMode, entityRegistry, m_eventBus)); addLayer(std::make_unique(*m_camera, entityRegistry, m_keyboard, m_mouse, engine::SphereFactory::createUVSphere(1.0f))); addLayer(std::make_unique(m_gameMode, entityRegistry, m_camera, m_pointLight, m_keyboard, m_mouse, assetManager->getModel("gameboard"), pieceModels)); diff --git a/game/src/GameScene.h b/game/src/GameScene.h index e6cdf8d..6c16062 100644 --- a/game/src/GameScene.h +++ b/game/src/GameScene.h @@ -6,6 +6,7 @@ #define COLORRACE_GAMESCENE_H #include "layer/Scene.h" #include "ludo/LudoGameMode.h" + #include "renderer/entities/PointLight.h" @@ -15,10 +16,13 @@ public: void onEnter() override; void onExit() override; std::vector getRequiredAssets() const override; + engine::EventBus& getEventBus() { return m_eventBus; } //Todo: Maybe introduce virtual base Event class to move this into engine private: std::shared_ptr m_camera; std::shared_ptr m_pointLight; std::shared_ptr m_gameMode; + + engine::EventBus m_eventBus; }; diff --git a/game/src/ludo/LudoGameMode.cpp b/game/src/ludo/LudoGameMode.cpp index c11c181..27a314b 100644 --- a/game/src/ludo/LudoGameMode.cpp +++ b/game/src/ludo/LudoGameMode.cpp @@ -89,12 +89,12 @@ ludo::LudoGameMode::MoveResult ludo::LudoGameMode::movePieceInternal(int pieceIn void ludo::LudoGameMode::autoReleasePiece(int pieceIndex) { auto result = movePieceInternal(pieceIndex, m_gameState.getDiceValue()); - m_pendingEvents.emplace_back(PieceMovedEvent::create(pieceIndex, result.fromPosition, result.toPosition, result.captured, result.startState, result.targetState)); + publishEvent(PieceMovedEvent::create(pieceIndex, result.fromPosition, result.toPosition, result.captured, result.startState, result.targetState)); if (result.captured) { //Todo: Determine real toPosition Index dependent on home belegung std::vector freeHomePositions = m_gameState.getFreeHomePositions(m_gameState.getColorOfPieceIndex(result.capturedPieceIndex)); const int smallestFreeHomePosition = *std::ranges::min_element(freeHomePositions); - m_pendingEvents.emplace_back(PieceMovedEvent::create(result.capturedPieceIndex, result.fromPosition, smallestFreeHomePosition, result.captured, result.startState, result.targetState)); + publishEvent(PieceMovedEvent::create(result.capturedPieceIndex, result.fromPosition, smallestFreeHomePosition, result.captured, result.startState, result.targetState)); } } @@ -102,7 +102,7 @@ void ludo::LudoGameMode::handle(const RollDiceCommand &command) { if (!isCurrentPlayer(command.player) || m_gameState.m_phase != TurnPhase::AwaitingRoll) return; m_gameState.m_diceValue = rollDice(); - m_pendingEvents.emplace_back(DiceRolledEvent(command.player, m_gameState.m_diceValue)); + publishEvent(DiceRolledEvent(command.player, m_gameState.m_diceValue)); if (m_gameState.m_diceValue == 6) { if (auto homeIndex = findLowestHomeSlotPiece(getColorOf(command.player))) { @@ -147,10 +147,10 @@ void ludo::LudoGameMode::handle(const MovePieceCommand &command) { spdlog::info("MovePieceCommand akzeptiert: pieceIndex={}", command.pieceIndex); auto result = movePieceInternal(command.pieceIndex, m_gameState.m_diceValue); - m_pendingEvents.emplace_back(PieceMovedEvent{command.pieceIndex, result.fromPosition, result.toPosition, result.captured, result.targetState, result.startState}); + publishEvent(PieceMovedEvent{command.pieceIndex, result.fromPosition, result.toPosition, result.captured, result.targetState, result.startState}); if (result.captured) { spdlog::info("MovePieceCommand: gegnerische Figur auf {} geworfen", result.toPosition); - m_pendingEvents.emplace_back(PieceMovedEvent{result.capturedPieceIndex, result.toPosition, -1, false}); + publishEvent(PieceMovedEvent{result.capturedPieceIndex, result.toPosition, -1, false}); } finishTurn(); } @@ -164,7 +164,7 @@ void ludo::LudoGameMode::finishTurn() { endTurn(extraTurn); m_gameState.m_phase = TurnPhase::AwaitingRoll; - m_pendingEvents.emplace_back(TurnChangedEvent{getCurrentPlayer()}); + publishEvent(TurnChangedEvent{getCurrentPlayer()}); } std::vector ludo::LudoGameMode::getMoveablePieceIndices(PlayerColor color, int diceValue) const { @@ -223,6 +223,11 @@ bool ludo::LudoGameMode::isBlockedByOwnPiece(PlayerColor owner, PieceState state return false; } +void ludo::LudoGameMode::publishEvent(LudoEvent event) { + m_pendingEvents.push_back(event); + m_eventBus.publish(event); +} + namespace { int stateOrder(ludo::PieceState state) { switch (state) { diff --git a/game/src/ludo/LudoGameMode.h b/game/src/ludo/LudoGameMode.h index f3b020f..20efadb 100644 --- a/game/src/ludo/LudoGameMode.h +++ b/game/src/ludo/LudoGameMode.h @@ -11,6 +11,7 @@ #include "DiceSource.h" #include "LudoGameState.h" #include "LudoTypes.h" +#include "core/events/EventBus.h" #include "game/TurnBasedGameMode.h" namespace ludo { @@ -30,8 +31,8 @@ namespace ludo { class LudoGameMode: public engine::TurnBasedGameMode { public: - explicit LudoGameMode(std::vector players, std::optional seed = std::nullopt) : TurnBasedGameMode(std::move(players)), - m_diceSource(std::make_unique()), m_gameState(getPlayerOrder()) {} + explicit LudoGameMode(engine::EventBus& eventBus, std::vector players, std::optional seed = std::nullopt) : TurnBasedGameMode(std::move(players)), + m_diceSource(std::make_unique()), m_gameState(getPlayerOrder()), m_eventBus(eventBus) {} void sendCommand(const std::variant &command) override; std::vector pollEvents() override; @@ -46,6 +47,7 @@ namespace ludo { void setDiceSource(std::unique_ptr diceSource) { m_diceSource = std::move(diceSource); } private: + engine::EventBus& m_eventBus; struct MoveResult { int fromPosition = 0; int toPosition = 0; @@ -81,6 +83,7 @@ namespace ludo { [[nodiscard]] bool isBlockedByOwnPiece(PlayerColor owner, PieceState state, int position) const; + void publishEvent(LudoEvent event); }; }