ADD: PlaySound when rolling dice

This commit is contained in:
sebastian 2026-08-05 10:40:01 +02:00
parent 89435a66a4
commit a5643d794c
17 changed files with 198 additions and 26 deletions

View File

@ -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

Binary file not shown.

View File

@ -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<Entity> toRemove;
for (auto& [entity, playSound] : m_registry.getPool<PlaySoundComponent>()) {
auto& source = m_registry.getComponent<SoundSource>(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<PlaySoundComponent>(entity);
if (m_registry.hasComponent<BlockingSoundComponent>(entity)) {
m_registry.removeComponent<BlockingSoundComponent>(entity);
}
}
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,22 @@
//
// Created by sebastian on 05.08.26.
//
#ifndef COLORRACE_PLAYSOUNDCOMPONENT_H
#define COLORRACE_PLAYSOUNDCOMPONENT_H
#include <functional>
#include <memory>
#include "../SoundBuffer.h"
namespace engine::audio {
struct PlaySoundComponent {
std::shared_ptr<SoundBuffer> soundBuffer;
float gain = 1.f;
bool started = false;
bool blocking = false;
std::function<void()> onFinished;
};
}
#endif //COLORRACE_PLAYSOUNDCOMPONENT_H

View File

@ -0,0 +1,8 @@
//
// Created by sebastian on 05.08.26.
//
#include "EventBus.h"
namespace engine {
} // engine

View File

@ -0,0 +1,29 @@
//
// Created by sebastian on 05.08.26.
//
#ifndef COLORRACE_EVENTBUS_H
#define COLORRACE_EVENTBUS_H
#include <functional>
namespace engine {
template<typename Event>
class EventBus {
public:
using Handler = std::function<void(Event&)>;
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<Handler> m_handlers;
};
} // engine
#endif //COLORRACE_EVENTBUS_H

View File

@ -8,6 +8,7 @@
#include <vector>
#include "Layer.h"
#include "core/events/EventBus.h"
#include "loader/assets/AssetManager.h"
#include "loader/assets/AssetRequests.h"
#include "loader/assets/LoadedAssets.h"

View File

@ -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<engine::audio::SoundSource>(m_diceSoundEntity, engine::audio::SoundSource());
soundSources.push_back(std::move(rollingDiceSoundSource));
m_eventBus.subscribe([this, rollingDiceSoundBuffer](const ludo::LudoEvent& event) {
std::visit([this, rollingDiceSoundBuffer]<typename T>(const T& e) {
if constexpr (std::is_same_v<T, ludo::DiceRolledEvent>) {
engine::audio::PlaySoundComponent playSound;
playSound.soundBuffer = rollingDiceSoundBuffer;
playSound.gain = 1.0f;
playSound.blocking = true;
m_registry.addComponent<engine::audio::PlaySoundComponent>(m_diceSoundEntity, playSound);
m_registry.addComponent<engine::audio::BlockingSoundComponent>(m_diceSoundEntity, engine::audio::BlockingSoundComponent());
}
}, event);
});
}

View File

@ -4,15 +4,24 @@
#ifndef COLORRACE_AUDIOLAYER_H
#define COLORRACE_AUDIOLAYER_H
#include <utility>
#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<LudoGameMode> gameMode,
engine::EntityRegistry& registry, engine::EventBus<LudoEvent>& 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<engine::audio::SoundSource> soundSources;
std::shared_ptr<ludo::LudoGameMode> m_gameMode;
engine::EntityRegistry& m_registry;
engine::Entity m_diceSoundEntity;
engine::EventBus<LudoEvent>& m_eventBus;
engine::audio::AudioSystem m_audioSystem;
};
}

View File

@ -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<animation::BlockingAnimationComponent>().size() > 0;
}
bool GameLayer::isSoundBlocking() const {
return m_entityRegistry.getPool<audio::BlockingSoundComponent>().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);

View File

@ -66,8 +66,9 @@ private:
void renderHud();
bool isAnimationBlocking();
bool isAnimationBlocking() const;
bool isSoundBlocking() const;
};
#endif //COLORRACE_GAMELAYER_H

View File

@ -15,7 +15,7 @@
GameScene::GameScene(engine::InputContextStack& inputStack, engine::Keyboard& keyboard, engine::Mouse& mouse) : Scene(inputStack, keyboard, mouse), m_camera(std::make_unique<engine::Camera>()) {
m_pointLight = std::make_shared<engine::PointLight>(glm::vec3(.0f, 10.f, -5.0f), glm::vec3(1.0f, 1.0f, 1.0f));
const std::vector<unsigned int> players = {0,1,2,3};
m_gameMode = std::make_shared<ludo::LudoGameMode>(players);
m_gameMode = std::make_shared<ludo::LudoGameMode>(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<ludo::AudioLayer>(*assetManager, m_keyboard, m_mouse));
addLayer(std::make_unique<ludo::AudioLayer>(*assetManager, m_keyboard, m_mouse, m_gameMode, entityRegistry, m_eventBus));
addLayer(std::make_unique<engine::DebugLayer>(*m_camera, entityRegistry, m_keyboard, m_mouse, engine::SphereFactory::createUVSphere(1.0f)));
addLayer(std::make_unique<GameLayer>(m_gameMode, entityRegistry, m_camera, m_pointLight, m_keyboard, m_mouse, assetManager->getModel("gameboard"), pieceModels));

View File

@ -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<engine::AssetRequest> getRequiredAssets() const override;
engine::EventBus<ludo::LudoEvent>& getEventBus() { return m_eventBus; } //Todo: Maybe introduce virtual base Event class to move this into engine
private:
std::shared_ptr<engine::Camera> m_camera;
std::shared_ptr<engine::PointLight> m_pointLight;
std::shared_ptr<ludo::LudoGameMode> m_gameMode;
engine::EventBus<ludo::LudoEvent> m_eventBus;
};

View File

@ -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<int> 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<int> 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) {

View File

@ -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<LudoCommand, LudoEvent, LudoGameState> {
public:
explicit LudoGameMode(std::vector<engine::PlayerID> players, std::optional<unsigned int> seed = std::nullopt) : TurnBasedGameMode(std::move(players)),
m_diceSource(std::make_unique<RandomDiceSource>()), m_gameState(getPlayerOrder()) {}
explicit LudoGameMode(engine::EventBus<LudoEvent>& eventBus, std::vector<engine::PlayerID> players, std::optional<unsigned int> seed = std::nullopt) : TurnBasedGameMode(std::move(players)),
m_diceSource(std::make_unique<RandomDiceSource>()), m_gameState(getPlayerOrder()), m_eventBus(eventBus) {}
void sendCommand(const std::variant<MovePieceCommand, RollDiceCommand> &command) override;
std::vector<LudoEvent> pollEvents() override;
@ -46,6 +47,7 @@ namespace ludo {
void setDiceSource(std::unique_ptr<IDiceSource> diceSource) { m_diceSource = std::move(diceSource); }
private:
engine::EventBus<LudoEvent>& 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);
};
}