ADD: AnimationSystem + Tests
This commit is contained in:
parent
dedc37f6ac
commit
befd8b3cd9
@ -67,6 +67,18 @@ CPMAddPackage(
|
||||
GIT_TAG v1.15.3
|
||||
)
|
||||
|
||||
# --- GoogleTest ---
|
||||
CPMAddPackage(
|
||||
NAME googletest
|
||||
GITHUB_REPOSITORY google/googletest
|
||||
GIT_TAG v1.15.2
|
||||
OPTIONS
|
||||
"INSTALL_GTEST OFF"
|
||||
"gtest_force_shared_crt ON"
|
||||
)
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_library(imgui STATIC
|
||||
${imgui_SOURCE_DIR}/imgui.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_draw.cpp
|
||||
@ -203,15 +215,6 @@ add_library(Engine STATIC
|
||||
engine/src/core/animation/components/TransformAnimationComponent.h
|
||||
engine/src/core/animation/AnimationSystem.cpp
|
||||
engine/src/core/animation/AnimationSystem.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)
|
||||
|
||||
# --- Executable ---
|
||||
add_executable(ColorRace
|
||||
game/src/main.cpp
|
||||
game/src/ColorRaceApp.cpp
|
||||
game/src/ColorRaceApp.h
|
||||
engine/src/renderer/shader/StaticShader.cpp
|
||||
engine/src/renderer/shader/StaticShader.h
|
||||
engine/src/renderer/entities/Camera.cpp
|
||||
@ -236,14 +239,22 @@ add_executable(ColorRace
|
||||
engine/src/renderer/MeshRenderer.h
|
||||
engine/src/layer/Layer.cpp
|
||||
engine/src/layer/Layer.h
|
||||
game/src/GameLayer.cpp
|
||||
game/src/GameLayer.h
|
||||
engine/src/layer/SceneManager.cpp
|
||||
engine/src/layer/SceneManager.h
|
||||
game/src/GameScene.cpp
|
||||
game/src/GameScene.h
|
||||
engine/src/core/inputsOutputs/context/InputContext.cpp
|
||||
engine/src/core/inputsOutputs/context/InputContext.h
|
||||
engine/src/layer/SceneManager.cpp
|
||||
engine/src/layer/SceneManager.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)
|
||||
|
||||
# --- GameLib: Spiel-Logik, unabhängig von main.cpp testbar ---
|
||||
add_library(GameLib STATIC
|
||||
game/src/ColorRaceApp.cpp
|
||||
game/src/ColorRaceApp.h
|
||||
game/src/GameLayer.cpp
|
||||
game/src/GameLayer.h
|
||||
game/src/GameScene.cpp
|
||||
game/src/GameScene.h
|
||||
game/src/ludo/LudoGameState.cpp
|
||||
game/src/ludo/LudoGameState.h
|
||||
game/src/ludo/LudoTypes.h
|
||||
@ -259,9 +270,34 @@ add_executable(ColorRace
|
||||
game/src/ludo/ecs/highlight/HighlightSystem.h
|
||||
game/src/AudioLayer.cpp
|
||||
game/src/AudioLayer.h
|
||||
game/src/ludo/DiceSource.cpp
|
||||
game/src/ludo/DiceSource.h
|
||||
)
|
||||
target_include_directories(GameLib PUBLIC game/src)
|
||||
target_link_libraries(GameLib PUBLIC Engine)
|
||||
|
||||
# --- Executable ---
|
||||
add_executable(ColorRace
|
||||
game/src/main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(ColorRace PRIVATE
|
||||
Engine
|
||||
GameLib
|
||||
)
|
||||
|
||||
# --- Tests ---
|
||||
add_executable(ColorRaceTests
|
||||
tests/LoduGameMode.cpp
|
||||
tests/helpers/SequenceDiceSource.cpp
|
||||
tests/helpers/SequenceDiceSource.h
|
||||
)
|
||||
|
||||
target_link_libraries(ColorRaceTests PRIVATE
|
||||
GameLib
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(ColorRaceTests)
|
||||
@ -30,24 +30,4 @@ namespace engine::animation {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 AnimationSystem::evaluate(const AnimationTrack &track, float time) {
|
||||
Keyframe a;
|
||||
Keyframe b;
|
||||
findSurroundingKeys(track, time, a, b);
|
||||
|
||||
float localT = (time - a.time) / (b.time - a.time);
|
||||
float easedT = a.easingFunction(localT);
|
||||
return glm::mix(a.value, b.value, easedT);
|
||||
}
|
||||
|
||||
void AnimationSystem::findSurroundingKeys(const AnimationTrack &track, float time, Keyframe &a, Keyframe &b) {
|
||||
for (int i = 1; i < track.keys.size(); i++) {
|
||||
if (track.keys[i-1].time < 0.f && track.keys[i].time > 0.f) {
|
||||
a = track.keys[i-1];
|
||||
b = track.keys[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // engine
|
||||
@ -14,8 +14,43 @@ namespace engine::animation {
|
||||
void update(float deltaTime);
|
||||
private:
|
||||
EntityRegistry& m_registry;
|
||||
glm::vec3 evaluate(const AnimationTrack& track, float time);
|
||||
void findSurroundingKeys(const AnimationTrack& track, float time, Keyframe& a, Keyframe& b);
|
||||
|
||||
template<typename T>
|
||||
glm::vec3 evaluate(const AnimationTrack<T>& track, float time) {
|
||||
Keyframe<T> a;
|
||||
Keyframe<T> b;
|
||||
findSurroundingKeys(track, time, a, b);
|
||||
|
||||
float localT = (time - a.time) / (b.time - a.time);
|
||||
float easedT = a.easingFunction(localT);
|
||||
return glm::mix(a.value, b.value, easedT);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void findSurroundingKeys(const AnimationTrack<T>& track, float time, Keyframe<T>& a, Keyframe<T>& b) {
|
||||
if (track.keys.size() == 1) {
|
||||
a = b = track.keys.front();
|
||||
return;
|
||||
}
|
||||
|
||||
if (time <= track.keys.front().time) {
|
||||
a = b = track.keys.front();
|
||||
return;
|
||||
}
|
||||
|
||||
if (time >= track.keys.back().time) {
|
||||
a = b = track.keys.back();
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < track.keys.size(); ++i) {
|
||||
if (track.keys[i - 1].time <= time && track.keys[i].time >= time) {
|
||||
a = track.keys[i - 1];
|
||||
b = track.keys[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
} // engine
|
||||
|
||||
@ -11,26 +11,29 @@
|
||||
namespace engine::animation {
|
||||
using EasingFunction = std::function<float(float)>;
|
||||
|
||||
template<typename T>
|
||||
struct Keyframe {
|
||||
float time;
|
||||
glm::vec3 value;
|
||||
T value;
|
||||
|
||||
EasingFunction easingFunction = [](float t) {
|
||||
return t;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct AnimationTrack {
|
||||
std::vector<Keyframe> keys;
|
||||
std::vector<Keyframe<T>> keys;
|
||||
};
|
||||
|
||||
struct TransformAnimationComponent {
|
||||
float time = 0.0f;
|
||||
float duration = 0.0f;
|
||||
|
||||
AnimationTrack position;
|
||||
AnimationTrack rotation;
|
||||
AnimationTrack scale;
|
||||
AnimationTrack<glm::vec3> position;
|
||||
AnimationTrack<glm::vec3> rotation;
|
||||
AnimationTrack<glm::vec3> scale;
|
||||
|
||||
std::function<void()> onFinished;
|
||||
bool loop = false;
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
#include "BoardLayout.h"
|
||||
|
||||
#include <format>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
glm::vec3 ludo::BoardLayout::getWorldPosition(ludo::PlayerColor color, ludo::PieceState state, int position) const {
|
||||
|
||||
10
game/src/ludo/DiceSource.cpp
Normal file
10
game/src/ludo/DiceSource.cpp
Normal file
@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by sebastian on 05.08.26.
|
||||
//
|
||||
|
||||
#include "DiceSource.h"
|
||||
|
||||
int ludo::RandomDiceSource::roll() {
|
||||
std::uniform_int_distribution<int> distribution(1, 6);
|
||||
return distribution(m_generator);
|
||||
}
|
||||
26
game/src/ludo/DiceSource.h
Normal file
26
game/src/ludo/DiceSource.h
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// Created by sebastian on 05.08.26.
|
||||
//
|
||||
|
||||
#ifndef COLORRACE_DICESOURCE_H
|
||||
#define COLORRACE_DICESOURCE_H
|
||||
#include <random>
|
||||
|
||||
|
||||
namespace ludo {
|
||||
class IDiceSource {
|
||||
public:
|
||||
virtual ~IDiceSource() = default;
|
||||
virtual int roll() = 0;
|
||||
};
|
||||
|
||||
class RandomDiceSource : public IDiceSource {
|
||||
public:
|
||||
int roll() override;
|
||||
private:
|
||||
std::mt19937 m_generator{std::random_device{}()};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif //COLORRACE_DICESOURCE_H
|
||||
@ -52,6 +52,7 @@ std::optional<int> ludo::LudoGameMode::findLowestHomeSlotPiece(PlayerColor color
|
||||
return i; // erste Übereinstimmung = kleinster Home-Slot, dank Erzeugungsreihenfolge
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ludo::LudoGameMode::MoveResult ludo::LudoGameMode::movePieceInternal(int pieceIndex, int diceValue) {
|
||||
@ -59,10 +60,13 @@ ludo::LudoGameMode::MoveResult ludo::LudoGameMode::movePieceInternal(int pieceIn
|
||||
int fromPosition = piece.position;
|
||||
|
||||
Destination destination = computeDestination(piece, diceValue).value();
|
||||
|
||||
PieceState startState = piece.state;
|
||||
PieceState targetState = destination.state;
|
||||
piece.state = destination.state;
|
||||
piece.position = destination.position;
|
||||
MoveResult result{fromPosition, piece.position, false, -1, targetState, startState};
|
||||
|
||||
MoveResult result{fromPosition, piece.position, false, -1};
|
||||
|
||||
if (piece.state == PieceState::OnTrack) {
|
||||
for (int i = 0; i < static_cast<int>(m_gameState.m_pieces.size()); ++i) {
|
||||
@ -185,11 +189,10 @@ std::optional<int> ludo::LudoGameMode::wouldCapture(int pieceIndex, int diceValu
|
||||
|
||||
|
||||
int ludo::LudoGameMode::rollDice() {
|
||||
std::uniform_int_distribution<int> distribution(1, 6);
|
||||
return distribution(m_rng);
|
||||
return m_diceSource->roll();
|
||||
}
|
||||
|
||||
int ludo::LudoGameMode::getEntryOffset(PlayerColor color) const {
|
||||
int ludo::LudoGameMode::getEntryOffset(PlayerColor color) {
|
||||
return static_cast<int>(color) * 10;
|
||||
}
|
||||
|
||||
@ -201,3 +204,42 @@ bool ludo::LudoGameMode::isBlockedByOwnPiece(PlayerColor owner, PieceState state
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<ludo::PathNode> ludo::LudoGameMode::computePath(PlayerColor color, PieceState startingState, int startPosition, PieceState targetState, int targetPosition) {
|
||||
std::vector<PathNode> path;
|
||||
|
||||
if (startPosition == targetPosition && startingState == targetState) return path;
|
||||
|
||||
PieceState state = startingState;
|
||||
int position = startPosition;
|
||||
|
||||
path.push_back({state, position});
|
||||
|
||||
while (state != targetState || position != targetPosition) {
|
||||
switch (state) {
|
||||
case PieceState::AtHome:
|
||||
state = PieceState::OnTrack;
|
||||
position = getEntryOffset(color);
|
||||
break;
|
||||
case PieceState::OnTrack:
|
||||
if (position == getEntryOffset(color) && (targetState == PieceState::InHomeStretch || targetState == PieceState::Finished)) {
|
||||
state = PieceState::InHomeStretch;
|
||||
position = 0;
|
||||
} else {
|
||||
position = (position + 1) % kTrackLength;
|
||||
}
|
||||
break;
|
||||
case PieceState::InHomeStretch:
|
||||
if (targetState == PieceState::Finished && position == kHomeStretchLength - 1) {
|
||||
state = PieceState::Finished;
|
||||
} else {
|
||||
++position;
|
||||
}
|
||||
break;
|
||||
case PieceState::Finished:
|
||||
break;
|
||||
}
|
||||
path.push_back({state, position});
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
@ -4,18 +4,25 @@
|
||||
|
||||
#ifndef COLORRACE_LUDOGAMEMODE_H
|
||||
#define COLORRACE_LUDOGAMEMODE_H
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
|
||||
#include "DiceSource.h"
|
||||
#include "LudoGameState.h"
|
||||
#include "LudoTypes.h"
|
||||
#include "game/TurnBasedGameMode.h"
|
||||
|
||||
namespace ludo {
|
||||
struct PathNode {
|
||||
PieceState state;
|
||||
int position;
|
||||
};
|
||||
|
||||
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_rng(seed.value_or(std::random_device{}())), m_gameState(getPlayerOrder()) {}
|
||||
m_diceSource(std::make_unique<RandomDiceSource>()), m_gameState(getPlayerOrder()) {}
|
||||
void sendCommand(const std::variant<MovePieceCommand, RollDiceCommand> &command) override;
|
||||
std::vector<LudoEvent> pollEvents() override;
|
||||
|
||||
@ -25,12 +32,18 @@ namespace ludo {
|
||||
|
||||
std::vector<int> getMoveablePieceIndices(PlayerColor color, int diceValue) const;
|
||||
std::optional<int> wouldCapture(int pieceIndex, int diceValue) const;
|
||||
[[nodiscard]] static int getEntryOffset(PlayerColor color);
|
||||
static std::vector<PathNode> computePath(PlayerColor color, PieceState startingState, int startPosition, PieceState targetState, int targetPosition);
|
||||
|
||||
void setDiceSource(std::unique_ptr<IDiceSource> diceSource) { m_diceSource = std::move(diceSource); }
|
||||
private:
|
||||
struct MoveResult {
|
||||
int fromPosition;
|
||||
int toPosition;
|
||||
int fromPosition = 0;
|
||||
int toPosition = 0;
|
||||
bool captured = false;
|
||||
int capturedPieceIndex = -1;
|
||||
PieceState targetState = PieceState::OnTrack;
|
||||
PieceState startState = PieceState::OnTrack;
|
||||
};
|
||||
|
||||
struct Destination {
|
||||
@ -46,8 +59,6 @@ namespace ludo {
|
||||
|
||||
void handle(const RollDiceCommand &command);
|
||||
|
||||
|
||||
|
||||
void handle(const MovePieceCommand &command);
|
||||
void finishTurn();
|
||||
|
||||
@ -56,9 +67,9 @@ namespace ludo {
|
||||
std::vector<LudoEvent> m_pendingEvents;
|
||||
|
||||
int rollDice();
|
||||
std::mt19937 m_rng;
|
||||
std::unique_ptr<IDiceSource> m_diceSource;
|
||||
|
||||
|
||||
[[nodiscard]] int getEntryOffset(PlayerColor color) const;
|
||||
[[nodiscard]] bool isBlockedByOwnPiece(PlayerColor owner, PieceState state, int position) const;
|
||||
|
||||
};
|
||||
|
||||
@ -66,7 +66,7 @@ namespace ludo {
|
||||
struct RollDiceCommand { engine::PlayerID player; };
|
||||
using LudoCommand = std::variant<MovePieceCommand, RollDiceCommand>;
|
||||
|
||||
struct PieceMovedEvent { int pieceIndex; int fromPosition; int toPosition; bool captured; };
|
||||
struct PieceMovedEvent { int pieceIndex; int fromPosition; int toPosition; bool captured; PieceState targetState; PieceState startState; };
|
||||
struct DiceRolledEvent { engine::PlayerID player; int diceValue; };
|
||||
struct TurnChangedEvent {engine::PlayerID newPlayer; };
|
||||
using LudoEvent = std::variant<PieceMovedEvent, DiceRolledEvent, TurnChangedEvent>;
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
#include "PiecePresenter.h"
|
||||
|
||||
|
||||
#include "LudoGameMode.h"
|
||||
#include "core/animation/components/TransformAnimationComponent.h"
|
||||
#include "ecs/standardComponents/MeshComponent.h"
|
||||
#include "ecs/standardComponents/PickableComponent.h"
|
||||
#include "ecs/standardComponents/TransformComponent.h"
|
||||
@ -37,3 +39,5 @@ std::optional<int> ludo::PiecePresenter::getPieceIndex(std::optional<engine::Ent
|
||||
if (it == m_pieceEntities.end()) return std::nullopt;
|
||||
return static_cast<int>(std::distance(m_pieceEntities.begin(), it));
|
||||
}
|
||||
|
||||
|
||||
|
||||
19
tests/LoduGameMode.cpp
Normal file
19
tests/LoduGameMode.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "gtest/gtest.h"
|
||||
//
|
||||
// Created by sebastian on 05.08.26.
|
||||
//
|
||||
#include "helpers/SequenceDiceSource.h"
|
||||
#include "ludo/LudoGameMode.h"
|
||||
|
||||
TEST(LudoGameMode, SecondSixMovesOutOfBlockedEntry) {
|
||||
ludo::LudoGameMode mode({0}); // ein Spieler reicht zum Isolieren
|
||||
mode.setDiceSource(std::make_unique<SequenceDiceSource>(std::vector<int>{6,6}));
|
||||
|
||||
mode.sendCommand(ludo::RollDiceCommand{0});
|
||||
ASSERT_EQ(mode.getCurrentPlayer(), 0u) << "Spieler sollte nach Extra-Turn gleich bleiben";
|
||||
ASSERT_EQ(mode.getState().getPhase(), ludo::TurnPhase::AwaitingRoll);
|
||||
|
||||
mode.sendCommand(ludo::RollDiceCommand{0});
|
||||
EXPECT_EQ(mode.getState().getPhase(), ludo::TurnPhase::AwaitingPieceSelection)
|
||||
<< "Erwartet: Figur-Auswahl, da Entry blockiert";
|
||||
}
|
||||
11
tests/helpers/SequenceDiceSource.cpp
Normal file
11
tests/helpers/SequenceDiceSource.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by sebastian on 05.08.26.
|
||||
//
|
||||
|
||||
#include "SequenceDiceSource.h"
|
||||
|
||||
int SequenceDiceSource::roll() {
|
||||
int v = m_diceValues.at(m_index);
|
||||
m_index = std::min(m_index +1, m_diceValues.size() -1);
|
||||
return v;
|
||||
}
|
||||
21
tests/helpers/SequenceDiceSource.h
Normal file
21
tests/helpers/SequenceDiceSource.h
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Created by sebastian on 05.08.26.
|
||||
//
|
||||
|
||||
#ifndef COLORRACE_SEQUENCEDICESOURCE_H
|
||||
#define COLORRACE_SEQUENCEDICESOURCE_H
|
||||
#include "ludo/DiceSource.h"
|
||||
|
||||
|
||||
class SequenceDiceSource : public ludo::IDiceSource{
|
||||
public:
|
||||
explicit SequenceDiceSource(const std::vector<int> &diceValues) : m_diceValues(diceValues) {}
|
||||
int roll() override;
|
||||
|
||||
private:
|
||||
std::vector<int> m_diceValues;
|
||||
size_t m_index = 0;
|
||||
};
|
||||
|
||||
|
||||
#endif //COLORRACE_SEQUENCEDICESOURCE_H
|
||||
Loading…
Reference in New Issue
Block a user