diff options
68 files changed, 1180 insertions, 318 deletions
@@ -19,15 +19,17 @@ TAB_SIZE = 2 HTML_INDEX_NUM_ENTRIES = 2 HTML_EXTRA_STYLESHEET = src/doc/style.css +SHOW_HEADERFILE = NO USE_MDFILE_AS_MAINPAGE = ./readme.md REPEAT_BRIEF = NO -INTERNAL_DOCS = YES -EXTRACT_PRIVATE = YES EXTRACT_STATIC = YES HIDE_UNDOC_NAMESPACES = YES HIDE_UNDOC_CLASSES = YES QUIET = YES +# set these to NO for user-only docs +INTERNAL_DOCS = YES +EXTRACT_PRIVATE = YES diff --git a/contributing.md b/contributing.md index 77a2908..0a90e86 100644 --- a/contributing.md +++ b/contributing.md @@ -17,6 +17,7 @@ that you can click on to open them. working/compiling version of the project - Pull requests for new code include either automated tests for the new code or an explanation as to why the code can not (reliably) be tested + <!-- - TODO: tagging / versions --> @@ -495,6 +496,12 @@ that you can click on to open them. </td></tr></table></details> - <details><summary> Ensure const-correctness + + > [!IMPORTANT] + > C-style APIs that work on (possibly internal) references to structs can be + > called from const member functions in C++. If the compiler allows you to + > mark a function as `const` even though it has side effects, it should + > **not** be marked as `const`. </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> ```cpp @@ -795,6 +802,23 @@ that you can click on to open them. ``` </td></tr></table></details> - Do not implement new classes as singletons +- <details><summary> + Retrieving the first or last indices for iterators with a known or expected + size should be done using <code>.front()</code> or <code>.back()</code> + instead of by index + </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> + + ```cpp + vector<int> foo = { 1, 2, 3 }; + int bar = foo.first(); + ``` + </td><td> + + ```cpp + vector<int> foo = { 1, 2, 3 }; + int bar = foo[0]; + ``` + </td></tr></table></details> ## CMakeLists-specific diff --git a/lib/sdl2 b/lib/sdl2 -Subproject c98c4fbff6d8f3016a3ce6685bf8f43433c3efc +Subproject 9519b9916cd29a14587af0507292f2bd31dd575 diff --git a/lib/sdl_image b/lib/sdl_image -Subproject 56560190a6c5b5740e5afdce747e2a2bfbf16db +Subproject abcf63aa71b4e3ac32120fa9870a6500ddcdcc8 diff --git a/lib/sdl_ttf b/lib/sdl_ttf -Subproject a3d0895c1b60c41ff9e85d9203ddd7485c014da +Subproject 4a318f8dfaa1bb6f10e0c5e54052e25d3c7f344 diff --git a/src/crepe/ComponentManager.cpp b/src/crepe/ComponentManager.cpp index e310577..e4de027 100644 --- a/src/crepe/ComponentManager.cpp +++ b/src/crepe/ComponentManager.cpp @@ -26,8 +26,7 @@ void ComponentManager::delete_all_components() { } GameObject ComponentManager::new_object(const string & name, const string & tag, - const Vector2 & position, double rotation, - double scale) { + const vec2 & position, double rotation, double scale) { GameObject object{*this, this->next_id, name, tag, position, rotation, scale}; this->next_id++; return object; diff --git a/src/crepe/ComponentManager.h b/src/crepe/ComponentManager.h index 0956d1e..1cb0b5f 100644 --- a/src/crepe/ComponentManager.h +++ b/src/crepe/ComponentManager.h @@ -5,8 +5,6 @@ #include <unordered_map> #include <vector> -#include "api/Vector2.h" - #include "Component.h" #include "types.h" @@ -45,7 +43,7 @@ public: * \note This method automatically assigns a new entity ID */ GameObject new_object(const std::string & name, const std::string & tag = "", - const Vector2 & position = {0, 0}, double rotation = 0, + const vec2 & position = {0, 0}, double rotation = 0, double scale = 1); protected: diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp index 1068cbf..485a0d4 100644 --- a/src/crepe/Particle.cpp +++ b/src/crepe/Particle.cpp @@ -2,7 +2,7 @@ using namespace crepe; -void Particle::reset(uint32_t lifespan, const Vector2 & position, const Vector2 & velocity, +void Particle::reset(uint32_t lifespan, const vec2 & position, const vec2 & velocity, double angle) { // Initialize the particle state this->time_in_life = 0; diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h index 19859fe..d0397c9 100644 --- a/src/crepe/Particle.h +++ b/src/crepe/Particle.h @@ -2,7 +2,7 @@ #include <cstdint> -#include "api/Vector2.h" +#include "types.h" namespace crepe { @@ -18,11 +18,11 @@ class Particle { public: //! Position of the particle in 2D space. - Vector2 position; + vec2 position; //! Velocity vector indicating the speed and direction of the particle. - Vector2 velocity; + vec2 velocity; //! Accumulated force affecting the particle over time. - Vector2 force_over_time; + vec2 force_over_time; //! Total lifespan of the particle in milliseconds. uint32_t lifespan; //! Active state of the particle; true if it is in use, false otherwise. @@ -43,8 +43,7 @@ public: * \param velocity The initial velocity of the particle. * \param angle The angle of the particle's trajectory or orientation. */ - void reset(uint32_t lifespan, const Vector2 & position, const Vector2 & velocity, - double angle); + void reset(uint32_t lifespan, const vec2 & position, const vec2 & velocity, double angle); /** * \brief Updates the particle's state. * diff --git a/src/crepe/api/BehaviorScript.h b/src/crepe/api/BehaviorScript.h index 9d85d4c..d556fe5 100644 --- a/src/crepe/api/BehaviorScript.h +++ b/src/crepe/api/BehaviorScript.h @@ -39,11 +39,14 @@ public: * \brief Set the concrete script of this component * * \tparam T Concrete script type (derived from \c crepe::Script) + * \tparam Args Arguments for concrete script constructor + * + * \param args Arguments for concrete script constructor (forwarded using perfect forwarding) * * \returns Reference to BehaviorScript component (`*this`) */ - template <class T> - BehaviorScript & set_script(); + template <class T, typename... Args> + BehaviorScript & set_script(Args &&... args); protected: //! Script instance diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp index d80321d..bd59337 100644 --- a/src/crepe/api/BehaviorScript.hpp +++ b/src/crepe/api/BehaviorScript.hpp @@ -9,13 +9,17 @@ namespace crepe { -template <class T> -BehaviorScript & BehaviorScript::set_script() { +template <class T, typename... Args> +BehaviorScript & BehaviorScript::set_script(Args &&... args) { dbg_trace(); static_assert(std::is_base_of<Script, T>::value); - Script * s = new T(); + Script * s = new T(std::forward<Args>(args)...); + s->game_object_id = this->game_object_id; - s->component_manager_ref = &this->component_manager; + s->active = this->active; + s->component_manager = this->component_manager; + s->event_manager = EventManager::get_instance(); + this->script = std::unique_ptr<Script>(s); return *this; } diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index d6b6801..50c51ed 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -12,9 +12,7 @@ target_sources(crepe PUBLIC SaveManager.cpp Config.cpp Metadata.cpp - Scene.cpp SceneManager.cpp - Vector2.cpp Camera.cpp Animator.cpp EventManager.cpp @@ -24,6 +22,7 @@ target_sources(crepe PUBLIC LoopTimer.cpp Asset.cpp EventHandler.cpp + Script.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -37,6 +36,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Rigidbody.h Sprite.h Vector2.h + Vector2.hpp Color.h Texture.h AssetManager.h diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h index 13eabd1..0c9d116 100644 --- a/src/crepe/api/Config.h +++ b/src/crepe/api/Config.h @@ -11,19 +11,18 @@ namespace crepe { * modified *before* execution is handed over from the game programmer to the engine (i.e. the * main loop is started). */ -class Config { +class Config final { public: //! Retrieve handle to global Config instance static Config & get_instance(); private: Config() = default; - - // singleton - Config(const Config &) = delete; - Config(Config &&) = delete; - Config & operator=(const Config &) = delete; - Config & operator=(Config &&) = delete; + ~Config() = default; + Config(const Config &) = default; + Config(Config &&) = default; + Config & operator=(const Config &) = default; + Config & operator=(Config &&) = default; public: //! Logging-related settings diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h index 06cf7f3..b267e3e 100644 --- a/src/crepe/api/Event.h +++ b/src/crepe/api/Event.h @@ -1,10 +1,12 @@ -// TODO discussing the location of these events #pragma once +// TODO discussing the location of these events #include <string> #include "KeyCodes.h" +namespace crepe { + /** * \brief Base class for all event types in the system. */ @@ -91,11 +93,7 @@ public: /** * \brief Event triggered during a collision between objects. */ -class CollisionEvent : public Event { -public: - //! Data describing the collision (currently not implemented). - // Collision collisionData; -}; +class CollisionEvent : public Event {}; /** * \brief Event triggered when text is submitted, e.g., from a text input. @@ -110,3 +108,5 @@ public: * \brief Event triggered to indicate the application is shutting down. */ class ShutDownEvent : public Event {}; + +} // namespace crepe diff --git a/src/crepe/api/EventHandler.hpp b/src/crepe/api/EventHandler.hpp index 391dcca..050e57e 100644 --- a/src/crepe/api/EventHandler.hpp +++ b/src/crepe/api/EventHandler.hpp @@ -1,3 +1,5 @@ +#pragma once + #include <typeindex> #include "EventHandler.h" diff --git a/src/crepe/api/EventManager.h b/src/crepe/api/EventManager.h index 348a04d..1a33023 100644 --- a/src/crepe/api/EventManager.h +++ b/src/crepe/api/EventManager.h @@ -77,7 +77,7 @@ public: * \param channel The channel to trigger the event on (default is CHANNEL_ALL, which triggers on all channels). */ template <typename EventType> - void trigger_event(const EventType & event, event_channel_t channel = CHANNEL_ALL); + void trigger_event(const EventType & event = {}, event_channel_t channel = CHANNEL_ALL); /** * \brief Queue an event for later processing. @@ -89,7 +89,7 @@ public: * \param channel The channel to associate with the event (default is CHANNEL_ALL). */ template <typename EventType> - void queue_event(const EventType & event, event_channel_t channel = CHANNEL_ALL); + void queue_event(const EventType & event = {}, event_channel_t channel = CHANNEL_ALL); /** * \brief Process all queued events. diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp index 4874426..3c36a21 100644 --- a/src/crepe/api/GameObject.cpp +++ b/src/crepe/api/GameObject.cpp @@ -9,7 +9,7 @@ using namespace std; GameObject::GameObject(ComponentManager & component_manager, game_object_id_t id, const std::string & name, const std::string & tag, - const Vector2 & position, double rotation, double scale) + const vec2 & position, double rotation, double scale) : id(id), component_manager(component_manager) { diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h index 34ef8bb..fcb8d9a 100644 --- a/src/crepe/api/GameObject.h +++ b/src/crepe/api/GameObject.h @@ -2,7 +2,6 @@ #include <string> -#include "Vector2.h" #include "types.h" namespace crepe { @@ -30,7 +29,7 @@ private: * \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 Vector2 & position, + const std::string & name, const std::string & tag, const vec2 & position, double rotation, double scale); //! ComponentManager instances GameObject friend class ComponentManager; diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index a64366f..7edf4d1 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -57,7 +57,7 @@ void LoopManager::loop() { void LoopManager::setup() { this->game_running = true; LoopTimer::get_instance().start(); - LoopTimer::get_instance().set_fps(60); + LoopTimer::get_instance().set_fps(200); } void LoopManager::render() { diff --git a/src/crepe/api/LoopTimer.cpp b/src/crepe/api/LoopTimer.cpp index a9800b7..15a0e3a 100644 --- a/src/crepe/api/LoopTimer.cpp +++ b/src/crepe/api/LoopTimer.cpp @@ -47,7 +47,7 @@ double LoopTimer::get_fixed_delta_time() const { return this->fixed_delta_time.c void LoopTimer::set_fps(int fps) { this->fps = fps; // target time per frame in seconds - this->frame_target_time = std::chrono::seconds(1) / fps; + this->frame_target_time = std::chrono::duration<double>(1.0) / fps; } int LoopTimer::get_fps() const { return this->fps; } diff --git a/src/crepe/api/LoopTimer.h b/src/crepe/api/LoopTimer.h index f277d7b..9393439 100644 --- a/src/crepe/api/LoopTimer.h +++ b/src/crepe/api/LoopTimer.h @@ -130,9 +130,9 @@ private: //! Delta time for the current frame in seconds std::chrono::duration<double> delta_time{0.0}; //! Target time per frame in seconds - std::chrono::duration<double> frame_target_time = std::chrono::seconds(1) / fps; + std::chrono::duration<double> frame_target_time = std::chrono::duration<double>(1.0) / fps; //! Fixed delta time for fixed updates in seconds - std::chrono::duration<double> fixed_delta_time = std::chrono::seconds(1) / 50; + std::chrono::duration<double> fixed_delta_time = std::chrono::duration<double>(1.0) / 50.0; //! Total elapsed game time in seconds std::chrono::duration<double> elapsed_time{0.0}; //! Total elapsed time for fixed updates in seconds diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h index 33112e1..b83fd61 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -4,7 +4,7 @@ #include "Component.h" #include "Particle.h" -#include "Vector2.h" +#include "types.h" namespace crepe { @@ -30,7 +30,7 @@ public: //! boundary height (midpoint is emitter location) double height = 0.0; //! boundary offset from particle emitter location - Vector2 offset; + vec2 offset; //! reset on exit or stop velocity and set max postion bool reset_on_exit = false; }; @@ -43,7 +43,7 @@ public: */ struct Data { //! position of the emitter - Vector2 position; + vec2 position; //! maximum number of particles const unsigned int max_particles = 0; //! rate of particle emission per update (Lowest value = 0.001 any lower is ignored) @@ -61,7 +61,7 @@ public: //! end Lifespan of particle double end_lifespan = 0.0; //! force over time (physics) - Vector2 force_over_time; + vec2 force_over_time; //! particle boundary Boundary boundary; //! collection of particles diff --git a/src/crepe/api/Rigidbody.cpp b/src/crepe/api/Rigidbody.cpp index 6b87695..576ca45 100644 --- a/src/crepe/api/Rigidbody.cpp +++ b/src/crepe/api/Rigidbody.cpp @@ -6,7 +6,7 @@ crepe::Rigidbody::Rigidbody(game_object_id_t id, const Data & data) : Component(id), data(data) {} -void crepe::Rigidbody::add_force_linear(const Vector2 & force) { +void crepe::Rigidbody::add_force_linear(const vec2 & force) { this->data.linear_velocity += force; } diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h index 3e5c7a3..3b0588f 100644 --- a/src/crepe/api/Rigidbody.h +++ b/src/crepe/api/Rigidbody.h @@ -2,7 +2,7 @@ #include "../Component.h" -#include "Vector2.h" +#include "types.h" namespace crepe { @@ -56,11 +56,11 @@ public: //! Changes if physics apply BodyType body_type = BodyType::DYNAMIC; //! linear velocity of object - Vector2 linear_velocity; + vec2 linear_velocity; //! maximum linear velocity of object - Vector2 max_linear_velocity; + vec2 max_linear_velocity; //! linear damping of object - Vector2 linear_damping; + vec2 linear_damping; //! angular velocity of object double angular_velocity = 0.0; //! max angular velocity of object @@ -90,7 +90,7 @@ public: * * \param force Vector2 that is added to the linear force. */ - void add_force_linear(const Vector2 & force); + void add_force_linear(const vec2 & force); /** * \brief add a angular force to the Rigidbody. * diff --git a/src/crepe/api/Scene.cpp b/src/crepe/api/Scene.cpp deleted file mode 100644 index 849945e..0000000 --- a/src/crepe/api/Scene.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "Scene.h" - -using namespace crepe; - -Scene::Scene(ComponentManager & mgr) : component_manager(mgr) {} diff --git a/src/crepe/api/Scene.h b/src/crepe/api/Scene.h index 869bf6f..f6fdb2a 100644 --- a/src/crepe/api/Scene.h +++ b/src/crepe/api/Scene.h @@ -2,6 +2,8 @@ #include <string> +#include "../util/OptionalRef.h" + namespace crepe { class SceneManager; @@ -15,11 +17,8 @@ class ComponentManager; */ class Scene { protected: - //TODO: Use Loek's custom reference class to set ComponentManger via SceneManager instead of via constructor - /** - * \param mgr Reference to the ComponentManager - */ - Scene(ComponentManager & mgr); + // NOTE: This must be the only constructor on Scene, see "Late references" below + Scene() = default; //! SceneManager instances Scene friend class SceneManager; @@ -36,8 +35,20 @@ public: virtual std::string get_name() const = 0; protected: + /** + * \name Late references + * + * These references are set by SceneManager immediately after calling the constructor of Scene. + * + * \note Scene must have a constructor without arguments so the game programmer doesn't need to + * manually add `using Scene::Scene` to their concrete scene class, if they want to add a + * constructor with arguments (e.g. for passing references to their own concrete Scene classes). + * + * \{ + */ //! Reference to the ComponentManager - ComponentManager & component_manager; + OptionalRef<ComponentManager> component_manager; + //! \} }; } // namespace crepe diff --git a/src/crepe/api/SceneManager.h b/src/crepe/api/SceneManager.h index 45ba668..f6f62cd 100644 --- a/src/crepe/api/SceneManager.h +++ b/src/crepe/api/SceneManager.h @@ -26,8 +26,8 @@ public: * * \tparam T Type of concrete scene */ - template <typename T> - void add_scene(); + template <typename T, typename... Args> + void add_scene(Args &&... args); /** * \brief Set the next scene * diff --git a/src/crepe/api/SceneManager.hpp b/src/crepe/api/SceneManager.hpp index 94e5946..5c8e417 100644 --- a/src/crepe/api/SceneManager.hpp +++ b/src/crepe/api/SceneManager.hpp @@ -4,13 +4,17 @@ namespace crepe { -template <typename T> -void SceneManager::add_scene() { +template <typename T, typename... Args> +void SceneManager::add_scene(Args &&... args) { using namespace std; static_assert(is_base_of<Scene, T>::value, "T must be derived from Scene"); - Scene * scene = new T(this->component_manager); - this->scenes.emplace_back(unique_ptr<Scene>(scene)); + Scene * scene = new T(std::forward<Args>(args)...); + unique_ptr<Scene> unique_scene(scene); + + unique_scene->component_manager = this->component_manager; + + this->scenes.emplace_back(std::move(unique_scene)); // The first scene added, is the one that will be loaded at the beginning if (next_scene.empty()) { diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp new file mode 100644 index 0000000..fcbe4c7 --- /dev/null +++ b/src/crepe/api/Script.cpp @@ -0,0 +1,15 @@ +#include "Script.h" + +using namespace crepe; + +Script::~Script() { + EventManager & evmgr = this->event_manager; + for (auto id : this->listeners) { + evmgr.unsubscribe(id); + } +} + +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback) { + this->subscribe_internal(callback, this->game_object_id); +} diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index 839d937..a0870cb 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -3,6 +3,9 @@ #include <vector> #include "../types.h" +#include "../util/OptionalRef.h" + +#include "EventManager.h" namespace crepe { @@ -16,13 +19,23 @@ class ComponentManager; * This class is used as a base class for user-defined scripts that can be added to game * objects using the \c BehaviorScript component. * - * \note Additional *events* (like Unity's OnDisable and OnEnable) should be implemented as + * \info Additional *events* (like Unity's OnDisable and OnEnable) should be implemented as * member or lambda methods in derivative user script classes and registered in \c init(). + * + * \warning Concrete scripts are allowed do create a custom constructor, but the utility + * functions should not be called inside the constructor as they rely on late references that + * are only available after the constructor returns. + * + * \see feature_script */ class Script { protected: /** - * \brief Script initialization function + * \name Interface functions + * \{ + */ + /** + * \brief Script initialization function (empty by default) * * This function is called during the ScriptSystem::update() routine *before* * Script::update() if it (a) has not yet been called and (b) the \c BehaviorScript component @@ -30,24 +43,31 @@ protected: */ virtual void init() {} /** - * \brief Script update function + * \brief Script update function (empty by default) * * This function is called during the ScriptSystem::update() routine if the \c BehaviorScript * component holding this script instance is active. */ virtual void update() {} + //! \} + //! ScriptSystem calls \c init() and \c update() friend class crepe::ScriptSystem; protected: /** - * \brief Get single component of type \c T on this game object (utility) + * \name Utility functions + * \{ + */ + + /** + * \brief Get single component of type \c T on this game object * * \tparam T Type of component * * \returns Reference to component * - * \throws nullptr if this game object does not have a component matching type \c T + * \throws std::runtime_error if this game object does not have a component with type \c T */ template <typename T> T & get_component() const; @@ -55,7 +75,7 @@ protected: // cause compile-time errors /** - * \brief Get all components of type \c T on this game object (utility) + * \brief Get all components of type \c T on this game object * * \tparam T Type of component * @@ -64,25 +84,108 @@ protected: template <typename T> RefVector<T> get_components() const; + /** + * \brief Log a message using Log::logf + * + * \tparam Args Log::logf parameters + * \param args Log::logf parameters + */ + template <typename... Args> + void logf(Args &&... args); + + /** + * \brief Subscribe to an event with an explicit channel + * \see 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 + */ + template <typename EventType> + void subscribe(const EventHandler<EventType> & callback); + + //! \} + +private: + /** + * \brief Internal subscribe function + * + * This function exists so certain template specializations of Script::subscribe can be + * explicitly deleted, and does the following: + * - Wrap the user-provided callback in a check that tests if the parent BehaviorScript + * component is still active + * - Store the subscriber handle returned by the event manager so this listener is + * automatically unsubscribed at the end of this Script instance's life + * + * \tparam EventType concrete Event class + * \param callback User-provided callback function + * \param channel Event channel (may have been overridden by template specializations) + */ + template <typename EventType> + void subscribe_internal(const EventHandler<EventType> & callback, event_channel_t channel); + protected: - // NOTE: Script must have a constructor without arguments so the game programmer doesn't need - // to manually add `using Script::Script` to their concrete script class. + // NOTE: This must be the only constructor on Script, see "Late references" below Script() = default; //! Only \c BehaviorScript instantiates Script friend class BehaviorScript; +public: + // std::unique_ptr destroys script + virtual ~Script(); + +private: + Script(const Script &) = delete; + Script(Script &&) = delete; + Script & operator=(const Script &) = delete; + Script & operator=(Script &&) = delete; + private: - // These references are set by BehaviorScript immediately after calling the constructor of - // Script. - game_object_id_t game_object_id = -1; - ComponentManager * component_manager_ref = nullptr; - // TODO: use OptionalRef instead of pointer + /** + * \name Late references + * + * These references are set by BehaviorScript immediately after calling the constructor of + * Script. + * + * \note Script must have a constructor without arguments so the game programmer doesn't need + * to manually add `using Script::Script` to their concrete script class if they want to + * implement a non-default constructor (e.g. for passing references to their own concrete + * Script classes). + * + * \{ + */ + //! Game object ID of game object parent BehaviorScript is attached to + game_object_id_t game_object_id; + //! Reference to parent component + OptionalRef<bool> active; + //! Reference to component manager instance + OptionalRef<ComponentManager> component_manager; + //! Reference to event manager instance + OptionalRef<EventManager> event_manager; + //! \} private: //! Flag to indicate if \c init() has been called already bool initialized = false; + //! List of subscribed events + std::vector<subscription_t> listeners; }; +/** + * \brief Subscribe to CollisionEvent for the current GameObject + * + * This is a template specialization for Script::subscribe which automatically sets the event + * channel so the callback handler is only called for CollisionEvent events that apply to the + * current GameObject the parent BehaviorScript is attached to. + */ +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback); +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback, event_channel_t) + = delete; + } // namespace crepe #include "Script.hpp" diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index a85d814..a2463bf 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -20,8 +20,38 @@ T & Script::get_component() const { template <typename T> RefVector<T> Script::get_components() const { - auto & mgr = *this->component_manager_ref; + ComponentManager & mgr = this->component_manager; + return mgr.get_components_by_id<T>(this->game_object_id); } +template <typename... Args> +void Script::logf(Args &&... args) { + Log::logf(std::forward<Args>(args)...); +} + +template <typename EventType> +void Script::subscribe_internal(const EventHandler<EventType> & callback, + event_channel_t channel) { + EventManager & mgr = this->event_manager; + subscription_t listener = mgr.subscribe<EventType>( + [this, callback](const EventType & data) -> bool { + bool & active = this->active; + if (!active) return false; + return callback(data); + }, + channel); + this->listeners.push_back(listener); +} + +template <typename EventType> +void Script::subscribe(const EventHandler<EventType> & callback, event_channel_t channel) { + this->subscribe_internal(callback, channel); +} + +template <typename EventType> +void Script::subscribe(const EventHandler<EventType> & callback) { + this->subscribe_internal(callback, EventManager::CHANNEL_ALL); +} + } // namespace crepe diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp index 9be9421..264d7b1 100644 --- a/src/crepe/api/Texture.cpp +++ b/src/crepe/api/Texture.cpp @@ -9,14 +9,9 @@ using namespace crepe; using namespace std; -Texture::Texture(unique_ptr<Asset> res) { +Texture::Texture(const Asset & src) { dbg_trace(); - this->load(std::move(res)); -} - -Texture::Texture(const char * src) { - dbg_trace(); - this->load(make_unique<Asset>(src)); + this->load(src); } Texture::~Texture() { @@ -24,9 +19,9 @@ Texture::~Texture() { this->texture.reset(); } -void Texture::load(unique_ptr<Asset> res) { +void Texture::load(const Asset & res) { SDLContext & ctx = SDLContext::get_instance(); - this->texture = std::move(ctx.texture_from_path(res->get_path())); + this->texture = ctx.texture_from_path(res.get_path()); } int Texture::get_width() const { diff --git a/src/crepe/api/Texture.h b/src/crepe/api/Texture.h index 6965223..b4f7d07 100644 --- a/src/crepe/api/Texture.h +++ b/src/crepe/api/Texture.h @@ -25,16 +25,10 @@ class Texture { public: /** - * \brief Constructs a Texture from a file path. - * \param src Path to the image file to be loaded as a texture. - */ - Texture(const char * src); - - /** * \brief Constructs a Texture from an Asset resource. - * \param res Unique pointer to an Asset resource containing texture data. + * \param src Asset with texture data to load. */ - Texture(std::unique_ptr<Asset> res); + Texture(const Asset & src); /** * \brief Destroys the Texture instance, freeing associated resources. @@ -59,7 +53,7 @@ private: * \brief Loads the texture from an Asset resource. * \param res Unique pointer to an Asset resource to load the texture from. */ - void load(std::unique_ptr<Asset> res); + void load(const Asset & res); private: //! The texture of the class from the library diff --git a/src/crepe/api/Transform.cpp b/src/crepe/api/Transform.cpp index cd944bd..a85b792 100644 --- a/src/crepe/api/Transform.cpp +++ b/src/crepe/api/Transform.cpp @@ -4,7 +4,7 @@ using namespace crepe; -Transform::Transform(game_object_id_t id, const Vector2 & point, double rotation, double scale) +Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, double scale) : Component(id), position(point), rotation(rotation), diff --git a/src/crepe/api/Transform.h b/src/crepe/api/Transform.h index 18aa293..6243a93 100644 --- a/src/crepe/api/Transform.h +++ b/src/crepe/api/Transform.h @@ -1,8 +1,7 @@ #pragma once -#include "api/Vector2.h" - #include "Component.h" +#include "types.h" namespace crepe { @@ -15,7 +14,7 @@ namespace crepe { class Transform : public Component { public: //! Translation (shift) - Vector2 position = {0, 0}; + vec2 position = {0, 0}; //! Rotation, in degrees double rotation = 0; //! Multiplication factor @@ -28,7 +27,7 @@ protected: * \param rotation The rotation of the GameObject * \param scale The scale of the GameObject */ - Transform(game_object_id_t id, const Vector2 & point, double rotation, double scale); + Transform(game_object_id_t id, const vec2 & point, double rotation, double scale); /** * There is always exactly one transform component per entity * \return 1 diff --git a/src/crepe/api/Vector2.cpp b/src/crepe/api/Vector2.cpp deleted file mode 100644 index 30b968e..0000000 --- a/src/crepe/api/Vector2.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "Vector2.h" - -using namespace crepe; - -Vector2 Vector2::operator-(const Vector2 & other) const { return {x - other.x, y - other.y}; } - -Vector2 Vector2::operator+(const Vector2 & other) const { return {x + other.x, y + other.y}; } - -Vector2 Vector2::operator*(double scalar) const { return {x * scalar, y * scalar}; } - -Vector2 & Vector2::operator*=(const Vector2 & other) { - x *= other.x; - y *= other.y; - return *this; -} - -Vector2 & Vector2::operator+=(const Vector2 & other) { - x += other.x; - y += other.y; - return *this; -} - -Vector2 & Vector2::operator+=(double other) { - x += other; - y += other; - return *this; -} - -Vector2 Vector2::operator-() const { return {-x, -y}; } - -bool Vector2::operator==(const Vector2 & other) const { return x == other.x && y == other.y; } - -bool Vector2::operator!=(const Vector2 & other) const { return !(*this == other); } diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h index 2fb6136..c278c87 100644 --- a/src/crepe/api/Vector2.h +++ b/src/crepe/api/Vector2.h @@ -3,38 +3,71 @@ namespace crepe { //! 2D vector +template <class T> struct Vector2 { //! X component of the vector - double x = 0; + T x = 0; //! Y component of the vector - double y = 0; + T y = 0; //! Subtracts another vector from this vector and returns the result. - Vector2 operator-(const Vector2 & other) const; + Vector2 operator-(const Vector2<T> & other) const; + + //! Subtracts a scalar value from both components of this vector and returns the result. + Vector2 operator-(T scalar) const; //! Adds another vector to this vector and returns the result. - Vector2 operator+(const Vector2 & other) const; + Vector2 operator+(const Vector2<T> & other) const; + + //! Adds a scalar value to both components of this vector and returns the result. + Vector2 operator+(T scalar) const; + + //! Multiplies this vector by another vector element-wise and returns the result. + Vector2 operator*(const Vector2<T> & other) const; //! Multiplies this vector by a scalar and returns the result. - Vector2 operator*(double scalar) const; + Vector2 operator*(T scalar) const; - //! Multiplies this vector by another vector element-wise and updates this vector. - Vector2 & operator*=(const Vector2 & other); + //! Divides this vector by another vector element-wise and returns the result. + Vector2 operator/(const Vector2<T> & other) const; + + //! Divides this vector by a scalar and returns the result. + Vector2 operator/(T scalar) const; //! Adds another vector to this vector and updates this vector. - Vector2 & operator+=(const Vector2 & other); + Vector2 & operator+=(const Vector2<T> & other); //! Adds a scalar value to both components of this vector and updates this vector. - Vector2 & operator+=(double other); + Vector2 & operator+=(T other); + + //! Subtracts another vector from this vector and updates this vector. + Vector2 & operator-=(const Vector2<T> & other); + + //! Subtracts a scalar value from both components of this vector and updates this vector. + Vector2 & operator-=(T other); + + //! Multiplies this vector by another vector element-wise and updates this vector. + Vector2 & operator*=(const Vector2<T> & other); + + //! Multiplies this vector by a scalar and updates this vector. + Vector2 & operator*=(T other); + + //! Divides this vector by another vector element-wise and updates this vector. + Vector2 & operator/=(const Vector2<T> & other); + + //! Divides this vector by a scalar and updates this vector. + Vector2 & operator/=(T other); //! Returns the negation of this vector. Vector2 operator-() const; //! Checks if this vector is equal to another vector. - bool operator==(const Vector2 & other) const; + bool operator==(const Vector2<T> & other) const; //! Checks if this vector is not equal to another vector. - bool operator!=(const Vector2 & other) const; + bool operator!=(const Vector2<T> & other) const; }; } // namespace crepe + +#include "Vector2.hpp" diff --git a/src/crepe/api/Vector2.hpp b/src/crepe/api/Vector2.hpp new file mode 100644 index 0000000..cad15f8 --- /dev/null +++ b/src/crepe/api/Vector2.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include "Vector2.h" + +namespace crepe { + +template <class T> +Vector2<T> Vector2<T>::operator-(const Vector2<T> & other) const { + return {x - other.x, y - other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator-(T scalar) const { + return {x - scalar, y - scalar}; +} + +template <class T> +Vector2<T> Vector2<T>::operator+(const Vector2<T> & other) const { + return {x + other.x, y + other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator+(T scalar) const { + return {x + scalar, y + scalar}; +} + +template <class T> +Vector2<T> Vector2<T>::operator*(const Vector2<T> & other) const { + return {x * other.x, y * other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator*(T scalar) const { + return {x * scalar, y * scalar}; +} + +template <class T> +Vector2<T> Vector2<T>::operator/(const Vector2<T> & other) const { + return {x / other.x, y / other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator/(T scalar) const { + return {x / scalar, y / scalar}; +} + +template <class T> +Vector2<T> & Vector2<T>::operator+=(const Vector2<T> & other) { + x += other.x; + y += other.y; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator+=(T other) { + x += other; + y += other; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator-=(const Vector2<T> & other) { + x -= other.x; + y -= other.y; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator-=(T other) { + x -= other; + y -= other; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator*=(const Vector2<T> & other) { + x *= other.x; + y *= other.y; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator*=(T other) { + x *= other; + y *= other; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator/=(const Vector2<T> & other) { + x /= other.x; + y /= other.y; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator/=(T other) { + x /= other; + y /= other; + return *this; +} + +template <class T> +Vector2<T> Vector2<T>::operator-() const { + return {-x, -y}; +} + +template <class T> +bool Vector2<T>::operator==(const Vector2<T> & other) const { + return x == other.x && y == other.y; +} + +template <class T> +bool Vector2<T>::operator!=(const Vector2<T> & other) const { + return !(*this == other); +} + +} // namespace crepe diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index 00523a6..b3298a7 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -104,13 +104,13 @@ SDL_Rect SDLContext::get_src_rect(const Sprite & sprite) const { .h = sprite.sprite_rect.h, }; } -SDL_Rect SDLContext::get_dst_rect(const Sprite & sprite, const Vector2 & pos, +SDL_Rect SDLContext::get_dst_rect(const Sprite & sprite, const vec2 & pos, const double & scale, const Camera & cam) const { - double adjusted_x = (pos.x - cam.x) * cam.zoom; - double adjusted_y = (pos.y - cam.y) * cam.zoom; double adjusted_w = sprite.sprite_rect.w * scale * cam.zoom; double adjusted_h = sprite.sprite_rect.h * scale * cam.zoom; + double adjusted_x = (pos.x - cam.x) * cam.zoom - adjusted_w / 2; + double adjusted_y = (pos.y - cam.y) * cam.zoom - adjusted_h / 2; return SDL_Rect{ .x = static_cast<int>(adjusted_x), @@ -120,9 +120,8 @@ SDL_Rect SDLContext::get_dst_rect(const Sprite & sprite, const Vector2 & pos, }; } -void SDLContext::draw_particle(const Sprite & sprite, const Vector2 & pos, - const double & angle, const double & scale, - const Camera & camera) { +void SDLContext::draw_particle(const Sprite & sprite, const vec2 & pos, const double & angle, + const double & scale, const Camera & camera) { SDL_RendererFlip render_flip = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index 841ffc9..20e30b3 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -12,7 +12,8 @@ #include "../api/Sprite.h" #include "../api/Transform.h" #include "api/Camera.h" -#include "api/Vector2.h" + +#include "types.h" namespace crepe { @@ -120,7 +121,7 @@ private: */ void draw(const Sprite & sprite, const Transform & transform, const Camera & camera); - void draw_particle(const Sprite & sprite, const Vector2 & pos, const double & angle, + void draw_particle(const Sprite & sprite, const vec2 & pos, const double & angle, const double & scale, const Camera & camera); //! Clears the screen, preparing for a new frame. @@ -153,7 +154,7 @@ private: * on the camera * \return sdl rectangle to draw a dst image to draw on the screen */ - SDL_Rect get_dst_rect(const Sprite & sprite, const Vector2 & pos, const double & scale, + SDL_Rect get_dst_rect(const Sprite & sprite, const vec2 & pos, const double & scale, const Camera & cam) const; private: diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index fcf7522..0e62a57 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -4,7 +4,6 @@ #include "api/ParticleEmitter.h" #include "api/Transform.h" -#include "api/Vector2.h" #include "ComponentManager.h" #include "ParticleSystem.h" @@ -42,17 +41,15 @@ void ParticleSystem::update() { } void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform & transform) { - constexpr double DEG_TO_RAD = M_PI / 180.0; + constexpr float DEG_TO_RAD = M_PI / 180.0; - Vector2 initial_position = emitter.data.position + transform.position; - double random_angle - = generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); + vec2 initial_position = emitter.data.position + transform.position; + float random_angle = generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); - double random_speed - = generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); - double angle_radians = random_angle * DEG_TO_RAD; + float random_speed = generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); + float angle_radians = random_angle * DEG_TO_RAD; - Vector2 velocity + vec2 velocity = {random_speed * std::cos(angle_radians), random_speed * std::sin(angle_radians)}; for (Particle & particle : emitter.data.particles) { @@ -77,7 +74,7 @@ int ParticleSystem::calculate_update(int count, double emission) const { } void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform & transform) { - Vector2 offset = emitter.data.boundary.offset + transform.position + emitter.data.position; + vec2 offset = emitter.data.boundary.offset + transform.position + emitter.data.position; double half_width = emitter.data.boundary.width / 2.0; double half_height = emitter.data.boundary.height / 2.0; @@ -87,7 +84,7 @@ void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform & t const double BOTTOM = offset.y + half_height; for (Particle & particle : emitter.data.particles) { - const Vector2 & position = particle.position; + const vec2 & position = particle.position; bool within_bounds = (position.x >= LEFT && position.x <= RIGHT && position.y >= TOP && position.y <= BOTTOM); diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index bcde431..514a4b3 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -34,7 +34,7 @@ void PhysicsSystem::update() { if (rigidbody.data.angular_damping != 0) { rigidbody.data.angular_velocity *= rigidbody.data.angular_damping; } - if (rigidbody.data.linear_damping != Vector2{0, 0}) { + if (rigidbody.data.linear_damping != vec2{0, 0}) { rigidbody.data.linear_velocity *= rigidbody.data.linear_damping; } diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp index c33309c..20a83f7 100644 --- a/src/crepe/system/ScriptSystem.cpp +++ b/src/crepe/system/ScriptSystem.cpp @@ -1,5 +1,3 @@ -#include <functional> - #include "../ComponentManager.h" #include "../api/BehaviorScript.h" #include "../api/Script.h" @@ -12,30 +10,19 @@ using namespace crepe; void ScriptSystem::update() { dbg_trace(); - RefVector<Script> scripts = this->get_scripts(); - - for (auto & script_ref : scripts) { - Script & script = script_ref.get(); - if (!script.initialized) { - script.init(); - script.initialized = true; - } - script.update(); - } -} - -RefVector<Script> ScriptSystem::get_scripts() const { - RefVector<Script> scripts = {}; ComponentManager & mgr = this->component_manager; RefVector<BehaviorScript> behavior_scripts = mgr.get_components_by_type<BehaviorScript>(); - for (auto behavior_script_ref : behavior_scripts) { - BehaviorScript & behavior_script = behavior_script_ref.get(); + for (BehaviorScript & behavior_script : behavior_scripts) { if (!behavior_script.active) continue; + Script * script = behavior_script.script.get(); if (script == nullptr) continue; - scripts.push_back(*script); - } - return scripts; + if (!script->initialized) { + script->init(); + script->initialized = true; + } + script->update(); + } } diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h index 32e1fcd..936e9ca 100644 --- a/src/crepe/system/ScriptSystem.h +++ b/src/crepe/system/ScriptSystem.h @@ -2,8 +2,6 @@ #include "System.h" -#include "../types.h" - namespace crepe { class Script; @@ -25,15 +23,6 @@ public: * the \c BehaviorScript instance. */ void update() override; - -private: - /** - * \brief Aggregate all active \c BehaviorScript components and return a list - * of references to their \c Script instances (utility) - * - * \returns List of active \c Script instances - */ - RefVector<Script> get_scripts() const; }; } // namespace crepe diff --git a/src/crepe/types.h b/src/crepe/types.h index 914c76c..17f1619 100644 --- a/src/crepe/types.h +++ b/src/crepe/types.h @@ -1,5 +1,7 @@ #pragma once +#include "api/Vector2.h" + #include <cstdint> #include <functional> #include <vector> @@ -13,4 +15,16 @@ typedef uint32_t game_object_id_t; template <typename T> using RefVector = std::vector<std::reference_wrapper<T>>; +//! Default Vector2<int> type +typedef Vector2<int> ivec2; + +//! Default Vector2<unsigned int> type +typedef Vector2<unsigned int> uvec2; + +//! Default Vector2<float> type +typedef Vector2<float> vec2; + +//! Default Vector2<double> type +typedef Vector2<double> dvec2; + } // namespace crepe diff --git a/src/crepe/util/Log.h b/src/crepe/util/Log.h index d55b11e..fc0bb3a 100644 --- a/src/crepe/util/Log.h +++ b/src/crepe/util/Log.h @@ -34,11 +34,11 @@ class Log { public: //! Log message severity enum Level { - TRACE, //< Include (internal) function calls - DEBUG, //< Include dbg_logf output - INFO, //< General-purpose messages - WARNING, //< Non-fatal errors - ERROR, //< Fatal errors + TRACE, //!< Include (internal) function calls + DEBUG, //!< Include dbg_logf output + INFO, //!< General-purpose messages + WARNING, //!< Non-fatal errors + ERROR, //!< Fatal errors }; /** diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h index 253bc07..3201667 100644 --- a/src/crepe/util/OptionalRef.h +++ b/src/crepe/util/OptionalRef.h @@ -31,7 +31,7 @@ public: * * \throws std::runtime_error if this function is called while the reference it not set */ - operator T & () const; + operator T &() const; /** * \brief Check if this reference is not empty * diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp index ae7c73e..4608c9e 100644 --- a/src/crepe/util/OptionalRef.hpp +++ b/src/crepe/util/OptionalRef.hpp @@ -12,7 +12,7 @@ OptionalRef<T>::OptionalRef(T & ref) { } template <typename T> -OptionalRef<T>::operator T & () const { +OptionalRef<T>::operator T &() const { if (this->ref == nullptr) throw std::runtime_error("OptionalRef: attempt to dereference nullptr"); return *this->ref; diff --git a/src/doc/feature/scene.dox b/src/doc/feature/scene.dox index 5f34446..d81df4c 100644 --- a/src/doc/feature/scene.dox +++ b/src/doc/feature/scene.dox @@ -36,18 +36,16 @@ concrete scene to be added. #include <crepe/api/LoopManager.h> #include <crepe/api/GameObject.h> #include <crepe/api/Scene.h> -#include <crepe/api/Vector2.h> +#include <crepe/types.h> using namespace crepe; class MyScene : public Scene { public: - using Scene::Scene; - void load_scene() { - auto & mgr = this->component_manager; - GameObject object1 = mgr.new_object("object1", "tag_my_scene", Vector2{0, 0}, 0, 1); - GameObject object2 = mgr.new_object("object2", "tag_my_scene", Vector2{1, 0}, 0, 1); + ComponentManager & mgr = this->component_manager; + GameObject object1 = mgr.new_object("object1", "tag_my_scene", vec2{0, 0}, 0, 1); + GameObject object2 = mgr.new_object("object2", "tag_my_scene", vec2{1, 0}, 0, 1); } string get_name() const { return "my_scene"; } diff --git a/src/doc/internal/component.dox b/src/doc/internal/component.dox new file mode 100644 index 0000000..0dd4cb5 --- /dev/null +++ b/src/doc/internal/component.dox @@ -0,0 +1,41 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup internal_component Components +\ingroup internal +\brief ECS Components + +Components are attached to GameObject instances and are composed by the game +programmer to create specific entities in the game world. While they are +implemented as C++ classes, components should be treated as C-style structs, +meaning all members are public and they do not contain functions. + +A basic component has the following structure: +```cpp +#include <crepe/Component.h> + +class MyComponent : public crepe::Component { +public: + // Add your custom component's ininitializer properties after the `id` + // parameter. The first parameter is controlled by GameObject::add_component, + // while all other parameters are forwarded using std::forward. + MyComponent(game_object_id_t id, ...); + + // Optionally define the `get_instances_max` method to limit the amount of + // instances of this component per GameObject. The default implementation for + // this function returns -1, which means the instance count does not have an + // upper limit: + virtual int get_instances_max() const { return -1; } + + // Properties + // ... +}; +``` + +Generally, components are "handled" by \ref internal_system "systems", which may +optionally change the components' state. Components' state may also be +controlled by the game programmer through \ref feature_script "scripts". + +*/ +} diff --git a/src/doc/internal/resource.dox b/src/doc/internal/resource.dox new file mode 100644 index 0000000..56f1de0 --- /dev/null +++ b/src/doc/internal/resource.dox @@ -0,0 +1,12 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup internal_resource Resources +\ingroup internal +\brief Concrete resources + +\todo This section is incomplete + +*/ +} diff --git a/src/doc/internal/style.dox b/src/doc/internal/style.dox new file mode 100644 index 0000000..dad2df0 --- /dev/null +++ b/src/doc/internal/style.dox @@ -0,0 +1,9 @@ +// vim:ft=doxygen +/** + +\defgroup internal_style Code style +\ingroup internal +\brief Coding conventions +\include{doc} contributing.md + +*/ diff --git a/src/doc/internal/system.dox b/src/doc/internal/system.dox new file mode 100644 index 0000000..17a101e --- /dev/null +++ b/src/doc/internal/system.dox @@ -0,0 +1,26 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup internal_system Systems +\ingroup internal +\brief ECS Systems + +\todo This section is incomplete + +A system is responsible for processing the data stored in \ref +internal_component "components". + +A basic system has the following structure: +```cpp +#include <crepe/system/System.h> + +class MySystem : public System { +public: + using System::System; + void update() override; +}; +``` + +*/ +} diff --git a/src/doc/internals.dox b/src/doc/internals.dox new file mode 100644 index 0000000..2d2ca56 --- /dev/null +++ b/src/doc/internals.dox @@ -0,0 +1,10 @@ +// vim:ft=doxygen +/** + +\defgroup internal Internals +\brief Internal engine structure and other conventions + +\todo This page is incomplete +\todo Anything about Contexts? + +*/ diff --git a/src/doc/layout.xml b/src/doc/layout.xml index 2244fa7..fb4cc0c 100644 --- a/src/doc/layout.xml +++ b/src/doc/layout.xml @@ -1,10 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <doxygenlayout version="1.0"> <navindex> - <tab type="mainpage" visible="yes" title=""/> + <tab type="mainpage" visible="yes" title="Intro"/> + <tab type="user" url="@ref install" title="Installation"/> + <tab type="user" url="@ref feature" title="Features"/> + <tab type="user" url="@ref internal" title="Internals"/> <tab type="pages" visible="no" title="" intro=""/> - <tab type="topics" visible="yes" title="" intro=""/> - <tab type="modules" visible="yes" title="" intro=""> + <tab type="topics" visible="no" title="" intro=""/> + <tab type="modules" visible="no" title="" intro=""> <tab type="modulelist" visible="yes" title="" intro=""/> <tab type="modulemembers" visible="yes" title="" intro=""/> </tab> @@ -12,9 +15,9 @@ <tab type="namespacelist" visible="yes" title="" intro=""/> <tab type="namespacemembers" visible="yes" title="" intro=""/> </tab> - <tab type="concepts" visible="yes" title=""> + <tab type="concepts" visible="no" title=""> </tab> - <tab type="interfaces" visible="yes" title=""> + <tab type="interfaces" visible="no" title=""> <tab type="interfacelist" visible="yes" title="" intro=""/> <tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/> <tab type="interfacehierarchy" visible="yes" title="" intro=""/> @@ -25,23 +28,24 @@ <tab type="hierarchy" visible="yes" title="" intro=""/> <tab type="classmembers" visible="yes" title="" intro=""/> </tab> - <tab type="structs" visible="yes" title=""> + <tab type="structs" visible="no" title=""> <tab type="structlist" visible="yes" title="" intro=""/> <tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/> </tab> - <tab type="exceptions" visible="yes" title=""> + <tab type="exceptions" visible="no" title=""> <tab type="exceptionlist" visible="yes" title="" intro=""/> <tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/> <tab type="exceptionhierarchy" visible="yes" title="" intro=""/> </tab> - <tab type="files" visible="yes" title=""> + <tab type="files" visible="no" title=""> <tab type="filelist" visible="yes" title="" intro=""/> <tab type="globals" visible="yes" title="" intro=""/> </tab> - <tab type="examples" visible="yes" title="" intro=""/> + <tab type="examples" visible="no" title="" intro=""/> </navindex> <class> <briefdescription visible="yes"/> + <detaileddescription title=""/> <includes visible="$SHOW_HEADERFILE"/> <inheritancegraph visible="yes"/> <collaborationgraph visible="yes"/> @@ -79,7 +83,6 @@ <related title="" subtitle=""/> <membergroups visible="yes"/> </memberdecl> - <detaileddescription title=""/> <memberdef> <inlineclasses title=""/> <typedefs title=""/> diff --git a/src/doc/style.css b/src/doc/style.css index 08bc9f5..daabd39 100644 --- a/src/doc/style.css +++ b/src/doc/style.css @@ -2,3 +2,5 @@ address { display: none; } + +h2.groupheader { margin-top: revert; } diff --git a/src/example/rendering_particle.cpp b/src/example/rendering_particle.cpp index 4571afb..b38e860 100644 --- a/src/example/rendering_particle.cpp +++ b/src/example/rendering_particle.cpp @@ -1,5 +1,6 @@ #include "api/Camera.h" #include "system/ParticleSystem.h" +#include "types.h" #include <SDL2/SDL_timer.h> #include <crepe/ComponentManager.h> @@ -23,14 +24,14 @@ using namespace std; int main(int argc, char * argv[]) { ComponentManager mgr; - GameObject game_object = mgr.new_object("", "", Vector2{100, 100}, 0, 0.1); + GameObject game_object = mgr.new_object("", "", vec2{0, 0}, 0, 0.1); RenderSystem sys{mgr}; ParticleSystem psys{mgr}; Color color(255, 255, 255, 255); Sprite & test_sprite = game_object.add_component<Sprite>( - make_shared<Texture>("../asset/texture/img.png"), color, FlipSettings{false, false}); + make_shared<Texture>("asset/texture/img.png"), color, FlipSettings{false, false}); test_sprite.order_in_layer = 5; auto & test = game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{ @@ -43,11 +44,11 @@ int main(int argc, char * argv[]) { .max_angle = 20, .begin_lifespan = 0, .end_lifespan = 60, - .force_over_time = Vector2{0, 0}, + .force_over_time = vec2{0, 0}, .boundary{ .width = 1000, .height = 1000, - .offset = Vector2{0, 0}, + .offset = vec2{0, 0}, .reset_on_exit = false, }, .sprite = test_sprite, @@ -55,7 +56,7 @@ int main(int argc, char * argv[]) { game_object.add_component<Camera>(Color::WHITE); game_object - .add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), color, + .add_component<Sprite>(make_shared<Texture>("asset/texture/img.png"), color, FlipSettings{false, false}) .order_in_layer = 6; diff --git a/src/test/AssetTest.cpp b/src/test/AssetTest.cpp index 8aa7629..93fd6a9 100644 --- a/src/test/AssetTest.cpp +++ b/src/test/AssetTest.cpp @@ -7,17 +7,12 @@ using namespace std; using namespace crepe; using namespace testing; -class AssetTest : public Test { -public: - Config & cfg = Config::get_instance(); - void SetUp() override { this->cfg.asset.root_pattern = ".crepe-root"; } -}; - -TEST_F(AssetTest, Existant) { ASSERT_NO_THROW(Asset{"asset/texture/img.png"}); } +TEST(AssetTest, Existant) { ASSERT_NO_THROW(Asset{"asset/texture/img.png"}); } -TEST_F(AssetTest, Nonexistant) { ASSERT_ANY_THROW(Asset{"asset/nonexistant"}); } +TEST(AssetTest, Nonexistant) { ASSERT_ANY_THROW(Asset{"asset/nonexistant"}); } -TEST_F(AssetTest, Rootless) { +TEST(AssetTest, Rootless) { + Config & cfg = Config::get_instance(); cfg.asset.root_pattern.clear(); string arbitrary = "\\/this is / /../passed through as-is"; diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 8cb4232..d310f6a 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -11,5 +11,5 @@ target_sources(test_main PUBLIC SceneManagerTest.cpp ValueBrokerTest.cpp DBTest.cpp + Vector2Test.cpp ) - diff --git a/src/test/ECSTest.cpp b/src/test/ECSTest.cpp index d5a5826..80b936b 100644 --- a/src/test/ECSTest.cpp +++ b/src/test/ECSTest.cpp @@ -17,7 +17,7 @@ public: }; TEST_F(ECSTest, createGameObject) { - GameObject obj = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); + GameObject obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); @@ -37,8 +37,8 @@ TEST_F(ECSTest, createGameObject) { } TEST_F(ECSTest, deleteAllGameObjects) { - GameObject obj0 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); - GameObject obj1 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); + GameObject obj0 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); mgr.delete_all_components(); @@ -48,7 +48,7 @@ TEST_F(ECSTest, deleteAllGameObjects) { EXPECT_EQ(metadata.size(), 0); EXPECT_EQ(transform.size(), 0); - GameObject obj2 = mgr.new_object("body2", "person2", Vector2{1, 0}, 5, 1); + GameObject obj2 = mgr.new_object("body2", "person2", vec2{1, 0}, 5, 1); metadata = mgr.get_components_by_type<Metadata>(); transform = mgr.get_components_by_type<Transform>(); @@ -70,8 +70,8 @@ TEST_F(ECSTest, deleteAllGameObjects) { } TEST_F(ECSTest, deleteGameObject) { - GameObject obj0 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); - GameObject obj1 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); + GameObject obj0 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); mgr.delete_all_components_of_id(0); @@ -96,7 +96,7 @@ TEST_F(ECSTest, deleteGameObject) { TEST_F(ECSTest, manyGameObjects) { for (int i = 0; i < 5000; i++) { - GameObject obj = mgr.new_object("body", "person", Vector2{0, 0}, 0, i); + GameObject obj = mgr.new_object("body", "person", vec2{0, 0}, 0, i); } vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); @@ -128,7 +128,7 @@ TEST_F(ECSTest, manyGameObjects) { for (int i = 0; i < 10000 - 5000; i++) { string tag = "person" + to_string(i); - GameObject obj = mgr.new_object("body", tag, Vector2{0, 0}, i, 0); + GameObject obj = mgr.new_object("body", tag, vec2{0, 0}, i, 0); } metadata = mgr.get_components_by_type<Metadata>(); @@ -139,8 +139,8 @@ TEST_F(ECSTest, manyGameObjects) { } TEST_F(ECSTest, getComponentsByID) { - GameObject obj0 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); - GameObject obj1 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); + GameObject obj0 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_id<Metadata>(0); vector<reference_wrapper<Transform>> transform = mgr.get_components_by_id<Transform>(1); @@ -163,15 +163,15 @@ TEST_F(ECSTest, getComponentsByID) { TEST_F(ECSTest, tooMuchComponents) { try { - GameObject obj0 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); - obj0.add_component<Transform>(Vector2{10, 10}, 0, 1); + GameObject obj0 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + obj0.add_component<Transform>(vec2{10, 10}, 0, 1); } catch (const exception & e) { EXPECT_EQ(e.what(), string("Exceeded maximum number of instances for this component type")); } try { - GameObject obj1 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); obj1.add_component<Metadata>("body", "person"); } catch (const exception & e) { EXPECT_EQ(e.what(), @@ -187,11 +187,11 @@ TEST_F(ECSTest, tooMuchComponents) { TEST_F(ECSTest, partentChild) { { - GameObject body = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); - GameObject right_leg = mgr.new_object("rightLeg", "person", Vector2{1, 1}, 0, 1); - GameObject left_leg = mgr.new_object("leftLeg", "person", Vector2{1, 1}, 0, 1); - GameObject right_foot = mgr.new_object("rightFoot", "person", Vector2{2, 2}, 0, 1); - GameObject left_foot = mgr.new_object("leftFoot", "person", Vector2{2, 2}, 0, 1); + GameObject body = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + GameObject right_leg = mgr.new_object("rightLeg", "person", vec2{1, 1}, 0, 1); + GameObject left_leg = mgr.new_object("leftLeg", "person", vec2{1, 1}, 0, 1); + GameObject right_foot = mgr.new_object("rightFoot", "person", vec2{2, 2}, 0, 1); + GameObject left_foot = mgr.new_object("leftFoot", "person", vec2{2, 2}, 0, 1); // Set the parent of each GameObject right_foot.set_parent(right_leg); diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp index 1c69348..83f7b23 100644 --- a/src/test/OptionalRefTest.cpp +++ b/src/test/OptionalRefTest.cpp @@ -18,9 +18,7 @@ TEST(OptionalRefTest, Normal) { ref.clear(); EXPECT_FALSE(ref); - ASSERT_THROW({ - string & value_ref = ref; - }, runtime_error); + ASSERT_THROW({ string & value_ref = ref; }, runtime_error); } TEST(OptionalRefTest, Empty) { @@ -28,9 +26,7 @@ TEST(OptionalRefTest, Empty) { OptionalRef<string> ref; EXPECT_FALSE(ref); - ASSERT_THROW({ - string & value_ref = ref; - }, runtime_error); + ASSERT_THROW({ string & value_ref = ref; }, runtime_error); } TEST(OptionalRefTest, Chain) { @@ -44,4 +40,3 @@ TEST(OptionalRefTest, Chain) { value_ref = "bar"; EXPECT_EQ(value_ref, value); } - diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index eee022f..8b81e74 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -25,7 +25,7 @@ public: std::vector<std::reference_wrapper<Transform>> transforms = mgr.get_components_by_id<Transform>(0); if (transforms.empty()) { - GameObject game_object = mgr.new_object("", "", Vector2{0, 0}, 0, 0); + GameObject game_object = mgr.new_object("", "", vec2{0, 0}, 0, 0); Color color(0, 0, 0, 0); Sprite & test_sprite = game_object.add_component<Sprite>( @@ -42,11 +42,11 @@ public: .max_angle = 0, .begin_lifespan = 0, .end_lifespan = 0, - .force_over_time = Vector2{0, 0}, + .force_over_time = vec2{0, 0}, .boundary{ .width = 0, .height = 0, - .offset = Vector2{0, 0}, + .offset = vec2{0, 0}, .reset_on_exit = false, }, .sprite = test_sprite, @@ -68,8 +68,8 @@ public: emitter.data.max_angle = 0; emitter.data.begin_lifespan = 0; emitter.data.end_lifespan = 0; - emitter.data.force_over_time = Vector2{0, 0}; - emitter.data.boundary = {0, 0, Vector2{0, 0}, false}; + emitter.data.force_over_time = vec2{0, 0}; + emitter.data.boundary = {0, 0, vec2{0, 0}, false}; for (auto & particle : emitter.data.particles) { particle.active = false; } diff --git a/src/test/PhysicsTest.cpp b/src/test/PhysicsTest.cpp index 1e37c26..33b6020 100644 --- a/src/test/PhysicsTest.cpp +++ b/src/test/PhysicsTest.cpp @@ -20,12 +20,12 @@ public: vector<reference_wrapper<Transform>> transforms = mgr.get_components_by_id<Transform>(0); if (transforms.empty()) { - auto entity = mgr.new_object("", "", Vector2{0, 0}, 0, 0); + auto entity = mgr.new_object("", "", vec2{0, 0}, 0, 0); entity.add_component<Rigidbody>(Rigidbody::Data{ .mass = 1, .gravity_scale = 1, .body_type = Rigidbody::BodyType::DYNAMIC, - .max_linear_velocity = Vector2{10, 10}, + .max_linear_velocity = vec2{10, 10}, .max_angular_velocity = 10, .constraints = {0, 0}, .use_gravity = true, diff --git a/src/test/RenderSystemTest.cpp b/src/test/RenderSystemTest.cpp index ac479d3..f37fb56 100644 --- a/src/test/RenderSystemTest.cpp +++ b/src/test/RenderSystemTest.cpp @@ -30,7 +30,7 @@ public: void SetUp() override { auto & sprite1 - = entity1.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), + = entity1.add_component<Sprite>(make_shared<Texture>("asset/texture/img.png"), Color(0, 0, 0, 0), FlipSettings{false, false}); ASSERT_NE(sprite1.sprite_image.get(), nullptr); sprite1.order_in_layer = 5; @@ -38,7 +38,7 @@ public: EXPECT_EQ(sprite1.order_in_layer, 5); EXPECT_EQ(sprite1.sorting_in_layer, 5); auto & sprite2 - = entity2.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), + = entity2.add_component<Sprite>(make_shared<Texture>("asset/texture/img.png"), Color(0, 0, 0, 0), FlipSettings{false, false}); ASSERT_NE(sprite2.sprite_image.get(), nullptr); sprite2.sorting_in_layer = 2; @@ -48,7 +48,7 @@ public: EXPECT_EQ(sprite2.order_in_layer, 1); auto & sprite3 - = entity3.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), + = entity3.add_component<Sprite>(make_shared<Texture>("asset/texture/img.png"), Color(0, 0, 0, 0), FlipSettings{false, false}); ASSERT_NE(sprite3.sprite_image.get(), nullptr); sprite3.sorting_in_layer = 1; @@ -58,7 +58,7 @@ public: EXPECT_EQ(sprite3.order_in_layer, 2); auto & sprite4 - = entity4.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), + = entity4.add_component<Sprite>(make_shared<Texture>("asset/texture/img.png"), Color(0, 0, 0, 0), FlipSettings{false, false}); ASSERT_NE(sprite4.sprite_image.get(), nullptr); sprite4.sorting_in_layer = 1; diff --git a/src/test/SceneManagerTest.cpp b/src/test/SceneManagerTest.cpp index dab2ce9..62b7d33 100644 --- a/src/test/SceneManagerTest.cpp +++ b/src/test/SceneManagerTest.cpp @@ -1,3 +1,4 @@ +#include "types.h" #include <crepe/ComponentManager.h> #include <crepe/api/GameObject.h> #include <crepe/api/Metadata.h> @@ -12,31 +13,42 @@ using namespace crepe; class ConcreteScene1 : public Scene { public: - using Scene::Scene; - void load_scene() { - auto & mgr = this->component_manager; - GameObject object1 = mgr.new_object("scene_1", "tag_scene_1", Vector2{0, 0}, 0, 1); - GameObject object2 = mgr.new_object("scene_1", "tag_scene_1", Vector2{1, 0}, 0, 1); - GameObject object3 = mgr.new_object("scene_1", "tag_scene_1", Vector2{2, 0}, 0, 1); + ComponentManager & mgr = this->component_manager; + GameObject object1 = mgr.new_object("scene_1", "tag_scene_1", vec2{0, 0}, 0, 1); + GameObject object2 = mgr.new_object("scene_1", "tag_scene_1", vec2{1, 0}, 0, 1); + GameObject object3 = mgr.new_object("scene_1", "tag_scene_1", vec2{2, 0}, 0, 1); } - string get_name() const { return "scene1";} + string get_name() const { return "scene1"; } }; class ConcreteScene2 : public Scene { public: - using Scene::Scene; + void load_scene() { + ComponentManager & mgr = this->component_manager; + GameObject object1 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 0}, 0, 1); + GameObject object2 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 1}, 0, 1); + GameObject object3 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 2}, 0, 1); + GameObject object4 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 3}, 0, 1); + } + + string get_name() const { return "scene2"; } +}; + +class ConcreteScene3 : public Scene { +public: + ConcreteScene3(const string & name) : name(name) {} void load_scene() { - auto & mgr = this->component_manager; - GameObject object1 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 0}, 0, 1); - GameObject object2 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 1}, 0, 1); - GameObject object3 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 2}, 0, 1); - GameObject object4 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 3}, 0, 1); + ComponentManager & mgr = this->component_manager; + GameObject object1 = mgr.new_object("scene_3", "tag_scene_3", vec2{0, 0}, 0, 1); } - string get_name() const { return "scene2";} + string get_name() const { return name; } + +private: + const string name; }; class SceneManagerTest : public ::testing::Test { @@ -124,3 +136,25 @@ TEST_F(SceneManagerTest, loadScene) { EXPECT_EQ(transform[3].get().position.x, 0); EXPECT_EQ(transform[3].get().position.y, 3); } + +TEST_F(SceneManagerTest, perfectForwarding) { + scene_mgr.add_scene<ConcreteScene3>("scene3"); + + scene_mgr.load_next_scene(); + + vector<reference_wrapper<Metadata>> metadata + = component_mgr.get_components_by_type<Metadata>(); + vector<reference_wrapper<Transform>> transform + = component_mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 1); + + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[0].get().name, "scene_3"); + EXPECT_EQ(metadata[0].get().tag, "tag_scene_3"); + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[0].get().children.size(), 0); + EXPECT_EQ(transform[0].get().position.x, 0); + EXPECT_EQ(transform[0].get().position.y, 0); +} diff --git a/src/test/ScriptTest.cpp b/src/test/ScriptTest.cpp index 19fef6d..78d5061 100644 --- a/src/test/ScriptTest.cpp +++ b/src/test/ScriptTest.cpp @@ -6,6 +6,8 @@ #include <crepe/ComponentManager.h> #include <crepe/api/BehaviorScript.h> +#include <crepe/api/Event.h> +#include <crepe/api/EventManager.h> #include <crepe/api/GameObject.h> #include <crepe/api/Script.h> #include <crepe/api/Vector2.h> @@ -15,58 +17,113 @@ using namespace std; using namespace crepe; using namespace testing; +class MyEvent : public Event {}; + class ScriptTest : public Test { public: ComponentManager component_manager{}; ScriptSystem system{component_manager}; + EventManager & event_manager = EventManager::get_instance(); class MyScript : public Script { // NOTE: default (private) visibility of init and update shouldn't cause // issues! - void init() { this->init_count++; } + void init() { + this->init_count++; + + subscribe<MyEvent>([this](const MyEvent &) { + this->event_count++; + return true; + }); + + // init should never be called more than once + EXPECT_LE(this->init_count, 1); + } void update() { this->update_count++; } public: unsigned init_count = 0; unsigned update_count = 0; + unsigned event_count = 0; }; - BehaviorScript * behaviorscript_ref = nullptr; - MyScript * script_ref = nullptr; + OptionalRef<BehaviorScript> behaviorscript; + OptionalRef<MyScript> script; void SetUp() override { auto & mgr = this->component_manager; GameObject entity = mgr.new_object("name"); BehaviorScript & component = entity.add_component<BehaviorScript>(); - this->behaviorscript_ref = &component; - EXPECT_EQ(this->behaviorscript_ref->script.get(), nullptr); + this->behaviorscript = component; + ASSERT_TRUE(this->behaviorscript); + EXPECT_EQ(component.script.get(), nullptr); component.set_script<MyScript>(); - ASSERT_NE(this->behaviorscript_ref->script.get(), nullptr); + ASSERT_NE(component.script.get(), nullptr); - this->script_ref = (MyScript *) this->behaviorscript_ref->script.get(); - ASSERT_NE(this->script_ref, nullptr); + this->script = *(MyScript *) component.script.get(); + ASSERT_TRUE(this->script); + + // sanity + MyScript & script = this->script; + ASSERT_EQ(script.init_count, 0); + ASSERT_EQ(script.update_count, 0); + ASSERT_EQ(script.event_count, 0); } }; TEST_F(ScriptTest, Default) { - EXPECT_EQ(0, this->script_ref->init_count); - EXPECT_EQ(0, this->script_ref->update_count); + MyScript & script = this->script; + EXPECT_EQ(0, script.init_count); + EXPECT_EQ(0, script.update_count); + EXPECT_EQ(0, script.event_count); } TEST_F(ScriptTest, UpdateOnce) { - EXPECT_EQ(0, this->script_ref->init_count); - EXPECT_EQ(0, this->script_ref->update_count); + MyScript & script = this->script; - this->system.update(); - EXPECT_EQ(1, this->script_ref->init_count); - EXPECT_EQ(1, this->script_ref->update_count); + system.update(); + EXPECT_EQ(1, script.init_count); + EXPECT_EQ(1, script.update_count); + EXPECT_EQ(0, script.event_count); } -TEST_F(ScriptTest, ListScripts) { - size_t script_count = 0; - for (auto & _ : this->system.get_scripts()) { - script_count++; - } - ASSERT_EQ(1, script_count); +TEST_F(ScriptTest, UpdateInactive) { + BehaviorScript & behaviorscript = this->behaviorscript; + MyScript & script = this->script; + + behaviorscript.active = false; + system.update(); + EXPECT_EQ(0, script.init_count); + EXPECT_EQ(0, script.update_count); + EXPECT_EQ(0, script.event_count); + + behaviorscript.active = true; + system.update(); + EXPECT_EQ(1, script.init_count); + EXPECT_EQ(1, script.update_count); + EXPECT_EQ(0, script.event_count); +} + +TEST_F(ScriptTest, EventInactive) { + BehaviorScript & behaviorscript = this->behaviorscript; + MyScript & script = this->script; + EventManager & evmgr = this->event_manager; + + system.update(); + behaviorscript.active = false; + EXPECT_EQ(1, script.init_count); + EXPECT_EQ(1, script.update_count); + EXPECT_EQ(0, script.event_count); + + evmgr.trigger_event<MyEvent>(); + EXPECT_EQ(1, script.init_count); + EXPECT_EQ(1, script.update_count); + EXPECT_EQ(0, script.event_count); + + behaviorscript.active = true; + evmgr.trigger_event<MyEvent>(); + EXPECT_EQ(1, script.init_count); + EXPECT_EQ(1, script.update_count); + EXPECT_EQ(1, script.event_count); } diff --git a/src/test/Vector2Test.cpp b/src/test/Vector2Test.cpp new file mode 100644 index 0000000..17bca41 --- /dev/null +++ b/src/test/Vector2Test.cpp @@ -0,0 +1,384 @@ +#include <gtest/gtest.h> + +#include <crepe/api/Vector2.h> + +using namespace crepe; + +class Vector2Test : public ::testing::Test { +public: + Vector2<int> int_vec1; + Vector2<int> int_vec2; + Vector2<double> double_vec1; + Vector2<double> double_vec2; + Vector2<long> long_vec1; + Vector2<long> long_vec2; + Vector2<float> float_vec1; + Vector2<float> float_vec2; + + void SetUp() override { + int_vec1 = {1, 2}; + int_vec2 = {3, 4}; + double_vec1 = {1.0, 2.0}; + double_vec2 = {3.0, 4.0}; + long_vec1 = {1, 2}; + long_vec2 = {3, 4}; + float_vec1 = {1.0f, 2.0f}; + float_vec2 = {3.0f, 4.0f}; + } +}; + +TEST_F(Vector2Test, Subtract) { + Vector2<int> result = int_vec1 - int_vec2; + EXPECT_EQ(result.x, -2); + EXPECT_EQ(result.y, -2); + + Vector2<double> result2 = double_vec1 - double_vec2; + EXPECT_FLOAT_EQ(result2.x, -2.0); + EXPECT_FLOAT_EQ(result2.y, -2.0); + + Vector2<long> result3 = long_vec1 - long_vec2; + EXPECT_EQ(result3.x, -2); + EXPECT_EQ(result3.y, -2); + + Vector2<float> result4 = float_vec1 - float_vec2; + EXPECT_FLOAT_EQ(result4.x, -2.0f); + EXPECT_FLOAT_EQ(result4.y, -2.0f); +} + +TEST_F(Vector2Test, SubtractScalar) { + Vector2<int> result = int_vec1 - 1; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 1); + + Vector2<double> result2 = double_vec1 - 1.0; + EXPECT_FLOAT_EQ(result2.x, 0.0); + EXPECT_FLOAT_EQ(result2.y, 1.0); + + Vector2<long> result3 = long_vec1 - 1; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 1); + + Vector2<float> result4 = float_vec1 - 1.0f; + EXPECT_FLOAT_EQ(result4.x, 0.0f); + EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, Add) { + Vector2<int> result = int_vec1 + int_vec2; + EXPECT_EQ(result.x, 4); + EXPECT_EQ(result.y, 6); + + Vector2<double> result2 = double_vec1 + double_vec2; + EXPECT_FLOAT_EQ(result2.x, 4.0); + EXPECT_FLOAT_EQ(result2.y, 6.0); + + Vector2<long> result3 = long_vec1 + long_vec2; + EXPECT_EQ(result3.x, 4); + EXPECT_EQ(result3.y, 6); + + Vector2<float> result4 = float_vec1 + float_vec2; + EXPECT_FLOAT_EQ(result4.x, 4.0f); + EXPECT_FLOAT_EQ(result4.y, 6.0f); +} + +TEST_F(Vector2Test, AddScalar) { + Vector2<int> result = int_vec1 + 1; + EXPECT_EQ(result.x, 2); + EXPECT_EQ(result.y, 3); + + Vector2<double> result2 = double_vec1 + 1.0; + EXPECT_FLOAT_EQ(result2.x, 2.0); + EXPECT_FLOAT_EQ(result2.y, 3.0); + + Vector2<long> result3 = long_vec1 + 1; + EXPECT_EQ(result3.x, 2); + EXPECT_EQ(result3.y, 3); + + Vector2<float> result4 = float_vec1 + 1.0f; + EXPECT_FLOAT_EQ(result4.x, 2.0f); + EXPECT_FLOAT_EQ(result4.y, 3.0f); +} + +TEST_F(Vector2Test, Multiply) { + Vector2<int> result = int_vec1 * int_vec2; + EXPECT_EQ(result.x, 3); + EXPECT_EQ(result.y, 8); + + Vector2<double> result2 = double_vec1 * double_vec2; + EXPECT_FLOAT_EQ(result2.x, 3.0); + EXPECT_FLOAT_EQ(result2.y, 8.0); + + Vector2<long> result3 = long_vec1 * long_vec2; + EXPECT_EQ(result3.x, 3); + EXPECT_EQ(result3.y, 8); + + Vector2<float> result4 = float_vec1 * float_vec2; + EXPECT_FLOAT_EQ(result4.x, 3.0f); + EXPECT_FLOAT_EQ(result4.y, 8.0f); +} + +TEST_F(Vector2Test, MultiplyScalar) { + Vector2<int> result = int_vec1 * 2; + EXPECT_EQ(result.x, 2); + EXPECT_EQ(result.y, 4); + + Vector2<double> result2 = double_vec1 * 2.0; + EXPECT_FLOAT_EQ(result2.x, 2.0); + EXPECT_FLOAT_EQ(result2.y, 4.0); + + Vector2<long> result3 = long_vec1 * 2; + EXPECT_EQ(result3.x, 2); + EXPECT_EQ(result3.y, 4); + + Vector2<float> result4 = float_vec1 * 2.0f; + EXPECT_FLOAT_EQ(result4.x, 2.0f); + EXPECT_FLOAT_EQ(result4.y, 4.0f); +} + +TEST_F(Vector2Test, Divide) { + Vector2<int> result = int_vec1 / int_vec2; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 0); + + Vector2<double> result2 = double_vec1 / double_vec2; + EXPECT_FLOAT_EQ(result2.x, 0.33333333333333331); + EXPECT_FLOAT_EQ(result2.y, 0.5); + + Vector2<long> result3 = long_vec1 / long_vec2; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 0); + + Vector2<float> result4 = float_vec1 / float_vec2; + EXPECT_FLOAT_EQ(result4.x, 0.333333343f); + EXPECT_FLOAT_EQ(result4.y, 0.5f); +} + +TEST_F(Vector2Test, DivideScalar) { + Vector2<int> result = int_vec1 / 2; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 1); + + Vector2<double> result2 = double_vec1 / 2.0; + EXPECT_FLOAT_EQ(result2.x, 0.5); + EXPECT_FLOAT_EQ(result2.y, 1.0); + + Vector2<long> result3 = long_vec1 / 2; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 1); + + Vector2<float> result4 = float_vec1 / 2.0f; + EXPECT_FLOAT_EQ(result4.x, 0.5f); + EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, AddChain) { + Vector2<int> result = int_vec1; + result += int_vec2; + EXPECT_EQ(result.x, 4); + EXPECT_EQ(result.y, 6); + + Vector2<double> result2 = double_vec1; + result2 += double_vec2; + EXPECT_FLOAT_EQ(result2.x, 4.0); + EXPECT_FLOAT_EQ(result2.y, 6.0); + + Vector2<long> result3 = long_vec1; + result3 += long_vec2; + EXPECT_EQ(result3.x, 4); + EXPECT_EQ(result3.y, 6); + + Vector2<float> result4 = float_vec1; + result4 += float_vec2; + EXPECT_FLOAT_EQ(result4.x, 4.0f); + EXPECT_FLOAT_EQ(result4.y, 6.0f); +} + +TEST_F(Vector2Test, AddScalarChain) { + Vector2<int> result = int_vec1; + result += 1; + EXPECT_EQ(result.x, 2); + EXPECT_EQ(result.y, 3); + + Vector2<double> result2 = double_vec1; + result2 += 1.0; + EXPECT_FLOAT_EQ(result2.x, 2.0); + EXPECT_FLOAT_EQ(result2.y, 3.0); + + Vector2<long> result3 = long_vec1; + result3 += 1; + EXPECT_EQ(result3.x, 2); + EXPECT_EQ(result3.y, 3); + + Vector2<float> result4 = float_vec1; + result4 += 1.0f; + EXPECT_FLOAT_EQ(result4.x, 2.0f); + EXPECT_FLOAT_EQ(result4.y, 3.0f); +} + +TEST_F(Vector2Test, SubtractChain) { + Vector2<int> result = int_vec1; + result -= int_vec2; + EXPECT_EQ(result.x, -2); + EXPECT_EQ(result.y, -2); + + Vector2<double> result2 = double_vec1; + result2 -= double_vec2; + EXPECT_FLOAT_EQ(result2.x, -2.0); + EXPECT_FLOAT_EQ(result2.y, -2.0); + + Vector2<long> result3 = long_vec1; + result3 -= long_vec2; + EXPECT_EQ(result3.x, -2); + EXPECT_EQ(result3.y, -2); + + Vector2<float> result4 = float_vec1; + result4 -= float_vec2; + EXPECT_FLOAT_EQ(result4.x, -2.0f); + EXPECT_FLOAT_EQ(result4.y, -2.0f); +} + +TEST_F(Vector2Test, SubtractScalarChain) { + Vector2<int> result = int_vec1; + result -= 1; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 1); + + Vector2<double> result2 = double_vec1; + result2 -= 1.0; + EXPECT_FLOAT_EQ(result2.x, 0.0); + EXPECT_FLOAT_EQ(result2.y, 1.0); + + Vector2<long> result3 = long_vec1; + result3 -= 1; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 1); + + Vector2<float> result4 = float_vec1; + result4 -= 1.0f; + EXPECT_FLOAT_EQ(result4.x, 0.0f); + EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, MultiplyChain) { + Vector2<int> result = int_vec1; + result *= int_vec2; + EXPECT_EQ(result.x, 3); + EXPECT_EQ(result.y, 8); + + Vector2<double> result2 = double_vec1; + result2 *= double_vec2; + EXPECT_FLOAT_EQ(result2.x, 3.0); + EXPECT_FLOAT_EQ(result2.y, 8.0); + + Vector2<long> result3 = long_vec1; + result3 *= long_vec2; + EXPECT_EQ(result3.x, 3); + EXPECT_EQ(result3.y, 8); + + Vector2<float> result4 = float_vec1; + result4 *= float_vec2; + EXPECT_FLOAT_EQ(result4.x, 3.0f); + EXPECT_FLOAT_EQ(result4.y, 8.0f); +} + +TEST_F(Vector2Test, MultiplyScalarChain) { + Vector2<int> result = int_vec1; + result *= 2; + EXPECT_EQ(result.x, 2); + EXPECT_EQ(result.y, 4); + + Vector2<double> result2 = double_vec1; + result2 *= 2.0; + EXPECT_FLOAT_EQ(result2.x, 2.0); + EXPECT_FLOAT_EQ(result2.y, 4.0); + + Vector2<long> result3 = long_vec1; + result3 *= 2; + EXPECT_EQ(result3.x, 2); + EXPECT_EQ(result3.y, 4); + + Vector2<float> result4 = float_vec1; + result4 *= 2.0f; + EXPECT_FLOAT_EQ(result4.x, 2.0f); + EXPECT_FLOAT_EQ(result4.y, 4.0f); +} + +TEST_F(Vector2Test, DivideChain) { + Vector2<int> result = int_vec1; + result /= int_vec2; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 0); + + Vector2<double> result2 = double_vec1; + result2 /= double_vec2; + EXPECT_FLOAT_EQ(result2.x, 0.33333333333333331); + EXPECT_FLOAT_EQ(result2.y, 0.5); + + Vector2<long> result3 = long_vec1; + result3 /= long_vec2; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 0); + + Vector2<float> result4 = float_vec1; + result4 /= float_vec2; + EXPECT_FLOAT_EQ(result4.x, 0.333333343f); + EXPECT_FLOAT_EQ(result4.y, 0.5f); +} + +TEST_F(Vector2Test, DivideScalarChain) { + Vector2<int> result = int_vec1; + result /= 2; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 1); + + Vector2<double> result2 = double_vec1; + result2 /= 2.0; + EXPECT_FLOAT_EQ(result2.x, 0.5); + EXPECT_FLOAT_EQ(result2.y, 1.0); + + Vector2<long> result3 = long_vec1; + result3 /= 2; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 1); + + Vector2<float> result4 = float_vec1; + result4 /= 2.0f; + EXPECT_FLOAT_EQ(result4.x, 0.5f); + EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, Negatation) { + Vector2<int> result = -int_vec1; + EXPECT_EQ(result.x, -1); + EXPECT_EQ(result.y, -2); + + Vector2<double> result2 = -double_vec1; + EXPECT_FLOAT_EQ(result2.x, -1.0); + EXPECT_FLOAT_EQ(result2.y, -2.0); + + Vector2<long> result3 = -long_vec1; + EXPECT_EQ(result3.x, -1); + EXPECT_EQ(result3.y, -2); + + Vector2<float> result4 = -float_vec1; + EXPECT_FLOAT_EQ(result4.x, -1.0f); + EXPECT_FLOAT_EQ(result4.y, -2.0f); +} + +TEST_F(Vector2Test, Equals) { + EXPECT_TRUE(int_vec1 == int_vec1); + EXPECT_FALSE(int_vec1 == int_vec2); + EXPECT_TRUE(double_vec1 == double_vec1); + EXPECT_FALSE(double_vec1 == double_vec2); + EXPECT_TRUE(long_vec1 == long_vec1); + EXPECT_FALSE(long_vec1 == long_vec2); +} + +TEST_F(Vector2Test, NotEquals) { + EXPECT_FALSE(int_vec1 != int_vec1); + EXPECT_TRUE(int_vec1 != int_vec2); + EXPECT_FALSE(double_vec1 != double_vec1); + EXPECT_TRUE(double_vec1 != double_vec2); + EXPECT_FALSE(long_vec1 != long_vec1); + EXPECT_TRUE(long_vec1 != long_vec2); +} diff --git a/src/test/main.cpp b/src/test/main.cpp index 241015d..aece72d 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -1,15 +1,30 @@ -#include <crepe/api/Config.h> - #include <gtest/gtest.h> +#define protected public +#define private public + +#include <crepe/api/Config.h> + using namespace crepe; using namespace testing; +class GlobalConfigReset : public EmptyTestEventListener { +public: + Config & cfg = Config::get_instance(); + Config cfg_default = Config(); + + // This function is called before each test + void OnTestStart(const TestInfo &) override { + cfg = cfg_default; + cfg.log.level = Log::Level::WARNING; + } +}; + int main(int argc, char ** argv) { InitGoogleTest(&argc, argv); - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::ERROR; + UnitTest & ut = *UnitTest::GetInstance(); + ut.listeners().Append(new GlobalConfigReset); return RUN_ALL_TESTS(); } |