ADD: Enable AnimationSystem

This commit is contained in:
sebastian 2026-08-05 09:01:18 +02:00
parent 12c617587f
commit 97f8e71e18
11 changed files with 191 additions and 11 deletions

View File

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

View File

@ -4,19 +4,28 @@
#include "AnimationSystem.h"
#include <map>
#include "components/TransformAnimationComponent.h"
#include "ecs/standardComponents/TransformComponent.h"
namespace engine::animation {
void animation::AnimationSystem::update(float deltaTime) {
std::vector<engine::Entity> toRemove;
for (auto& [entity, animation] : m_registry.getPool<TransformAnimationComponent>()) {
animation.time += deltaTime;
auto& transform = m_registry.getComponent<TransformComponent>(entity);
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<TransformAnimationComponent>(entity);
}
}
} // engine

View File

@ -16,11 +16,13 @@ namespace engine::animation {
EntityRegistry& m_registry;
template<typename T>
glm::vec3 evaluate(const AnimationTrack<T>& track, float time) {
T evaluate(const AnimationTrack<T>& track, float time) {
Keyframe<T> a;
Keyframe<T> 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);

View File

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

View File

@ -12,6 +12,7 @@ namespace engine {
public:
virtual ~IComponentPool() = default;
virtual void remove(Entity entity) = 0;
virtual size_t size() = 0;
};
template<typename T>
@ -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(); }

View File

@ -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 &registry, engine::RenderQueue &renderQueue) {
@ -20,6 +21,8 @@ void engine::MeshRenderSystem::update(engine::EntityRegistry &registry, 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<HighlightComponent>(entity)) {

View File

@ -15,6 +15,7 @@
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/string_cast.hpp>
#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<animation::BlockingAnimationComponent>().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);
}

View File

@ -6,6 +6,7 @@
#define COLORRACE_GAMELAYER_H
#include <utility>
#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<engine::Model> boardModel, std::unordered_map<std::string, std::shared_ptr<engine::Model>> pieceModdels)
: Layer(keyboard, mouse), m_gameMode(std::move(gameMode)), m_entityRegistry(entityRegistry), m_camera(std::move(cam)), m_renderer(std::make_unique<engine::MeshRenderer>()),
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<ludo::LudoAiController> aiControllers;
engine::animation::AnimationSystem m_animationSystem;
std::unique_ptr<engine::audio::SoundBuffer> m_soundBuffer;
void renderHud();
bool isAnimationBlocking();
};

View File

@ -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::PathNode> 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<PathNode> path;
if (startPosition == targetPosition && startingState == targetState) return path;
@ -230,7 +293,17 @@ std::vector<ludo::PathNode> 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;

View File

@ -8,6 +8,7 @@
#include <array>
#include <cassert>
#include <cstdint>
#include <stdexcept>
#include <variant>
#include "game/TurnBasedGameMode.h"
@ -66,7 +67,34 @@ namespace ludo {
struct RollDiceCommand { engine::PlayerID player; };
using LudoCommand = std::variant<MovePieceCommand, RollDiceCommand>;
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<PieceMovedEvent, DiceRolledEvent, TurnChangedEvent>;

View File

@ -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<engine::TransformComponent>(entity);
transform.m_position = target;
engine::animation::TransformAnimationComponent animationComponent;
std::vector<PathNode> path = LudoGameMode::computePath(piece.color, event.startState, event.fromPosition, event.targetState, event.toPosition);
std::vector<glm::vec3> pathPositions;
for (const auto& node : path) {
pathPositions.push_back(m_layout.getWorldPosition(piece.color, node.state, node.position));
}
std::vector<engine::animation::Keyframe<glm::vec3>> 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<glm::vec3> positionTrack;
engine::animation::AnimationTrack<glm::vec3> rotationTrack;
engine::animation::AnimationTrack<glm::vec3> 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<engine::animation::BlockingAnimationComponent>(entity);
};
m_registry.addComponent<engine::animation::TransformAnimationComponent>(entity, animationComponent);
m_registry.addComponent<engine::animation::BlockingAnimationComponent>(entity, engine::animation::BlockingAnimationComponent());
// auto& transform = m_registry.getComponent<engine::TransformComponent>(entity);
// transform.m_position = target;
}
std::optional<int> ludo::PiecePresenter::getPieceIndex(std::optional<engine::Entity>::value_type entity) {