diff options
97 files changed, 2375 insertions, 1454 deletions
@@ -16,3 +16,4 @@ CTestTestfile.cmake _deps CMakeUserPresets.json compile.sh +asset/jetpack_joyride diff --git a/contributing.md b/contributing.md index 5555892..d217410 100644 --- a/contributing.md +++ b/contributing.md @@ -19,6 +19,20 @@ that you can click on to open them. an explanation as to why the code can not (reliably) be tested - Non-bugfix pull requests must be approved by at least 2 reviewers before being merged +- Pull requests should have the following labels (where appropriate) + |label|meaning| + |:-:|-| + |`fix me`|has feedback that should be resolved/discussed by its author| + |`review me`|needs additional reviewers (minimum of 2 per PR)| + |`do not review`|is actively being worked on or not ready for feedback| + |`high priority`|should be worked on before all the others| + - PRs start with the `review me` label + - Reviewers— + - Add the `fix me` label after adding comments + - Authors— + - Remove the `review me` label if the pull request has enough reviewers + - Add the `do not review` label while processing feedback / pushing + additional commits <!-- - TODO: tagging / versions @@ -163,40 +177,28 @@ that you can click on to open them. ``` </td></tr></table></details> - <details><summary> - <code>using namespace</code> may not be used in header files (.h, .hpp), only - in source files (.cpp). + <a href="https://en.cppreference.com/w/cpp/language/using_declaration">Using-declarations</a> + may not be used in header files (<code>.h</code>, <code>.hpp</code>), only in + source files (<code>.cpp</code>). </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> example.h: ```cpp namespace crepe { - void foo(); + std::string foo(); } ``` - example.cpp: - ```cpp - #include "example.h" - using namespace crepe; - void foo() {} - ``` </td><td> example.h: ```cpp + using namespace std; + namespace crepe { - template <typename T> - T foo(); + string foo(); } ``` - - example.hpp: - ```cpp - #include "example.h" - using namespace crepe; - template <typename T> - T foo(); - ``` </td></tr></table></details> - <details><summary> diff --git a/src/crepe/Component.cpp b/src/crepe/Component.cpp index acfd35c..ae76e65 100644 --- a/src/crepe/Component.cpp +++ b/src/crepe/Component.cpp @@ -1,5 +1,17 @@ #include "Component.h" using namespace crepe; +using namespace std; Component::Component(game_object_id_t id) : game_object_id(id) {} + +Component & Component::operator=(const Component & other) { + this->active = other.active; + return *this; +} + +unique_ptr<Component> Component::save() const { + return unique_ptr<Component>(new Component(*this)); +} + +void Component::restore(const Component & snapshot) { *this = snapshot; } diff --git a/src/crepe/Component.h b/src/crepe/Component.h index eff5a58..52e06d5 100644 --- a/src/crepe/Component.h +++ b/src/crepe/Component.h @@ -1,5 +1,7 @@ #pragma once +#include <memory> + #include "types.h" namespace crepe { @@ -32,11 +34,33 @@ protected: //! Only ComponentManager can create components friend class ComponentManager; - Component(const Component &) = delete; + // components are never moved Component(Component &&) = delete; - virtual Component & operator=(const Component &) = delete; virtual Component & operator=(Component &&) = delete; +protected: + /** + * \name ReplayManager (Memento) functions + * \{ + */ + /** + * \brief Save a snapshot of this component's state + * \note This function should only be implemented on components that should be saved/restored + * by ReplayManager. + * \returns Unique pointer to a deep copy of this component + */ + virtual std::unique_ptr<Component> save() const; + //! Copy constructor (used by \c save()) + Component(const Component &) = default; + /** + * \brief Restore this component from a snapshot + * \param snapshot Data to fill this component with (as returned by \c save()) + */ + virtual void restore(const Component & snapshot); + //! Copy assignment operator (used by \c restore()) + virtual Component & operator=(const Component &); + //! \} + public: virtual ~Component() = default; diff --git a/src/crepe/api/Animator.cpp b/src/crepe/api/Animator.cpp index 4ce4bf0..203cef3 100644 --- a/src/crepe/api/Animator.cpp +++ b/src/crepe/api/Animator.cpp @@ -1,5 +1,5 @@ -#include "util/Log.h" +#include "util/dbg.h" #include "Animator.h" #include "Component.h" diff --git a/src/crepe/api/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp index d22afdf..af7572c 100644 --- a/src/crepe/api/BehaviorScript.cpp +++ b/src/crepe/api/BehaviorScript.cpp @@ -10,6 +10,6 @@ BehaviorScript::BehaviorScript(game_object_id_t id, Mediator & mediator) template <> BehaviorScript & GameObject::add_component<BehaviorScript>() { - ComponentManager & mgr = this->component_manager; - return mgr.add_component<BehaviorScript>(this->id, mgr.mediator); + ComponentManager & mgr = this->mediator.component_manager; + return mgr.add_component<BehaviorScript>(this->id, this->mediator); } diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp index b9bb1e2..353d5e2 100644 --- a/src/crepe/api/BehaviorScript.hpp +++ b/src/crepe/api/BehaviorScript.hpp @@ -2,8 +2,6 @@ #include <type_traits> -#include "../util/Log.h" - #include "BehaviorScript.h" #include "Script.h" @@ -11,7 +9,6 @@ namespace crepe { template <class T, typename... Args> BehaviorScript & BehaviorScript::set_script(Args &&... args) { - dbg_trace(); static_assert(std::is_base_of<Script, T>::value); this->script = std::unique_ptr<Script>(new T(std::forward<Args>(args)...)); diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index e8d6f92..2bee3fb 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -13,7 +13,7 @@ target_sources(crepe PUBLIC Animator.cpp BoxCollider.cpp CircleCollider.cpp - LoopManager.cpp + Engine.cpp Asset.cpp EventHandler.cpp Script.cpp @@ -47,7 +47,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES EventHandler.h EventHandler.hpp Event.h - LoopManager.h + Engine.h + Engine.hpp Asset.h Button.h UIObject.h diff --git a/src/crepe/api/Camera.cpp b/src/crepe/api/Camera.cpp index 179dc18..19a3296 100644 --- a/src/crepe/api/Camera.cpp +++ b/src/crepe/api/Camera.cpp @@ -1,4 +1,4 @@ -#include "util/Log.h" +#include "util/dbg.h" #include "Camera.h" #include "Component.h" diff --git a/src/crepe/api/Engine.cpp b/src/crepe/api/Engine.cpp new file mode 100644 index 0000000..2e9d35a --- /dev/null +++ b/src/crepe/api/Engine.cpp @@ -0,0 +1,63 @@ +#include "../util/Log.h" + +#include "Engine.h" + +using namespace crepe; +using namespace std; + +int Engine::main() noexcept { + try { + this->setup(); + } catch (const exception & e) { + Log::logf(Log::Level::ERROR, "Uncaught exception in setup: {}\n", e.what()); + return EXIT_FAILURE; + } + + try { + this->loop(); + } catch (const exception & e) { + Log::logf(Log::Level::ERROR, "Uncaught exception in main loop: {}\n", e.what()); + this->event_manager.trigger_event<ShutDownEvent>(); + } + + return EXIT_SUCCESS; +} + +void Engine::setup() { + this->loop_timer.start(); + this->scene_manager.load_next_scene(); + + this->event_manager.subscribe<ShutDownEvent>([this](const ShutDownEvent & event) { + this->game_running = false; + + // propagate to possible user ShutDownEvent listeners + return false; + }); +} + +void Engine::loop() { + LoopTimerManager & timer = this->loop_timer; + SystemManager & systems = this->system_manager; + + while (this->game_running) { + timer.update(); + + while (timer.get_lag() >= timer.get_fixed_delta_time()) { + try { + systems.fixed_update(); + } catch (const exception & e) { + Log::logf(Log::Level::WARNING, + "Uncaught exception in fixed update function: {}\n", e.what()); + } + timer.advance_fixed_elapsed_time(); + } + + try { + systems.frame_update(); + } catch (const exception & e) { + Log::logf(Log::Level::WARNING, "Uncaught exception in frame update function: {}\n", + e.what()); + } + timer.enforce_frame_rate(); + } +} diff --git a/src/crepe/api/Engine.h b/src/crepe/api/Engine.h new file mode 100644 index 0000000..700a0cd --- /dev/null +++ b/src/crepe/api/Engine.h @@ -0,0 +1,81 @@ +#pragma once + +#include "../facade/SDLContext.h" +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/ReplayManager.h" +#include "../manager/ResourceManager.h" +#include "../manager/SaveManager.h" +#include "../manager/SceneManager.h" +#include "../manager/SystemManager.h" + +namespace crepe { + +/** + * \brief Main game entrypoint + * + * This class is responsible for managing the game loop, including initialization and updating. + */ +class Engine { +public: + /** + * \brief Engine entrypoint + * + * This function is called by the game programmer after registering all scenes + * + * \returns process exit code + */ + int main() noexcept; + + //! \copydoc SceneManager::add_scene + template <typename T> + void add_scene(); + +private: + /** + * \brief Setup function for one-time initialization. + * + * This function initializes necessary components for the game. + */ + void setup(); + /** + * \brief Main game loop function. + * + * This function runs the main loop, handling game updates and rendering. + */ + void loop(); + + //! Game loop condition + bool game_running = true; + +private: + //! Global context + Mediator mediator; + + //! SystemManager + SystemManager system_manager{mediator}; + + //! SDLContext instance + SDLContext sdl_context{mediator}; + + //! Resource manager instance + ResourceManager resource_manager{mediator}; + + //! Component manager instance + ComponentManager component_manager{mediator}; + //! Scene manager instance + SceneManager scene_manager{mediator}; + //! LoopTimerManager instance + LoopTimerManager loop_timer{mediator}; + //! EventManager instance + EventManager event_manager{mediator}; + //! Save manager instance + SaveManager save_manager{mediator}; + //! ReplayManager instance + ReplayManager replay_manager{mediator}; +}; + +} // namespace crepe + +#include "Engine.hpp" diff --git a/src/crepe/api/Engine.hpp b/src/crepe/api/Engine.hpp new file mode 100644 index 0000000..f2fdc0a --- /dev/null +++ b/src/crepe/api/Engine.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "Engine.h" + +namespace crepe { + +template <class T> +void Engine::add_scene() { + this->scene_manager.add_scene<T>(); +} + +} // namespace crepe diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp index 9ef4682..9b94cad 100644 --- a/src/crepe/api/GameObject.cpp +++ b/src/crepe/api/GameObject.cpp @@ -7,20 +7,17 @@ using namespace crepe; using namespace std; -GameObject::GameObject(ComponentManager & component_manager, game_object_id_t id, - const std::string & name, const std::string & tag, - const vec2 & position, double rotation, double scale) +GameObject::GameObject(Mediator & mediator, game_object_id_t id, const std::string & name, + const std::string & tag, const vec2 & position, double rotation, + double scale) : id(id), - component_manager(component_manager) { - - // Add Transform and Metadata components - ComponentManager & mgr = this->component_manager; - mgr.add_component<Transform>(this->id, position, rotation, scale); - mgr.add_component<Metadata>(this->id, name, tag); -} + mediator(mediator), + transform(mediator.component_manager->add_component<Transform>(this->id, position, + rotation, scale)), + metadata(mediator.component_manager->add_component<Metadata>(this->id, name, tag)) {} void GameObject::set_parent(const GameObject & parent) { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; // Set parent on own Metadata component RefVector<Metadata> this_metadata = mgr.get_components_by_id<Metadata>(this->id); @@ -32,7 +29,7 @@ void GameObject::set_parent(const GameObject & parent) { } void GameObject::set_persistent(bool persistent) { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; mgr.set_persistent(this->id, persistent); } diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h index ff80f49..572ce3a 100644 --- a/src/crepe/api/GameObject.h +++ b/src/crepe/api/GameObject.h @@ -6,7 +6,9 @@ namespace crepe { -class ComponentManager; +class Mediator; +class Transform; +class Metadata; /** * \brief Represents a GameObject @@ -20,7 +22,7 @@ private: * This constructor creates a new GameObject. It creates a new Transform and Metadata * component and adds them to the ComponentManager. * - * \param component_manager Reference to component_manager + * \param mediator Reference to mediator * \param id The id of the GameObject * \param name The name of the GameObject * \param tag The tag of the GameObject @@ -28,13 +30,20 @@ private: * \param rotation The rotation of the GameObject * \param scale The scale of the GameObject */ - GameObject(ComponentManager & component_manager, game_object_id_t id, - const std::string & name, const std::string & tag, const vec2 & position, - double rotation, double scale); + GameObject(Mediator & mediator, game_object_id_t id, const std::string & name, + const std::string & tag, const vec2 & position, double rotation, double scale); //! ComponentManager instances GameObject friend class ComponentManager; public: + //! The id of the GameObject + const game_object_id_t id; + //! This entity's transform + Transform & transform; + //! This entity's metadata + Metadata & metadata; + +public: /** * \brief Set the parent of this GameObject * @@ -68,12 +77,8 @@ public: */ void set_persistent(bool persistent = true); -public: - //! The id of the GameObject - const game_object_id_t id; - protected: - ComponentManager & component_manager; + Mediator & mediator; }; } // namespace crepe diff --git a/src/crepe/api/GameObject.hpp b/src/crepe/api/GameObject.hpp index a6b45b0..69f7d73 100644 --- a/src/crepe/api/GameObject.hpp +++ b/src/crepe/api/GameObject.hpp @@ -8,7 +8,7 @@ namespace crepe { template <typename T, typename... Args> T & GameObject::add_component(Args &&... args) { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; return mgr.add_component<T>(this->id, std::forward<Args>(args)...); } diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp deleted file mode 100644 index 7a78019..0000000 --- a/src/crepe/api/LoopManager.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "../facade/SDLContext.h" -#include "../manager/EventManager.h" -#include "../manager/LoopTimerManager.h" -#include "../system/AISystem.h" -#include "../system/AnimatorSystem.h" -#include "../system/AudioSystem.h" -#include "../system/CollisionSystem.h" -#include "../system/InputSystem.h" -#include "../system/ParticleSystem.h" -#include "../system/PhysicsSystem.h" -#include "../system/RenderSystem.h" -#include "../system/ScriptSystem.h" -#include "../util/Log.h" - -#include "LoopManager.h" - -using namespace crepe; -using namespace std; - -LoopManager::LoopManager() { - this->load_system<AnimatorSystem>(); - this->load_system<CollisionSystem>(); - this->load_system<ParticleSystem>(); - this->load_system<PhysicsSystem>(); - this->load_system<RenderSystem>(); - this->load_system<ScriptSystem>(); - this->load_system<InputSystem>(); - this->event_manager.subscribe<ShutDownEvent>( - [this](const ShutDownEvent & event) { return this->on_shutdown(event); }); - this->load_system<AudioSystem>(); - this->load_system<AISystem>(); -} -void LoopManager::start() { - this->setup(); - this->loop(); -} - -void LoopManager::setup() { - this->game_running = true; - this->loop_timer.start(); - this->scene_manager.load_next_scene(); -} - -void LoopManager::loop() { - try { - while (game_running) { - this->loop_timer.update(); - - while (this->loop_timer.get_lag() >= this->loop_timer.get_fixed_delta_time()) { - this->fixed_update(); - this->loop_timer.advance_fixed_elapsed_time(); - } - - this->frame_update(); - this->loop_timer.enforce_frame_rate(); - } - } catch (const exception & e) { - Log::logf(Log::Level::ERROR, "Exception caught in main loop: {}", e.what()); - this->event_manager.trigger_event<ShutDownEvent>(ShutDownEvent{}); - } -} - -// will be called at a fixed interval -void LoopManager::fixed_update() { - this->get_system<InputSystem>().update(); - this->event_manager.dispatch_events(); - this->get_system<ScriptSystem>().update(); - this->get_system<ParticleSystem>().update(); - this->get_system<AISystem>().update(); - this->get_system<PhysicsSystem>().update(); - this->get_system<CollisionSystem>().update(); - this->get_system<AudioSystem>().update(); -} - -// will be called every frame -void LoopManager::frame_update() { - this->scene_manager.load_next_scene(); - this->get_system<AnimatorSystem>().update(); - //render - this->get_system<RenderSystem>().update(); -} - -bool LoopManager::on_shutdown(const ShutDownEvent & e) { - this->game_running = false; - // propagate to possible user ShutDownEvent listeners - return false; -} diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h deleted file mode 100644 index 124cd3a..0000000 --- a/src/crepe/api/LoopManager.h +++ /dev/null @@ -1,123 +0,0 @@ -#pragma once - -#include <memory> - -#include "../facade/SDLContext.h" -#include "../manager/ComponentManager.h" -#include "../manager/EventManager.h" -#include "../manager/LoopTimerManager.h" -#include "../manager/Mediator.h" -#include "../manager/ResourceManager.h" -#include "../manager/SaveManager.h" -#include "../manager/SceneManager.h" -#include "../system/System.h" - -namespace crepe { -/** - * \brief Main game loop manager - * - * This class is responsible for managing the game loop, including initialization and updating. - */ -class LoopManager { -public: - LoopManager(); - /** - * \brief Start the gameloop - * - * This is the start of the engine where the setup is called and then the loop keeps running until the game stops running. - * The Game programmer needs to call this function to run the game. This should be done after creating and adding all scenes. - */ - void start(); - - /** - * \brief Add a new concrete scene to the scene manager - * - * \tparam T Type of concrete scene - */ - template <typename T> - void add_scene(); - -private: - /** - * \brief Setup function for one-time initialization. - * - * This function initializes necessary components for the game. - */ - void setup(); - /** - * \brief Main game loop function. - * - * This function runs the main loop, handling game updates and rendering. - */ - void loop(); - - /** - * \brief Per-frame update. - * - * Updates the game state based on the elapsed time since the last frame. - */ - virtual void frame_update(); - - /** - * \brief Fixed update executed at a fixed rate. - * - * This function updates physics and game logic based on LoopTimer's fixed_delta_time. - */ - virtual void fixed_update(); - - //! Indicates whether the game is running. - bool game_running = false; - -private: - //! Global context - Mediator mediator; - /** - * \brief Collection of System instances - * - * This map holds System instances indexed by the system's class typeid. It is filled in the - * constructor of LoopManager using LoopManager::load_system. - */ - std::unordered_map<std::type_index, std::unique_ptr<System>> systems; - - //! SDLContext instance - SDLContext sdl_context{mediator}; - //! Resource manager instance - ResourceManager resource_manager{mediator}; - - //! Component manager instance - ComponentManager component_manager{mediator}; - //! Scene manager instance - SceneManager scene_manager{mediator}; - //! LoopTimerManager instance - LoopTimerManager loop_timer{mediator}; - //! EventManager instance - EventManager event_manager{mediator}; - //! Save manager instance - SaveManager save_manager{mediator}; - -private: - /** - * \brief Callback function for ShutDownEvent - * - * This function sets the game_running variable to false, stopping the gameloop and therefor quitting the game. - */ - bool on_shutdown(const ShutDownEvent & e); - /** - * \brief Initialize a system - * \tparam T System type (must be derivative of \c System) - */ - template <class T> - void load_system(); - /** - * \brief Retrieve a reference to ECS system - * \tparam T System type - * \returns Reference to system instance - * \throws std::runtime_error if the System is not initialized - */ - template <class T> - T & get_system(); -}; - -} // namespace crepe - -#include "LoopManager.hpp" diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp index 4f54bbd..9a70334 100644 --- a/src/crepe/api/ParticleEmitter.cpp +++ b/src/crepe/api/ParticleEmitter.cpp @@ -2,6 +2,7 @@ #include "api/Sprite.h" using namespace crepe; +using namespace std; ParticleEmitter::ParticleEmitter(game_object_id_t game_object_id, const Sprite & sprite, const Data & data) @@ -12,3 +13,16 @@ ParticleEmitter::ParticleEmitter(game_object_id_t game_object_id, const Sprite & this->particles.emplace_back(); } } + +unique_ptr<Component> ParticleEmitter::save() const { + return unique_ptr<Component>{new ParticleEmitter(*this)}; +} + +void ParticleEmitter::restore(const Component & snapshot) { + *this = static_cast<const ParticleEmitter &>(snapshot); +} + +ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter & other) { + this->particles = other.particles; + return *this; +} diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h index 8ac2e72..1edd2b5 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -84,6 +84,12 @@ public: //! Configuration data for particle emission settings. Data data; +protected: + virtual std::unique_ptr<Component> save() const; + ParticleEmitter(const ParticleEmitter &) = default; + virtual void restore(const Component & snapshot); + virtual ParticleEmitter & operator=(const ParticleEmitter &); + private: //! Only ParticleSystem can move and read particles friend ParticleSystem; diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h index 6900295..b63d941 100644 --- a/src/crepe/api/Rigidbody.h +++ b/src/crepe/api/Rigidbody.h @@ -2,6 +2,7 @@ #include <cmath> #include <set> +#include <string> #include "../Component.h" @@ -120,26 +121,49 @@ public: * above 0.0. * */ - float elastisity_coefficient = 0.0; + float elasticity_coefficient = 0.0; /** - * \brief Offset of all colliders relative to the object's transform position. + * \brief Enables collision handling for objects colliding with kinematic objects. + * + * Enables collision handling for objects colliding with kinematic objects in the collision system. + * If `kinematic_collision` is true, dynamic objects cannot pass through this kinematic object. + * This ensures that kinematic objects delegate collision handling to the collision system. + */ + bool kinematic_collision = true; + + /** + * \brief Defines the collision layers a GameObject interacts with. * - * The `offset` defines a positional shift applied to all colliders associated with the object, relative to the object's - * transform position. This allows for the colliders to be placed at a different position than the object's actual - * position, without modifying the object's transform itself. + * The `collision_layers` represent the set of layers the GameObject can detect collisions with. + * Each element in this set corresponds to a layer ID. The GameObject will only collide with other + * GameObjects that belong to one these layers. + */ + std::set<int> collision_layers = {0}; + + /** + * \brief Specifies the collision layer of the GameObject. * + * The `collision_layer` indicates the single layer that this GameObject belongs to. + * This determines which layers other objects must match to detect collisions with this object. */ - vec2 offset; + int collision_layer = 0; /** * \brief Defines the collision layers of a GameObject. * - * The `collision_layers` specifies the layers that the GameObject will collide with. - * Each element represents a layer ID, and the GameObject will only detect - * collisions with other GameObjects that belong to these layers. + * The `collision_names` specifies where the GameObject will collide with. + * Each element represents a name from the Metadata of the gameobject. */ - std::set<int> collision_layers = {0}; + std::set<std::string> collision_names; + + /** + * \brief Defines the collision layers of a GameObject. + * + * The `collision_tags` specifies where the GameObject will collide with. + * Each element represents a tag from the Metadata of the gameobject. + */ + std::set<std::string> collision_tags; }; public: diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp index 7531388..b147252 100644 --- a/src/crepe/api/Script.cpp +++ b/src/crepe/api/Script.cpp @@ -2,6 +2,7 @@ #include "../facade/SDLContext.h" #include "../manager/SceneManager.h" + #include "Script.h" using namespace crepe; @@ -28,6 +29,26 @@ SaveManager & Script::get_save_manager() const { return this->mediator->save_man LoopTimerManager & Script::get_loop_timer() const { return this->mediator->loop_timer; } +void Script::replay::record_start() { + ReplayManager & mgr = this->mediator->replay_manager; + return mgr.record_start(); +} + +recording_t Script::replay::record_end() { + ReplayManager & mgr = this->mediator->replay_manager; + return mgr.record_end(); +} + +void Script::replay::play(recording_t recording) { + ReplayManager & mgr = this->mediator->replay_manager; + return mgr.play(recording); +} + +void Script::replay::release(recording_t recording) { + ReplayManager & mgr = this->mediator->replay_manager; + return mgr.release(recording); +} + const keyboard_state_t & Script::get_keyboard_state() const { SDLContext & sdl_context = this->mediator->sdl_context; return sdl_context.get_keyboard_state(); diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index a87af4e..5f68928 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -6,8 +6,10 @@ #include "../manager/EventManager.h" #include "../manager/LoopTimerManager.h" #include "../manager/Mediator.h" +#include "../manager/ReplayManager.h" #include "../system/CollisionSystem.h" #include "../types.h" +#include "../util/Log.h" #include "../util/OptionalRef.h" namespace crepe { @@ -46,14 +48,23 @@ protected: */ virtual void init() {} /** - * \brief Script update function (empty by default) + * \brief Script fixed update function (empty by default) * * \param delta_time Time since last fixed update * - * This function is called during the ScriptSystem::update() routine if the \c BehaviorScript - * component holding this script instance is active. + * \note This function is called during the ScriptSystem::update() routine if the \c + * BehaviorScript component holding this script instance is active. */ - virtual void update(duration_t delta_time) {} + virtual void fixed_update(duration_t delta_time) {} + /** + * \brief Script frame update function (empty by default) + * + * \param delta_time Time since last frame update + * + * \note This function is called during the ScriptSystem::update() routine if the \c + * BehaviorScript component holding this script instance is active. + */ + virtual void frame_update(duration_t delta_time) {} //! \} //! ScriptSystem calls \c init() and \c update() @@ -61,93 +72,119 @@ protected: protected: /** - * \name Utility functions + * \name Component query functions + * \see ComponentManager * \{ */ - /** * \brief Get single component of type \c T on this game object - * * \tparam T Type of component - * * \returns Reference to component - * * \throws std::runtime_error if this game object does not have a component with type \c T */ template <typename T> T & get_component() const; - // TODO: make get_component calls for component types that can have more than 1 instance - // cause compile-time errors - /** * \brief Get all components of type \c T on this game object - * * \tparam T Type of component - * * \returns List of component references */ template <typename T> RefVector<T> get_components() const; - - /** - * \copydoc ComponentManager::get_components_by_id - * \see ComponentManager::get_components_by_id - */ + //! \copydoc ComponentManager::get_components_by_id template <typename T> RefVector<T> get_components_by_id(game_object_id_t id) const; - /** - * \copydoc ComponentManager::get_components_by_name - * \see ComponentManager::get_components_by_name - */ + //! \copydoc ComponentManager::get_components_by_name template <typename T> RefVector<T> get_components_by_name(const std::string & name) const; - /** - * \copydoc ComponentManager::get_components_by_tag - * \see ComponentManager::get_components_by_tag - */ + //! \copydoc ComponentManager::get_components_by_tag template <typename T> RefVector<T> get_components_by_tag(const std::string & tag) const; + //! \} /** - * \brief Log a message using Log::logf - * - * \tparam Args Log::logf parameters - * \param args Log::logf parameters + * \name Logging functions + * \see Log + * \{ */ - template <typename... Args> - void logf(Args &&... args); + //! \copydoc Log::logf + template <class... Args> + void logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args); + //! \copydoc Log::logf + template <class... Args> + void logf(std::format_string<Args...> fmt, Args &&... args); + // \} /** - * \brief Subscribe to an event with an explicit channel - * \see EventManager::subscribe + * \name Event manager functions + * \see EventManager + * \{ */ + //! \copydoc EventManager::subscribe template <typename EventType> void subscribe(const EventHandler<EventType> & callback, event_channel_t channel); - /** - * \brief Subscribe to an event on EventManager::CHANNEL_ALL - * \see EventManager::subscribe - */ + //! \copydoc EventManager::subscribe template <typename EventType> void subscribe(const EventHandler<EventType> & callback); + //! \copydoc EventManager::trigger_event + template <typename EventType> + void trigger_event(const EventType & event = {}, + event_channel_t channel = EventManager::CHANNEL_ALL); + //! \copydoc EventManager::queue_event + template <typename EventType> + void queue_event(const EventType & event = {}, + event_channel_t channel = EventManager::CHANNEL_ALL); + //! \} /** - * \brief Set the next scene using SceneManager - * \see SceneManager::set_next_scene + * \name Scene-related functions + * \see SceneManager + * \{ */ + //! \copydoc SceneManager::set_next_scene void set_next_scene(const std::string & name); + //! \} + /** + * \name Save data management functions + * \see SaveManager + * \{ + */ //! Retrieve SaveManager reference SaveManager & get_save_manager() const; + //! \} + /** + * \name Timing functions + * \see LoopTimerManager + * \{ + */ //! Retrieve LoopTimerManager reference LoopTimerManager & get_loop_timer() const; + //! \} + + //! Replay management functions + struct replay { // NOLINT + //! \copydoc ReplayManager::record_start + void record_start(); + //! \copydoc ReplayManager::record_end + recording_t record_end(); + //! \copydoc ReplayManager::play + void play(recording_t); + //! \copydoc ReplayManager::release + void release(recording_t); + + private: + OptionalRef<Mediator> & mediator; + replay(OptionalRef<Mediator> & mediator) : mediator(mediator) {} + friend class Script; + } replay{mediator}; /** * \brief Utility function to retrieve the keyboard state * \see SDLContext::get_keyboard_state * * \return current keyboard state map with Keycode as key and bool as value(true = pressed, false = not pressed) - * */ const keyboard_state_t & get_keyboard_state() const; /** @@ -155,7 +192,6 @@ protected: * \see SDLContext::get_keyboard_state * * \return Keycode state (true if pressed, false if not pressed). - * */ bool get_key_state(Keycode key) const noexcept; diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index 225a51c..4462a41 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -1,6 +1,7 @@ #pragma once #include "../manager/ComponentManager.h" +#include "../manager/ReplayManager.h" #include "BehaviorScript.h" #include "Script.h" @@ -23,9 +24,14 @@ RefVector<T> Script::get_components() const { return this->get_components_by_id<T>(this->game_object_id); } -template <typename... Args> -void Script::logf(Args &&... args) { - Log::logf(std::forward<Args>(args)...); +template <class... Args> +void Script::logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args) { + Log::logf(level, fmt, std::forward<Args>(args)...); +} + +template <class... Args> +void Script::logf(std::format_string<Args...> fmt, Args &&... args) { + Log::logf(fmt, std::forward<Args>(args)...); } template <typename EventType> @@ -34,8 +40,18 @@ void Script::subscribe_internal(const EventHandler<EventType> & callback, EventManager & mgr = this->mediator->event_manager; subscription_t listener = mgr.subscribe<EventType>( [this, callback](const EventType & data) -> bool { + // check if (parent) BehaviorScript component is active bool & active = this->active; if (!active) return false; + + // check if replay manager is playing (if initialized) + try { + ReplayManager & replay = this->mediator->replay_manager; + if (replay.get_state() == ReplayManager::PLAYING) return false; + } catch (const std::runtime_error &) { + } + + // call user-provided callback return callback(data); }, channel); @@ -52,6 +68,18 @@ void Script::subscribe(const EventHandler<EventType> & callback) { this->subscribe_internal(callback, EventManager::CHANNEL_ALL); } +template <typename EventType> +void Script::trigger_event(const EventType & event, event_channel_t channel) { + EventManager & mgr = this->mediator->event_manager; + mgr.trigger_event(event, channel); +} + +template <typename EventType> +void Script::queue_event(const EventType & event, event_channel_t channel) { + EventManager & mgr = this->mediator->event_manager; + mgr.queue_event(event, channel); +} + template <typename T> RefVector<T> Script::get_components_by_id(game_object_id_t id) const { Mediator & mediator = this->mediator; diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp index ba684ba..0107c7b 100644 --- a/src/crepe/api/Sprite.cpp +++ b/src/crepe/api/Sprite.cpp @@ -1,6 +1,6 @@ #include <cmath> -#include "../util/Log.h" +#include "../util/dbg.h" #include "api/Asset.h" #include "Component.h" diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h index a2409c2..a3fc319 100644 --- a/src/crepe/api/Sprite.h +++ b/src/crepe/api/Sprite.h @@ -66,6 +66,16 @@ public: //! independent sprite offset position vec2 position_offset; + + /** + * \brief gives the user the option to render this in world space or in camera space + * + * - if true will this be rendered in world space this means that the sprite can be + * rendered off the screen + * - if false --> will the sprite be rendered in camera space. this means that the + * coordinates given on the \c Sprite and \c Transform will be inside the camera + */ + bool world_space = true; }; public: diff --git a/src/crepe/api/Text.cpp b/src/crepe/api/Text.cpp index 54a4370..4a94180 100644 --- a/src/crepe/api/Text.cpp +++ b/src/crepe/api/Text.cpp @@ -1,5 +1,3 @@ -#include "../facade/FontFacade.h" - #include "Text.h" using namespace crepe; diff --git a/src/crepe/api/Text.h b/src/crepe/api/Text.h index c30dc80..da40141 100644 --- a/src/crepe/api/Text.h +++ b/src/crepe/api/Text.h @@ -3,8 +3,6 @@ #include <optional> #include <string> -#include "../Component.h" - #include "Asset.h" #include "Color.h" #include "UIObject.h" diff --git a/src/crepe/api/Transform.cpp b/src/crepe/api/Transform.cpp index a85b792..fcfce14 100644 --- a/src/crepe/api/Transform.cpp +++ b/src/crepe/api/Transform.cpp @@ -1,8 +1,9 @@ -#include "../util/Log.h" +#include "../util/dbg.h" #include "Transform.h" using namespace crepe; +using namespace std; Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, double scale) : Component(id), @@ -11,3 +12,11 @@ Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, d scale(scale) { dbg_trace(); } + +unique_ptr<Component> Transform::save() const { + return unique_ptr<Component>{new Transform(*this)}; +} + +void Transform::restore(const Component & snapshot) { + *this = static_cast<const Transform &>(snapshot); +} diff --git a/src/crepe/api/Transform.h b/src/crepe/api/Transform.h index 7ee6d65..a6f3486 100644 --- a/src/crepe/api/Transform.h +++ b/src/crepe/api/Transform.h @@ -35,6 +35,12 @@ protected: virtual int get_instances_max() const { return 1; } //! ComponentManager instantiates all components friend class ComponentManager; + +protected: + virtual std::unique_ptr<Component> save() const; + Transform(const Transform &) = default; + virtual void restore(const Component & snapshot); + virtual Transform & operator=(const Transform &) = default; }; } // namespace crepe diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h index bf9d124..52e1bb6 100644 --- a/src/crepe/api/Vector2.h +++ b/src/crepe/api/Vector2.h @@ -90,6 +90,9 @@ struct Vector2 { //! Returns the perpendicular vector to this vector. Vector2 perpendicular() const; + + //! Checks if both components of the vector are NaN. + bool is_nan() const; }; } // namespace crepe diff --git a/src/crepe/api/Vector2.hpp b/src/crepe/api/Vector2.hpp index ff53cb0..e195760 100644 --- a/src/crepe/api/Vector2.hpp +++ b/src/crepe/api/Vector2.hpp @@ -163,4 +163,9 @@ Vector2<T> Vector2<T>::perpendicular() const { return {-y, x}; } +template <class T> +bool Vector2<T>::is_nan() const { + return std::isnan(x) && std::isnan(y); +} + } // namespace crepe diff --git a/src/crepe/facade/DB.cpp b/src/crepe/facade/DB.cpp index ae2d4bc..7a3e473 100644 --- a/src/crepe/facade/DB.cpp +++ b/src/crepe/facade/DB.cpp @@ -1,6 +1,6 @@ #include <cstring> -#include "util/Log.h" +#include "util/dbg.h" #include "DB.h" diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index fffbe34..536ea43 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -11,7 +11,6 @@ #include <array> #include <cmath> #include <cstddef> -#include <cstdint> #include <functional> #include <memory> #include <stdexcept> @@ -20,8 +19,12 @@ #include "../api/Color.h" #include "../api/Config.h" #include "../api/Sprite.h" -#include "../util/Log.h" +#include "../util/dbg.h" +#include "api/Text.h" +#include "api/Transform.h" +#include "facade/Font.h" #include "manager/Mediator.h" +#include "util/AbsolutePosition.h" #include "SDLContext.h" #include "Texture.h" @@ -32,9 +35,6 @@ using namespace std; SDLContext::SDLContext(Mediator & mediator) { dbg_trace(); - if (TTF_Init() == -1) { - throw runtime_error(format("SDL_ttf initialization failed: {}", TTF_GetError())); - } if (SDL_Init(SDL_INIT_VIDEO) != 0) { throw runtime_error(format("SDLContext: SDL_Init error: {}", SDL_GetError())); } @@ -63,6 +63,10 @@ SDLContext::SDLContext(Mediator & mediator) { throw runtime_error("SDLContext: SDL_image could not initialize!"); } + if (TTF_Init() == -1) { + throw runtime_error(format("SDL_ttf initialization failed: {}", TTF_GetError())); + } + mediator.sdl_context = *this; } @@ -75,8 +79,8 @@ SDLContext::~SDLContext() { // TODO: how are we going to ensure that these are called from the same // thread that SDL_Init() was called on? This has caused problems for me // before. - IMG_Quit(); TTF_Quit(); + IMG_Quit(); SDL_Quit(); } @@ -137,6 +141,8 @@ SDL_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const { = (ctx.sprite.aspect_ratio == 0) ? ctx.texture.get_ratio() : ctx.sprite.aspect_ratio; vec2 size = data.size; + vec2 screen_pos = ctx.pos; + if (data.size.x == 0 && data.size.y != 0) { size.x = data.size.y * aspect_ratio; } @@ -145,10 +151,15 @@ SDL_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const { } size *= cam_aux_data.render_scale * ctx.img_scale * data.scale_offset; - vec2 screen_pos = (ctx.pos + data.position_offset - cam_aux_data.cam_pos - + (cam_aux_data.zoomed_viewport) / 2) - * cam_aux_data.render_scale - - size / 2 + cam_aux_data.bar_size; + if (ctx.sprite.data.world_space) { + screen_pos = (screen_pos - cam_aux_data.cam_pos + cam_aux_data.zoomed_viewport / 2) + * cam_aux_data.render_scale + - size / 2 + cam_aux_data.bar_size; + } else { + screen_pos + = (screen_pos + cam_aux_data.zoomed_viewport / 2) * cam_aux_data.render_scale + - size / 2 + cam_aux_data.bar_size; + } return SDL_FRect{ .x = screen_pos.x, @@ -188,6 +199,52 @@ void SDLContext::draw(const RenderContext & ctx) { angle, NULL, render_flip); } +void SDLContext::draw_text(const RenderText & data) { + + const Text & text = data.text; + const Font & font = data.font; + vec2 absoluut_pos = AbsolutePosition::get_position(data.transform, data.text.offset); + std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>> font_surface; + std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> font_texture; + + SDL_Color color{ + .r = text.data.text_color.r, + .g = text.data.text_color.g, + .b = text.data.text_color.b, + .a = text.data.text_color.a, + }; + SDL_Surface * tmp_font_surface + = TTF_RenderText_Solid(font.get_font(), text.text.c_str(), color); + if (tmp_font_surface == NULL) { + throw runtime_error(format("draw_text: font surface error: {}", SDL_GetError())); + } + font_surface = {tmp_font_surface, [](SDL_Surface * surface) { SDL_FreeSurface(surface); }}; + + SDL_Texture * tmp_font_texture + = SDL_CreateTextureFromSurface(this->game_renderer.get(), font_surface.get()); + if (tmp_font_texture == NULL) { + throw runtime_error(format("draw_text: font texture error: {}", SDL_GetError())); + } + font_texture + = {tmp_font_texture, [](SDL_Texture * texture) { SDL_DestroyTexture(texture); }}; + + vec2 size = text.dimensions * cam_aux_data.render_scale * data.transform.scale; + vec2 screen_pos + = (absoluut_pos - cam_aux_data.cam_pos + (cam_aux_data.zoomed_viewport) / 2) + * cam_aux_data.render_scale + - size / 2 + cam_aux_data.bar_size; + + SDL_FRect dstrect{ + .x = screen_pos.x, + .y = screen_pos.y, + .w = size.x, + .h = size.y, + }; + + SDL_RenderCopyExF(this->game_renderer.get(), font_texture.get(), NULL, &dstrect, + data.transform.rotation, NULL, SDL_FLIP_NONE); +} + void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) { const Camera::Data & cam_data = cam.data; diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index b687f87..e570073 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -12,18 +12,21 @@ #include <unordered_map> #include "../types.h" +#include "EventData.h" #include "api/Camera.h" #include "api/Color.h" #include "api/KeyCodes.h" #include "api/Sprite.h" #include "api/Transform.h" -#include "types.h" #include "EventData.h" #include "FontFacade.h" +#include "types.h" namespace crepe { class Texture; +class Text; +class Font; class Mediator; /** @@ -71,12 +74,11 @@ public: const double & scale; }; -public: - /** - * \brief Gets the singleton instance of SDLContext. - * \return Reference to the SDLContext instance. - */ - static SDLContext & get_instance(); + struct RenderText { + const Text & text; + const Font & font; + const Transform & transform; + }; public: SDLContext(const SDLContext &) = delete; @@ -186,6 +188,13 @@ public: */ void draw(const RenderContext & ctx); + /** + * \brief draws a text to the screen + * + * \param data Reference to the rendering data needed to draw + */ + void draw_text(const RenderText & data); + //! Clears the screen, preparing for a new frame. void clear_screen(); diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index 97e455e..b1e6463 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -1,5 +1,5 @@ #include "../api/Asset.h" -#include "../util/Log.h" +#include "../util/dbg.h" #include "Sound.h" diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp index b1f8cb3..5091e07 100644 --- a/src/crepe/facade/SoundContext.cpp +++ b/src/crepe/facade/SoundContext.cpp @@ -1,4 +1,4 @@ -#include "../util/Log.h" +#include "../util/dbg.h" #include "SoundContext.h" diff --git a/src/crepe/facade/Texture.cpp b/src/crepe/facade/Texture.cpp index b63403d..06caa54 100644 --- a/src/crepe/facade/Texture.cpp +++ b/src/crepe/facade/Texture.cpp @@ -1,10 +1,11 @@ -#include "../util/Log.h" -#include "facade/SDLContext.h" -#include "manager/Mediator.h" +#include "../Resource.h" +#include "../facade/SDLContext.h" +#include "../manager/Mediator.h" +#include "../types.h" +#include "../util/dbg.h" -#include "Resource.h" +#include "SDLContext.h" #include "Texture.h" -#include "types.h" using namespace crepe; using namespace std; @@ -23,6 +24,7 @@ Texture::~Texture() { } const ivec2 & Texture::get_size() const noexcept { return this->size; } + const float & Texture::get_ratio() const noexcept { return this->aspect_ratio; } SDL_Texture * Texture::get_img() const noexcept { return this->texture.get(); } diff --git a/src/crepe/manager/CMakeLists.txt b/src/crepe/manager/CMakeLists.txt index f73e165..48e444f 100644 --- a/src/crepe/manager/CMakeLists.txt +++ b/src/crepe/manager/CMakeLists.txt @@ -6,6 +6,8 @@ target_sources(crepe PUBLIC SceneManager.cpp LoopTimerManager.cpp ResourceManager.cpp + ReplayManager.cpp + SystemManager.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -21,5 +23,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES LoopTimerManager.h ResourceManager.h ResourceManager.hpp + ReplayManager.h + SystemManager.h + SystemManager.hpp ) diff --git a/src/crepe/manager/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp index df30d27..745ddae 100644 --- a/src/crepe/manager/ComponentManager.cpp +++ b/src/crepe/manager/ComponentManager.cpp @@ -1,7 +1,7 @@ #include "../api/GameObject.h" #include "../api/Metadata.h" #include "../types.h" -#include "../util/Log.h" +#include "../util/dbg.h" #include "ComponentManager.h" @@ -53,7 +53,7 @@ GameObject ComponentManager::new_object(const string & name, const string & tag, this->next_id++; } - GameObject object{*this, this->next_id, name, tag, position, rotation, scale}; + GameObject object{this->mediator, this->next_id, name, tag, position, rotation, scale}; this->next_id++; return object; @@ -72,3 +72,28 @@ set<game_object_id_t> ComponentManager::get_objects_by_tag(const string & tag) c return this->get_objects_by_predicate<Metadata>( [tag](const Metadata & data) { return data.tag == tag; }); } + +ComponentManager::Snapshot ComponentManager::save() { + Snapshot snapshot{}; + for (const auto & [type, by_id_index] : this->components) { + for (game_object_id_t id = 0; id < by_id_index.size(); id++) { + const auto & components = by_id_index[id]; + for (size_t index = 0; index < components.size(); index++) { + const Component & component = *components[index]; + snapshot.components.push_back(SnapshotComponent{ + .type = type, + .id = id, + .index = index, + .component = component.save(), + }); + } + } + } + return snapshot; +} + +void ComponentManager::restore(const Snapshot & snapshot) { + for (const SnapshotComponent & info : snapshot.components) { + this->components[info.type][info.id][info.index]->restore(*info.component); + } +} diff --git a/src/crepe/manager/ComponentManager.h b/src/crepe/manager/ComponentManager.h index 19a8e81..c3a5b4a 100644 --- a/src/crepe/manager/ComponentManager.h +++ b/src/crepe/manager/ComponentManager.h @@ -21,13 +21,6 @@ class GameObject; * This class manages all components. It provides methods to add, delete and get components. */ class ComponentManager : public Manager { - // TODO: This relation should be removed! I (loek) believe that the scene manager should - // create/destroy components because the GameObject's are stored in concrete Scene classes, - // which will in turn call GameObject's destructor, which will in turn call - // ComponentManager::delete_components_by_id or something. This is a pretty major change, so - // here is a comment and temporary fix instead :tada: - friend class SceneManager; - public: ComponentManager(Mediator & mediator); ~ComponentManager(); // dbg_trace @@ -49,12 +42,7 @@ public: const vec2 & position = {0, 0}, double rotation = 0, double scale = 1); -protected: - /** - * GameObject is used as an interface to add/remove components, and the game programmer is - * supposed to use it instead of interfacing with the component manager directly. - */ - friend class GameObject; +public: /** * \brief Add a component to the ComponentManager * @@ -154,6 +142,40 @@ public: template <typename T> RefVector<T> get_components_by_tag(const std::string & tag) const; + //! Snapshot of single component (including path in \c components) + struct SnapshotComponent { + //! \c components path + std::type_index type; + //! \c components path + game_object_id_t id; + //! \c components path + size_t index; + //! Actual component snapshot + std::unique_ptr<Component> component; + }; + //! Snapshot of the entire component manager state + struct Snapshot { + //! All components + std::vector<SnapshotComponent> components; + // TODO: some kind of hash code that ensures components exist in all the same places as + // this snapshot + }; + /** + * \name ReplayManager (Memento) functions + * \{ + */ + /** + * \brief Save a snapshot of the component manager state + * \returns Deep copy of the component manager's internal state + */ + Snapshot save(); + /** + * \brief Restore component manager from a snapshot + * \param snapshot Snapshot to restore from (as returned by \c save()) + */ + void restore(const Snapshot & snapshot); + //! \} + private: /** * \brief Get object IDs by predicate function diff --git a/src/crepe/manager/LoopTimerManager.cpp b/src/crepe/manager/LoopTimerManager.cpp index a6e4788..e78f92f 100644 --- a/src/crepe/manager/LoopTimerManager.cpp +++ b/src/crepe/manager/LoopTimerManager.cpp @@ -1,7 +1,7 @@ #include <chrono> #include <thread> -#include "../util/Log.h" +#include "../util/dbg.h" #include "LoopTimerManager.h" diff --git a/src/crepe/manager/LoopTimerManager.h b/src/crepe/manager/LoopTimerManager.h index 76b02d3..2f1e6b6 100644 --- a/src/crepe/manager/LoopTimerManager.h +++ b/src/crepe/manager/LoopTimerManager.h @@ -6,6 +6,8 @@ namespace crepe { +class Engine; + typedef std::chrono::duration<float> duration_t; typedef std::chrono::duration<unsigned long long, std::micro> elapsed_time_t; @@ -107,7 +109,7 @@ public: private: //! Friend relation to use start,enforce_frame_rate,get_lag,update,advance_fixed_update. - friend class LoopManager; + friend class Engine; /** * \brief Start the loop timer. * diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h index a336410..842f1de 100644 --- a/src/crepe/manager/Mediator.h +++ b/src/crepe/manager/Mediator.h @@ -11,6 +11,8 @@ class LoopTimerManager; class SaveManager; class ResourceManager; class SDLContext; +class ReplayManager; +class SystemManager; /** * Struct to pass references to classes that would otherwise need to be singletons down to @@ -32,6 +34,8 @@ struct Mediator { OptionalRef<LoopTimerManager> loop_timer; OptionalRef<SaveManager> save_manager; OptionalRef<ResourceManager> resource_manager; + OptionalRef<ReplayManager> replay_manager; + OptionalRef<SystemManager> system_manager; }; } // namespace crepe diff --git a/src/crepe/manager/ReplayManager.cpp b/src/crepe/manager/ReplayManager.cpp new file mode 100644 index 0000000..090a94e --- /dev/null +++ b/src/crepe/manager/ReplayManager.cpp @@ -0,0 +1,70 @@ +#include <format> + +#include "Manager.h" +#include "ReplayManager.h" + +using namespace crepe; +using namespace std; + +ReplayManager::ReplayManager(Mediator & mediator) : Manager(mediator) { + mediator.replay_manager = *this; +} + +void ReplayManager::record_start() { + if (this->state == RECORDING) this->release(this->id); + this->id++; + this->memory[this->id] = make_unique<Recording>(); + this->recording = *this->memory.at(this->id); + this->state = RECORDING; +} + +recording_t ReplayManager::record_end() { + this->state = IDLE; + return this->id; +} + +void ReplayManager::play(recording_t handle) { + if (!this->memory.contains(handle)) + throw out_of_range(format("ReplayManager: no recording for handle {}", handle)); + this->recording = *this->memory.at(handle); + this->recording->frame = 0; + this->state = PLAYING; +} + +void ReplayManager::release(recording_t handle) { + if (!this->memory.contains(handle)) return; + this->memory.erase(handle); +} + +void ReplayManager::frame_record() { + if (this->state != RECORDING) + throw runtime_error("ReplayManager: frame_step called while not playing"); + + ComponentManager & components = this->mediator.component_manager; + Recording & recording = this->recording; + + recording.frames.push_back(components.save()); + recording.frame++; +} + +bool ReplayManager::frame_step() { + if (this->state != PLAYING) + throw runtime_error("ReplayManager: frame_step called while not playing"); + + ComponentManager & components = this->mediator.component_manager; + Recording & recording = this->recording; + + ComponentManager::Snapshot & frame = recording.frames.at(recording.frame); + + components.restore(frame); + recording.frame++; + + if (recording.frame < recording.frames.size()) return false; + // end of recording + recording.frame = 0; + this->state = IDLE; + this->recording.clear(); + return true; +} + +ReplayManager::State ReplayManager::get_state() const { return this->state; } diff --git a/src/crepe/manager/ReplayManager.h b/src/crepe/manager/ReplayManager.h new file mode 100644 index 0000000..f06a58b --- /dev/null +++ b/src/crepe/manager/ReplayManager.h @@ -0,0 +1,96 @@ +#pragma once + +#include <unordered_map> + +#include "../util/OptionalRef.h" + +#include "ComponentManager.h" +#include "Manager.h" + +namespace crepe { + +//! Handle to recording held by ReplayManager +typedef size_t recording_t; + +/** + * \brief Replay manager + * + * The replay manager is responsible for creating, storing and restoring ComponentManager + * snapshots. Sequential snapshots can be recorded and replayed in combination with + * ReplaySystem. + */ +class ReplayManager : public Manager { + // TODO: Delete recordings at end of scene + +public: + ReplayManager(Mediator & mediator); + +public: + //! Start a new recording + void record_start(); + /** + * \brief End the latest recording started by \c record_start() + * \returns Handle to recording + */ + recording_t record_end(); + /** + * \brief Play a recording + * \param handle Handle to recording (as returned by \c record_end()) + */ + void play(recording_t handle); + /** + * \brief Delete a recording from memory + * \param handle Handle to recording (as returned by \c record_end()) + */ + void release(recording_t handle); + +public: + //! Internal state + enum State { + IDLE, //!< Not doing anything + RECORDING, //!< Currently recording + PLAYING, //!< Currently playing back a recording + }; + //! Get current internal state + State get_state() const; + +public: + /** + * \brief Record a single frame to the current recording + * + * This function is called by ReplaySystem after the game programmer has called \c + * record_start() + */ + void frame_record(); + /** + * \brief Play the next frame of the current recording + * + * \returns `true` if the recording is finished playing + * \returns `false` if there are more frames + * + * This function also automatically resets the internal state from PLAYING to IDLE at the end + * of a recording. + */ + bool frame_step(); + +private: + /** + * \brief Recording data + */ + struct Recording { + //! Current frame being shown + size_t frame = 0; + //! All frames in recording + std::vector<ComponentManager::Snapshot> frames; + }; + //! Internal state + State state = IDLE; + //! Current recording handle + recording_t id = -1; + //! Current recording data + OptionalRef<Recording> recording; + //! Recording storage + std::unordered_map<recording_t, std::unique_ptr<Recording>> memory; +}; + +} // namespace crepe diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp index a141a46..5713183 100644 --- a/src/crepe/manager/ResourceManager.cpp +++ b/src/crepe/manager/ResourceManager.cpp @@ -1,4 +1,4 @@ -#include "util/Log.h" +#include "util/dbg.h" #include "ResourceManager.h" diff --git a/src/crepe/manager/SystemManager.cpp b/src/crepe/manager/SystemManager.cpp new file mode 100644 index 0000000..5ada30f --- /dev/null +++ b/src/crepe/manager/SystemManager.cpp @@ -0,0 +1,66 @@ +#include "../system/AISystem.h" +#include "../system/AnimatorSystem.h" +#include "../system/AudioSystem.h" +#include "../system/CollisionSystem.h" +#include "../system/EventSystem.h" +#include "../system/InputSystem.h" +#include "../system/ParticleSystem.h" +#include "../system/PhysicsSystem.h" +#include "../system/RenderSystem.h" +#include "../system/ReplaySystem.h" +#include "../system/ScriptSystem.h" + +#include "SystemManager.h" + +using namespace crepe; +using namespace std; + +SystemManager::SystemManager(Mediator & mediator) : Manager(mediator) { + this->load_system<InputSystem>(); + this->load_system<EventSystem>(); + this->load_system<ScriptSystem>(); + this->load_system<AISystem>(); + this->load_system<PhysicsSystem>(); + this->load_system<CollisionSystem>(); + this->load_system<AudioSystem>(); + this->load_system<AnimatorSystem>(); + this->load_system<ParticleSystem>(); + this->load_system<RenderSystem>(); + this->load_system<ReplaySystem>(); + + this->mediator.system_manager = *this; +} + +void SystemManager::fixed_update() { + for (auto & [type, system] : this->systems) { + if (!system->active) continue; + system->fixed_update(); + } +} + +void SystemManager::frame_update() { + for (auto & [type, system] : this->systems) { + if (!system->active) continue; + system->frame_update(); + } +} + +SystemManager::Snapshot SystemManager::save() { + Snapshot snapshot; + for (auto & [type, system] : this->systems) { + snapshot[type] = system->active; + } + return snapshot; +} + +void SystemManager::restore(const Snapshot & snapshot) { + for (auto & [type, active] : snapshot) { + this->systems[type]->active = active; + } +} + +void SystemManager::disable_all() { + for (auto & [type, system] : this->systems) { + system->active = false; + } +} diff --git a/src/crepe/manager/SystemManager.h b/src/crepe/manager/SystemManager.h new file mode 100644 index 0000000..50acf77 --- /dev/null +++ b/src/crepe/manager/SystemManager.h @@ -0,0 +1,85 @@ +#pragma once + +#include <memory> +#include <typeindex> +#include <unordered_map> + +#include "../system/System.h" + +#include "Manager.h" + +namespace crepe { + +/** + * \brief Collection of all systems + * + * This manager aggregates all systems and provides utility functions to retrieve references to + * and update systems. + */ +class SystemManager : public Manager { +public: + SystemManager(Mediator &); + + /** + * \brief Per-frame update. + * + * Updates the game state based on the elapsed time since the last frame. + */ + void frame_update(); + + /** + * \brief Fixed update executed at a fixed rate. + * + * This function updates physics and game logic based on LoopTimer's fixed_delta_time. + */ + void fixed_update(); + +private: + /** + * \brief Collection of System instances + * + * This map holds System instances indexed by the system's class typeid. It is filled in the + * constructor of \c SystemManager using SystemManager::load_system. + */ + std::unordered_map<std::type_index, std::unique_ptr<System>> systems; + /** + * \brief Initialize a system + * \tparam T System type (must be derivative of \c System) + */ + template <class T> + void load_system(); + +public: + /** + * \brief Retrieve a reference to ECS system + * \tparam T System type + * \returns Reference to system instance + * \throws std::runtime_error if the System is not initialized + */ + template <class T> + T & get_system(); + +public: + /** + * \brief SystemManager snapshot + * + * The SystemManager snapshot only stores which systems are active + */ + typedef std::unordered_map<std::type_index, bool> Snapshot; + /** + * \brief Save a snapshot of the systems' state + * \returns Copy of each system's active property + */ + Snapshot save(); + /** + * \brief Restore system active state from a snapshot + * \param snapshot Snapshot to restore from (as returned by \c save()) + */ + void restore(const Snapshot & snapshot); + //! Disable all systems + void disable_all(); +}; + +} // namespace crepe + +#include "SystemManager.hpp" diff --git a/src/crepe/api/LoopManager.hpp b/src/crepe/manager/SystemManager.hpp index 266758a..3d26e4c 100644 --- a/src/crepe/api/LoopManager.hpp +++ b/src/crepe/manager/SystemManager.hpp @@ -4,26 +4,19 @@ #include <format> #include <memory> -#include "../system/System.h" - -#include "LoopManager.h" +#include "SystemManager.h" namespace crepe { template <class T> -void LoopManager::add_scene() { - this->scene_manager.add_scene<T>(); -} - -template <class T> -T & LoopManager::get_system() { +T & SystemManager::get_system() { using namespace std; static_assert(is_base_of<System, T>::value, "get_system must recieve a derivative class of System"); const type_info & type = typeid(T); if (!this->systems.contains(type)) - throw runtime_error(format("LoopManager: {} is not initialized", type.name())); + throw runtime_error(format("SystemManager: {} is not initialized", type.name())); System * system = this->systems.at(type).get(); T * concrete_system = dynamic_cast<T *>(system); @@ -33,14 +26,14 @@ T & LoopManager::get_system() { } template <class T> -void LoopManager::load_system() { +void SystemManager::load_system() { using namespace std; static_assert(is_base_of<System, T>::value, "load_system must recieve a derivative class of System"); const type_info & type = typeid(T); if (this->systems.contains(type)) - throw runtime_error(format("LoopManager: {} is already initialized", type.name())); + throw runtime_error(format("SystemManager: {} is already initialized", type.name())); System * system = new T(this->mediator); this->systems[type] = unique_ptr<System>(system); } diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp index 680dbb8..0f35010 100644 --- a/src/crepe/system/AISystem.cpp +++ b/src/crepe/system/AISystem.cpp @@ -10,7 +10,7 @@ using namespace crepe; using namespace std::chrono; -void AISystem::update() { +void AISystem::fixed_update() { const Mediator & mediator = this->mediator; ComponentManager & mgr = mediator.component_manager; LoopTimerManager & loop_timer = mediator.loop_timer; diff --git a/src/crepe/system/AISystem.h b/src/crepe/system/AISystem.h index d5f8a8e..04807cf 100644 --- a/src/crepe/system/AISystem.h +++ b/src/crepe/system/AISystem.h @@ -20,7 +20,7 @@ public: using System::System; //! Update the AI system - void update() override; + void fixed_update() override; private: /** diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index 107b25d..e5ab2fa 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -1,5 +1,3 @@ - - #include "../api/Animator.h" #include "../manager/ComponentManager.h" #include "../manager/LoopTimerManager.h" @@ -10,7 +8,7 @@ using namespace crepe; using namespace std::chrono; -void AnimatorSystem::update() { +void AnimatorSystem::frame_update() { ComponentManager & mgr = this->mediator.component_manager; LoopTimerManager & timer = this->mediator.loop_timer; RefVector<Animator> animations = mgr.get_components_by_type<Animator>(); diff --git a/src/crepe/system/AnimatorSystem.h b/src/crepe/system/AnimatorSystem.h index 7d3f565..092e131 100644 --- a/src/crepe/system/AnimatorSystem.h +++ b/src/crepe/system/AnimatorSystem.h @@ -22,7 +22,7 @@ public: * Animator components, moving the animations forward and managing their behavior (e.g., * looping). */ - void update() override; + void frame_update() override; }; } // namespace crepe diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp index b1aa0f8..d4e8b9f 100644 --- a/src/crepe/system/AudioSystem.cpp +++ b/src/crepe/system/AudioSystem.cpp @@ -7,7 +7,7 @@ using namespace crepe; using namespace std; -void AudioSystem::update() { +void AudioSystem::fixed_update() { ComponentManager & component_manager = this->mediator.component_manager; ResourceManager & resource_manager = this->mediator.resource_manager; RefVector<AudioSource> components diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h index 2ddc443..56fc98c 100644 --- a/src/crepe/system/AudioSystem.h +++ b/src/crepe/system/AudioSystem.h @@ -11,7 +11,7 @@ namespace crepe { class AudioSystem : public System { public: using System::System; - void update() override; + void fixed_update() override; private: /** diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index 0e2db76..52369d0 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -8,6 +8,8 @@ target_sources(crepe PUBLIC AudioSystem.cpp AnimatorSystem.cpp InputSystem.cpp + EventSystem.cpp + ReplaySystem.cpp AISystem.cpp ) @@ -20,5 +22,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES AudioSystem.h AnimatorSystem.h InputSystem.h + EventSystem.h + ReplaySystem.h AISystem.h ) diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp index af8adce..654d4c6 100644 --- a/src/crepe/system/CollisionSystem.cpp +++ b/src/crepe/system/CollisionSystem.cpp @@ -1,6 +1,7 @@ #include <algorithm> #include <cmath> #include <cstddef> +#include <emmintrin.h> #include <functional> #include <optional> #include <utility> @@ -15,15 +16,25 @@ #include "api/Rigidbody.h" #include "api/Transform.h" #include "api/Vector2.h" +#include "util/AbsolutePosition.h" +#include "util/OptionalRef.h" -#include "Collider.h" #include "CollisionSystem.h" #include "types.h" -#include "util/OptionalRef.h" using namespace crepe; +using enum Rigidbody::BodyType; + +CollisionSystem::CollisionInfo CollisionSystem::CollisionInfo::operator-() const { + return { + .self = this->other, + .other = this->self, + .resolution = -this->resolution, + .resolution_direction = this->resolution_direction, + }; +} -void CollisionSystem::update() { +void CollisionSystem::fixed_update() { std::vector<CollisionInternal> all_colliders; game_object_id_t id = 0; ComponentManager & mgr = this->mediator.component_manager; @@ -33,6 +44,7 @@ void CollisionSystem::update() { if (!rigidbody.active) continue; id = rigidbody.game_object_id; Transform & transform = mgr.get_components_by_id<Transform>(id).front().get(); + Metadata & metadata = mgr.get_components_by_id<Metadata>(id).front().get(); // Check if the boxcollider is active and has the same id as the rigidbody. RefVector<BoxCollider> boxcolliders = mgr.get_components_by_type<BoxCollider>(); for (BoxCollider & boxcollider : boxcolliders) { @@ -40,8 +52,7 @@ void CollisionSystem::update() { if (!boxcollider.active) continue; all_colliders.push_back({.id = id, .collider = collider_variant{boxcollider}, - .transform = transform, - .rigidbody = rigidbody}); + .info = {transform, rigidbody, metadata}}); } // Check if the circlecollider is active and has the same id as the rigidbody. RefVector<CircleCollider> circlecolliders @@ -51,310 +62,450 @@ void CollisionSystem::update() { if (!circlecollider.active) continue; all_colliders.push_back({.id = id, .collider = collider_variant{circlecollider}, - .transform = transform, - .rigidbody = rigidbody}); + .info = {transform, rigidbody, metadata}}); } } - // Check between all colliders if there is a collision + // Check between all colliders if there is a collision (collision handling) std::vector<std::pair<CollisionInternal, CollisionInternal>> collided = this->gather_collisions(all_colliders); - // For both objects call the collision handler + // For the object convert the info and call the collision handler if needed for (auto & collision_pair : collided) { - this->collision_handler_request(collision_pair.first, collision_pair.second); - this->collision_handler_request(collision_pair.second, collision_pair.first); + // Convert internal struct to external struct + CollisionInfo info + = this->get_collision_info(collision_pair.first, collision_pair.second); + // Determine if and/or what collison handler is needed. + this->determine_collision_handler(info); } } -void CollisionSystem::collision_handler_request(CollisionInternal & this_data, - CollisionInternal & other_data) { +// Below is for collision detection +std::vector<std::pair<CollisionSystem::CollisionInternal, CollisionSystem::CollisionInternal>> +CollisionSystem::gather_collisions(std::vector<CollisionInternal> & colliders) { - CollisionInternalType type - = this->get_collider_type(this_data.collider, other_data.collider); - std::pair<vec2, CollisionSystem::Direction> resolution_data - = this->collision_handler(this_data, other_data, type); - ComponentManager & mgr = this->mediator.component_manager; - OptionalRef<Metadata> this_metadata - = mgr.get_components_by_id<Metadata>(this_data.id).front().get(); - OptionalRef<Metadata> other_metadata - = mgr.get_components_by_id<Metadata>(other_data.id).front().get(); - OptionalRef<Collider> this_collider; - OptionalRef<Collider> other_collider; - switch (type) { - case CollisionInternalType::BOX_BOX: { - this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider); - other_collider - = std::get<std::reference_wrapper<BoxCollider>>(other_data.collider); - break; - } - case CollisionInternalType::BOX_CIRCLE: { - this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider); - other_collider - = std::get<std::reference_wrapper<CircleCollider>>(other_data.collider); - break; - } - case CollisionInternalType::CIRCLE_BOX: { - this_collider - = std::get<std::reference_wrapper<CircleCollider>>(this_data.collider); - other_collider - = std::get<std::reference_wrapper<BoxCollider>>(other_data.collider); - break; - } - case CollisionInternalType::CIRCLE_CIRCLE: { - this_collider - = std::get<std::reference_wrapper<CircleCollider>>(this_data.collider); - other_collider - = std::get<std::reference_wrapper<CircleCollider>>(other_data.collider); - break; + // TODO: + // If no colliders skip + // Check if colliders has rigidbody if not skip + + // TODO: + // If amount is higer than lets say 16 for now use quadtree otwerwise skip + // Quadtree code + // Quadtree is placed over the input vector + + // Return data of collided colliders which are variants + std::vector<std::pair<CollisionInternal, CollisionInternal>> collisions_ret; + //using visit to visit the variant to access the active and id. + for (size_t i = 0; i < colliders.size(); ++i) { + for (size_t j = i + 1; j < colliders.size(); ++j) { + if (colliders[i].id == colliders[j].id) continue; + if (!should_collide(colliders[i], colliders[j])) continue; + CollisionInternalType type + = get_collider_type(colliders[i].collider, colliders[j].collider); + if (!detect_collision(colliders[i], colliders[j], type)) continue; + //fet + collisions_ret.emplace_back(colliders[i], colliders[j]); } } + return collisions_ret; +} - // collision info - crepe::CollisionSystem::CollisionInfo collision_info{ - .this_collider = this_collider, - .this_transform = this_data.transform, - .this_rigidbody = this_data.rigidbody, - .this_metadata = this_metadata, - .other_collider = other_collider, - .other_transform = other_data.transform, - .other_rigidbody = other_data.rigidbody, - .other_metadata = other_metadata, - .resolution = resolution_data.first, - .resolution_direction = resolution_data.second, - }; +bool CollisionSystem::should_collide(const CollisionInternal & self, + const CollisionInternal & other) const { + + const Rigidbody::Data & self_rigidbody = self.info.rigidbody.data; + const Rigidbody::Data & other_rigidbody = other.info.rigidbody.data; + const Metadata & self_metadata = self.info.metadata; + const Metadata & other_metadata = other.info.metadata; + + // Check collision layers + if (self_rigidbody.collision_layers.contains(other_rigidbody.collision_layer)) return true; + if (other_rigidbody.collision_layers.contains(self_rigidbody.collision_layer)) return true; - // Determine if static needs to be called - this->determine_collision_handler(collision_info); + // Check names + if (self_rigidbody.collision_names.contains(other_metadata.name)) return true; + if (other_rigidbody.collision_names.contains(self_metadata.name)) return true; + + // Check tags + if (self_rigidbody.collision_tags.contains(other_metadata.tag)) return true; + if (other_rigidbody.collision_tags.contains(self_metadata.tag)) return true; + + return false; +} + +CollisionSystem::CollisionInternalType +CollisionSystem::get_collider_type(const collider_variant & collider1, + const collider_variant & collider2) const { + if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider1)) { + if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { + return CollisionInternalType::CIRCLE_CIRCLE; + } else { + return CollisionInternalType::CIRCLE_BOX; + } + } else { + if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { + return CollisionInternalType::BOX_CIRCLE; + } else { + return CollisionInternalType::BOX_BOX; + } + } } -std::pair<vec2, CollisionSystem::Direction> -CollisionSystem::collision_handler(CollisionInternal & data1, CollisionInternal & data2, - CollisionInternalType type) { +bool CollisionSystem::detect_collision(CollisionInternal & self, CollisionInternal & other, + const CollisionInternalType & type) { vec2 resolution; switch (type) { case CollisionInternalType::BOX_BOX: { - const BoxCollider & collider1 - = std::get<std::reference_wrapper<BoxCollider>>(data1.collider); - const BoxCollider & collider2 - = std::get<std::reference_wrapper<BoxCollider>>(data2.collider); - vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, - data1.rigidbody); - vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, - data2.rigidbody); - resolution = this->get_box_box_resolution(collider1, collider2, collider_pos1, - collider_pos2); + // Box-Box collision detection + const BoxColliderInternal BOX1 + = {.collider = std::get<std::reference_wrapper<BoxCollider>>(self.collider), + .transform = self.info.transform, + .rigidbody = self.info.rigidbody}; + const BoxColliderInternal BOX2 + = {.collider = std::get<std::reference_wrapper<BoxCollider>>(other.collider), + .transform = other.info.transform, + .rigidbody = other.info.rigidbody}; + // Get resolution vector from box-box collision detection + resolution = this->get_box_box_detection(BOX1, BOX2); + // If no collision (NaN values), return false + if (resolution.is_nan()) return false; break; } case CollisionInternalType::BOX_CIRCLE: { - const BoxCollider & collider1 - = std::get<std::reference_wrapper<BoxCollider>>(data1.collider); - const CircleCollider & collider2 - = std::get<std::reference_wrapper<CircleCollider>>(data2.collider); - vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, - data1.rigidbody); - vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, - data2.rigidbody); - resolution = -this->get_circle_box_resolution(collider2, collider1, collider_pos2, - collider_pos1); + // Box-Circle collision detection + const BoxColliderInternal BOX1 + = {.collider = std::get<std::reference_wrapper<BoxCollider>>(self.collider), + .transform = self.info.transform, + .rigidbody = self.info.rigidbody}; + const CircleColliderInternal CIRCLE2 = { + .collider = std::get<std::reference_wrapper<CircleCollider>>(other.collider), + .transform = other.info.transform, + .rigidbody = other.info.rigidbody}; + // Get resolution vector from box-circle collision detection + resolution = this->get_box_circle_detection(BOX1, CIRCLE2); + // If no collision (NaN values), return false + if (resolution.is_nan()) return false; + // Invert the resolution vector for proper collision response + resolution = -resolution; break; } case CollisionInternalType::CIRCLE_CIRCLE: { - const CircleCollider & collider1 - = std::get<std::reference_wrapper<CircleCollider>>(data1.collider); - const CircleCollider & collider2 - = std::get<std::reference_wrapper<CircleCollider>>(data2.collider); - vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, - data1.rigidbody); - vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, - data2.rigidbody); - resolution = this->get_circle_circle_resolution(collider1, collider2, - collider_pos1, collider_pos2); + // Circle-Circle collision detection + const CircleColliderInternal CIRCLE1 + = {.collider = std::get<std::reference_wrapper<CircleCollider>>(self.collider), + .transform = self.info.transform, + .rigidbody = self.info.rigidbody}; + const CircleColliderInternal CIRCLE2 = { + .collider = std::get<std::reference_wrapper<CircleCollider>>(other.collider), + .transform = other.info.transform, + .rigidbody = other.info.rigidbody}; + // Get resolution vector from circle-circle collision detection + resolution = this->get_circle_circle_detection(CIRCLE1, CIRCLE2); + // If no collision (NaN values), return false + if (resolution.is_nan()) return false; break; } case CollisionInternalType::CIRCLE_BOX: { - const CircleCollider & collider1 - = std::get<std::reference_wrapper<CircleCollider>>(data1.collider); - const BoxCollider & collider2 - = std::get<std::reference_wrapper<BoxCollider>>(data2.collider); - vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, - data1.rigidbody); - vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, - data2.rigidbody); - resolution = this->get_circle_box_resolution(collider1, collider2, collider_pos1, - collider_pos2); + // Circle-Box collision detection + const CircleColliderInternal CIRCLE1 + = {.collider = std::get<std::reference_wrapper<CircleCollider>>(self.collider), + .transform = self.info.transform, + .rigidbody = self.info.rigidbody}; + const BoxColliderInternal BOX2 + = {.collider = std::get<std::reference_wrapper<BoxCollider>>(other.collider), + .transform = other.info.transform, + .rigidbody = other.info.rigidbody}; + // Get resolution vector from box-circle collision detection (order swapped) + resolution = this->get_box_circle_detection(BOX2, CIRCLE1); + // If no collision (NaN values), return false + if (resolution.is_nan()) return false; break; } + case CollisionInternalType::NONE: + // No collision detection needed if the type is NONE + return false; + break; } + // Store the calculated resolution vector for the 'self' collider + self.resolution = resolution; + // Calculate the resolution direction based on the rigidbody data + self.resolution_direction + = this->resolution_correction(self.resolution, self.info.rigidbody.data); + // For the 'other' collider, the resolution is the opposite direction of 'self' + other.resolution = -self.resolution; + other.resolution_direction = self.resolution_direction; + + // Return true if a collision was detected and resolution was calculated + return true; +} - Direction resolution_direction = Direction::NONE; - if (resolution.x != 0 && resolution.y != 0) { - resolution_direction = Direction::BOTH; - } else if (resolution.x != 0) { - resolution_direction = Direction::X_DIRECTION; - //checks if the other velocity has a value and if this object moved - if (data1.rigidbody.data.linear_velocity.x != 0 - && data1.rigidbody.data.linear_velocity.y != 0) - resolution.y = -data1.rigidbody.data.linear_velocity.y - * (resolution.x / data1.rigidbody.data.linear_velocity.x); - } else if (resolution.y != 0) { - resolution_direction = Direction::Y_DIRECTION; - //checks if the other velocity has a value and if this object moved - if (data1.rigidbody.data.linear_velocity.x != 0 - && data1.rigidbody.data.linear_velocity.y != 0) - resolution.x = -data1.rigidbody.data.linear_velocity.x - * (resolution.y / data1.rigidbody.data.linear_velocity.y); - } +vec2 CollisionSystem::get_box_box_detection(const BoxColliderInternal & box1, + const BoxColliderInternal & box2) const { + vec2 resolution{NAN, NAN}; + // Get current positions of colliders + vec2 pos1 = AbsolutePosition::get_position(box1.transform, box1.collider.offset); + vec2 pos2 = AbsolutePosition::get_position(box2.transform, box2.collider.offset); - return std::make_pair(resolution, resolution_direction); -} + // Scale dimensions + vec2 scaled_box1 = box1.collider.dimensions * box1.transform.scale; + vec2 scaled_box2 = box2.collider.dimensions * box2.transform.scale; + vec2 delta = pos2 - pos1; -vec2 CollisionSystem::get_box_box_resolution(const BoxCollider & box_collider1, - const BoxCollider & box_collider2, - const vec2 & final_position1, - const vec2 & final_position2) const { - vec2 resolution; // Default resolution vector - vec2 delta = final_position2 - final_position1; - - // Compute half-dimensions of the boxes - float half_width1 = box_collider1.dimensions.x / 2.0; - float half_height1 = box_collider1.dimensions.y / 2.0; - float half_width2 = box_collider2.dimensions.x / 2.0; - float half_height2 = box_collider2.dimensions.y / 2.0; - - // Calculate overlaps along X and Y axes - float overlap_x = (half_width1 + half_width2) - std::abs(delta.x); - float overlap_y = (half_height1 + half_height2) - std::abs(delta.y); - - // Check if there is a collision should always be true - if (overlap_x > 0 && overlap_y > 0) { - // Determine the direction of resolution - if (overlap_x < overlap_y) { - // Resolve along the X-axis (smallest overlap) - resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; - } else if (overlap_y < overlap_x) { - // Resolve along the Y-axis (smallest overlap) - resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; - } else { - // Equal overlap, resolve both directions with preference - resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; - resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; + // Calculate half-extents (half width and half height) + float half_width1 = scaled_box1.x / 2.0; + float half_height1 = scaled_box1.y / 2.0; + float half_width2 = scaled_box2.x / 2.0; + float half_height2 = scaled_box2.y / 2.0; + + if (pos1.x + half_width1 > pos2.x - half_width2 + && pos1.x - half_width1 < pos2.x + half_width2 + && pos1.y + half_height1 > pos2.y - half_height2 + && pos1.y - half_height1 < pos2.y + half_height2) { + resolution = {0, 0}; + float overlap_x = (half_width1 + half_width2) - std::abs(delta.x); + float overlap_y = (half_height1 + half_height2) - std::abs(delta.y); + if (overlap_x > 0 && overlap_y > 0) { + // Determine the direction of resolution + if (overlap_x < overlap_y) { + // Resolve along the X-axis (smallest overlap) + resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; + } else if (overlap_y < overlap_x) { + // Resolve along the Y-axis (smallest overlap) + resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; + } else { + // Equal overlap, resolve both directions with preference + resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; + resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; + } } } - return resolution; } -vec2 CollisionSystem::get_circle_circle_resolution(const CircleCollider & circle_collider1, - const CircleCollider & circle_collider2, - const vec2 & final_position1, - const vec2 & final_position2) const { - vec2 delta = final_position2 - final_position1; +vec2 CollisionSystem::get_box_circle_detection(const BoxColliderInternal & box, + const CircleColliderInternal & circle) const { + /// Get current positions of colliders + vec2 box_pos = AbsolutePosition::get_position(box.transform, box.collider.offset); + vec2 circle_pos = AbsolutePosition::get_position(circle.transform, circle.collider.offset); - // Compute the distance between the two circle centers - float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y); + // Scale dimensions + vec2 scaled_box = box.collider.dimensions * box.transform.scale; + float scaled_circle_radius = circle.collider.radius * circle.transform.scale; - // Compute the combined radii of the two circles - float combined_radius = circle_collider1.radius + circle_collider2.radius; + // Calculate box half-extents + float half_width = scaled_box.x / 2.0f; + float half_height = scaled_box.y / 2.0f; - // Compute the penetration depth - float penetration_depth = combined_radius - distance; + // Find the closest point on the box to the circle's center + float closest_x + = std::max(box_pos.x - half_width, std::min(circle_pos.x, box_pos.x + half_width)); + float closest_y + = std::max(box_pos.y - half_height, std::min(circle_pos.y, box_pos.y + half_height)); - // Normalize the delta vector to get the collision direction - vec2 collision_normal = delta / distance; + float distance_x = circle_pos.x - closest_x; + float distance_y = circle_pos.y - closest_y; + float distance_squared = distance_x * distance_x + distance_y * distance_y; + if (distance_squared < scaled_circle_radius * scaled_circle_radius) { + vec2 delta = circle_pos - box_pos; - // Compute the resolution vector - vec2 resolution = -collision_normal * penetration_depth; + // Clamp circle center to the nearest point on the box + vec2 closest_point; + closest_point.x = std::clamp(delta.x, -half_width, half_width); + closest_point.y = std::clamp(delta.y, -half_height, half_height); - return resolution; + // Find the vector from the circle center to the closest point + vec2 closest_delta = delta - closest_point; + + float distance + = std::sqrt(closest_delta.x * closest_delta.x + closest_delta.y * closest_delta.y); + vec2 collision_normal = closest_delta / distance; + + // Compute penetration depth + float penetration_depth = scaled_circle_radius - distance; + + // Compute the resolution vector + return vec2{collision_normal * penetration_depth}; + } + // No collision + return vec2{NAN, NAN}; } -vec2 CollisionSystem::get_circle_box_resolution(const CircleCollider & circle_collider, - const BoxCollider & box_collider, - const vec2 & circle_position, - const vec2 & box_position) const { - vec2 delta = circle_position - box_position; +vec2 CollisionSystem::get_circle_circle_detection( + const CircleColliderInternal & circle1, const CircleColliderInternal & circle2) const { + // Get current positions of colliders + vec2 final_position1 + = AbsolutePosition::get_position(circle1.transform, circle1.collider.offset); + vec2 final_position2 + = AbsolutePosition::get_position(circle2.transform, circle2.collider.offset); - // Compute half-dimensions of the box - float half_width = box_collider.dimensions.x / 2.0f; - float half_height = box_collider.dimensions.y / 2.0f; + // Scale dimensions + float scaled_circle1 = circle1.collider.radius * circle1.transform.scale; + float scaled_circle2 = circle2.collider.radius * circle2.transform.scale; - // Clamp circle center to the nearest point on the box - vec2 closest_point; - closest_point.x = std::clamp(delta.x, -half_width, half_width); - closest_point.y = std::clamp(delta.y, -half_height, half_height); + float distance_x = final_position1.x - final_position2.x; + float distance_y = final_position1.y - final_position2.y; + float distance_squared = distance_x * distance_x + distance_y * distance_y; - // Find the vector from the circle center to the closest point - vec2 closest_delta = delta - closest_point; + // Calculate the sum of the radii + float radius_sum = scaled_circle1 + scaled_circle2; - // Normalize the delta to get the collision direction - float distance - = std::sqrt(closest_delta.x * closest_delta.x + closest_delta.y * closest_delta.y); - vec2 collision_normal = closest_delta / distance; + // Check for collision (distance squared must be less than the square of the radius sum) + if (distance_squared < radius_sum * radius_sum) { + vec2 delta = final_position2 - final_position1; - // Compute penetration depth - float penetration_depth = circle_collider.radius - distance; + // Compute the distance between the two circle centers + float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y); - // Compute the resolution vector - vec2 resolution = collision_normal * penetration_depth; + // Compute the combined radii of the two circles + float combined_radius = scaled_circle1 + scaled_circle2; - return resolution; + // Compute the penetration depth + float penetration_depth = combined_radius - distance; + + // Normalize the delta vector to get the collision direction + vec2 collision_normal = delta / distance; + + // Compute the resolution vector + vec2 resolution = -collision_normal * penetration_depth; + + return resolution; + } + // No collision + return vec2{NAN, NAN}; + ; } -void CollisionSystem::determine_collision_handler(CollisionInfo & info) { - // Check rigidbody type for static - if (info.this_rigidbody.data.body_type == Rigidbody::BodyType::STATIC) return; - // If second body is static perform the static collision handler in this system - if (info.other_rigidbody.data.body_type == Rigidbody::BodyType::STATIC) { - this->static_collision_handler(info); +CollisionSystem::Direction +CollisionSystem::resolution_correction(vec2 & resolution, const Rigidbody::Data & rigidbody) { + + // Calculate the other value to move back correctly + // If only X or Y has a value determine what is should be to move back. + Direction resolution_direction = Direction::NONE; + // If both are not zero a perfect corner has been hit + if (resolution.x != 0 && resolution.y != 0) { + resolution_direction = Direction::BOTH; + // If x is not zero a horizontal action was latest action. + } else if (resolution.x != 0) { + resolution_direction = Direction::X_DIRECTION; + // If both are 0 resolution y should not be changed (y_velocity can be 0 by kinematic object movement) + if (rigidbody.linear_velocity.x != 0 && rigidbody.linear_velocity.y != 0) + resolution.y + = -rigidbody.linear_velocity.y * (resolution.x / rigidbody.linear_velocity.x); + } else if (resolution.y != 0) { + resolution_direction = Direction::Y_DIRECTION; + // If both are 0 resolution x should not be changed (x_velocity can be 0 by kinematic object movement) + if (rigidbody.linear_velocity.x != 0 && rigidbody.linear_velocity.y != 0) + resolution.x + = -rigidbody.linear_velocity.x * (resolution.y / rigidbody.linear_velocity.y); + } + + return resolution_direction; +} + +CollisionSystem::CollisionInfo +CollisionSystem::get_collision_info(const CollisionInternal & in_self, + const CollisionInternal & in_other) const { + + crepe::CollisionSystem::ColliderInfo self{ + .transform = in_self.info.transform, + .rigidbody = in_self.info.rigidbody, + .metadata = in_self.info.metadata, }; - // Call collision event for user - CollisionEvent data(info); - EventManager & emgr = this->mediator.event_manager; - emgr.trigger_event<CollisionEvent>(data, info.this_collider.game_object_id); + + crepe::CollisionSystem::ColliderInfo other{ + .transform = in_other.info.transform, + .rigidbody = in_other.info.rigidbody, + .metadata = in_other.info.metadata, + }; + + struct CollisionInfo collision_info { + .self = self, .other = other, .resolution = in_self.resolution, + .resolution_direction = in_self.resolution_direction, + }; + return collision_info; } -void CollisionSystem::static_collision_handler(CollisionInfo & info) { +void CollisionSystem::determine_collision_handler(const CollisionInfo & info) { + Rigidbody::BodyType self_type = info.self.rigidbody.data.body_type; + Rigidbody::BodyType other_type = info.other.rigidbody.data.body_type; + bool self_kinematic = info.self.rigidbody.data.kinematic_collision; + bool other_kinematic = info.other.rigidbody.data.kinematic_collision; + // Inverted collision info + CollisionInfo inverted = -info; + // If both objects are static skip handle call collision script + if (self_type == STATIC && other_type == STATIC) return; + + // First body is not dynamic + if (self_type != DYNAMIC) { + bool static_collision = self_type == STATIC && other_type == DYNAMIC; + bool kinematic_collision + = self_type == KINEMATIC && other_type == DYNAMIC && self_kinematic; + + // Handle collision + if (static_collision || kinematic_collision) this->static_collision_handler(inverted); + // Call scripts + this->call_collision_events(inverted); + return; + } + + // Second body is not dynamic + if (other_type != DYNAMIC) { + bool static_collision = other_type == STATIC; + bool kinematic_collision = other_type == KINEMATIC && other_kinematic; + // Handle collision + if (static_collision || kinematic_collision) this->static_collision_handler(info); + // Call scripts + this->call_collision_events(info); + return; + } + + // Dynamic + // Handle collision + this->dynamic_collision_handler(info); + // Call scripts + this->call_collision_events(info); +} + +void CollisionSystem::static_collision_handler(const CollisionInfo & info) { + + vec2 & transform_pos = info.self.transform.position; + float elasticity = info.self.rigidbody.data.elasticity_coefficient; + vec2 & rigidbody_vel = info.self.rigidbody.data.linear_velocity; + // Move object back using calculate move back value - info.this_transform.position += info.resolution; + transform_pos += info.resolution; switch (info.resolution_direction) { case Direction::BOTH: //bounce - if (info.this_rigidbody.data.elastisity_coefficient > 0) { - info.this_rigidbody.data.linear_velocity - = -info.this_rigidbody.data.linear_velocity - * info.this_rigidbody.data.elastisity_coefficient; + if (elasticity > 0) { + rigidbody_vel = -rigidbody_vel * elasticity; } //stop movement else { - info.this_rigidbody.data.linear_velocity = {0, 0}; + rigidbody_vel = {0, 0}; } break; case Direction::Y_DIRECTION: // Bounce - if (info.this_rigidbody.data.elastisity_coefficient > 0) { - info.this_rigidbody.data.linear_velocity.y - = -info.this_rigidbody.data.linear_velocity.y - * info.this_rigidbody.data.elastisity_coefficient; + if (elasticity > 0) { + rigidbody_vel.y = -rigidbody_vel.y * elasticity; } // Stop movement else { - info.this_rigidbody.data.linear_velocity.y = 0; - info.this_transform.position.x -= info.resolution.x; + rigidbody_vel.y = 0; + transform_pos.x -= info.resolution.x; } break; case Direction::X_DIRECTION: // Bounce - if (info.this_rigidbody.data.elastisity_coefficient > 0) { - info.this_rigidbody.data.linear_velocity.x - = -info.this_rigidbody.data.linear_velocity.x - * info.this_rigidbody.data.elastisity_coefficient; + if (elasticity > 0) { + rigidbody_vel.x = -rigidbody_vel.x * elasticity; } // Stop movement else { - info.this_rigidbody.data.linear_velocity.x = 0; - info.this_transform.position.y -= info.resolution.y; + rigidbody_vel.x = 0; + transform_pos.y -= info.resolution.y; } break; case Direction::NONE: @@ -363,213 +514,80 @@ void CollisionSystem::static_collision_handler(CollisionInfo & info) { } } -std::vector<std::pair<CollisionSystem::CollisionInternal, CollisionSystem::CollisionInternal>> -CollisionSystem::gather_collisions(std::vector<CollisionInternal> & colliders) { - - // TODO: - // If no colliders skip - // Check if colliders has rigidbody if not skip - - // TODO: - // If amount is higer than lets say 16 for now use quadtree otwerwise skip - // Quadtree code - // Quadtree is placed over the input vector +void CollisionSystem::dynamic_collision_handler(const CollisionInfo & info) { - // Return data of collided colliders which are variants - std::vector<std::pair<CollisionInternal, CollisionInternal>> collisions_ret; - //using visit to visit the variant to access the active and id. - for (size_t i = 0; i < colliders.size(); ++i) { - for (size_t j = i + 1; j < colliders.size(); ++j) { - if (colliders[i].id == colliders[j].id) continue; - if (!have_common_layer(colliders[i].rigidbody.data.collision_layers, - colliders[j].rigidbody.data.collision_layers)) - continue; - CollisionInternalType type - = get_collider_type(colliders[i].collider, colliders[j].collider); - if (!get_collision( - { - .collider = colliders[i].collider, - .transform = colliders[i].transform, - .rigidbody = colliders[i].rigidbody, - }, - { - .collider = colliders[j].collider, - .transform = colliders[j].transform, - .rigidbody = colliders[j].rigidbody, - }, - type)) - continue; - collisions_ret.emplace_back(colliders[i], colliders[j]); - } - } + vec2 & self_transform_pos = info.self.transform.position; + vec2 & other_transform_pos = info.other.transform.position; + float self_elasticity = info.self.rigidbody.data.elasticity_coefficient; + float other_elasticity = info.other.rigidbody.data.elasticity_coefficient; + vec2 & self_rigidbody_vel = info.self.rigidbody.data.linear_velocity; + vec2 & other_rigidbody_vel = info.other.rigidbody.data.linear_velocity; - return collisions_ret; -} + self_transform_pos += info.resolution / 2; + other_transform_pos += -(info.resolution / 2); -bool CollisionSystem::have_common_layer(const std::set<int> & layers1, - const std::set<int> & layers2) { + switch (info.resolution_direction) { + case Direction::BOTH: + if (self_elasticity > 0) { + self_rigidbody_vel = -self_rigidbody_vel * self_elasticity; + } else { + self_rigidbody_vel = {0, 0}; + } - // Check if any number is equal in the layers - for (int num : layers1) { - if (layers2.contains(num)) { - // Common layer found - return true; + if (other_elasticity > 0) { + other_rigidbody_vel = -other_rigidbody_vel * other_elasticity; + } else { + other_rigidbody_vel = {0, 0}; + } break; - } - } - // No common layer found - return false; -} + case Direction::Y_DIRECTION: + if (self_elasticity > 0) { + self_rigidbody_vel.y = -self_rigidbody_vel.y * self_elasticity; + } + // Stop movement + else { + self_rigidbody_vel.y = 0; + self_transform_pos.x -= info.resolution.x; + } -CollisionSystem::CollisionInternalType -CollisionSystem::get_collider_type(const collider_variant & collider1, - const collider_variant & collider2) const { - if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider1)) { - if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { - return CollisionInternalType::CIRCLE_CIRCLE; - } else { - return CollisionInternalType::CIRCLE_BOX; - } - } else { - if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { - return CollisionInternalType::BOX_CIRCLE; - } else { - return CollisionInternalType::BOX_BOX; - } - } -} + if (other_elasticity > 0) { + other_rigidbody_vel.y = -other_rigidbody_vel.y * other_elasticity; + } + // Stop movement + else { + other_rigidbody_vel.y = 0; + other_transform_pos.x -= info.resolution.x; + } + break; + case Direction::X_DIRECTION: + if (self_elasticity > 0) { + self_rigidbody_vel.x = -self_rigidbody_vel.x * self_elasticity; + } + // Stop movement + else { + self_rigidbody_vel.x = 0; + self_transform_pos.y -= info.resolution.y; + } -bool CollisionSystem::get_collision(const CollisionInternal & first_info, - const CollisionInternal & second_info, - CollisionInternalType type) const { - switch (type) { - case CollisionInternalType::BOX_BOX: { - const BoxCollider & box_collider1 - = std::get<std::reference_wrapper<BoxCollider>>(first_info.collider); - const BoxCollider & box_collider2 - = std::get<std::reference_wrapper<BoxCollider>>(second_info.collider); - return this->get_box_box_collision(box_collider1, box_collider2, - first_info.transform, second_info.transform, - second_info.rigidbody, second_info.rigidbody); - } - case CollisionInternalType::BOX_CIRCLE: { - const BoxCollider & box_collider - = std::get<std::reference_wrapper<BoxCollider>>(first_info.collider); - const CircleCollider & circle_collider - = std::get<std::reference_wrapper<CircleCollider>>(second_info.collider); - return this->get_box_circle_collision( - box_collider, circle_collider, first_info.transform, second_info.transform, - second_info.rigidbody, second_info.rigidbody); - } - case CollisionInternalType::CIRCLE_CIRCLE: { - const CircleCollider & circle_collider1 - = std::get<std::reference_wrapper<CircleCollider>>(first_info.collider); - const CircleCollider & circle_collider2 - = std::get<std::reference_wrapper<CircleCollider>>(second_info.collider); - return this->get_circle_circle_collision( - circle_collider1, circle_collider2, first_info.transform, - second_info.transform, second_info.rigidbody, second_info.rigidbody); - } - case CollisionInternalType::CIRCLE_BOX: { - const CircleCollider & circle_collider - = std::get<std::reference_wrapper<CircleCollider>>(first_info.collider); - const BoxCollider & box_collider - = std::get<std::reference_wrapper<BoxCollider>>(second_info.collider); - return this->get_box_circle_collision( - box_collider, circle_collider, first_info.transform, second_info.transform, - second_info.rigidbody, second_info.rigidbody); - } + if (other_elasticity > 0) { + other_rigidbody_vel.x = -other_rigidbody_vel.x * other_elasticity; + } + // Stop movement + else { + other_rigidbody_vel.x = 0; + other_transform_pos.y -= info.resolution.y; + } + break; + case Direction::NONE: + // Not possible + break; } - return false; } -bool CollisionSystem::get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, - const Transform & transform1, - const Transform & transform2, - const Rigidbody & rigidbody1, - const Rigidbody & rigidbody2) const { - // Get current positions of colliders - vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1); - vec2 final_position2 = this->get_current_position(box2.offset, transform2, rigidbody2); - - // Calculate half-extents (half width and half height) - float half_width1 = box1.dimensions.x / 2.0; - float half_height1 = box1.dimensions.y / 2.0; - float half_width2 = box2.dimensions.x / 2.0; - float half_height2 = box2.dimensions.y / 2.0; - - // Check if the boxes overlap along the X and Y axes - return (final_position1.x + half_width1 > final_position2.x - half_width2 - && final_position1.x - half_width1 < final_position2.x + half_width2 - && final_position1.y + half_height1 > final_position2.y - half_height2 - && final_position1.y - half_height1 < final_position2.y + half_height2); -} - -bool CollisionSystem::get_box_circle_collision(const BoxCollider & box1, - const CircleCollider & circle2, - const Transform & transform1, - const Transform & transform2, - const Rigidbody & rigidbody1, - const Rigidbody & rigidbody2) const { - // Get current positions of colliders - vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1); - vec2 final_position2 = this->get_current_position(circle2.offset, transform2, rigidbody2); - - // Calculate box half-extents - float half_width = box1.dimensions.x / 2.0; - float half_height = box1.dimensions.y / 2.0; - - // Find the closest point on the box to the circle's center - float closest_x = std::max(final_position1.x - half_width, - std::min(final_position2.x, final_position1.x + half_width)); - float closest_y = std::max(final_position1.y - half_height, - std::min(final_position2.y, final_position1.y + half_height)); - - // Calculate the distance squared between the circle's center and the closest point on the box - float distance_x = final_position2.x - closest_x; - float distance_y = final_position2.y - closest_y; - float distance_squared = distance_x * distance_x + distance_y * distance_y; - - // Compare distance squared with the square of the circle's radius - return distance_squared < circle2.radius * circle2.radius; -} - -bool CollisionSystem::get_circle_circle_collision(const CircleCollider & circle1, - const CircleCollider & circle2, - const Transform & transform1, - const Transform & transform2, - const Rigidbody & rigidbody1, - const Rigidbody & rigidbody2) const { - // Get current positions of colliders - vec2 final_position1 = this->get_current_position(circle1.offset, transform1, rigidbody1); - vec2 final_position2 = this->get_current_position(circle2.offset, transform2, rigidbody2); - - float distance_x = final_position1.x - final_position2.x; - float distance_y = final_position1.y - final_position2.y; - float distance_squared = distance_x * distance_x + distance_y * distance_y; - - // Calculate the sum of the radii - float radius_sum = circle1.radius + circle2.radius; - - // Check if the distance between the centers is less than or equal to the sum of the radii - return distance_squared < radius_sum * radius_sum; -} - -vec2 CollisionSystem::get_current_position(const vec2 & collider_offset, - const Transform & transform, - const Rigidbody & rigidbody) const { - // Get the rotation in radians - float radians1 = transform.rotation * (M_PI / 180.0); - - // Calculate total offset with scale - vec2 total_offset = (rigidbody.data.offset + collider_offset) * transform.scale; - - // Rotate - float rotated_total_offset_x1 - = total_offset.x * cos(radians1) - total_offset.y * sin(radians1); - float rotated_total_offset_y1 - = total_offset.x * sin(radians1) + total_offset.y * cos(radians1); - - // Final positions considering scaling and rotation - return (transform.position + vec2(rotated_total_offset_x1, rotated_total_offset_y1)); +void CollisionSystem::call_collision_events(const CollisionInfo & info) { + CollisionEvent data(info); + CollisionEvent data_inverted(-info); + EventManager & emgr = this->mediator.event_manager; + emgr.trigger_event<CollisionEvent>(data, info.self.transform.game_object_id); + emgr.trigger_event<CollisionEvent>(data_inverted, info.other.transform.game_object_id); } diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h index 5b136c6..3fb9723 100644 --- a/src/crepe/system/CollisionSystem.h +++ b/src/crepe/system/CollisionSystem.h @@ -23,6 +23,42 @@ public: using System::System; private: + //! Enum representing movement directions during collision resolution. + enum class Direction { + //! No movement required. + NONE, + //! Movement in the X direction. + X_DIRECTION, + //! Movement in the Y direction. + Y_DIRECTION, + //! Movement in both X and Y directions. + BOTH, + }; + +public: + //! Structure representing components of the collider + struct ColliderInfo { + Transform & transform; + Rigidbody & rigidbody; + Metadata & metadata; + }; + + /** + * \brief Structure representing detailed collision information between two colliders. + * + * Includes information about the colliding objects and the resolution data for handling the collision. + */ + struct CollisionInfo { + ColliderInfo self; + ColliderInfo other; + //! The resolution vector for the collision. + vec2 resolution; + //! The direction of movement for resolving the collision. + Direction resolution_direction = Direction::NONE; + CollisionInfo operator-() const; + }; + +private: //! A variant type that can hold either a BoxCollider or a CircleCollider. using collider_variant = std::variant<std::reference_wrapper<BoxCollider>, std::reference_wrapper<CircleCollider>>; @@ -33,12 +69,13 @@ private: CIRCLE_CIRCLE, BOX_CIRCLE, CIRCLE_BOX, + NONE, }; /** * \brief A structure to store the collision data of a single collider. * - * This structure all components and id that are for needed within this system when calculating or handeling collisions. + * This structure all components and id that are for needed within this system when calculating or handling collisions. * The transform and rigidbody are mostly needed for location and rotation. * In rigidbody additional info is written about what the body of the object is, * and how it should respond on a collision. @@ -46,46 +83,26 @@ private: struct CollisionInternal { game_object_id_t id = 0; collider_variant collider; - Transform & transform; - Rigidbody & rigidbody; - }; - - //! Enum representing movement directions during collision resolution. - enum class Direction { - //! No movement required. - NONE, - //! Movement in the X direction. - X_DIRECTION, - //! Movement in the Y direction. - Y_DIRECTION, - //! Movement in both X and Y directions. - BOTH - }; - -public: - /** - * \brief Structure representing detailed collision information between two colliders. - * - * Includes information about the colliding objects and the resolution data for handling the collision. - */ - struct CollisionInfo { - Collider & this_collider; - Transform & this_transform; - Rigidbody & this_rigidbody; - Metadata & this_metadata; - Collider & other_collider; - Transform & other_transform; - Rigidbody & other_rigidbody; - Metadata & other_metadata; - //! The resolution vector for the collision. + ColliderInfo info; vec2 resolution; - //! The direction of movement for resolving the collision. Direction resolution_direction = Direction::NONE; }; + //! Structure of a collider with additional components + template <typename ColliderType> + struct ColliderInternal { + ColliderType & collider; + Transform & transform; + Rigidbody & rigidbody; + }; + //! Predefined BoxColliderInternal. (System is only made for this type) + using BoxColliderInternal = ColliderInternal<BoxCollider>; + //! Predefined CircleColliderInternal. (System is only made for this type) + using CircleColliderInternal = ColliderInternal<CircleCollider>; + public: //! Updates the collision system by checking for collisions between colliders and handling them. - void update() override; + void fixed_update() override; private: /** @@ -100,114 +117,90 @@ private: CollisionInternalType get_collider_type(const collider_variant & collider1, const collider_variant & collider2) const; - /** - * \brief Calculates the current position of a collider. - * - * Combines the Collider offset, Transform position, and Rigidbody offset to compute the position of the collider. - * - * \param collider_offset The offset of the collider. - * \param transform The Transform of the associated game object. - * \param rigidbody The Rigidbody of the associated game object. - * \return The calculated position of the collider. - */ - vec2 get_current_position(const vec2 & collider_offset, const Transform & transform, - const Rigidbody & rigidbody) const; - private: /** - * \brief Handles collision resolution between two colliders. + * \brief Converts internal collision data into user-accessible collision information. * - * Processes collision data and adjusts objects to resolve collisions and/or calls the user oncollision script function. + * This function processes collision data from two colliding entities and packages it + * into a structured format that is accessible for further use, + * such as resolving collisions and triggering user-defined collision scripts. * * \param data1 Collision data for the first collider. * \param data2 Collision data for the second collider. */ - void collision_handler_request(CollisionInternal & data1, CollisionInternal & data2); + CollisionInfo get_collision_info(const CollisionInternal & data1, + const CollisionInternal & data2) const; /** - * \brief Resolves collision between two colliders and calculates the movement required. + * \brief Corrects the collision resolution vector and determines its direction. * - * Determines the displacement and direction needed to separate colliders based on their types. + * This function adjusts the provided resolution vector based on the + * rigidbody's linear velocity to ensure consistent collision correction. If the resolution + * vector has only one non-zero component (either x or y), the missing component is computed + * based on the rigidbody's velocity. If both components are non-zero, it indicates a corner + * collision. The function also identifies the direction of the resolution and returns it. * - * \param data1 Collision data for the first collider. - * \param data2 Collision data for the second collider. - * \param type The type of collider pair. - * \return A pair containing the resolution vector and direction for the first collider. + * \param resolution resolution vector that needs to be corrected + * \param rigidbody rigidbody data used to correct resolution + * \return A Direction indicating the resolution direction */ - std::pair<vec2, Direction> collision_handler(CollisionInternal & data1, - CollisionInternal & data2, - CollisionInternalType type); + Direction resolution_correction(vec2 & resolution, const Rigidbody::Data & rigidbody); /** - * \brief Calculates the resolution vector for two BoxColliders. + * \brief Determines the appropriate collision handler for a given collision event. * - * Computes the displacement required to separate two overlapping BoxColliders. + * This function identifies the correct collision resolution process based on the body types + * of the colliders involved in the collision. It delegates + * collision handling to specific handlers and calls collision event scripts + * as needed. * - * \param box_collider1 The first BoxCollider. - * \param box_collider2 The second BoxCollider. - * \param position1 The position of the first BoxCollider. - * \param position2 The position of the second BoxCollider. - * \return The resolution vector for the collision. + * \param info Collision information containing data about both colliders. */ - vec2 get_box_box_resolution(const BoxCollider & box_collider1, - const BoxCollider & box_collider2, const vec2 & position1, - const vec2 & position2) const; + void determine_collision_handler(const CollisionInfo & info); /** - * \brief Calculates the resolution vector for two CircleCollider. + * \brief Calls both collision script * - * Computes the displacement required to separate two overlapping CircleCollider. + * Calls both collision script to let user add additonal handling or handle full collision. * - * \param circle_collider1 The first CircleCollider. - * \param circle_collider2 The second CircleCollider. - * \param final_position1 The position of the first CircleCollider. - * \param final_position2 The position of the second CircleCollider. - * \return The resolution vector for the collision. + * \param info Collision information containing data about both colliders. */ - vec2 get_circle_circle_resolution(const CircleCollider & circle_collider1, - const CircleCollider & circle_collider2, - const vec2 & final_position1, - const vec2 & final_position2) const; + void call_collision_events(const CollisionInfo & info); /** - * \brief Calculates the resolution vector for two CircleCollider. - * - * Computes the displacement required to separate two overlapping CircleCollider. + * \brief Handles collisions involving static objects. * - * \param circle_collider The first CircleCollider. - * \param box_collider The second CircleCollider. - * \param circle_position The position of the CircleCollider. - * \param box_position The position of the BoxCollider. - * \return The resolution vector for the collision. - */ - vec2 get_circle_box_resolution(const CircleCollider & circle_collider, - const BoxCollider & box_collider, - const vec2 & circle_position, - const vec2 & box_position) const; - - /** - * \brief Determines the appropriate collision handler for a collision. + * This function resolves collisions between static and dynamic objects by adjusting + * the position of the static object and modifying the velocity of the dynamic object + * if elasticity is enabled. The position of the static object is corrected + * based on the collision resolution, and the dynamic object's velocity is adjusted + * accordingly to reflect the collision response. * - * Decides the correct resolution process based on the dynamic or static nature of the colliders involved. + * The handling includes stopping movement, applying bouncing based on the elasticity + * coefficient, and adjusting the position of the dynamic object if needed. * * \param info Collision information containing data about both colliders. */ - void determine_collision_handler(CollisionInfo & info); + void static_collision_handler(const CollisionInfo & info); /** - * \brief Handles collisions involving static objects. + * \brief Handles collisions involving dynamic objects. * - * Resolves collisions by adjusting positions and modifying velocities if bounce is enabled. + * Resolves collisions between two dynamic objects by adjusting their positions and modifying + * their velocities based on the collision resolution. If elasticity is enabled, + * the velocity of both objects is reversed and scaled by the respective elasticity coefficient. + * The positions of the objects are adjusted based on the collision resolution. * * \param info Collision information containing data about both colliders. */ - void static_collision_handler(CollisionInfo & info); + void dynamic_collision_handler(const CollisionInfo & info); private: /** * \brief Checks for collisions between colliders. * - * Identifies collisions and generates pairs of colliding objects for further processing. + * This function checks all active colliders and identifies pairs of colliding objects. + * For each identified collision, the appropriate collision data is returned as pairs for further processing. * * \param colliders A collection of all active colliders. * \return A list of collision pairs with their associated data. @@ -216,86 +209,77 @@ private: gather_collisions(std::vector<CollisionInternal> & colliders); /** - * \brief Checks if two collision layers have at least one common layer. + * \brief Checks if the settings allow collision * - * This function checks if there is any overlapping layer between the two inputs. - * It compares each layer from the first input to see - * if it exists in the second input. If at least one common layer is found, - * the function returns true, indicating that the two colliders share a common - * collision layer. + * This function checks if there is any collison layer where each object is located in. + * After checking the layers it checks the names and at last the tags. + * if in all three sets nothing is found collision can not happen. * - * \param layers1 all collision layers for the first collider. - * \param layers2 all collision layers for the second collider. - * \return Returns true if there is at least one common layer, false otherwise. + * \param this_rigidbody Rigidbody of first object + * \param other_rigidbody Rigidbody of second collider + * \param this_metadata Rigidbody of first object + * \param other_metadata Rigidbody of second object + * \return Returns true if there is at least one comparison found. */ - - bool have_common_layer(const std::set<int> & layers1, const std::set<int> & layers2); + bool should_collide(const CollisionInternal & self, + const CollisionInternal & other) const; //done /** * \brief Checks for collision between two colliders. * - * Calls the appropriate collision detection function based on the collider types. + * This function determines whether two colliders are colliding based on their types. + * It calls the appropriate collision detection function based on the collider pair type and stores the collision resolution data. + * If a collision is detected, it returns true, otherwise false. * * \param first_info Collision data for the first collider. * \param second_info Collision data for the second collider. * \param type The type of collider pair. * \return True if a collision is detected, otherwise false. */ - bool get_collision(const CollisionInternal & first_info, - const CollisionInternal & second_info, - CollisionInternalType type) const; + bool detect_collision(CollisionInternal & first_info, CollisionInternal & second_info, + const CollisionInternalType & type); /** * \brief Detects collisions between two BoxColliders. * - * \param box1 The first BoxCollider. - * \param box2 The second BoxCollider. - * \param transform1 Transform of the first object. - * \param transform2 Transform of the second object. - * \param rigidbody1 Rigidbody of the first object. - * \param rigidbody2 Rigidbody of the second object. - * \return True if a collision is detected, otherwise false. + * This function checks whether two `BoxCollider` are colliding based on their positions and scaled dimensions. + * If a collision is detected, it calculates the overlap along the X and Y axes and returns the resolution vector. + * If no collision is detected, it returns a vector with NaN values. + + * \param box1 Information about the first BoxCollider. + * \param box2 Information about the second BoxCollider. + * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}. */ - bool get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, - const Transform & transform1, const Transform & transform2, - const Rigidbody & rigidbody1, - const Rigidbody & rigidbody2) const; + vec2 get_box_box_detection(const BoxColliderInternal & box1, + const BoxColliderInternal & box2) const; /** * \brief Check collision for box on circle collider * - * \param box1 The BoxCollider - * \param circle2 The CircleCollider - * \param transform1 Transform of the first object. - * \param transform2 Transform of the second object. - * \param rigidbody1 Rigidbody of the first object. - * \param rigidbody2 Rigidbody of the second object. - * \return True if a collision is detected, otherwise false. + * This function detects if a collision occurs between a rectangular box and a circular collider. + * If a collision is detected, the function calculates the resolution vector to resolve the collision. + * If no collision is detected, it returns a vector with NaN values + * + * \param box1 Information about the BoxCollider. + * \param circle2 Information about the circleCollider. + * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}. */ - bool get_box_circle_collision(const BoxCollider & box1, const CircleCollider & circle2, - const Transform & transform1, const Transform & transform2, - const Rigidbody & rigidbody1, - const Rigidbody & rigidbody2) const; + vec2 get_box_circle_detection(const BoxColliderInternal & box1, + const CircleColliderInternal & circle2) const; /** * \brief Check collision for circle on circle collider * - * \param circle1 First CircleCollider - * \param circle2 Second CircleCollider - * \param transform1 Transform of the first object. - * \param transform2 Transform of the second object. - * \param rigidbody1 Rigidbody of the first object. - * \param rigidbody2 Rigidbody of the second object. - * \return True if a collision is detected, otherwise false. + * This function detects if a collision occurs between two circular colliders. + * If a collision is detected, it calculates the resolution vector to resolve the collision. + * If no collision is detected, it returns a vector with NaN values. * - * \return status of collision + * \param circle1 Information about the first circleCollider. + * \param circle2 Information about the second circleCollider. + * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}. */ - bool get_circle_circle_collision(const CircleCollider & circle1, - const CircleCollider & circle2, - const Transform & transform1, - const Transform & transform2, - const Rigidbody & rigidbody1, - const Rigidbody & rigidbody2) const; + vec2 get_circle_circle_detection(const CircleColliderInternal & circle1, + const CircleColliderInternal & circle2) const; }; /** diff --git a/src/crepe/system/EventSystem.cpp b/src/crepe/system/EventSystem.cpp new file mode 100644 index 0000000..7e168ab --- /dev/null +++ b/src/crepe/system/EventSystem.cpp @@ -0,0 +1,9 @@ +#include "EventSystem.h" +#include "../manager/EventManager.h" + +using namespace crepe; + +void EventSystem::fixed_update() { + EventManager & ev = this->mediator.event_manager; + ev.dispatch_events(); +} diff --git a/src/crepe/system/EventSystem.h b/src/crepe/system/EventSystem.h new file mode 100644 index 0000000..0ae48d2 --- /dev/null +++ b/src/crepe/system/EventSystem.h @@ -0,0 +1,21 @@ +#pragma once + +#include "System.h" + +namespace crepe { + +/** + * \brief EventManager dispatch helper system + */ +class EventSystem : public System { +public: + using System::System; + + /** + * \brief Dispatch queued events + * \see EventManager::dispatch_events + */ + void fixed_update() override; +}; + +} // namespace crepe diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp index cdcd8c7..858a645 100644 --- a/src/crepe/system/InputSystem.cpp +++ b/src/crepe/system/InputSystem.cpp @@ -8,7 +8,7 @@ using namespace crepe; -void InputSystem::update() { +void InputSystem::fixed_update() { ComponentManager & mgr = this->mediator.component_manager; SDLContext & context = this->mediator.sdl_context; std::vector<EventData> event_list = context.get_events(); diff --git a/src/crepe/system/InputSystem.h b/src/crepe/system/InputSystem.h index fbcf3f6..2cb80e5 100644 --- a/src/crepe/system/InputSystem.h +++ b/src/crepe/system/InputSystem.h @@ -61,7 +61,7 @@ public: * \brief Updates the system, processing all input events. * This method processes all events and triggers corresponding actions. */ - void update() override; + void fixed_update() override; private: //! Stores the last position of the mouse when the button was pressed. diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index 35a1d41..5e575e4 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -7,12 +7,13 @@ #include "../api/Transform.h" #include "../manager/ComponentManager.h" #include "../manager/LoopTimerManager.h" +#include "util/AbsolutePosition.h" #include "ParticleSystem.h" using namespace crepe; -void ParticleSystem::update() { +void ParticleSystem::fixed_update() { // Get all emitters const Mediator & mediator = this->mediator; LoopTimerManager & loop_timer = mediator.loop_timer; @@ -48,9 +49,10 @@ void ParticleSystem::update() { void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform & transform) { constexpr float DEG_TO_RAD = M_PI / 180.0; - vec2 initial_position = emitter.data.offset + transform.position; + vec2 initial_position = AbsolutePosition::get_position(transform, emitter.data.offset); float random_angle - = this->generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); + = this->generate_random_angle(emitter.data.min_angle + transform.rotation, + emitter.data.max_angle + transform.rotation); float random_speed = this->generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index 154521d..4296ff3 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -20,7 +20,7 @@ public: * \brief Updates all particle emitters by emitting particles, updating particle states, and * checking bounds. */ - void update() override; + void fixed_update() override; private: /** diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index 3b3b8ab..62f8132 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -12,8 +12,7 @@ using namespace crepe; -void PhysicsSystem::update() { - +void PhysicsSystem::fixed_update() { const Mediator & mediator = this->mediator; ComponentManager & mgr = mediator.component_manager; LoopTimerManager & loop_timer = mediator.loop_timer; diff --git a/src/crepe/system/PhysicsSystem.h b/src/crepe/system/PhysicsSystem.h index 26152a5..5ed624f 100644 --- a/src/crepe/system/PhysicsSystem.h +++ b/src/crepe/system/PhysicsSystem.h @@ -18,7 +18,7 @@ public: * * It calculates new velocties and changes the postion in the transform. */ - void update() override; + void fixed_update() override; }; } // namespace crepe diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index 62d42ec..698301e 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -16,6 +16,9 @@ #include "../facade/Texture.h" #include "../manager/ComponentManager.h" #include "../manager/ResourceManager.h" +#include "api/Text.h" +#include "facade/Font.h" +#include "util/AbsolutePosition.h" #include "RenderSystem.h" #include "types.h" @@ -67,14 +70,37 @@ RefVector<Sprite> RenderSystem::sort(RefVector<Sprite> & objs) const { return sorted_objs; } -void RenderSystem::update() { +void RenderSystem::frame_update() { this->clear_screen(); this->render(); + this->render_text(); this->present_screen(); } -bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) { +void RenderSystem::render_text() { + SDLContext & ctx = this->mediator.sdl_context; + ComponentManager & mgr = this->mediator.component_manager; + ResourceManager & resource_manager = this->mediator.resource_manager; + + RefVector<Text> texts = mgr.get_components_by_type<Text>(); + + for (Text & text : texts) { + if (!text.active) continue; + if (!text.font.has_value()) + text.font.emplace(ctx.get_font_from_name(text.font_family)); + + const Font & font = resource_manager.get<Font>(text.font.value()); + const auto & transform + = mgr.get_components_by_id<Transform>(text.game_object_id).front().get(); + ctx.draw_text(SDLContext::RenderText{ + .text = text, + .font = font, + .transform = transform, + }); + } +} +bool RenderSystem::render_particle(const Sprite & sprite, const Transform & transform) { ComponentManager & mgr = this->mediator.component_manager; SDLContext & ctx = this->mediator.sdl_context; ResourceManager & resource_manager = this->mediator.resource_manager; @@ -92,29 +118,30 @@ bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) for (const Particle & p : em.particles) { if (!p.active) continue; + if (p.time_in_life < em.data.begin_lifespan) continue; ctx.draw(SDLContext::RenderContext{ .sprite = sprite, .texture = res, .pos = p.position, - .angle = p.angle, - .scale = scale, + .angle = p.angle + transform.rotation, + .scale = transform.scale, }); } } return rendering_particles; } -void RenderSystem::render_normal(const Sprite & sprite, const Transform & tm) { +void RenderSystem::render_normal(const Sprite & sprite, const Transform & transform) { SDLContext & ctx = this->mediator.sdl_context; ResourceManager & resource_manager = this->mediator.resource_manager; const Texture & res = resource_manager.get<Texture>(sprite.source); - + vec2 pos = AbsolutePosition::get_position(transform, sprite.data.position_offset); ctx.draw(SDLContext::RenderContext{ .sprite = sprite, .texture = res, - .pos = tm.position, - .angle = tm.rotation, - .scale = tm.scale, + .pos = pos, + .angle = transform.rotation, + .scale = transform.scale, }); } @@ -126,35 +153,16 @@ void RenderSystem::render() { ResourceManager & resource_manager = this->mediator.resource_manager; RefVector<Sprite> sorted_sprites = this->sort(sprites); RefVector<Text> text_components = mgr.get_components_by_type<Text>(); - for (Text & text : text_components) { - const Transform & transform - = mgr.get_components_by_id<Transform>(text.game_object_id).front().get(); - this->render_text(text, transform); - } + for (const Sprite & sprite : sorted_sprites) { if (!sprite.active) continue; const Transform & transform = mgr.get_components_by_id<Transform>(sprite.game_object_id).front().get(); - bool rendered_particles = this->render_particle(sprite, transform.scale); + bool rendered_particles = this->render_particle(sprite, transform); if (rendered_particles) continue; this->render_normal(sprite, transform); } } -void RenderSystem::render_text(Text & text, const Transform & tm) { - SDLContext & ctx = this->mediator.sdl_context; - - if (!text.font.has_value()) { - text.font.emplace(ctx.get_font_from_name(text.font_family)); - } - - ResourceManager & resource_manager = this->mediator.resource_manager; - - if (!text.font.has_value()) { - return; - } - const Asset & font_asset = text.font.value(); - const Font & res = resource_manager.get<Font>(font_asset); -} diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h index 56a0553..627a743 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -24,7 +24,7 @@ public: * \brief Updates the RenderSystem for the current frame. * This method is called to perform all rendering operations for the current game frame. */ - void update() override; + void frame_update() override; private: //! Clears the screen in preparation for rendering. @@ -36,34 +36,28 @@ private: //! Updates the active camera used for rendering. void update_camera(); - //! Renders the whole screen + //! Renders all the sprites and particles void render(); + //! Renders all Text components + void render_text(); + +private: /** * \brief Renders all the particles on the screen from a given sprite. * * \param sprite renders the particles with given texture - * \param tm the Transform component for scale. This is not a const reference because each - * particle has a position and rotation that needs to overwrite the transform position and - * rotation without overwriting the current transform. and because the transform - * constructor is now protected i cannot make tmp inside + * \param transform the component that holds the position, rotation, and scale. * \return true if particles have been rendered */ - bool render_particle(const Sprite & sprite, const double & scale); - /** - * \brief Renders all Text components - * - * \param text The text component to be rendered. - * \param tm the Transform component that holds the position,rotation and scale - */ - void render_text(Text & text, const Transform & tm); + bool render_particle(const Sprite & sprite, const Transform & transform); /** * \brief renders a sprite with a Transform component on the screen * * \param sprite the sprite component that holds all the data - * \param tm the Transform component that holds the position,rotation and scale + * \param transform the Transform component that holds the position,rotation and scale */ - void render_normal(const Sprite & sprite, const Transform & tm); + void render_normal(const Sprite & sprite, const Transform & transform); /** * \brief sort a vector sprite objects with @@ -72,12 +66,6 @@ private: * \return returns a sorted reference vector */ RefVector<Sprite> sort(RefVector<Sprite> & objs) const; - - /** - * \todo Add text rendering using SDL_ttf for text components. - * \todo Implement a text component and a button component. - * \todo Consider adding text input functionality. - */ }; } // namespace crepe diff --git a/src/crepe/system/ReplaySystem.cpp b/src/crepe/system/ReplaySystem.cpp new file mode 100644 index 0000000..efc3be4 --- /dev/null +++ b/src/crepe/system/ReplaySystem.cpp @@ -0,0 +1,54 @@ +#include "../manager/ReplayManager.h" +#include "../manager/SystemManager.h" + +#include "EventSystem.h" +#include "RenderSystem.h" +#include "ReplaySystem.h" + +using namespace crepe; +using namespace std; + +void ReplaySystem::fixed_update() { + ReplayManager & replay = this->mediator.replay_manager; + ReplayManager::State state = replay.get_state(); + ReplayManager::State last_state = this->last_state; + this->last_state = state; + + switch (state) { + case ReplayManager::IDLE: + break; + case ReplayManager::RECORDING: { + replay.frame_record(); + break; + } + case ReplayManager::PLAYING: { + if (last_state != ReplayManager::PLAYING) this->playback_begin(); + bool last = replay.frame_step(); + if (last) this->playback_end(); + break; + } + } +} + +void ReplaySystem::playback_begin() { + SystemManager & systems = this->mediator.system_manager; + ComponentManager & components = this->mediator.component_manager; + + this->playback = { + .components = components.save(), + .systems = systems.save(), + }; + + systems.disable_all(); + systems.get_system<RenderSystem>().active = true; + systems.get_system<ReplaySystem>().active = true; + systems.get_system<EventSystem>().active = true; +} + +void ReplaySystem::playback_end() { + SystemManager & systems = this->mediator.system_manager; + ComponentManager & components = this->mediator.component_manager; + + components.restore(this->playback.components); + systems.restore(this->playback.systems); +} diff --git a/src/crepe/system/ReplaySystem.h b/src/crepe/system/ReplaySystem.h new file mode 100644 index 0000000..bbc8d76 --- /dev/null +++ b/src/crepe/system/ReplaySystem.h @@ -0,0 +1,44 @@ +#pragma once + +#include "../manager/ReplayManager.h" +#include "../manager/SystemManager.h" + +#include "System.h" + +namespace crepe { + +/** + * \brief ReplayManager helper system + * + * This system records and replays recordings using ReplayManager. + */ +class ReplaySystem : public System { +public: + using System::System; + + void fixed_update() override; + +private: + //! Last ReplayManager state + ReplayManager::State last_state = ReplayManager::IDLE; + + /** + * \brief Playback snapshot + * + * When starting playback, the component state is saved and most systems are disabled. This + * struct stores the engine state before ReplayManager::play is called. + */ + struct Snapshot { + ComponentManager::Snapshot components; + SystemManager::Snapshot systems; + }; + //! Before playback snapshot + Snapshot playback; + + //! Snapshot state and disable systems during playback + void playback_begin(); + //! Restore state from before \c playback_begin() + void playback_end(); +}; + +} // namespace crepe diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp index 0605c7a..93b4853 100644 --- a/src/crepe/system/ScriptSystem.cpp +++ b/src/crepe/system/ScriptSystem.cpp @@ -7,11 +7,21 @@ using namespace std; using namespace crepe; -void ScriptSystem::update() { - dbg_trace(); +void ScriptSystem::fixed_update() { + LoopTimerManager & timer = this->mediator.loop_timer; + duration_t delta_time = timer.get_scaled_fixed_delta_time(); + this->update(&Script::fixed_update, delta_time); +} - ComponentManager & mgr = this->mediator.component_manager; +void ScriptSystem::frame_update() { LoopTimerManager & timer = this->mediator.loop_timer; + duration_t delta_time = timer.get_delta_time(); + this->update(&Script::frame_update, delta_time); +} + +void ScriptSystem::update(void (Script::*update_function)(duration_t), + const duration_t & delta_time) { + ComponentManager & mgr = this->mediator.component_manager; RefVector<BehaviorScript> behavior_scripts = mgr.get_components_by_type<BehaviorScript>(); for (BehaviorScript & behavior_script : behavior_scripts) { @@ -25,7 +35,6 @@ void ScriptSystem::update() { script->initialized = true; } - duration_t delta_time = timer.get_scaled_fixed_delta_time(); - script->update(delta_time); + (*script.*update_function)(delta_time); } } diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h index 3db1b1e..257b615 100644 --- a/src/crepe/system/ScriptSystem.h +++ b/src/crepe/system/ScriptSystem.h @@ -1,5 +1,7 @@ #pragma once +#include "../manager/LoopTimerManager.h" + #include "System.h" namespace crepe { @@ -9,20 +11,27 @@ class Script; /** * \brief Script system * - * The script system is responsible for all \c BehaviorScript components, and - * calls the methods on classes derived from \c Script. + * The script system is responsible for all \c BehaviorScript components, and calls the methods + * on classes derived from \c Script. */ class ScriptSystem : public System { public: using System::System; + +public: + //! Call Script::fixed_update() on all active \c BehaviorScript instances + void fixed_update() override; + //! Call Script::frame_update() on all active \c BehaviorScript instances + void frame_update() override; + +private: /** - * \brief Call Script::update() on all active \c BehaviorScript instances + * \brief Call Script `*_update` member function on all active \c BehaviorScript instances * - * This routine updates all scripts sequentially using the Script::update() - * method. It also calls Script::init() if this has not been done before on - * the \c BehaviorScript instance. + * \note This routine also calls Script::init() if this has not been done before on the \c + * BehaviorScript instance. */ - void update() override; + void update(void (Script::*update_function)(duration_t), const duration_t & delta_time); }; } // namespace crepe diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h index 063dfbf..e2ce7eb 100644 --- a/src/crepe/system/System.h +++ b/src/crepe/system/System.h @@ -14,10 +14,12 @@ class ComponentManager; */ class System { public: - /** - * \brief Process all components this system is responsible for. - */ - virtual void update() = 0; + //! Code that runs in the fixed loop + virtual void fixed_update() {}; + //! Code that runs in the frame loop + virtual void frame_update() {}; + //! Indicates that the update functions of this system should be run + bool active = true; public: System(const Mediator & m); diff --git a/src/crepe/util/AbsolutePosition.cpp b/src/crepe/util/AbsolutePosition.cpp new file mode 100644 index 0000000..29ade23 --- /dev/null +++ b/src/crepe/util/AbsolutePosition.cpp @@ -0,0 +1,20 @@ +#include "AbsolutePosition.h" + +using namespace crepe; + +vec2 AbsolutePosition::get_position(const Transform & transform, const vec2 & offset) { + // Get the rotation in radians + float radians1 = transform.rotation * (M_PI / 180.0); + + // Calculate total offset with scale + vec2 total_offset = offset * transform.scale; + + // Rotate + float rotated_total_offset_x1 + = total_offset.x * cos(radians1) - total_offset.y * sin(radians1); + float rotated_total_offset_y1 + = total_offset.x * sin(radians1) + total_offset.y * cos(radians1); + + // Final positions considering scaling and rotation + return (transform.position + vec2(rotated_total_offset_x1, rotated_total_offset_y1)); +} diff --git a/src/crepe/util/AbsolutePosition.h b/src/crepe/util/AbsolutePosition.h new file mode 100644 index 0000000..857c1ac --- /dev/null +++ b/src/crepe/util/AbsolutePosition.h @@ -0,0 +1,29 @@ +#pragma once + +#include "api/Transform.h" + +#include "types.h" + +namespace crepe { + +/** + * \brief A class for calculating the absolute position of an object. + * + * This class provides a utility function to get the position of an object in the world space, + * taking into account the transform and any additional offset. + */ +class AbsolutePosition { +public: + /** + * \brief Get the absolute position of an object. + * + * This function calculates the absolute position by combining the transform position with an optional offset. + * + * \param transform The transform of the object, which contains its position, rotation, and scale. + * \param offset The offset to apply to the object's position (in local space). + * \return The absolute position of the object as a 2D vector. + */ + static vec2 get_position(const Transform & transform, const vec2 & offset); +}; + +} // namespace crepe diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt index 94ed906..33160a7 100644 --- a/src/crepe/util/CMakeLists.txt +++ b/src/crepe/util/CMakeLists.txt @@ -1,6 +1,7 @@ target_sources(crepe PUBLIC LogColor.cpp Log.cpp + AbsolutePosition.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -11,5 +12,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Proxy.hpp OptionalRef.h OptionalRef.hpp + AbsolutePosition.h ) diff --git a/src/crepe/util/Log.cpp b/src/crepe/util/Log.cpp index 84d80a8..ce25a1d 100644 --- a/src/crepe/util/Log.cpp +++ b/src/crepe/util/Log.cpp @@ -4,6 +4,7 @@ #include "../api/Config.h" #include "Log.h" +#include "LogColor.h" using namespace crepe; using namespace std; diff --git a/src/crepe/util/Log.h b/src/crepe/util/Log.h index fc0bb3a..b43fe30 100644 --- a/src/crepe/util/Log.h +++ b/src/crepe/util/Log.h @@ -2,27 +2,6 @@ #include <format> -// allow user to disable debug macros -#ifndef CREPE_DISABLE_MACROS - -#include "LogColor.h" - -// utility macros -#define _crepe_logf_here(level, fmt, ...) \ - crepe::Log::logf(level, "{}" fmt, \ - crepe::LogColor().fg_white(false).str(std::format( \ - "{} ({}:{})", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__)), \ - __VA_ARGS__) - -// very illegal global function-style macros -// NOLINTBEGIN -#define dbg_logf(fmt, ...) _crepe_logf_here(crepe::Log::Level::DEBUG, ": " fmt, __VA_ARGS__) -#define dbg_log(str) _crepe_logf_here(crepe::Log::Level::DEBUG, ": {}", str) -#define dbg_trace() _crepe_logf_here(crepe::Log::Level::TRACE, "", "") -// NOLINTEND - -#endif - namespace crepe { /** diff --git a/src/crepe/util/dbg.h b/src/crepe/util/dbg.h new file mode 100644 index 0000000..c7283ee --- /dev/null +++ b/src/crepe/util/dbg.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Log.h" +#include "LogColor.h" + +// utility macros +#define _crepe_logf_here(level, fmt, ...) \ + crepe::Log::logf(level, "{}" fmt, \ + crepe::LogColor().fg_white(false).str(std::format( \ + "{} ({}:{})", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__)), \ + __VA_ARGS__) + +// very illegal global function-style macros +// NOLINTBEGIN +#define dbg_logf(fmt, ...) _crepe_logf_here(crepe::Log::Level::DEBUG, ": " fmt, __VA_ARGS__) +#define dbg_log(str) _crepe_logf_here(crepe::Log::Level::DEBUG, ": {}", str) +#define dbg_trace() _crepe_logf_here(crepe::Log::Level::TRACE, "", "") +// NOLINTEND diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index f62414e..f9da7ce 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -19,6 +19,7 @@ endfunction() add_example(rendering_particle) add_example(game) add_example(button) +add_example(replay) add_example(loadfont) add_example(FontExample) add_example(AITest) diff --git a/src/example/game.cpp b/src/example/game.cpp index 3975650..7ee784a 100644 --- a/src/example/game.cpp +++ b/src/example/game.cpp @@ -1,9 +1,12 @@ +#include "api/Asset.h" +#include "api/BehaviorScript.h" #include "api/CircleCollider.h" #include "api/ParticleEmitter.h" #include "api/Scene.h" #include "manager/ComponentManager.h" #include "manager/Mediator.h" #include "types.h" +#include <cmath> #include <crepe/api/BoxCollider.h> #include <crepe/api/Camera.h> #include <crepe/api/Color.h> @@ -17,267 +20,293 @@ #include <crepe/api/Vector2.h> using namespace crepe; - using namespace std; -class MyScript1 : public Script { - bool flip = false; +class ScriptBox : public Script { +public: bool oncollision(const CollisionEvent & test) { - Log::logf("Box {} script on_collision()", test.info.this_collider.game_object_id); + Log::logf("Box {} on_collision() with {}", test.info.self.metadata.game_object_id, test.info.other.metadata.game_object_id); return true; } - bool keypressed(const KeyPressEvent & test) { - Log::logf("Box script keypressed()"); - switch (test.key) { - case Keycode::A: { - Rigidbody & tf = this->get_component<Rigidbody>(); - tf.data.linear_velocity.x -= 1; - break; - } - case Keycode::W: { - Rigidbody & tf = this->get_component<Rigidbody>(); - // tf.data.linear_velocity.y -= 1; - tf.add_force_linear({0, -1}); - break; - } - case Keycode::S: { - Rigidbody & tf = this->get_component<Rigidbody>(); - tf.data.linear_velocity.y += 1; - break; - } - case Keycode::D: { - Rigidbody & tf = this->get_component<Rigidbody>(); - tf.data.linear_velocity.x += 1; - break; - } - case Keycode::E: { - if (flip) { - flip = false; - this->get_component<BoxCollider>().active = true; - this->get_components<Sprite>()[0].get().active = true; - this->get_component<CircleCollider>().active = false; - this->get_components<Sprite>()[1].get().active = false; - } else { - flip = true; - this->get_component<BoxCollider>().active = false; - this->get_components<Sprite>()[0].get().active = false; - this->get_component<CircleCollider>().active = true; - this->get_components<Sprite>()[1].get().active = true; - } - - //add collider switch - break; - } - case Keycode::Q: { - Rigidbody & rg = this->get_component<Rigidbody>(); - rg.data.angular_velocity = 1; - break; - } - default: - break; - } - return false; - } void init() { - Log::logf("init"); subscribe<CollisionEvent>( [this](const CollisionEvent & ev) -> bool { return this->oncollision(ev); }); - subscribe<KeyPressEvent>( - [this](const KeyPressEvent & ev) -> bool { return this->keypressed(ev); }); - } - void update(duration_t) { - Rigidbody & tf = this->get_component<Rigidbody>(); - Log::logf("linear_velocity.x {}", tf.data.linear_velocity.x); - Log::logf("linear_velocity.y {}", tf.data.linear_velocity.y); - // tf.data.linear_velocity = {0,0}; } }; -class MyScript2 : public Script { - bool flip = false; +class ScriptCircle : public Script { +public: bool oncollision(const CollisionEvent & test) { - Log::logf("Box {} script on_collision()", test.info.this_collider.game_object_id); + Log::logf("Circle {} on_collision() with {}", test.info.self.metadata.game_object_id, test.info.other.metadata.game_object_id); return true; } - bool keypressed(const KeyPressEvent & test) { - Log::logf("Box script keypressed()"); - switch (test.key) { - case Keycode::LEFT: { - Transform & tf = this->get_component<Transform>(); - tf.position.x -= 1; - break; - } - case Keycode::UP: { - Transform & tf = this->get_component<Transform>(); - tf.position.y -= 1; - break; - } - case Keycode::DOWN: { - Transform & tf = this->get_component<Transform>(); - tf.position.y += 1; - break; - } - case Keycode::RIGHT: { - Transform & tf = this->get_component<Transform>(); - tf.position.x += 1; - break; - } - case Keycode::PAUSE: { - if (flip) { - flip = false; - this->get_component<BoxCollider>().active = true; - this->get_components<Sprite>()[0].get().active = true; - this->get_component<CircleCollider>().active = false; - this->get_components<Sprite>()[1].get().active = false; - } else { - flip = true; - this->get_component<BoxCollider>().active = false; - this->get_components<Sprite>()[0].get().active = false; - this->get_component<CircleCollider>().active = true; - this->get_components<Sprite>()[1].get().active = true; - } - - //add collider switch - break; - } - default: - break; - } - return false; - } void init() { - Log::logf("init"); subscribe<CollisionEvent>( [this](const CollisionEvent & ev) -> bool { return this->oncollision(ev); }); - subscribe<KeyPressEvent>( - [this](const KeyPressEvent & ev) -> bool { return this->keypressed(ev); }); } +}; + +class ScriptMoveToLeft : public Script { +public: void update() { - // Retrieve component from the same GameObject this script is on + Transform & transform = this->get_component<Transform>(); + transform.position.x -= 0.02; } }; -class ConcreteScene1 : public Scene { +class ScriptMoveToRight : public Script { public: - using Scene::Scene; + void update() { + Transform & transform = this->get_component<Transform>(); + transform.position.x += 0.02; + } +}; +class ConcreteScene1 : public Scene { +public: void load_scene() { + GameObject camera = this->new_object("camera"); + camera.add_component<Camera>(ivec2(1080, 720), vec2(10, 10), Camera::Data{ + .bg_color = Color::WHITE, + .zoom = 1 + }); - Color color(0, 0, 0, 255); + GameObject reference = this->new_object("reference", "tag", vec2(0, 0), 0, 1); + Asset reference_asset = Asset("asset/texture/square.png"); + reference.add_component<Sprite>(reference_asset, Sprite::Data{ + .color = Color::RED, + .sorting_in_layer = 10, + .order_in_layer = 0, + .size = vec2(0.1, 0.1), + .angle_offset = 0, + .scale_offset = 1, + .position_offset = vec2(0, 0), + }); - float screen_size_width = 320; - float screen_size_height = 240; - float world_collider = 1000; - //define playable world - GameObject world = new_object( - "Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1); - world.add_component<Rigidbody>(Rigidbody::Data{ - .mass = 0, - .gravity_scale = 0, - .body_type = Rigidbody::BodyType::STATIC, - .offset = {0, 0}, + /*GameObject box_1 = this->new_object("box_1", "tag", vec2(0, 0), 0, 1); + Asset box_1_asset = Asset("asset/texture/square.png"); + box_1.add_component<Sprite>(box_1_asset, Sprite::Data{ + .sorting_in_layer = 0, + .order_in_layer = 0, + .size = vec2(1, 1), + .angle_offset = 0, + .scale_offset = 1, + .position_offset = vec2(0, 0.5), }); - world.add_component<BoxCollider>( - vec2{world_collider, world_collider}, - vec2{0, 0 - (screen_size_height / 2 + world_collider / 2)}); // Top - world.add_component<BoxCollider>( - vec2{world_collider, world_collider}, - vec2{0, screen_size_height / 2 + world_collider / 2}); // Bottom - world.add_component<BoxCollider>( - vec2{world_collider, world_collider}, - vec2{0 - (screen_size_width / 2 + world_collider / 2), 0}); // Left - world.add_component<BoxCollider>( - vec2{world_collider, world_collider}, - vec2{screen_size_width / 2 + world_collider / 2, 0}); // right - world.add_component<Camera>( - ivec2{static_cast<int>(screen_size_width), static_cast<int>(screen_size_height)}, - vec2{screen_size_width, screen_size_height}, - Camera::Data{ - .bg_color = Color::WHITE, - .zoom = 1, - }); + Asset box_1_asset2 = Asset("asset/texture/test_ap43.png"); + box_1.add_component<Sprite>(box_1_asset2, Sprite::Data{ + .sorting_in_layer = 0, + .order_in_layer = 0, + .size = vec2(1, 1), + .angle_offset = 0, + .scale_offset = 1, + .position_offset = vec2(0, -0.5), + });*/ + + /*GameObject particles = this->new_object("particles", "tag", vec2(-1, 0), 180, 1); + Asset particles_asset = Asset("asset/texture/test_ap43.png"); + Sprite & particles_sprite = particles.add_component<Sprite>(particles_asset, Sprite::Data{ + .sorting_in_layer = 0, + .order_in_layer = 0, + .size = vec2(1, 1), + .angle_offset = 0, + .scale_offset = 1, + .position_offset = vec2(0, 0), + }); + ParticleEmitter & particles_emitter = particles.add_component<ParticleEmitter>(particles_sprite, ParticleEmitter::Data{ + .offset = vec2(0, 0), + .emission_rate = 1, + .min_speed = 1, + .max_speed = 1, + .min_angle = 0, + .max_angle = 0, + .end_lifespan = 4, + });*/ + + const bool SCRIPT = false; - GameObject game_object1 = new_object( - "Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1); - game_object1.add_component<Rigidbody>(Rigidbody::Data{ - .mass = 1, - .gravity_scale = 1, - .body_type = Rigidbody::BodyType::DYNAMIC, - .linear_velocity = {0, 1}, - .constraints = {0, 0, 0}, - .elastisity_coefficient = 0, - .offset = {0, 0}, + const Rigidbody::BodyType BODYTYPE_LEFT = Rigidbody::BodyType::DYNAMIC; + const Rigidbody::BodyType BODYTYPE_RIGHT = Rigidbody::BodyType::DYNAMIC; + const bool BOUNCE_LEFT = false; + const bool BOUNCE_RIGHT = false; + const bool KINEMATIC_COLLISION = true; + const bool ONTOP = false; + + const float SCALE = 0.5; + const float OFFSET_X_LEFT = 0; + const float OFFSET_X_RIGHT = 0; + const float OFFSET_Y_LEFT = 0; + const float OFFSET_Y_RIGHT = 0; + + GameObject box_1 = this->new_object("box_1", "tag", ONTOP ? vec2(-0.25, -4) : vec2(-2, -4), 0, SCALE); + Asset box_1_asset = Asset("asset/texture/square.png"); + box_1.add_component<Sprite>(box_1_asset, Sprite::Data{ + .sorting_in_layer = 0, + .order_in_layer = 0, + .size = vec2(1, 1), + .angle_offset = 0, + .scale_offset = 1, + .position_offset = vec2(OFFSET_X_LEFT, OFFSET_Y_LEFT), + }); + box_1.add_component<Rigidbody>(Rigidbody::Data{ + .gravity_scale = 0, + .body_type = BODYTYPE_LEFT, + .linear_velocity = SCRIPT ? vec2{0, 0} : vec2{1, 0}, + .elasticity_coefficient = BOUNCE_LEFT ? 0.5 : 0, + .kinematic_collision = KINEMATIC_COLLISION, }); - // add box with boxcollider - game_object1.add_component<BoxCollider>(vec2{20, 20}); - game_object1.add_component<BehaviorScript>().set_script<MyScript1>(); + box_1.add_component<BehaviorScript>().set_script<ScriptMoveToRight>().active = SCRIPT; + box_1.add_component<BoxCollider>(vec2(1, 1), vec2(OFFSET_X_LEFT, OFFSET_Y_LEFT)); + box_1.add_component<BehaviorScript>().set_script<ScriptBox>(); - Asset img1{"asset/texture/square.png"}; - game_object1.add_component<Sprite>(img1, Sprite::Data{ - .size = {20, 20}, - }); + GameObject circle_1 = this->new_object("ricle_1", "tag", ONTOP ? vec2(0.25, -4) : vec2(2, -4), 0, SCALE); + Asset circle_1_asset = Asset("asset/texture/circle.png"); + circle_1.add_component<Sprite>(circle_1_asset, Sprite::Data{ + .sorting_in_layer = 0, + .order_in_layer = 0, + .size = vec2(1, 1), + .angle_offset = 0, + .scale_offset = 1, + .position_offset = vec2(OFFSET_X_RIGHT, OFFSET_Y_RIGHT), + }); + circle_1.add_component<Rigidbody>(Rigidbody::Data{ + .gravity_scale = 0, + .body_type = BODYTYPE_RIGHT, + .linear_velocity = SCRIPT ? vec2{0, 0} : vec2{-1, 0}, + .elasticity_coefficient = BOUNCE_RIGHT ? 0.5 : 0, + .kinematic_collision = KINEMATIC_COLLISION, + }); + circle_1.add_component<BehaviorScript>().set_script<ScriptMoveToLeft>().active = SCRIPT; + circle_1.add_component<CircleCollider>(0.5, vec2(OFFSET_X_RIGHT, OFFSET_Y_RIGHT)); + circle_1.add_component<BehaviorScript>().set_script<ScriptCircle>(); - //add circle with cirlcecollider deactiveated - game_object1.add_component<CircleCollider>(10).active = false; - Asset img2{"asset/texture/circle.png"}; - game_object1 - .add_component<Sprite>(img2, - Sprite::Data{ - .size = {20, 20}, - }) - .active - = false; + GameObject circle_2 = this->new_object("ricle_2", "tag", ONTOP ? vec2(-0.25, -1.5) : vec2(-2.5, -1.5), 0, SCALE); + Asset circle_2_asset = Asset("asset/texture/circle.png"); + circle_2.add_component<Sprite>(circle_2_asset, Sprite::Data{ + .sorting_in_layer = 0, + .order_in_layer = 0, + .size = vec2(1, 1), + .angle_offset = 0, + .scale_offset = 1, + .position_offset = vec2(OFFSET_X_LEFT, OFFSET_Y_LEFT), + }); + circle_2.add_component<Rigidbody>(Rigidbody::Data{ + .gravity_scale = 0, + .body_type = BODYTYPE_LEFT, + .linear_velocity = SCRIPT ? vec2{0, 0} : vec2{1, 0}, + .elasticity_coefficient = BOUNCE_LEFT ? 0.5 : 0, + .kinematic_collision = KINEMATIC_COLLISION, + }); + circle_2.add_component<BehaviorScript>().set_script<ScriptMoveToRight>().active = SCRIPT; + circle_2.add_component<CircleCollider>(0.5, vec2(OFFSET_X_LEFT, OFFSET_Y_LEFT)); + circle_2.add_component<BehaviorScript>().set_script<ScriptCircle>(); - GameObject game_object2 = new_object( - "Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1); - game_object2.add_component<Rigidbody>(Rigidbody::Data{ - .mass = 1, + GameObject box_2 = this->new_object("box_2", "tag", ONTOP ? vec2(0.25, -1.5) : vec2(2.5, -1.5), 0, SCALE); + Asset box_2_asset = Asset("asset/texture/square.png"); + box_2.add_component<Sprite>(box_2_asset, Sprite::Data{ + .sorting_in_layer = 0, + .order_in_layer = 0, + .size = vec2(1, 1), + .angle_offset = 0, + .scale_offset = 1, + .position_offset = vec2(OFFSET_X_RIGHT, OFFSET_Y_RIGHT), + }); + box_2.add_component<Rigidbody>(Rigidbody::Data{ .gravity_scale = 0, - .body_type = Rigidbody::BodyType::STATIC, - .linear_velocity = {0, 0}, - .constraints = {0, 0, 0}, - .elastisity_coefficient = 1, - .offset = {0, 0}, + .body_type = BODYTYPE_RIGHT, + .linear_velocity = SCRIPT ? vec2{0, 0} : vec2{-1, 0}, + .elasticity_coefficient = BOUNCE_RIGHT ? 0.5 : 0, + .kinematic_collision = KINEMATIC_COLLISION, }); - // add box with boxcollider - game_object2.add_component<BoxCollider>(vec2{20, 20}); - game_object2.add_component<BehaviorScript>().set_script<MyScript2>(); + box_2.add_component<BehaviorScript>().set_script<ScriptMoveToLeft>().active = SCRIPT; + box_2.add_component<BoxCollider>(vec2(1, 1), vec2(OFFSET_X_RIGHT, OFFSET_Y_RIGHT)); + box_2.add_component<BehaviorScript>().set_script<ScriptBox>(); - game_object2.add_component<Sprite>(img1, Sprite::Data{ - .size = {20, 20}, - }); + GameObject box_3 = this->new_object("box_3", "tag", ONTOP ? vec2(-0.25, 1.5) : vec2(-3, 1.5), 0, SCALE); + Asset box_3_asset = Asset("asset/texture/square.png"); + box_3.add_component<Sprite>(box_3_asset, Sprite::Data{ + .sorting_in_layer = 0, + .order_in_layer = 0, + .size = vec2(1, 1), + .angle_offset = 0, + .scale_offset = 1, + .position_offset = vec2(OFFSET_X_LEFT, OFFSET_Y_LEFT), + }); + box_3.add_component<Rigidbody>(Rigidbody::Data{ + .gravity_scale = 0, + .body_type = BODYTYPE_LEFT, + .linear_velocity = SCRIPT ? vec2{0, 0} : vec2{1, 0}, + .elasticity_coefficient = BOUNCE_LEFT ? 0.5 : 0, + .kinematic_collision = KINEMATIC_COLLISION, + }); + box_3.add_component<BehaviorScript>().set_script<ScriptMoveToRight>().active = SCRIPT; + box_3.add_component<BoxCollider>(vec2(1, 1), vec2(OFFSET_X_LEFT, OFFSET_Y_LEFT)); + box_3.add_component<BehaviorScript>().set_script<ScriptBox>(); - //add circle with cirlcecollider deactiveated - game_object2.add_component<CircleCollider>(10).active = false; + GameObject box_4 = this->new_object("box_4", "tag", ONTOP ? vec2(0.25, 1.5) : vec2(3, 1.5), 0, SCALE); + Asset box_4_asset = Asset("asset/texture/square.png"); + box_4.add_component<Sprite>(box_4_asset, Sprite::Data{ + .sorting_in_layer = 0, + .order_in_layer = 0, + .size = vec2(1, 1), + .angle_offset = 0, + .scale_offset = 1, + .position_offset = vec2(OFFSET_X_RIGHT, OFFSET_Y_RIGHT), + }); + box_4.add_component<Rigidbody>(Rigidbody::Data{ + .gravity_scale = 0, + .body_type = BODYTYPE_RIGHT, + .linear_velocity = SCRIPT ? vec2{0, 0} : vec2{-1, 0}, + .elasticity_coefficient = BOUNCE_RIGHT ? 0.5 : 0, + .kinematic_collision = KINEMATIC_COLLISION, + }); + box_4.add_component<BehaviorScript>().set_script<ScriptMoveToLeft>().active = SCRIPT; + box_4.add_component<BoxCollider>(vec2(1, 1), vec2(OFFSET_X_RIGHT, OFFSET_Y_RIGHT)); + box_4.add_component<BehaviorScript>().set_script<ScriptBox>(); - game_object2 - .add_component<Sprite>(img2, - Sprite::Data{ - .size = {20, 20}, - }) - .active - = false; - Asset img5{"asset/texture/square.png"}; + GameObject circle_3 = this->new_object("ricle_3", "tag", ONTOP ? vec2(-0.25, 4) : vec2(-3.5, 4), 0, SCALE); + Asset circle_3_asset = Asset("asset/texture/circle.png"); + circle_3.add_component<Sprite>(circle_3_asset, Sprite::Data{ + .sorting_in_layer = 0, + .order_in_layer = 0, + .size = vec2(1, 1), + .angle_offset = 0, + .scale_offset = 1, + .position_offset = vec2(OFFSET_X_LEFT, OFFSET_Y_LEFT), + }); + circle_3.add_component<Rigidbody>(Rigidbody::Data{ + .gravity_scale = 0, + .body_type = BODYTYPE_LEFT, + .linear_velocity = SCRIPT ? vec2{0, 0} : vec2{1, 0}, + .elasticity_coefficient = BOUNCE_LEFT ? 0.5 : 0, + .kinematic_collision = KINEMATIC_COLLISION, + }); + circle_3.add_component<BehaviorScript>().set_script<ScriptMoveToRight>().active = SCRIPT; + circle_3.add_component<CircleCollider>(0.5, vec2(OFFSET_X_LEFT, OFFSET_Y_LEFT)); + circle_3.add_component<BehaviorScript>().set_script<ScriptCircle>(); - GameObject particle = new_object( - "Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1); - auto & particle_image = particle.add_component<Sprite>(img5, Sprite::Data{ - .size = {5, 5}, - }); - auto & test - = particle.add_component<ParticleEmitter>(particle_image, ParticleEmitter::Data{ - .offset = {0, 0}, - .max_particles = 256, - .emission_rate = 1, - .min_speed = 10, - .max_speed = 20, - .min_angle = -20, - .max_angle = 20, - .begin_lifespan = 0, - .end_lifespan = 5, - }); + GameObject circle_4 = this->new_object("ricle_4", "tag", ONTOP ? vec2(0.25, 4) : vec2(3.5, 4), 0, SCALE); + Asset circle_4_asset = Asset("asset/texture/circle.png"); + circle_4.add_component<Sprite>(circle_4_asset, Sprite::Data{ + .sorting_in_layer = 0, + .order_in_layer = 0, + .size = vec2(1, 1), + .angle_offset = 0, + .scale_offset = 1, + .position_offset = vec2(OFFSET_X_RIGHT, OFFSET_Y_RIGHT), + }); + circle_4.add_component<Rigidbody>(Rigidbody::Data{ + .gravity_scale = 0, + .body_type = BODYTYPE_RIGHT, + .linear_velocity = SCRIPT ? vec2{0, 0} : vec2{-1, 0}, + .elasticity_coefficient = BOUNCE_RIGHT ? 0.5 : 0, + .kinematic_collision = KINEMATIC_COLLISION, + }); + circle_4.add_component<BehaviorScript>().set_script<ScriptMoveToLeft>().active = SCRIPT; + circle_4.add_component<CircleCollider>(0.5, vec2(OFFSET_X_RIGHT, OFFSET_Y_RIGHT)); + circle_4.add_component<BehaviorScript>().set_script<ScriptCircle>(); } string get_name() const { return "scene1"; } diff --git a/src/example/rendering_particle.cpp b/src/example/rendering_particle.cpp index add43f4..5440fdd 100644 --- a/src/example/rendering_particle.cpp +++ b/src/example/rendering_particle.cpp @@ -1,4 +1,7 @@ + + #include "api/Asset.h" +#include "api/Text.h" #include <crepe/Component.h> #include <crepe/api/Animator.h> #include <crepe/api/Button.h> @@ -13,7 +16,6 @@ #include <crepe/manager/ComponentManager.h> #include <crepe/manager/Mediator.h> #include <crepe/types.h> -#include <iostream> using namespace crepe; using namespace std; @@ -21,15 +23,11 @@ using namespace std; class TestScene : public Scene { public: void load_scene() { - - cout << "TestScene" << endl; - Mediator & mediator = this->mediator; - ComponentManager & mgr = mediator.component_manager; - GameObject game_object = mgr.new_object("", "", vec2{0, 0}, 0, 1); + GameObject game_object = new_object("", "", vec2{0, 0}, 0, 1); Color color(255, 255, 255, 255); - Asset img{"asset/spritesheet/spritesheet_test.png"}; + Asset img{"asset/texture/square.png"}; Sprite & test_sprite = game_object.add_component<Sprite>( img, Sprite::Data{ @@ -37,29 +35,34 @@ public: .flip = Sprite::FlipSettings{false, false}, .sorting_in_layer = 2, .order_in_layer = 2, - .size = {0, 100}, + .size = {1, 1}, .angle_offset = 0, - .position_offset = {0, 0}, + .position_offset = {0, 1}, + .world_space = false, }); + //auto & emitter = game_object.add_component<ParticleEmitter>(test_sprite, ParticleEmitter::Data{}); - //auto & anim = game_object.add_component<Animator>(test_sprite,ivec2{32, 64}, uvec2{4,1}, Animator::Data{}); - //anim.set_anim(0); + Sprite & test_sprite1 + = game_object.add_component<Sprite>(img, Sprite::Data{ + .color = color, + .size = {1, 1}, + .position_offset = {0, -1}, + .world_space = false, + }); - auto & cam = game_object.add_component<Camera>(ivec2{720, 1280}, vec2{400, 400}, + auto & cam = game_object.add_component<Camera>(ivec2{1280, 720}, vec2{5, 5}, Camera::Data{ .bg_color = Color::WHITE, + .postion_offset = {1000, 1000}, }); - function<void()> on_click = [&]() { cout << "button clicked" << std::endl; }; - function<void()> on_enter = [&]() { cout << "enter" << std::endl; }; - function<void()> on_exit = [&]() { cout << "exit" << std::endl; }; + /* + game_object.add_component<Text>(vec2{1, 1}, vec2{0, -0.5}, "ComicSansMS", + Text::Data{.text_color = Color::RED}, "test TEST"); - auto & button - = game_object.add_component<Button>(vec2{200, 200}, vec2{0, 0}, on_click, false); - button.on_mouse_enter = on_enter; - button.on_mouse_exit = on_exit; - button.is_toggle = true; - button.active = true; + game_object.add_component<Text>(vec2{1, 1}, vec2{0, 0.5}, "ComicSansMS", + Text::Data{.text_color = Color::BLACK}, "TEST test"); + */ } string get_name() const { return "TestScene"; }; diff --git a/src/example/replay.cpp b/src/example/replay.cpp new file mode 100644 index 0000000..82fd478 --- /dev/null +++ b/src/example/replay.cpp @@ -0,0 +1,84 @@ +#include <crepe/api/Config.h> +#include <crepe/api/Engine.h> +#include <crepe/api/Script.h> + +using namespace crepe; +using namespace std; + +class AnimationScript : public Script { + Transform * transform; + float t = 0; + + void init() { transform = &get_component<Transform>(); } + + void update() { + t += 0.05; + transform->position = {sin(t), cos(t)}; + } +}; + +class Timeline : public Script { + unsigned i = 0; + recording_t recording; + + void update() { + switch (i++) { + default: + break; + case 10: + logf("record start"); + replay.record_start(); + break; + case 60: + logf("record end, playing recording"); + this->recording = replay.record_end(); + replay.play(this->recording); + break; + case 61: + logf("done, releasing recording"); + replay.release(this->recording); + break; + case 72: + logf("exit"); + queue_event<ShutDownEvent>(); + break; + }; + } +}; + +class TestScene : public Scene { +public: + using Scene::Scene; + + void load_scene() { + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + + GameObject cam = mgr.new_object("cam"); + cam.add_component<Camera>(ivec2{640, 480}, vec2{3, 3}, + Camera::Data{ + .bg_color = Color::WHITE, + }); + + GameObject square = mgr.new_object("square"); + square.add_component<Sprite>(Asset{"asset/texture/square.png"}, Sprite::Data{ + .size = {0.5, 0.5}, + }); + square.add_component<BehaviorScript>().set_script<AnimationScript>(); + + GameObject scapegoat = mgr.new_object(""); + scapegoat.add_component<BehaviorScript>().set_script<Timeline>(); + } + + string get_name() const { return "scene1"; } +}; + +int main(int argc, char * argv[]) { + Config & cfg = Config::get_instance(); + cfg.log.level = Log::Level::DEBUG; + + Engine engine; + + engine.add_scene<TestScene>(); + return engine.main(); +} diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp index 48bba1b..415a12e 100644 --- a/src/test/AudioTest.cpp +++ b/src/test/AudioTest.cpp @@ -50,11 +50,11 @@ TEST_F(AudioTest, Default) { EXPECT_CALL(context, stop(_)).Times(0); EXPECT_CALL(context, set_volume(_, _)).Times(0); EXPECT_CALL(context, set_loop(_, _)).Times(0); - system.update(); + system.fixed_update(); } TEST_F(AudioTest, Play) { - system.update(); + system.fixed_update(); { InSequence seq; @@ -67,12 +67,12 @@ TEST_F(AudioTest, Play) { InSequence seq; EXPECT_CALL(context, play(_)).Times(1); - system.update(); + system.fixed_update(); } } TEST_F(AudioTest, Stop) { - system.update(); + system.fixed_update(); { InSequence seq; @@ -85,12 +85,12 @@ TEST_F(AudioTest, Stop) { InSequence seq; EXPECT_CALL(context, stop(_)).Times(1); - system.update(); + system.fixed_update(); } } TEST_F(AudioTest, Volume) { - system.update(); + system.fixed_update(); { InSequence seq; @@ -103,12 +103,12 @@ TEST_F(AudioTest, Volume) { InSequence seq; EXPECT_CALL(context, set_volume(_, component.volume)).Times(1); - system.update(); + system.fixed_update(); } } TEST_F(AudioTest, Looping) { - system.update(); + system.fixed_update(); { InSequence seq; @@ -121,33 +121,33 @@ TEST_F(AudioTest, Looping) { InSequence seq; EXPECT_CALL(context, set_loop(_, component.loop)).Times(1); - system.update(); + system.fixed_update(); } } TEST_F(AudioTest, StopOnDeactivate) { - system.update(); + system.fixed_update(); { InSequence seq; EXPECT_CALL(context, stop(_)).Times(1); component.active = false; - system.update(); + system.fixed_update(); } } TEST_F(AudioTest, PlayOnActive) { component.active = false; component.play_on_awake = true; - system.update(); + system.fixed_update(); { InSequence seq; EXPECT_CALL(context, play(_)).Times(1); component.active = true; - system.update(); + system.fixed_update(); } } @@ -157,5 +157,5 @@ TEST_F(AudioTest, PlayImmediately) { EXPECT_CALL(context, play(_)).Times(1); - system.update(); + system.fixed_update(); } diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 11b4ca9..ea92d96 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -15,7 +15,7 @@ target_sources(test_main PUBLIC ValueBrokerTest.cpp DBTest.cpp Vector2Test.cpp - LoopManagerTest.cpp + # LoopManagerTest.cpp LoopTimerTest.cpp InputTest.cpp ScriptEventTest.cpp @@ -24,4 +24,5 @@ target_sources(test_main PUBLIC SaveManagerTest.cpp ScriptSaveManagerTest.cpp ScriptECSTest.cpp + ReplayManagerTest.cpp ) diff --git a/src/test/CollisionTest.cpp b/src/test/CollisionTest.cpp index baa95c1..c8d804c 100644 --- a/src/test/CollisionTest.cpp +++ b/src/test/CollisionTest.cpp @@ -67,7 +67,6 @@ public: world.add_component<Rigidbody>(Rigidbody::Data{ // TODO: remove unrelated properties: .body_type = Rigidbody::BodyType::STATIC, - .offset = {0, 0}, }); // Create a box with an inner size of 10x10 units world.add_component<BoxCollider>(vec2{100, 100}, vec2{0, -100}); // Top @@ -81,8 +80,7 @@ public: .body_type = Rigidbody::BodyType::DYNAMIC, .linear_velocity = {0, 0}, .constraints = {0, 0, 0}, - .elastisity_coefficient = 1, - .offset = {0, 0}, + .elasticity_coefficient = 1, .collision_layers = {0}, }); game_object1.add_component<BoxCollider>(vec2{10, 10}, vec2{0, 0}); @@ -97,8 +95,7 @@ public: .body_type = Rigidbody::BodyType::DYNAMIC, .linear_velocity = {0, 0}, .constraints = {0, 0, 0}, - .elastisity_coefficient = 1, - .offset = {0, 0}, + .elasticity_coefficient = 1, .collision_layers = {0}, }); game_object2.add_component<BoxCollider>(vec2{10, 10}, vec2{0, 0}); @@ -108,7 +105,7 @@ public: ASSERT_NE(script_object2_ref, nullptr); // Ensure Script::init() is called on all BehaviorScript instances - script_sys.update(); + script_sys.fixed_update(); } }; @@ -116,14 +113,14 @@ TEST_F(CollisionTest, collision_example) { bool collision_happend = false; script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); }; script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 2); + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); }; EXPECT_FALSE(collision_happend); - collision_sys.update(); + collision_sys.fixed_update(); EXPECT_FALSE(collision_happend); } @@ -131,22 +128,22 @@ TEST_F(CollisionTest, collision_box_box_dynamic_both_no_velocity) { bool collision_happend = false; script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); EXPECT_EQ(ev.info.resolution.x, 10); EXPECT_EQ(ev.info.resolution.y, 10); EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); }; script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 2); - EXPECT_EQ(ev.info.resolution.x, 10); - EXPECT_EQ(ev.info.resolution.y, 10); + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, -10); + EXPECT_EQ(ev.info.resolution.y, -10); EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); }; EXPECT_FALSE(collision_happend); Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); tf.position = {50, 30}; - collision_sys.update(); + collision_sys.fixed_update(); EXPECT_TRUE(collision_happend); } @@ -154,7 +151,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_x_direction_no_velocity) { bool collision_happend = false; script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); EXPECT_EQ(ev.info.resolution.x, -5); EXPECT_EQ(ev.info.resolution.y, 0); EXPECT_EQ(ev.info.resolution_direction, @@ -162,7 +159,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_x_direction_no_velocity) { }; script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 2); + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); EXPECT_EQ(ev.info.resolution.x, 5); EXPECT_EQ(ev.info.resolution.y, 0); EXPECT_EQ(ev.info.resolution_direction, @@ -171,7 +168,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_x_direction_no_velocity) { EXPECT_FALSE(collision_happend); Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); tf.position = {45, 30}; - collision_sys.update(); + collision_sys.fixed_update(); EXPECT_TRUE(collision_happend); } @@ -179,7 +176,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_y_direction_no_velocity) { bool collision_happend = false; script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); EXPECT_EQ(ev.info.resolution.x, 0); EXPECT_EQ(ev.info.resolution.y, -5); EXPECT_EQ(ev.info.resolution_direction, @@ -187,7 +184,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_y_direction_no_velocity) { }; script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 2); + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); EXPECT_EQ(ev.info.resolution.x, 0); EXPECT_EQ(ev.info.resolution.y, 5); EXPECT_EQ(ev.info.resolution_direction, @@ -196,7 +193,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_y_direction_no_velocity) { EXPECT_FALSE(collision_happend); Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); tf.position = {50, 25}; - collision_sys.update(); + collision_sys.fixed_update(); EXPECT_TRUE(collision_happend); } @@ -204,16 +201,16 @@ TEST_F(CollisionTest, collision_box_box_dynamic_both) { bool collision_happend = false; script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); EXPECT_EQ(ev.info.resolution.x, 10); EXPECT_EQ(ev.info.resolution.y, 10); EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); }; script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 2); - EXPECT_EQ(ev.info.resolution.x, 10); - EXPECT_EQ(ev.info.resolution.y, 10); + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, -10); + EXPECT_EQ(ev.info.resolution.y, -10); EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); }; EXPECT_FALSE(collision_happend); @@ -223,7 +220,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_both) { rg1.data.linear_velocity = {10, 10}; Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); rg2.data.linear_velocity = {10, 10}; - collision_sys.update(); + collision_sys.fixed_update(); EXPECT_TRUE(collision_happend); } @@ -231,7 +228,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_x_direction) { bool collision_happend = false; script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); EXPECT_EQ(ev.info.resolution.x, -5); EXPECT_EQ(ev.info.resolution.y, 5); EXPECT_EQ(ev.info.resolution_direction, @@ -239,7 +236,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_x_direction) { }; script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 2); + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); EXPECT_EQ(ev.info.resolution.x, 5); EXPECT_EQ(ev.info.resolution.y, -5); EXPECT_EQ(ev.info.resolution_direction, @@ -252,7 +249,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_x_direction) { rg1.data.linear_velocity = {10, 10}; Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); rg2.data.linear_velocity = {10, 10}; - collision_sys.update(); + collision_sys.fixed_update(); EXPECT_TRUE(collision_happend); } @@ -260,7 +257,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_y_direction) { bool collision_happend = false; script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); EXPECT_EQ(ev.info.resolution.x, 5); EXPECT_EQ(ev.info.resolution.y, -5); EXPECT_EQ(ev.info.resolution_direction, @@ -268,7 +265,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_y_direction) { }; script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 2); + EXPECT_EQ(ev.info.self.transform.game_object_id, 2); EXPECT_EQ(ev.info.resolution.x, -5); EXPECT_EQ(ev.info.resolution.y, 5); EXPECT_EQ(ev.info.resolution_direction, @@ -281,7 +278,7 @@ TEST_F(CollisionTest, collision_box_box_dynamic_y_direction) { rg1.data.linear_velocity = {10, 10}; Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); rg2.data.linear_velocity = {10, 10}; - collision_sys.update(); + collision_sys.fixed_update(); EXPECT_TRUE(collision_happend); } @@ -289,21 +286,19 @@ TEST_F(CollisionTest, collision_box_box_static_both) { bool collision_happend = false; script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); EXPECT_EQ(ev.info.resolution.x, 10); EXPECT_EQ(ev.info.resolution.y, 10); EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); }; - script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { - // is static should not be called - FAIL(); - }; + script_object2_ref->test_fn + = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; }; EXPECT_FALSE(collision_happend); Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); tf.position = {50, 30}; Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; - collision_sys.update(); + collision_sys.fixed_update(); EXPECT_TRUE(collision_happend); } @@ -311,7 +306,7 @@ TEST_F(CollisionTest, collision_box_box_static_x_direction) { bool collision_happend = false; script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); EXPECT_EQ(ev.info.resolution.x, -5); EXPECT_EQ(ev.info.resolution.y, 5); EXPECT_EQ(ev.info.resolution_direction, @@ -319,7 +314,7 @@ TEST_F(CollisionTest, collision_box_box_static_x_direction) { }; script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { // is static should not be called - FAIL(); + //FAIL(); }; EXPECT_FALSE(collision_happend); Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); @@ -328,7 +323,7 @@ TEST_F(CollisionTest, collision_box_box_static_x_direction) { rg1.data.linear_velocity = {10, 10}; Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; - collision_sys.update(); + collision_sys.fixed_update(); EXPECT_TRUE(collision_happend); } @@ -336,7 +331,7 @@ TEST_F(CollisionTest, collision_box_box_static_y_direction) { bool collision_happend = false; script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.self.transform.game_object_id, 1); EXPECT_EQ(ev.info.resolution.x, 5); EXPECT_EQ(ev.info.resolution.y, -5); EXPECT_EQ(ev.info.resolution_direction, @@ -344,7 +339,7 @@ TEST_F(CollisionTest, collision_box_box_static_y_direction) { }; script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { // is static should not be called - FAIL(); + //FAIL(); }; EXPECT_FALSE(collision_happend); Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); @@ -353,40 +348,6 @@ TEST_F(CollisionTest, collision_box_box_static_y_direction) { rg1.data.linear_velocity = {10, 10}; Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; - collision_sys.update(); - EXPECT_TRUE(collision_happend); -} - -TEST_F(CollisionTest, collision_box_box_static_multiple) { //todo check visually - bool collision_happend = false; - float offset_value = 0; - float resolution = 0; - script_object1_ref->test_fn = [&](const CollisionEvent & ev) { - collision_happend = true; - EXPECT_EQ(ev.info.this_collider.game_object_id, 1); - EXPECT_EQ(ev.info.this_collider.offset.x, offset_value); - EXPECT_EQ(ev.info.resolution.x, resolution); - }; - script_object2_ref->test_fn = [&](const CollisionEvent & ev) { - // is static should not be called - FAIL(); - }; - EXPECT_FALSE(collision_happend); - Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); - tf.position = {45, 30}; - Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); - rg1.data.linear_velocity = {10, 10}; - Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); - rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; - BoxCollider & bxc = this->mgr.get_components_by_id<BoxCollider>(1).front().get(); - bxc.offset = {5, 0}; - this->game_object1.add_component<BoxCollider>(vec2{-5, 0}, vec2{10, 10}); - offset_value = 5; - resolution = 10; - collision_sys.update(); - offset_value = -5; - resolution = 10; - tf.position = {55, 30}; - collision_sys.update(); + collision_sys.fixed_update(); EXPECT_TRUE(collision_happend); } diff --git a/src/test/ECSTest.cpp b/src/test/ECSTest.cpp index af2b7b0..8f86a91 100644 --- a/src/test/ECSTest.cpp +++ b/src/test/ECSTest.cpp @@ -466,3 +466,17 @@ TEST_F(ECSTest, ComponentsByTag) { EXPECT_EQ(objects.size(), 3); } } + +TEST_F(ECSTest, Snapshot) { + GameObject foo = mgr.new_object("foo"); + + foo.transform.position = {1, 1}; + + ComponentManager::Snapshot snapshot = mgr.save(); + + foo.transform.position = {0, 0}; + + mgr.restore(snapshot); + + EXPECT_EQ(foo.transform.position, (vec2{1, 1})); +} diff --git a/src/test/InputTest.cpp b/src/test/InputTest.cpp index 7cff34b..86afad9 100644 --- a/src/test/InputTest.cpp +++ b/src/test/InputTest.cpp @@ -45,7 +45,7 @@ protected: auto & camera = obj.add_component<Camera>(ivec2{500, 500}, vec2{500, 500}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); - render.update(); + render.frame_update(); //mediator.event_manager = event_manager; //mediator.component_manager = mgr; //event_manager.clear(); @@ -93,7 +93,7 @@ TEST_F(InputTest, MouseDown) { event.button.button = SDL_BUTTON_LEFT; SDL_PushEvent(&event); - input_system.update(); + input_system.fixed_update(); event_manager.dispatch_events(); EXPECT_TRUE(mouse_triggered); } @@ -117,7 +117,7 @@ TEST_F(InputTest, MouseUp) { event.button.button = SDL_BUTTON_LEFT; SDL_PushEvent(&event); - input_system.update(); + input_system.fixed_update(); event_manager.dispatch_events(); EXPECT_TRUE(function_triggered); } @@ -143,7 +143,7 @@ TEST_F(InputTest, MouseMove) { event.motion.yrel = 10; SDL_PushEvent(&event); - input_system.update(); + input_system.fixed_update(); event_manager.dispatch_events(); EXPECT_TRUE(function_triggered); } @@ -169,7 +169,7 @@ TEST_F(InputTest, KeyDown) { test_event.key.repeat = 1; // Set repeat flag SDL_PushEvent(&test_event); - input_system.update(); // Process the event + input_system.fixed_update(); // Process the event event_manager.dispatch_events(); // Dispatch events to handlers EXPECT_TRUE(function_triggered); // Check if the handler was triggered @@ -190,7 +190,7 @@ TEST_F(InputTest, KeyUp) { event.key.keysym.scancode = SDL_SCANCODE_B; SDL_PushEvent(&event); - input_system.update(); + input_system.fixed_update(); event_manager.dispatch_events(); EXPECT_TRUE(function_triggered); } @@ -207,7 +207,7 @@ TEST_F(InputTest, MouseClick) { event_manager.subscribe<MouseClickEvent>(on_mouse_click); this->simulate_mouse_click(250, 250, SDL_BUTTON_LEFT); - input_system.update(); + input_system.fixed_update(); event_manager.dispatch_events(); EXPECT_TRUE(on_click_triggered); } @@ -225,12 +225,12 @@ TEST_F(InputTest, testButtonClick) { bool hover = false; button.active = true; this->simulate_mouse_click(999, 999, SDL_BUTTON_LEFT); - input_system.update(); + input_system.fixed_update(); event_manager.dispatch_events(); EXPECT_FALSE(button_clicked); this->simulate_mouse_click(250, 250, SDL_BUTTON_LEFT); - input_system.update(); + input_system.fixed_update(); event_manager.dispatch_events(); EXPECT_TRUE(button_clicked); } @@ -304,7 +304,7 @@ TEST_F(InputTest, testButtonHover) { hover_event.motion.yrel = 10; SDL_PushEvent(&hover_event); - input_system.update(); + input_system.fixed_update(); event_manager.dispatch_events(); EXPECT_TRUE(button.hover); EXPECT_TRUE(button_hover); @@ -318,7 +318,7 @@ TEST_F(InputTest, testButtonHover) { event.motion.yrel = 10; SDL_PushEvent(&event); - input_system.update(); + input_system.fixed_update(); event_manager.dispatch_events(); EXPECT_FALSE(button.hover); EXPECT_FALSE(button_hover); diff --git a/src/test/LoopManagerTest.cpp b/src/test/LoopManagerTest.cpp index df132ae..f6653fa 100644 --- a/src/test/LoopManagerTest.cpp +++ b/src/test/LoopManagerTest.cpp @@ -4,7 +4,7 @@ #include <thread> #define private public #define protected public -#include <crepe/api/LoopManager.h> +#include <crepe/api/Engine.h> #include <crepe/manager/EventManager.h> #include <crepe/manager/LoopTimerManager.h> using namespace std::chrono; @@ -12,7 +12,7 @@ using namespace crepe; class DISABLED_LoopManagerTest : public ::testing::Test { protected: - class TestGameLoop : public crepe::LoopManager { + class TestGameLoop : public crepe::Engine { public: MOCK_METHOD(void, fixed_update, (), (override)); MOCK_METHOD(void, frame_update, (), (override)); diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index 9263e00..7a731a3 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -97,18 +97,18 @@ TEST_F(ParticlesTest, spawnParticle) { emitter.data.max_angle = 0.1; emitter.data.max_speed = 10; emitter.data.max_angle = 10; - particle_system.update(); + particle_system.fixed_update(); //check if nothing happend EXPECT_EQ(emitter.particles[0].active, false); emitter.data.emission_rate = 50; //check particle spawnes - particle_system.update(); + particle_system.fixed_update(); EXPECT_EQ(emitter.particles[0].active, true); - particle_system.update(); + particle_system.fixed_update(); EXPECT_EQ(emitter.particles[1].active, true); - particle_system.update(); + particle_system.fixed_update(); EXPECT_EQ(emitter.particles[2].active, true); - particle_system.update(); + particle_system.fixed_update(); EXPECT_EQ(emitter.particles[3].active, true); for (auto & particle : emitter.particles) { @@ -142,7 +142,7 @@ TEST_F(ParticlesTest, moveParticleHorizontal) { emitter.data.max_angle = 0; emitter.data.emission_rate = 50; for (int a = 1; a < emitter.data.boundary.width / 2; a++) { - particle_system.update(); + particle_system.fixed_update(); EXPECT_EQ(emitter.particles[0].position.x, a); } } @@ -160,7 +160,7 @@ TEST_F(ParticlesTest, moveParticleVertical) { emitter.data.max_angle = 90; emitter.data.emission_rate = 50; for (int a = 1; a < emitter.data.boundary.width / 2; a++) { - particle_system.update(); + particle_system.fixed_update(); EXPECT_EQ(emitter.particles[0].position.y, a); } } @@ -179,7 +179,7 @@ TEST_F(ParticlesTest, boundaryParticleReset) { emitter.data.max_angle = 90; emitter.data.emission_rate = 1; for (int a = 0; a < emitter.data.boundary.width / 2 + 1; a++) { - particle_system.update(); + particle_system.fixed_update(); } EXPECT_EQ(emitter.particles[0].active, false); } @@ -198,7 +198,7 @@ TEST_F(ParticlesTest, boundaryParticleStop) { emitter.data.max_angle = 90; emitter.data.emission_rate = 1; for (int a = 0; a < emitter.data.boundary.width / 2 + 1; a++) { - particle_system.update(); + particle_system.fixed_update(); } const double TOLERANCE = 0.01; EXPECT_NEAR(emitter.particles[0].velocity.x, 0, TOLERANCE); diff --git a/src/test/PhysicsTest.cpp b/src/test/PhysicsTest.cpp index 3afb3c7..79ed0b8 100644 --- a/src/test/PhysicsTest.cpp +++ b/src/test/PhysicsTest.cpp @@ -57,10 +57,10 @@ TEST_F(PhysicsTest, gravity) { ASSERT_FALSE(transforms.empty()); EXPECT_EQ(transform.position.y, 0); - system.update(); + system.fixed_update(); EXPECT_NEAR(transform.position.y, 0.0004, 0.0001); - system.update(); + system.fixed_update(); EXPECT_NEAR(transform.position.y, 0.002, 0.001); } @@ -74,14 +74,14 @@ TEST_F(PhysicsTest, max_velocity) { rigidbody.add_force_linear({100, 100}); rigidbody.add_force_angular(100); - system.update(); + system.fixed_update(); EXPECT_NEAR(rigidbody.data.linear_velocity.y, 7.07, 0.01); EXPECT_NEAR(rigidbody.data.linear_velocity.x, 7.07, 0.01); EXPECT_EQ(rigidbody.data.angular_velocity, 10); rigidbody.add_force_linear({-100, -100}); rigidbody.add_force_angular(-100); - system.update(); + system.fixed_update(); EXPECT_NEAR(rigidbody.data.linear_velocity.y, -7.07, 0.01); EXPECT_NEAR(rigidbody.data.linear_velocity.x, -7.07, 0.01); EXPECT_EQ(rigidbody.data.angular_velocity, -10); @@ -99,7 +99,7 @@ TEST_F(PhysicsTest, movement) { rigidbody.add_force_linear({1, 1}); rigidbody.add_force_angular(1); - system.update(); + system.fixed_update(); EXPECT_NEAR(transform.position.x, 0.02, 0.001); EXPECT_NEAR(transform.position.y, 0.02, 0.001); EXPECT_NEAR(transform.rotation, 0.02, 0.001); @@ -112,7 +112,7 @@ TEST_F(PhysicsTest, movement) { rigidbody.data.linear_velocity_coefficient.x = 0.5; rigidbody.data.linear_velocity_coefficient.y = 0.5; rigidbody.data.angular_velocity_coefficient = 0.5; - system.update(); + system.fixed_update(); EXPECT_NEAR(rigidbody.data.linear_velocity.x, 0.98, 0.01); EXPECT_NEAR(rigidbody.data.linear_velocity.y, 0.98, 0.01); EXPECT_NEAR(rigidbody.data.angular_velocity, 0.98, 0.01); @@ -121,12 +121,12 @@ TEST_F(PhysicsTest, movement) { rigidbody.data.angular_velocity_coefficient = 0; rigidbody.data.max_angular_velocity = 1000; rigidbody.data.angular_velocity = 360; - system.update(); + system.fixed_update(); EXPECT_NEAR(transform.rotation, 7.24, 0.01); rigidbody.data.angular_velocity = -360; - system.update(); + system.fixed_update(); EXPECT_NEAR(transform.rotation, 0.04, 0.001); - system.update(); + system.fixed_update(); EXPECT_NEAR(transform.rotation, 352.84, 0.01); } diff --git a/src/test/Profiling.cpp b/src/test/Profiling.cpp index 16736b8..d1da5a0 100644 --- a/src/test/Profiling.cpp +++ b/src/test/Profiling.cpp @@ -32,14 +32,14 @@ using namespace testing; class TestScript : public Script { bool oncollision(const CollisionEvent & test) { - Log::logf("Box {} script on_collision()", test.info.this_collider.game_object_id); + Log::logf("Box {} script on_collision()", test.info.self.transform.game_object_id); return true; } void init() { subscribe<CollisionEvent>( [this](const CollisionEvent & ev) -> bool { return this->oncollision(ev); }); } - void update() { + void fixed_update() { // Retrieve component from the same GameObject this script is on } }; @@ -50,7 +50,7 @@ public: // Minimum amount to let test pass const int min_gameobject_count = 100; // Maximum amount to stop test - const int max_gameobject_count = 150; + const int max_gameobject_count = 3000; // Amount of times a test runs to calculate average const int average = 5; // Maximum duration to stop test @@ -61,6 +61,8 @@ public: ResourceManager resman{m}; ComponentManager mgr{m}; // Add system used for profling tests + EventManager evmgr{m}; + LoopTimerManager loopmgr{m}; CollisionSystem collision_sys{m}; PhysicsSystem physics_sys{m}; ParticleSystem particle_sys{m}; @@ -82,9 +84,9 @@ public: }); // initialize systems here: //calls init - script_sys.update(); + script_sys.fixed_update(); //creates window - render_sys.update(); + render_sys.frame_update(); } // Helper function to time an update call and store its duration @@ -102,12 +104,14 @@ public: // Run and profile all systems, return the total time in milliseconds std::chrono::microseconds run_all_systems() { std::chrono::microseconds total_microseconds = 0us; - total_microseconds += time_function("PhysicsSystem", [&]() { physics_sys.update(); }); total_microseconds - += time_function("CollisionSystem", [&]() { collision_sys.update(); }); + += time_function("PhysicsSystem", [&]() { physics_sys.fixed_update(); }); total_microseconds - += time_function("ParticleSystem", [&]() { particle_sys.update(); }); - total_microseconds += time_function("RenderSystem", [&]() { render_sys.update(); }); + += time_function("CollisionSystem", [&]() { collision_sys.fixed_update(); }); + total_microseconds + += time_function("ParticleSystem", [&]() { particle_sys.fixed_update(); }); + total_microseconds + += time_function("RenderSystem", [&]() { render_sys.frame_update(); }); return total_microseconds; } @@ -233,7 +237,7 @@ TEST_F(DISABLED_ProfilingTest, Profiling_3) { }); } - render_sys.update(); + render_sys.frame_update(); this->game_object_count++; this->total_time = 0us; diff --git a/src/test/RenderSystemTest.cpp b/src/test/RenderSystemTest.cpp index b4519cb..689a6d4 100644 --- a/src/test/RenderSystemTest.cpp +++ b/src/test/RenderSystemTest.cpp @@ -85,7 +85,7 @@ public: TEST_F(RenderSystemTest, NoCamera) { // No camera - EXPECT_ANY_THROW({ this->sys.update(); }); + EXPECT_ANY_THROW({ this->sys.frame_update(); }); } TEST_F(RenderSystemTest, make_sprites) {} @@ -139,7 +139,7 @@ TEST_F(RenderSystemTest, Update) { EXPECT_EQ(sprites[2].get().game_object_id, 2); EXPECT_EQ(sprites[3].get().game_object_id, 3); } - this->sys.update(); + this->sys.frame_update(); { vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>(); ASSERT_EQ(sprites.size(), 4); @@ -178,7 +178,7 @@ TEST_F(RenderSystemTest, Color) { EXPECT_EQ(sprite.data.color.g, Color::GREEN.g); EXPECT_EQ(sprite.data.color.b, Color::GREEN.b); EXPECT_EQ(sprite.data.color.a, Color::GREEN.a); - this->sys.update(); + this->sys.frame_update(); EXPECT_EQ(sprite.data.color.r, Color::GREEN.r); EXPECT_EQ(sprite.data.color.g, Color::GREEN.g); EXPECT_EQ(sprite.data.color.b, Color::GREEN.b); diff --git a/src/test/ReplayManagerTest.cpp b/src/test/ReplayManagerTest.cpp new file mode 100644 index 0000000..5ee4b40 --- /dev/null +++ b/src/test/ReplayManagerTest.cpp @@ -0,0 +1,38 @@ +#include <gtest/gtest.h> + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Script.h> +#include <crepe/manager/ReplayManager.h> +#include <crepe/system/ReplaySystem.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class ReplayManagerTest : public Test { + Mediator mediator; + +public: + ComponentManager component_manager{mediator}; + ReplayManager replay_manager{mediator}; + ReplaySystem replay_system{mediator}; + + GameObject entity = component_manager.new_object("foo"); + Transform & entity_transform + = component_manager.get_components_by_id<Transform>(entity.id).back(); + Metadata & entity_metadata + = component_manager.get_components_by_id<Metadata>(entity.id).back(); +}; + +TEST_F(ReplayManagerTest, Default) { + // replay_manager.record_start(); + + // replay_system.fixed_update(); + // entity_transform.position += {1, 1}; + // replay_system.fixed_update(); + // entity_transform.position += {1, 1}; + // replay_system.fixed_update(); + + // recording_t recording = replay_manager.record_end(); +} diff --git a/src/test/ScriptEventTest.cpp b/src/test/ScriptEventTest.cpp index c1b4028..479e3f5 100644 --- a/src/test/ScriptEventTest.cpp +++ b/src/test/ScriptEventTest.cpp @@ -37,7 +37,7 @@ TEST_F(ScriptEventTest, Default) { return true; }); - system.update(); + system.fixed_update(); behaviorscript.active = false; EXPECT_EQ(0, event_count); diff --git a/src/test/ScriptTest.cpp b/src/test/ScriptTest.cpp index 846e398..40aa25c 100644 --- a/src/test/ScriptTest.cpp +++ b/src/test/ScriptTest.cpp @@ -28,7 +28,8 @@ void ScriptTest::SetUp() { TEST_F(ScriptTest, Default) { MyScript & script = this->script; EXPECT_CALL(script, init()).Times(0); - EXPECT_CALL(script, update(_)).Times(0); + EXPECT_CALL(script, fixed_update(_)).Times(0); + EXPECT_CALL(script, frame_update(_)).Times(0); } TEST_F(ScriptTest, UpdateOnce) { @@ -38,16 +39,23 @@ TEST_F(ScriptTest, UpdateOnce) { InSequence seq; EXPECT_CALL(script, init()).Times(1); - EXPECT_CALL(script, update(_)).Times(1); - system.update(); + EXPECT_CALL(script, fixed_update(_)).Times(1); + system.fixed_update(); } { InSequence seq; EXPECT_CALL(script, init()).Times(0); - EXPECT_CALL(script, update(_)).Times(1); - system.update(); + EXPECT_CALL(script, fixed_update(_)).Times(1); + system.fixed_update(); + } + + { + InSequence seq; + + EXPECT_CALL(script, frame_update(_)).Times(1); + system.frame_update(); } } @@ -59,18 +67,18 @@ TEST_F(ScriptTest, UpdateInactive) { InSequence seq; EXPECT_CALL(script, init()).Times(0); - EXPECT_CALL(script, update(_)).Times(0); + EXPECT_CALL(script, fixed_update(_)).Times(0); behaviorscript.active = false; - system.update(); + system.fixed_update(); } { InSequence seq; EXPECT_CALL(script, init()).Times(1); - EXPECT_CALL(script, update(_)).Times(1); + EXPECT_CALL(script, fixed_update(_)).Times(1); behaviorscript.active = true; - system.update(); + system.fixed_update(); } } diff --git a/src/test/ScriptTest.h b/src/test/ScriptTest.h index f3dbda4..8637df0 100644 --- a/src/test/ScriptTest.h +++ b/src/test/ScriptTest.h @@ -29,7 +29,8 @@ public: public: MOCK_METHOD(void, init, (), (override)); - MOCK_METHOD(void, update, (crepe::duration_t), (override)); + MOCK_METHOD(void, fixed_update, (crepe::duration_t), (override)); + MOCK_METHOD(void, frame_update, (crepe::duration_t), (override)); }; crepe::OptionalRef<crepe::BehaviorScript> behaviorscript; |