From 97f8e71e187ebbea58a32941acff3d1ee57ac40f Mon Sep 17 00:00:00 2001 From: sebastian Date: Wed, 5 Aug 2026 09:01:18 +0200 Subject: [PATCH] ADD: Enable AnimationSystem --- CMakeLists.txt | 1 + engine/src/core/animation/AnimationSystem.cpp | 20 ++++- engine/src/core/animation/AnimationSystem.h | 4 +- .../components/BlockingAnimationComponent.h | 11 +++ engine/src/ecs/ComponentPool.h | 5 ++ engine/src/renderer/MeshRenderSystem.cpp | 3 + game/src/GameLayer.cpp | 8 ++ game/src/GameLayer.h | 5 +- game/src/ludo/LudoGameMode.cpp | 79 ++++++++++++++++++- game/src/ludo/LudoTypes.h | 30 ++++++- game/src/ludo/PiecePresenter.cpp | 36 ++++++++- 11 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 engine/src/core/animation/components/BlockingAnimationComponent.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6551ad6..a313ce0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -279,6 +279,7 @@ target_link_libraries(GameLib PUBLIC Engine) # --- Executable --- add_executable(ColorRace game/src/main.cpp + engine/src/core/animation/components/BlockingAnimationComponent.h ) target_link_libraries(ColorRace PRIVATE diff --git a/engine/src/core/animation/AnimationSystem.cpp b/engine/src/core/animation/AnimationSystem.cpp index 57f6e12..21b16f4 100644 --- a/engine/src/core/animation/AnimationSystem.cpp +++ b/engine/src/core/animation/AnimationSystem.cpp @@ -4,19 +4,28 @@ #include "AnimationSystem.h" +#include + #include "components/TransformAnimationComponent.h" #include "ecs/standardComponents/TransformComponent.h" namespace engine::animation { void animation::AnimationSystem::update(float deltaTime) { + std::vector toRemove; for (auto& [entity, animation] : m_registry.getPool()) { animation.time += deltaTime; auto& transform = m_registry.getComponent(entity); - transform.m_position = evaluate(animation.position, animation.time); - transform.m_rotation = evaluate(animation.rotation, animation.time); - transform.m_scale = evaluate(animation.scale, animation.time); + if (!animation.position.keys.empty()) { + transform.m_position = evaluate(animation.position, animation.time); + } + if (!animation.rotation.keys.empty()) { + transform.m_rotation = evaluate(animation.rotation, animation.time); + } + if (!animation.scale.keys.empty()) { + transform.m_scale = evaluate(animation.scale, animation.time); + } if (animation.time >= animation.duration) { if (animation.loop) { @@ -26,8 +35,13 @@ namespace engine::animation { animation.onFinished(); } animation.finished = true; + toRemove.push_back(entity); } } } + + for (auto entity : toRemove) { + m_registry.removeComponent(entity); + } } } // engine \ No newline at end of file diff --git a/engine/src/core/animation/AnimationSystem.h b/engine/src/core/animation/AnimationSystem.h index 488a343..73f7df9 100644 --- a/engine/src/core/animation/AnimationSystem.h +++ b/engine/src/core/animation/AnimationSystem.h @@ -16,11 +16,13 @@ namespace engine::animation { EntityRegistry& m_registry; template - glm::vec3 evaluate(const AnimationTrack& track, float time) { + T evaluate(const AnimationTrack& track, float time) { Keyframe a; Keyframe b; findSurroundingKeys(track, time, a, b); + if (a.time == b.time) return a.value; + float localT = (time - a.time) / (b.time - a.time); float easedT = a.easingFunction(localT); return glm::mix(a.value, b.value, easedT); diff --git a/engine/src/core/animation/components/BlockingAnimationComponent.h b/engine/src/core/animation/components/BlockingAnimationComponent.h new file mode 100644 index 0000000..2761be7 --- /dev/null +++ b/engine/src/core/animation/components/BlockingAnimationComponent.h @@ -0,0 +1,11 @@ +// +// Created by sebastian on 05.08.26. +// + +#ifndef COLORRACE_BLOCKINGANIMATIONCOMPONENT_H +#define COLORRACE_BLOCKINGANIMATIONCOMPONENT_H +namespace engine::animation { + struct BlockingAnimationComponent { + }; +} +#endif //COLORRACE_BLOCKINGANIMATIONCOMPONENT_H diff --git a/engine/src/ecs/ComponentPool.h b/engine/src/ecs/ComponentPool.h index 6bfd6bb..64ea340 100644 --- a/engine/src/ecs/ComponentPool.h +++ b/engine/src/ecs/ComponentPool.h @@ -12,6 +12,7 @@ namespace engine { public: virtual ~IComponentPool() = default; virtual void remove(Entity entity) = 0; + virtual size_t size() = 0; }; template @@ -33,6 +34,10 @@ namespace engine { return m_components.at(entity); } + size_t size() override { + return m_components.size(); + } + // Für Iteration in Systemen auto begin() { return m_components.begin(); } auto end() { return m_components.end(); } diff --git a/engine/src/renderer/MeshRenderSystem.cpp b/engine/src/renderer/MeshRenderSystem.cpp index 1748679..0f520e4 100644 --- a/engine/src/renderer/MeshRenderSystem.cpp +++ b/engine/src/renderer/MeshRenderSystem.cpp @@ -7,6 +7,7 @@ #include "ecs/standardComponents/HighlightComponent.h" #include "ecs/standardComponents/MeshComponent.h" #include "ecs/standardComponents/TransformComponent.h" +#include "spdlog/spdlog.h" void engine::MeshRenderSystem::update(engine::EntityRegistry ®istry, engine::RenderQueue &renderQueue) { @@ -20,6 +21,8 @@ void engine::MeshRenderSystem::update(engine::EntityRegistry ®istry, engine:: continue; auto& transform = transforms.get(entity); + // spdlog::info("Entity {}: pos=({}, {}, {})", entity.value(), + // transform.m_position.x, transform.m_position.y, transform.m_position.z); RenderCommand renderCommand(mesh.model, transform.computeModelMatrix()); if (registry.hasComponent(entity)) { diff --git a/game/src/GameLayer.cpp b/game/src/GameLayer.cpp index 439c0a7..a3dd5d7 100644 --- a/game/src/GameLayer.cpp +++ b/game/src/GameLayer.cpp @@ -15,6 +15,7 @@ #define GLM_ENABLE_EXPERIMENTAL #include +#include "core/animation/components/BlockingAnimationComponent.h" #include "core/audio/SoundSource.h" #include "GLFW/glfw3.h" using namespace engine; @@ -55,9 +56,16 @@ void GameLayer::renderHud() { } +bool GameLayer::isAnimationBlocking() { + 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; + for (auto& ai : aiControllers) { ai.update(deltaTime); } diff --git a/game/src/GameLayer.h b/game/src/GameLayer.h index c8f8632..d60079e 100644 --- a/game/src/GameLayer.h +++ b/game/src/GameLayer.h @@ -6,6 +6,7 @@ #define COLORRACE_GAMELAYER_H #include +#include "core/animation/AnimationSystem.h" #include "core/audio/AudioDevice.h" #include "core/audio/SoundBuffer.h" #include "core/inputsOutputs/controller/CameraController.h" @@ -27,7 +28,7 @@ public: engine::Keyboard& keyboard, engine::Mouse& mouse, std::shared_ptr boardModel, std::unordered_map> pieceModdels) : Layer(keyboard, mouse), m_gameMode(std::move(gameMode)), m_entityRegistry(entityRegistry), m_camera(std::move(cam)), m_renderer(std::make_unique()), m_pointLight(std::move(light)), m_boardLayout(std::move(boardModel)), m_piecePresenter(m_entityRegistry, std::move(pieceModdels), m_boardLayout), - m_pickingSystem(*m_camera), m_highlightSystem(*m_camera) { + m_pickingSystem(*m_camera), m_highlightSystem(*m_camera), m_animationSystem(m_entityRegistry) { for (auto playerID : m_gameMode->getPlayerOrder()) { if (playerID != m_localPlayer) { aiControllers.emplace_back(m_gameMode, playerID); @@ -58,12 +59,14 @@ private: std::string m_lastEventMessage; std::vector aiControllers; + engine::animation::AnimationSystem m_animationSystem; std::unique_ptr m_soundBuffer; void renderHud(); + bool isAnimationBlocking(); }; diff --git a/game/src/ludo/LudoGameMode.cpp b/game/src/ludo/LudoGameMode.cpp index 732e68b..1fd92f8 100644 --- a/game/src/ludo/LudoGameMode.cpp +++ b/game/src/ludo/LudoGameMode.cpp @@ -89,9 +89,10 @@ 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{pieceIndex, result.fromPosition, result.toPosition, result.captured}); + m_pendingEvents.emplace_back(PieceMovedEvent::create(pieceIndex, result.fromPosition, result.toPosition, result.captured, result.startState, result.targetState)); if (result.captured) { - m_pendingEvents.emplace_back(PieceMovedEvent{result.capturedPieceIndex, result.toPosition, -1, false}); + //Todo: Determine real toPosition Index dependent on home belegung + m_pendingEvents.emplace_back(PieceMovedEvent::create(result.capturedPieceIndex, result.fromPosition, -1, result.captured, result.startState, result.targetState)); } } @@ -144,7 +145,7 @@ 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}); + m_pendingEvents.emplace_back(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}); @@ -220,7 +221,69 @@ bool ludo::LudoGameMode::isBlockedByOwnPiece(PlayerColor owner, PieceState state return false; } +namespace { + int stateOrder(ludo::PieceState state) { + switch (state) { + case ludo::PieceState::AtHome: return 0; + case ludo::PieceState::OnTrack: return 1; + case ludo::PieceState::InHomeStretch: return 2; + case ludo::PieceState::Finished: return 3; + } + throw std::invalid_argument("computePath: unbekannter PieceState"); + } +} + std::vector ludo::LudoGameMode::computePath(PlayerColor color, PieceState startingState, int startPosition, PieceState targetState, int targetPosition) { + if (startingState == targetState && startPosition == targetPosition) { + return {}; + } + + // --- Nur Vorwärtsbewegungen sind erlaubt --- + if (stateOrder(targetState) < stateOrder(startingState)) { + throw std::invalid_argument( + "computePath: targetState liegt vor startingState (Rueckwaerts nicht unterstuetzt)"); + } + + if (targetState == PieceState::AtHome) { + throw std::invalid_argument( + "computePath: AtHome kann kein Zielzustand sein (ausser Start==Ziel)"); + } + + if (startingState == PieceState::Finished) { + // hier nur erreichbar, wenn targetState/targetPosition abweicht (early return greift sonst) + throw std::invalid_argument( + "computePath: startingState ist bereits Finished, aber Ziel weicht ab"); + } + + if (startingState == PieceState::InHomeStretch && targetState == PieceState::InHomeStretch + && targetPosition < startPosition) { + throw std::invalid_argument( + "computePath: targetPosition liegt in InHomeStretch vor startPosition"); + } + + // --- Positionsbereiche --- + if (targetState == PieceState::OnTrack + && (targetPosition < 0 || targetPosition >= kTrackLength)) { + throw std::invalid_argument("computePath: targetPosition ausserhalb OnTrack-Bereich"); + } + if (targetState == PieceState::InHomeStretch + && (targetPosition < 0 || targetPosition >= kHomeStretchLength)) { + throw std::invalid_argument("computePath: targetPosition ausserhalb InHomeStretch-Bereich"); + } + if (targetState == PieceState::Finished && targetPosition != kHomeStretchLength - 1) { + // <-- vermutlich dein aktueller Bug + throw std::invalid_argument( + "computePath: targetPosition fuer Finished muss kHomeStretchLength - 1 sein"); + } + if (startingState == PieceState::OnTrack + && (startPosition < 0 || startPosition >= kTrackLength)) { + throw std::invalid_argument("computePath: startPosition ausserhalb OnTrack-Bereich"); + } + if (startingState == PieceState::InHomeStretch + && (startPosition < 0 || startPosition >= kHomeStretchLength)) { + throw std::invalid_argument("computePath: startPosition ausserhalb InHomeStretch-Bereich"); + } + std::vector path; if (startPosition == targetPosition && startingState == targetState) return path; @@ -230,7 +293,17 @@ std::vector ludo::LudoGameMode::computePath(PlayerColor color, P path.push_back({state, position}); + // Defense in depth: falls die Validierung oben eine Kombination übersieht + // (z.B. nach zukünftigen Erweiterungen), lieber laut abstürzen als RAM fressen. + const int maxSteps = kTrackLength + kHomeStretchLength + 4; + int steps = 0; + while (state != targetState || position != targetPosition) { + if (++steps > maxSteps) { + throw std::logic_error( + "computePath: Iterationslimit ueberschritten - unreachable Ziel trotz Validierung"); + } + switch (state) { case PieceState::AtHome: state = PieceState::OnTrack; diff --git a/game/src/ludo/LudoTypes.h b/game/src/ludo/LudoTypes.h index df79e3a..14c1c18 100644 --- a/game/src/ludo/LudoTypes.h +++ b/game/src/ludo/LudoTypes.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "game/TurnBasedGameMode.h" @@ -66,7 +67,34 @@ namespace ludo { struct RollDiceCommand { engine::PlayerID player; }; using LudoCommand = std::variant; - struct PieceMovedEvent { int pieceIndex; int fromPosition; int toPosition; bool captured; PieceState targetState; PieceState startState; }; + struct PieceMovedEvent { + int pieceIndex; + int fromPosition; + int toPosition; + bool captured; + PieceState targetState; + PieceState startState; + + static PieceMovedEvent create(int pieceIndex, int fromPosition, int toPosition, + bool captured, PieceState startState, PieceState targetState) { + validate(startState, fromPosition, targetState, toPosition, captured); + return PieceMovedEvent{pieceIndex, fromPosition, toPosition, captured, targetState, startState}; + } + + private: + static void validate(PieceState startState, int fromPosition, + PieceState targetState, int toPosition, bool captured) { + if (captured && targetState != PieceState::AtHome) { + throw std::invalid_argument( + "PieceMovedEvent: captured==true erwartet targetState==AtHome"); + } + if (!captured && targetState == PieceState::AtHome && startState != PieceState::AtHome) { + throw std::invalid_argument( + "PieceMovedEvent: targetState==AtHome ohne captured-Flag ist nicht vorgesehen"); + } + // weitere Regeln nach Bedarf, z.B. Bereichschecks für fromPosition/toPosition + } + }; struct DiceRolledEvent { engine::PlayerID player; int diceValue; }; struct TurnChangedEvent {engine::PlayerID newPlayer; }; using LudoEvent = std::variant; diff --git a/game/src/ludo/PiecePresenter.cpp b/game/src/ludo/PiecePresenter.cpp index 5da2349..a3e5ff9 100644 --- a/game/src/ludo/PiecePresenter.cpp +++ b/game/src/ludo/PiecePresenter.cpp @@ -6,6 +6,7 @@ #include "LudoGameMode.h" +#include "core/animation/components/BlockingAnimationComponent.h" #include "core/animation/components/TransformAnimationComponent.h" #include "ecs/standardComponents/MeshComponent.h" #include "ecs/standardComponents/PickableComponent.h" @@ -30,8 +31,39 @@ void ludo::PiecePresenter::onPieceMoved(const ludo::PieceMovedEvent &event, cons const auto& piece = state.getPieces()[event.pieceIndex]; auto target = m_layout.getWorldPosition(piece.color, piece.state, piece.position); auto entity = m_pieceEntities[event.pieceIndex]; - auto& transform = m_registry.getComponent(entity); - transform.m_position = target; + + engine::animation::TransformAnimationComponent animationComponent; + std::vector path = LudoGameMode::computePath(piece.color, event.startState, event.fromPosition, event.targetState, event.toPosition); + std::vector pathPositions; + for (const auto& node : path) { + pathPositions.push_back(m_layout.getWorldPosition(piece.color, node.state, node.position)); + } + + std::vector> keyframes; + float time = 0; + for (const auto& position : pathPositions) { + keyframes.emplace_back(time, position); + time += 0.5f; // Todo: Check if this is in ms or s + } + + engine::animation::AnimationTrack positionTrack; + engine::animation::AnimationTrack rotationTrack; + engine::animation::AnimationTrack scaleTrack; + + positionTrack.keys = keyframes; + animationComponent.position = positionTrack; + animationComponent.rotation = rotationTrack; + animationComponent.scale = scaleTrack; + animationComponent.loop = false; + animationComponent.duration = time; + animationComponent.onFinished = [this, entity]() { + m_registry.removeComponent(entity); + }; + + m_registry.addComponent(entity, animationComponent); + m_registry.addComponent(entity, engine::animation::BlockingAnimationComponent()); + // auto& transform = m_registry.getComponent(entity); + // transform.m_position = target; } std::optional ludo::PiecePresenter::getPieceIndex(std::optional::value_type entity) {