diff options
author | Loek Le Blansch <loek@pipeframe.xyz> | 2024-12-07 14:21:31 +0100 |
---|---|---|
committer | Loek Le Blansch <loek@pipeframe.xyz> | 2024-12-07 14:21:31 +0100 |
commit | e7423549a26c0df6db930329e2c4067b1c6777b7 (patch) | |
tree | 0de96a7b935fead6cc9b5d04d09d713047372b1f /src/crepe | |
parent | f3009ab8f0785a54d3fd83c0d758c8ebd901adda (diff) | |
parent | fdb4c99e139a264d4e15e6913a3756fc6cccb2f2 (diff) |
merge with master
Diffstat (limited to 'src/crepe')
44 files changed, 1522 insertions, 357 deletions
diff --git a/src/crepe/Collider.cpp b/src/crepe/Collider.cpp index bbec488..77e11c8 100644 --- a/src/crepe/Collider.cpp +++ b/src/crepe/Collider.cpp @@ -2,4 +2,4 @@ using namespace crepe; -Collider::Collider(game_object_id_t id) : Component(id) {} +Collider::Collider(game_object_id_t id, const vec2 & offset) : Component(id), offset(offset) {} diff --git a/src/crepe/Collider.h b/src/crepe/Collider.h index 827f83d..42ccfd4 100644 --- a/src/crepe/Collider.h +++ b/src/crepe/Collider.h @@ -1,14 +1,23 @@ #pragma once #include "Component.h" +#include "types.h" namespace crepe { class Collider : public Component { public: - Collider(game_object_id_t id); + Collider(game_object_id_t id, const vec2 & offset); - int size; +public: + /** + * \brief Offset of the collider relative to the rigidbody position. + * + * The `offset` defines the positional shift applied to the collider relative to the position of the rigidbody it is attached to. + * This allows the collider to be placed at a different position than the rigidbody. + * + */ + vec2 offset; }; } // namespace crepe diff --git a/src/crepe/Component.h b/src/crepe/Component.h index dc17721..c30419d 100644 --- a/src/crepe/Component.h +++ b/src/crepe/Component.h @@ -8,7 +8,7 @@ class ComponentManager; /** * \brief Base class for all components - * + * * This class is the base class for all components. It provides a common interface for all * components. */ diff --git a/src/crepe/api/Animator.cpp b/src/crepe/api/Animator.cpp index 45f67f6..b8a91dc 100644 --- a/src/crepe/api/Animator.cpp +++ b/src/crepe/api/Animator.cpp @@ -7,21 +7,53 @@ using namespace crepe; -Animator::Animator(game_object_id_t id, Sprite & ss, int row, int col, int col_animator) +Animator::Animator(game_object_id_t id, Sprite & spritesheet, unsigned int max_row, + unsigned int max_col, const Animator::Data & data) : Component(id), - spritesheet(ss), - row(row), - col(col) { + spritesheet(spritesheet), + max_rows(max_row), + max_columns(max_col), + data(data) { dbg_trace(); - this->spritesheet.mask.h /= col; - this->spritesheet.mask.w /= row; - this->spritesheet.mask.x = 0; - this->spritesheet.mask.y = col_animator * this->spritesheet.mask.h; - this->active = false; + this->spritesheet.mask.h /= this->max_columns; + this->spritesheet.mask.w /= this->max_rows; + this->spritesheet.mask.x = this->data.row * this->spritesheet.mask.w; + this->spritesheet.mask.y = this->data.col * this->spritesheet.mask.h; // need to do this for to get the aspect ratio for a single clipping in the spritesheet this->spritesheet.aspect_ratio = static_cast<double>(this->spritesheet.mask.w) / this->spritesheet.mask.h; } + Animator::~Animator() { dbg_trace(); } + +void Animator::loop() { this->data.looping = true; } + +void Animator::play() { this->active = true; } + +void Animator::pause() { this->active = false; } + +void Animator::stop() { + this->active = false; + this->data.col = 0; + this->data.row = 0; +} +void Animator::set_fps(int fps) { this->data.fps = fps; } + +void Animator::set_cycle_range(int start, int end) { + this->data.cycle_start = start, this->data.cycle_end = end; +} + +void Animator::set_anim(int col) { + Animator::Data & ctx = this->data; + this->spritesheet.mask.x = ctx.row = 0; + ctx.col = col; + this->spritesheet.mask.y = ctx.col * this->spritesheet.mask.h; +} + +void Animator::next_anim() { + Animator::Data & ctx = this->data; + ctx.row = ctx.row++ % this->max_rows; + this->spritesheet.mask.x = ctx.row * this->spritesheet.mask.w; +} diff --git a/src/crepe/api/Animator.h b/src/crepe/api/Animator.h index 6c506aa..7c850b8 100644 --- a/src/crepe/api/Animator.h +++ b/src/crepe/api/Animator.h @@ -1,5 +1,7 @@ #pragma once +#include "../types.h" + #include "Component.h" #include "Sprite.h" @@ -16,56 +18,86 @@ class SDLContext; * sheet. It can be used to play animations, loop them, or stop them. */ class Animator : public Component { +public: + struct Data { + //! frames per second for animation + unsigned int fps = 1; + //! The current col being animated. + unsigned int col = 0; + //! The current row being animated. + unsigned int row = 0; + //! should the animation loop + bool looping = false; + //! starting frame for cycling + unsigned int cycle_start = 0; + //! end frame for cycling (-1 = use last frame) + int cycle_end = -1; + }; public: - //TODO: need to implement this + //! Animator will repeat the animation void loop(); + //! starts the animation + void play(); + //! pauses the animation + void pause(); + /** + * \brief stops the animation + * + * sets the active on false and resets all the current rows and columns + */ void stop(); + /** + * \brief set frames per second + * + * \param fps frames per second + */ + void set_fps(int fps); + /** + * \brief set the range in the row + * + * \param start of row animation + * \param end of row animation + */ + void set_cycle_range(int start, int end); + /** + * \brief select which column to animate from + * + * \param col animation column + */ + void set_anim(int col); + //! will go to the next animaiton of current row + void next_anim(); public: /** * \brief Constructs an Animator object that will control animations for a sprite sheet. * * \param id The unique identifier for the component, typically assigned automatically. - * \param spritesheet A reference to the Sprite object which holds the sprite sheet for - * animation. - * \param row The maximum number of rows in the sprite sheet. - * \param col The maximum number of columns in the sprite sheet. - * \param col_animate The specific col index of the sprite sheet to animate. This allows - * selecting which col to animate from multiple col in the sheet. + * \param spritesheet the reference to the spritesheet + * \param max_row maximum of rows inside the given spritesheet + * \param max_col maximum of columns inside the given spritesheet + * \param data extra animation data for more control * * This constructor sets up the Animator with the given parameters, and initializes the * animation system. */ - Animator(uint32_t id, Sprite & spritesheet, int row, int col, int col_animate); - + Animator(game_object_id_t id, Sprite & spritesheet, unsigned int max_row, + unsigned int max_col, const Animator::Data & data); ~Animator(); // dbg_trace -private: - //! A reference to the Sprite sheet containing the animation frames. - Sprite & spritesheet; - +public: //! The maximum number of columns in the sprite sheet. - const int col; - + const unsigned int max_columns; //! The maximum number of rows in the sprite sheet. - const int row; - - //! The current col being animated. - int curr_col = 0; - - //! The current row being animated. - int curr_row = 0; - - //TODO: Is this necessary? - //int fps; + const unsigned int max_rows; + Animator::Data data; private: - //! AnimatorSystem adjust the private member parameters of Animator; - friend class AnimatorSystem; - - //! SDLContext reads the Animator member var's - friend class SDLContext; + //! A reference to the Sprite sheet containing. + Sprite & spritesheet; + //! Uses the spritesheet + friend AnimatorSystem; }; + } // namespace crepe -// diff --git a/src/crepe/api/AssetManager.h b/src/crepe/api/AssetManager.h index fee6780..3b1cc4b 100644 --- a/src/crepe/api/AssetManager.h +++ b/src/crepe/api/AssetManager.h @@ -9,7 +9,7 @@ namespace crepe { /** * \brief The AssetManager is responsible for storing and managing assets over multiple scenes. - * + * * The AssetManager ensures that assets are loaded once and can be accessed across different * scenes. It caches assets to avoid reloading them every time a scene is loaded. Assets are * retained in memory until the AssetManager is destroyed, at which point the cached assets are @@ -46,9 +46,9 @@ public: * \param reload If true, the asset will be reloaded from the file, even if it is already * cached. * \tparam T The type of asset to cache (e.g., texture, sound, etc.). - * + * * \return A shared pointer to the cached asset. - * + * * This template function caches the asset at the given file path. If the asset is already * cached and `reload` is false, the existing cached version will be returned. Otherwise, the * asset will be reloaded and added to the cache. diff --git a/src/crepe/api/BoxCollider.cpp b/src/crepe/api/BoxCollider.cpp new file mode 100644 index 0000000..c097a24 --- /dev/null +++ b/src/crepe/api/BoxCollider.cpp @@ -0,0 +1,10 @@ +#include "BoxCollider.h" + +#include "../Collider.h" + +using namespace crepe; + +BoxCollider::BoxCollider(game_object_id_t game_object_id, const vec2 & offset, + const vec2 & dimensions) + : Collider(game_object_id, offset), + dimensions(dimensions) {} diff --git a/src/crepe/api/BoxCollider.h b/src/crepe/api/BoxCollider.h new file mode 100644 index 0000000..1ac4d46 --- /dev/null +++ b/src/crepe/api/BoxCollider.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../Collider.h" +#include "Vector2.h" +#include "types.h" + +namespace crepe { + +/** + * \brief A class representing a box-shaped collider. + * + * This class is used for collision detection with other colliders (e.g., CircleCollider). + */ +class BoxCollider : public Collider { +public: + BoxCollider(game_object_id_t game_object_id, const vec2 & offset, const vec2 & dimensions); + + //! Width and height of the box collider + vec2 dimensions; +}; + +} // namespace crepe diff --git a/src/crepe/api/Button.h b/src/crepe/api/Button.h index 26e7526..61b18d7 100644 --- a/src/crepe/api/Button.h +++ b/src/crepe/api/Button.h @@ -11,7 +11,7 @@ class Button : public UIObject { public: /** * \brief Constructs a Button with the specified game object ID and dimensions. - * + * * \param id The unique ID of the game object associated with this button. * \param dimensions The width and height of the UIObject * \param offset The offset relative this GameObjects Transform @@ -23,7 +23,7 @@ public: /** * \brief Indicates if the button is a toggle button (can be pressed and released). - * + * * A toggle button allows for a pressed/released state, whereas a regular button * typically only has an on-click state. */ @@ -31,7 +31,7 @@ public: // TODO: create separate toggle button class /** * \brief The callback function to be executed when the button is clicked. - * + * * This function is invoked whenever the button is clicked. It can be set to any * function that matches the signature `void()`. */ diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index ac8f301..593c4e6 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -13,6 +13,8 @@ target_sources(crepe PUBLIC Metadata.cpp Camera.cpp Animator.cpp + BoxCollider.cpp + CircleCollider.cpp IKeyListener.cpp IMouseListener.cpp LoopManager.cpp @@ -37,13 +39,15 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Vector2.h Vector2.hpp Color.h - Texture.h - AssetManager.h + Texture.h + AssetManager.h AssetManager.hpp Scene.h Metadata.h Camera.h Animator.h + BoxCollider.h + CircleCollider.h EventHandler.h EventHandler.hpp Event.h diff --git a/src/crepe/api/Camera.cpp b/src/crepe/api/Camera.cpp index 39d8ab0..179dc18 100644 --- a/src/crepe/api/Camera.cpp +++ b/src/crepe/api/Camera.cpp @@ -1,20 +1,17 @@ -#include "types.h" #include "util/Log.h" #include "Camera.h" -#include "Color.h" #include "Component.h" +#include "types.h" using namespace crepe; -Camera::Camera(game_object_id_t id, const Color & bg_color, const ivec2 & screen, - const vec2 & viewport_size, const double & zoom, const vec2 & offset) +Camera::Camera(game_object_id_t id, const ivec2 & screen, const vec2 & viewport_size, + const Data & data) : Component(id), - bg_color(bg_color), - offset(offset), screen(screen), viewport_size(viewport_size), - zoom(zoom) { + data(data) { dbg_trace(); } diff --git a/src/crepe/api/Camera.h b/src/crepe/api/Camera.h index 2d8fa48..54d9a73 100644 --- a/src/crepe/api/Camera.h +++ b/src/crepe/api/Camera.h @@ -14,23 +14,42 @@ namespace crepe { * position, and zoom level. It controls what part of the game world is visible on the screen. */ class Camera : public Component { +public: + struct Data { + /** + * \bg_color background color of the game + * + * This will make the background the same color as the given value. + */ + const Color bg_color = Color::BLACK; + + /** + * \zoom Zooming level of the game + * + * zoom = 1 --> no zoom. + * zoom < 1 --> zoom out + * zoom > 1 --> zoom in + */ + double zoom = 1; + + //! offset postion from the game object transform component + vec2 postion_offset; + }; public: /** * \brief Constructs a Camera with the specified ID and background color. * \param id Unique identifier for the camera component. - * \param bg_color Background color for the camera view. + * \param screen is the actual screen size in pixels + * \param viewport_size is the view of the world in game units + * \param data the camera component data */ - Camera(game_object_id_t id, const Color & bg_color, const ivec2 & screen, - const vec2 & viewport_size, const double & zoom, const vec2 & offset = {0, 0}); + Camera(game_object_id_t id, const ivec2 & screen, const vec2 & viewport_size, + const Camera::Data & data); ~Camera(); // dbg_trace only public: - //! Background color of the camera view. - const Color bg_color; - - //! offset postion from the game object transform component - vec2 offset; + Camera::Data data; //! screen the display size in pixels ( output resolution ) const ivec2 screen; @@ -38,9 +57,6 @@ public: //! viewport is the area of the world visible through the camera (in world units) const vec2 viewport_size; - //! Zoom level of the camera view. - const double zoom; - public: /** * \brief Gets the maximum number of camera instances allowed. diff --git a/src/crepe/api/CircleCollider.cpp b/src/crepe/api/CircleCollider.cpp new file mode 100644 index 0000000..a4271e9 --- /dev/null +++ b/src/crepe/api/CircleCollider.cpp @@ -0,0 +1,8 @@ +#include "CircleCollider.h" + +using namespace crepe; + +CircleCollider::CircleCollider(game_object_id_t game_object_id, const vec2 & offset, + float radius) + : Collider(game_object_id, offset), + radius(radius) {} diff --git a/src/crepe/api/CircleCollider.h b/src/crepe/api/CircleCollider.h index e77a592..c7bf66e 100644 --- a/src/crepe/api/CircleCollider.h +++ b/src/crepe/api/CircleCollider.h @@ -1,14 +1,22 @@ #pragma once + +#include "Vector2.h" + #include "../Collider.h" namespace crepe { +/** + * \brief A class representing a circle-shaped collider. + * + * This class is used for collision detection with other colliders (e.g., BoxCollider). + */ class CircleCollider : public Collider { public: - CircleCollider(game_object_id_t game_object_id, int radius) - : Collider(game_object_id), - radius(radius) {} - int radius; + CircleCollider(game_object_id_t game_object_id, const vec2 & offset, float radius); + + //! Radius of the circle collider. + float radius; }; } // namespace crepe diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h index 225e9b9..a9745c3 100644 --- a/src/crepe/api/Config.h +++ b/src/crepe/api/Config.h @@ -1,8 +1,10 @@ #pragma once +#include <string> + #include "../util/Log.h" + #include "types.h" -#include <string> namespace crepe { @@ -66,10 +68,9 @@ public: //! default window settings struct { - //TODO make this constexpr because this will never change - ivec2 default_size = {1080, 720}; + //! default screen size in pixels + ivec2 default_size = {1280, 720}; std::string window_title = "Jetpack joyride clone"; - } window_settings; //! Asset loading options diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h index 6298118..f2f3daf 100644 --- a/src/crepe/api/Event.h +++ b/src/crepe/api/Event.h @@ -112,10 +112,6 @@ public: //! scroll amount in y axis (from and away from the person). float scroll_delta = 0; }; -/** - * \brief Event triggered during a collision between objects. - */ -class CollisionEvent : public Event {}; /** * \brief Event triggered when text is submitted, e.g., from a text input. diff --git a/src/crepe/api/EventHandler.h b/src/crepe/api/EventHandler.h index 7bdd9a3..7bb501b 100644 --- a/src/crepe/api/EventHandler.h +++ b/src/crepe/api/EventHandler.h @@ -8,12 +8,12 @@ namespace crepe { /** * \brief A type alias for an event handler function. - * - * The EventHandler is a std::function that takes an EventType reference and returns a boolean value + * + * The EventHandler is a std::function that takes an EventType reference and returns a boolean value * indicating whether the event is handled. - * + * * \tparam EventType The type of event this handler will handle. - * + * * Returning \c false from an event handler results in the event being propogated to other listeners for the same event type, while returning \c true stops propogation altogether. */ template <typename EventType> @@ -22,7 +22,7 @@ using EventHandler = std::function<bool(const EventType & e)>; /** * \class IEventHandlerWrapper * \brief An abstract base class for event handler wrappers. - * + * * This class provides the interface for handling events. Derived classes must implement the * `call()` method to process events */ @@ -35,9 +35,9 @@ public: /** * \brief Executes the handler with the given event. - * + * * This method calls the `call()` method of the derived class, passing the event to the handler. - * + * * \param e The event to be processed. * \return A boolean value indicating whether the event is handled. */ @@ -46,9 +46,9 @@ public: private: /** * \brief The method responsible for handling the event. - * + * * This method is implemented by derived classes to process the event. - * + * * \param e The event to be processed. * \return A boolean value indicating whether the event is handled. */ @@ -58,11 +58,11 @@ private: /** * \class EventHandlerWrapper * \brief A wrapper for event handler functions. - * - * This class wraps an event handler function of a specific event type. It implements the - * `call()` and `get_type()` methods to allow the handler to be executed and its type to be + * + * This class wraps an event handler function of a specific event type. It implements the + * `call()` and `get_type()` methods to allow the handler to be executed and its type to be * queried. - * + * * \tparam EventType The type of event this handler will handle. */ template <typename EventType> @@ -70,9 +70,9 @@ class EventHandlerWrapper : public IEventHandlerWrapper { public: /** * \brief Constructs an EventHandlerWrapper with a given handler. - * + * * The constructor takes an event handler function and stores it in the wrapper. - * + * * \param handler The event handler function. */ explicit EventHandlerWrapper(const EventHandler<EventType> & handler); @@ -80,9 +80,9 @@ public: private: /** * \brief Calls the stored event handler with the event. - * + * * This method casts the event to the appropriate type and calls the handler. - * + * * \param e The event to be handled. * \return A boolean value indicating whether the event is handled. */ diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h index 4cd2bc0..ff80f49 100644 --- a/src/crepe/api/GameObject.h +++ b/src/crepe/api/GameObject.h @@ -10,7 +10,7 @@ class ComponentManager; /** * \brief Represents a GameObject - * + * * This class represents a GameObject. The GameObject class is only used as an interface for * the game programmer. The actual implementation is done in the ComponentManager. */ @@ -19,7 +19,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 id The id of the GameObject * \param name The name of the GameObject @@ -37,20 +37,20 @@ private: public: /** * \brief Set the parent of this GameObject - * + * * This method sets the parent of this GameObject. It sets the parent in the Metadata * component of this GameObject and adds this GameObject to the children list of the parent * GameObject. - * + * * \param parent The parent GameObject */ void set_parent(const GameObject & parent); /** * \brief Add a component to the GameObject - * + * * This method adds a component to the GameObject. It forwards the arguments to the * ComponentManager. - * + * * \tparam T The type of the component * \tparam Args The types of the arguments * \param args The arguments to create the component diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index 8fef0c4..044f096 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -5,6 +5,7 @@ #include "../system/PhysicsSystem.h" #include "../system/RenderSystem.h" #include "../system/ScriptSystem.h" +#include "manager/EventManager.h" #include "LoopManager.h" @@ -29,7 +30,14 @@ void LoopManager::start() { } void LoopManager::set_running(bool running) { this->game_running = running; } -void LoopManager::fixed_update() {} +void LoopManager::fixed_update() { + // TODO: retrieve EventManager from direct member after singleton refactor + EventManager & ev = this->mediator.event_manager; + ev.dispatch_events(); + this->get_system<ScriptSystem>().update(); + this->get_system<PhysicsSystem>().update(); + this->get_system<CollisionSystem>().update(); +} void LoopManager::loop() { LoopTimer & timer = this->loop_timer; @@ -53,15 +61,17 @@ void LoopManager::loop() { void LoopManager::setup() { LoopTimer & timer = this->loop_timer; - this->game_running = true; + this->scene_manager.load_next_scene(); timer.start(); timer.set_fps(200); + this->scene_manager.load_next_scene(); } void LoopManager::render() { if (!this->game_running) return; + this->get_system<AnimatorSystem>().update(); this->get_system<RenderSystem>().update(); } diff --git a/src/crepe/api/Metadata.h b/src/crepe/api/Metadata.h index 235d42f..f404703 100644 --- a/src/crepe/api/Metadata.h +++ b/src/crepe/api/Metadata.h @@ -9,7 +9,7 @@ namespace crepe { /** * \brief Metadata component - * + * * This class represents the Metadata component. It stores the name, tag, parent and children * of a GameObject. */ diff --git a/src/crepe/api/Rigidbody.cpp b/src/crepe/api/Rigidbody.cpp index 576ca45..8213afb 100644 --- a/src/crepe/api/Rigidbody.cpp +++ b/src/crepe/api/Rigidbody.cpp @@ -10,6 +10,4 @@ void crepe::Rigidbody::add_force_linear(const vec2 & force) { this->data.linear_velocity += force; } -void crepe::Rigidbody::add_force_angular(double force) { - this->data.angular_velocity += force; -} +void crepe::Rigidbody::add_force_angular(float force) { this->data.angular_velocity += force; } diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h index 3b0588f..40c6bf1 100644 --- a/src/crepe/api/Rigidbody.h +++ b/src/crepe/api/Rigidbody.h @@ -1,5 +1,8 @@ #pragma once +#include <cmath> +#include <set> + #include "../Component.h" #include "types.h" @@ -8,7 +11,7 @@ namespace crepe { /** * \brief Rigidbody class - * + * * This class is used by the physics sytem and collision system. It configures how to system * interact with the gameobject for movement and collisions. */ @@ -16,7 +19,7 @@ class Rigidbody : public Component { public: /** * \brief BodyType enum - * + * * This enum provides three bodytypes the physics sytem and collision system use. */ enum class BodyType { @@ -29,54 +32,118 @@ public: }; /** * \brief PhysicsConstraints to constrain movement - * + * * This struct configures the movement constraint for this object. If a constraint is enabled * the systems will not move the object. */ struct PhysicsConstraints { - //! X constraint + //! Prevent movement along X axis bool x = false; - //! Y constraint + //! Prevent movement along Y axis bool y = false; - //! rotation constraint + //! Prevent rotation bool rotation = false; }; public: - /** + /** * \brief struct for Rigidbody data - * + * * This struct holds the data for the Rigidbody. */ struct Data { //! objects mass - double mass = 0.0; - //! gravtiy scale - double gravity_scale = 0.0; - //! Changes if physics apply + float mass = 0.0; + /** + * \brief Gravity scale factor. + * + * The `gravity_scale` controls how much gravity affects the object. It is a multiplier applied to the default + * gravity force, allowing for fine-grained control over how the object responds to gravity. + * + */ + float gravity_scale = 0; + + //! Defines the type of the physics body, which determines how the physics system interacts with the object. BodyType body_type = BodyType::DYNAMIC; - //! linear velocity of object + + /** + * \name Linear (positional) motion + * + * These variables define the linear motion (movement along the position) of an object. + * The linear velocity is applied to the object's position in each update of the PhysicsSystem. + * The motion is affected by the object's linear velocity, its maximum velocity, and a coefficient + * that can scale the velocity over time. + * + * \{ + */ + //! Linear velocity of the object (speed and direction). vec2 linear_velocity; - //! maximum linear velocity of object - vec2 max_linear_velocity; - //! linear damping of object - vec2 linear_damping; - //! angular velocity of object - double angular_velocity = 0.0; - //! max angular velocity of object - double max_angular_velocity = 0.0; - //! angular damping of object - double angular_damping = 0.0; - //! movements constraints of object + //! Maximum linear velocity of the object. This limits the object's speed. + vec2 max_linear_velocity = {INFINITY, INFINITY}; + //! Linear velocity coefficient. This scales the object's velocity for adjustment or damping. + vec2 linear_velocity_coefficient = {1, 1}; + //! \} + + /** + * \name Angular (rotational) motion + * + * These variables define the angular motion (rotation) of an object. + * The angular velocity determines how quickly the object rotates, while the maximum angular velocity + * sets a limit on the rotation speed. The angular velocity coefficient applies damping or scaling + * to the angular velocity, which can be used to simulate friction or other effects that slow down rotation. + * + * \{ + */ + //! Angular velocity of the object, representing the rate of rotation (in degrees). + float angular_velocity = 0; + //! Maximum angular velocity of the object. This limits the maximum rate of rotation. + float max_angular_velocity = INFINITY; + //! Angular velocity coefficient. This scales the object's angular velocity, typically used for damping. + float angular_velocity_coefficient = 1; + //! \} + + /** + * \brief Movement constraints for an object. + * + * The `PhysicsConstraints` struct defines the constraints that restrict an object's movement + * in certain directions or prevent rotation. These constraints effect only the physics system + * to prevent the object from moving or rotating in specified ways. + * + */ PhysicsConstraints constraints; - //! if gravity applies - bool use_gravity = true; - //! if object bounces - bool bounce = false; + + /** + * \brief Elasticity factor of the material (bounce factor). + * + * The `elasticity_coefficient` controls how much of the object's velocity is retained after a collision. + * It represents the material's ability to bounce or recover velocity upon impact. The coefficient is a value + * above 0.0. + * + */ + float elastisity_coefficient = 0.0; + + /** + * \brief Offset of all colliders relative to the object's transform position. + * + * 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. + * + */ + vec2 offset; + + /** + * \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. + */ + std::set<int> collision_layers; }; public: - /** + /** * \param game_object_id id of the gameobject the rigibody is added to. * \param data struct to configure the rigidbody. */ @@ -85,18 +152,27 @@ public: Data data; public: - /** + /** * \brief add a linear force to the Rigidbody. - * + * * \param force Vector2 that is added to the linear force. */ void add_force_linear(const vec2 & force); - /** + /** * \brief add a angular force to the Rigidbody. - * + * * \param force Vector2 that is added to the angular force. */ - void add_force_angular(double force); + void add_force_angular(float force); + +protected: + /** + * Ensures there is at most one Rigidbody component per entity. + * \return Always returns 1, indicating this constraint. + */ + virtual int get_instances_max() const { return 1; } + //! ComponentManager instantiates all components + friend class ComponentManager; }; } // namespace crepe diff --git a/src/crepe/api/Scene.h b/src/crepe/api/Scene.h index 9f1e8ce..ba9bb76 100644 --- a/src/crepe/api/Scene.h +++ b/src/crepe/api/Scene.h @@ -12,7 +12,7 @@ class ComponentManager; /** * \brief Represents a Scene - * + * * This class represents a Scene. The Scene class is only used as an interface for the game * programmer. */ @@ -41,7 +41,7 @@ public: 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 diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index a040608..49bdcf5 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -4,6 +4,7 @@ #include "../manager/EventManager.h" #include "../manager/Mediator.h" +#include "../system/CollisionSystem.h" #include "../types.h" #include "../util/OptionalRef.h" @@ -19,7 +20,7 @@ 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. * - * \info Additional *events* (like Unity's OnDisable and OnEnable) should be implemented as + * \note 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 diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp index 0a2ad4c..cc0e20a 100644 --- a/src/crepe/api/Sprite.cpp +++ b/src/crepe/api/Sprite.cpp @@ -6,24 +6,20 @@ #include "Component.h" #include "Sprite.h" #include "Texture.h" +#include "types.h" using namespace std; using namespace crepe; -Sprite::Sprite(game_object_id_t id, Texture & image, const Color & color, - const FlipSettings & flip, int sort_layer, int order_layer, int height) +Sprite::Sprite(game_object_id_t id, Texture & texture, const Sprite::Data & data) : Component(id), - color(color), - flip(flip), - sprite_image(std::move(image)), - sorting_in_layer(sort_layer), - order_in_layer(order_layer), - height(height) { + texture(std::move(texture)), + data(data) { dbg_trace(); - this->mask.w = sprite_image.get_size().x; - this->mask.h = sprite_image.get_size().y; + this->mask.w = this->texture.get_size().x; + this->mask.h = this->texture.get_size().y; this->aspect_ratio = static_cast<double>(this->mask.w) / this->mask.h; } diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h index a0e90a0..dbf41e4 100644 --- a/src/crepe/api/Sprite.h +++ b/src/crepe/api/Sprite.h @@ -1,11 +1,10 @@ #pragma once -#include <cstdint> - #include "../Component.h" #include "Color.h" #include "Texture.h" +#include "types.h" namespace crepe { @@ -20,60 +19,79 @@ class AnimatorSystem; * flip settings, and is managed in layers with defined sorting orders. */ class Sprite : public Component { - public: + //! settings to flip the image struct FlipSettings { + //! horizantal flip bool flip_x = false; + //! vertical flip bool flip_y = false; }; + //! Sprite data that does not have to be set in the constructor + struct Data { + /** + * \brief Sprite tint (multiplied) + * + * The sprite texture's pixels are multiplied by this color before being displayed + * (including alpha channel for transparency). + */ + Color color = Color::WHITE; + + //! Flip settings for the sprite + FlipSettings flip; + + //! Layer sorting level of the sprite + const int sorting_in_layer = 0; + + //! Order within the sorting layer + const int order_in_layer = 0; + + /** + * \brief width and height of the sprite in game units + * + * - if exclusively width is specified, the height is calculated using the texture's aspect + * ratio + * - if exclusively height is specified, the width is calculated using the texture's aspect + * ratio + * - if both are specified the texture is streched to fit the specified size + */ + vec2 size = {0, 0}; + + //! independent sprite angle. rotating clockwise direction in degrees + float angle_offset = 0; + + //! independent sprite scale multiplier + float scale_offset = 1; + + //! independent sprite offset position + vec2 position_offset; + }; + public: - // TODO: Loek comment in github #27 will be looked another time - // about shared_ptr Texture /** - * \brief Constructs a Sprite with specified parameters. * \param game_id Unique identifier for the game object this sprite belongs to. - * \param image Shared pointer to the texture for this sprite. - * \param color Color tint applied to the sprite. - * \param flip Flip settings for horizontal and vertical orientation. - * \param order_layer decides the sorting in layer of the sprite. - * \param sort_layer decides the order in layer of the sprite. - * \param height the height of the image in game units - */ - Sprite(game_object_id_t id, Texture & image, const Color & color, - const FlipSettings & flip, int sort_layer, int order_layer, int height); - - /** - * \brief Destroys the Sprite instance. + * \param texture asset of the image + * \param ctx all the sprite data */ + Sprite(game_object_id_t id, Texture & texture, const Data & data); ~Sprite(); //! Texture used for the sprite - const Texture sprite_image; - - //! Color tint of the sprite - Color color; - - //! Flip settings for the sprite - FlipSettings flip; + const Texture texture; - //! Layer sorting level of the sprite - const int sorting_in_layer; - //! Order within the sorting layer - const int order_in_layer; - - //! height in world units - const int height; + Data data; +private: /** - * \aspect_ratio ratio of the img so that scaling will not become weird + * \brief ratio of the img * - * cannot be const because if Animator component is addded then ratio becomes scuffed and - * does it need to be calculated again in the Animator + * - This will multiply one of \c size variable if it is 0. + * - Will be adjusted if \c Animator component is added to an GameObject that is why this + * value cannot be const. */ - double aspect_ratio; + float aspect_ratio; -private: //! Reads the mask of sprite friend class SDLContext; diff --git a/src/crepe/api/Transform.h b/src/crepe/api/Transform.h index 6243a93..7ee6d65 100644 --- a/src/crepe/api/Transform.h +++ b/src/crepe/api/Transform.h @@ -7,7 +7,7 @@ namespace crepe { /** * \brief Transform component - * + * * This class represents the Transform component. It stores the position, rotation and scale of * a GameObject. */ @@ -15,10 +15,10 @@ class Transform : public Component { public: //! Translation (shift) vec2 position = {0, 0}; - //! Rotation, in degrees - double rotation = 0; + //! Rotation, in degrees clockwise + float rotation = 0; //! Multiplication factor - double scale = 0; + float scale = 0; protected: /** diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index ad9f1f0..4cc2206 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -11,19 +11,17 @@ #include <cstddef> #include <cstdint> #include <functional> -#include <iostream> #include <memory> #include <stdexcept> #include "../api/Camera.h" +#include "../api/Color.h" #include "../api/Config.h" #include "../api/Sprite.h" #include "../api/Texture.h" -#include "../manager/EventManager.h" #include "../util/Log.h" #include "SDLContext.h" -#include "api/Color.h" #include "types.h" using namespace crepe; @@ -215,11 +213,13 @@ MouseButton SDLContext::sdl_to_mousebutton(Uint8 sdl_button) { return MOUSE_BUTTON_LOOKUP_TABLE[sdl_button]; } -void SDLContext::clear_screen() { +void SDLContext::clear_screen() { SDL_RenderClear(this->game_renderer.get()); } +void SDLContext::present_screen() { SDL_SetRenderDrawColor(this->game_renderer.get(), 0, 0, 0, 255); - SDL_RenderClear(this->game_renderer.get()); + SDL_RenderFillRectF(this->game_renderer.get(), &black_bars[0]); + SDL_RenderFillRectF(this->game_renderer.get(), &black_bars[1]); + SDL_RenderPresent(this->game_renderer.get()); } -void SDLContext::present_screen() { SDL_RenderPresent(this->game_renderer.get()); } SDL_Rect SDLContext::get_src_rect(const Sprite & sprite) const { return SDL_Rect{ @@ -229,40 +229,62 @@ SDL_Rect SDLContext::get_src_rect(const Sprite & sprite) const { .h = sprite.mask.h, }; } -SDL_Rect SDLContext::get_dst_rect(const Sprite & sprite, const vec2 & pos, const Camera & cam, - const vec2 & cam_pos, const double & img_scale) const { - int width = sprite.height * sprite.aspect_ratio; - int height = sprite.height; +SDL_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const { - width *= img_scale * cam.zoom; - height *= img_scale * cam.zoom; + const Sprite::Data & data = ctx.sprite.data; - return SDL_Rect{ - .x = static_cast<int>((pos.x - cam_pos.x + (cam.viewport_size.x / 2) - width / 2)), - .y = static_cast<int>((pos.y - cam_pos.y + (cam.viewport_size.y / 2) - height / 2)), - .w = width, - .h = height, + vec2 size; + if (data.size.x == 0 && data.size.y != 0) { + size.x = data.size.y * ctx.sprite.aspect_ratio; + } + if (data.size.y == 0 && data.size.x != 0) { + size.y = data.size.x / ctx.sprite.aspect_ratio; + } + + const CameraValues & cam = ctx.cam; + + size *= cam.render_scale * ctx.img_scale * data.scale_offset; + + vec2 screen_pos + = (ctx.pos + data.position_offset - cam.cam_pos + (cam.zoomed_viewport) / 2) + * cam.render_scale + - size / 2 + cam.bar_size; + + return SDL_FRect{ + .x = screen_pos.x, + .y = screen_pos.y, + .w = size.x, + .h = size.y, }; } void SDLContext::draw(const RenderContext & ctx) { + const Sprite::Data & data = ctx.sprite.data; SDL_RendererFlip render_flip - = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * ctx.sprite.flip.flip_x) - | (SDL_FLIP_VERTICAL * ctx.sprite.flip.flip_y)); + = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * data.flip.flip_x) + | (SDL_FLIP_VERTICAL * data.flip.flip_y)); SDL_Rect srcrect = this->get_src_rect(ctx.sprite); - SDL_Rect dstrect - = this->get_dst_rect(ctx.sprite, ctx.pos, ctx.cam, ctx.cam_pos, ctx.scale); - - this->set_color_texture(ctx.sprite.sprite_image, ctx.sprite.color); - SDL_RenderCopyEx(this->game_renderer.get(), ctx.sprite.sprite_image.texture.get(), - &srcrect, &dstrect, ctx.angle, NULL, render_flip); + SDL_FRect dstrect = this->get_dst_rect(SDLContext::DestinationRectangleData{ + .sprite = ctx.sprite, + .cam = ctx.cam, + .pos = ctx.pos, + .img_scale = ctx.scale, + }); + + double angle = ctx.angle + data.angle_offset; + + this->set_color_texture(ctx.sprite.texture, ctx.sprite.data.color); + SDL_RenderCopyExF(this->game_renderer.get(), ctx.sprite.texture.texture.get(), &srcrect, + &dstrect, angle, NULL, render_flip); } -void SDLContext::set_camera(const Camera & cam) { +SDLContext::CameraValues SDLContext::set_camera(const Camera & cam) { + const Camera::Data & cam_data = cam.data; + CameraValues ret_cam; // resize window int w, h; SDL_GetWindowSize(this->game_window.get(), &w, &h); @@ -270,42 +292,52 @@ void SDLContext::set_camera(const Camera & cam) { SDL_SetWindowSize(this->game_window.get(), cam.screen.x, cam.screen.y); } - double screen_aspect = cam.screen.x / cam.screen.y; - double viewport_aspect = cam.viewport_size.x / cam.viewport_size.y; + vec2 & zoomed_viewport = ret_cam.zoomed_viewport; + vec2 & bar_size = ret_cam.bar_size; + vec2 & render_scale = ret_cam.render_scale; + + zoomed_viewport = cam.viewport_size * cam_data.zoom; + float screen_aspect = static_cast<float>(cam.screen.x) / cam.screen.y; + float viewport_aspect = zoomed_viewport.x / zoomed_viewport.y; - SDL_Rect view; // calculate black bars if (screen_aspect > viewport_aspect) { - // letterboxing - view.h = cam.screen.y / cam.zoom; - view.w = cam.screen.y * viewport_aspect; - view.x = (cam.screen.x - view.w) / 2; - view.y = 0; - } else { // pillarboxing - view.h = cam.screen.x / viewport_aspect; - view.w = cam.screen.x / cam.zoom; - view.x = 0; - view.y = (cam.screen.y - view.h) / 2; + float scale = cam.screen.y / zoomed_viewport.y; + float adj_width = zoomed_viewport.x * scale; + float bar_width = (cam.screen.x - adj_width) / 2; + this->black_bars[0] = {0, 0, bar_width, (float) cam.screen.y}; + this->black_bars[1] = {(cam.screen.x - bar_width), 0, bar_width, (float) cam.screen.y}; + + bar_size = {bar_width, 0}; + render_scale.x = render_scale.y = scale; + } else { + // letterboxing + float scale = cam.screen.x / (cam.viewport_size.x * cam_data.zoom); + float adj_height = cam.viewport_size.y * scale; + float bar_height = (cam.screen.y - adj_height) / 2; + this->black_bars[0] = {0, 0, (float) cam.screen.x, bar_height}; + this->black_bars[1] + = {0, (cam.screen.y - bar_height), (float) cam.screen.x, bar_height}; + + bar_size = {0, bar_height}; + render_scale.x = render_scale.y = scale; } - // set drawing area - SDL_RenderSetViewport(this->game_renderer.get(), &view); - - SDL_RenderSetLogicalSize(this->game_renderer.get(), static_cast<int>(cam.viewport_size.x), - static_cast<int>(cam.viewport_size.y)); - // set bg color - SDL_SetRenderDrawColor(this->game_renderer.get(), cam.bg_color.r, cam.bg_color.g, - cam.bg_color.b, cam.bg_color.a); + SDL_SetRenderDrawColor(this->game_renderer.get(), cam_data.bg_color.r, cam_data.bg_color.g, + cam_data.bg_color.b, cam_data.bg_color.a); SDL_Rect bg = { .x = 0, .y = 0, - .w = static_cast<int>(cam.viewport_size.x), - .h = static_cast<int>(cam.viewport_size.y), + .w = cam.screen.x, + .h = cam.screen.y, }; + // fill bg color SDL_RenderFillRect(this->game_renderer.get(), &bg); + + return ret_cam; } uint64_t SDLContext::get_ticks() const { return SDL_GetTicks64(); } diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index a2b34c1..e232511 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -9,11 +9,9 @@ #include <functional> #include <memory> #include <string> -#include <utility> #include "api/Camera.h" #include "api/Color.h" -#include "api/Event.h" #include "api/KeyCodes.h" #include "api/Sprite.h" #include "api/Texture.h" @@ -27,16 +25,44 @@ class InputSystem; /** * \class SDLContext * \brief Facade for the SDL library - * + * * SDLContext is a singleton that handles the SDL window and renderer, provides methods for * event handling, and rendering to the screen. It is never used directly by the user */ class SDLContext { public: + //! data that the camera component cannot hold + struct CameraValues { + + //! zoomed in viewport in game_units + vec2 zoomed_viewport; + + /** + * \brief scaling factor + * + * depending on the black bars type will the scaling be different. + * - letterboxing --> scaling on the y-as + * - pillarboxing --> scaling on the x-as + */ + vec2 render_scale; + + /** + * \brief size of calculated black bars + * + * depending on the black bars type will the size be different + * - lettorboxing --> {0, bar_height} + * - pillarboxing --> {bar_width , 0} + */ + vec2 bar_size; + + //! Calculated camera position + vec2 cam_pos; + }; + + //! rendering data needed to render on screen struct RenderContext { const Sprite & sprite; - const Camera & cam; - const vec2 & cam_pos; + const CameraValues & cam; const vec2 & pos; const double & angle; const double & scale; @@ -82,21 +108,21 @@ private: friend class InputSystem; /** * \brief Retrieves a list of all events from the SDL context. - * + * * This method retrieves all the events from the SDL context that are currently * available. It is primarily used by the InputSystem to process various * input events such as mouse clicks, mouse movements, and keyboard presses. - * + * * \return Events that occurred since last call to `get_events()` */ std::vector<SDLContext::EventData> get_events(); /** * \brief Converts an SDL key code to the custom Keycode type. - * + * * This method maps an SDL key code to the corresponding `Keycode` enum value, * which is used internally by the system to identify the keys. - * + * * \param sdl_key The SDL key code to convert. * \return The corresponding `Keycode` value or `Keycode::NONE` if the key is unrecognized. */ @@ -104,18 +130,16 @@ private: /** * \brief Converts an SDL mouse button code to the custom MouseButton type. - * - * This method maps an SDL mouse button code to the corresponding `MouseButton` + * + * This method maps an SDL mouse button code to the corresponding `MouseButton` * enum value, which is used internally by the system to identify mouse buttons. - * + * * \param sdl_button The SDL mouse button code to convert. * \return The corresponding `MouseButton` value or `MouseButton::NONE` if the key is unrecognized */ MouseButton sdl_to_mousebutton(Uint8 sdl_button); private: - //! Will only use get_ticks - friend class AnimatorSystem; //! Will only use delay friend class LoopTimer; /** @@ -170,7 +194,7 @@ private: /** * \brief Draws a sprite to the screen using the specified transform and camera. - * \param RenderCtx Reference to rendering data to draw + * \param RenderContext Reference to rendering data to draw */ void draw(const RenderContext & ctx); @@ -184,9 +208,16 @@ private: * \brief sets the background of the camera (will be adjusted in future PR) * \param camera Reference to the Camera object. */ - void set_camera(const Camera & camera); + CameraValues set_camera(const Camera & camera); private: + //! the data needed to construct a sdl dst rectangle + struct DestinationRectangleData { + const Sprite & sprite; + const CameraValues & cam; + const vec2 & pos; + const double & img_scale; + }; /** * \brief calculates the sqaure size of the image * @@ -202,15 +233,14 @@ private: * \param pos the pos in world units * \param cam the camera of the current scene * \param cam_pos the current postion of the camera - * \param img_scale the image multiplier for increasing img size + * \param img_scale the image multiplier for increasing img size * \return sdl rectangle to draw a dst image to draw on the screen */ - SDL_Rect get_dst_rect(const Sprite & sprite, const vec2 & pos, const Camera & cam, - const vec2 & cam_pos, const double & img_scale) const; + SDL_FRect get_dst_rect(const DestinationRectangleData & data) const; /** * \brief Set an additional color value multiplied into render copy operations. * - * \param texture the given texture to adjust + * \param texture the given texture to adjust * \param color the color data for the texture */ void set_color_texture(const Texture & texture, const Color & color); @@ -221,6 +251,9 @@ private: //! renderer for the crepe engine std::unique_ptr<SDL_Renderer, std::function<void(SDL_Renderer *)>> game_renderer; + + //! black bars rectangle to draw + SDL_FRect black_bars[2] = {}; }; } // namespace crepe diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index 4c68f32..ee43d94 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -35,7 +35,7 @@ public: void play(); /** * \brief Reset playhead position - * + * * Resets the playhead position so that calling \c play() after this function makes it play * from the start of the sample. If the sound is not paused before calling this function, * this function will stop playback. diff --git a/src/crepe/manager/ComponentManager.h b/src/crepe/manager/ComponentManager.h index 685cae5..e7eeaa2 100644 --- a/src/crepe/manager/ComponentManager.h +++ b/src/crepe/manager/ComponentManager.h @@ -17,7 +17,7 @@ class GameObject; /** * \brief Manages all components - * + * * This class manages all components. It provides methods to add, delete and get components. */ class ComponentManager : public Manager { @@ -57,10 +57,10 @@ protected: friend class GameObject; /** * \brief Add a component to the ComponentManager - * + * * This method adds a component to the ComponentManager. The component is created with the * given arguments and added to the ComponentManager. - * + * * \tparam T The type of the component * \tparam Args The types of the arguments * \param id The id of the GameObject this component belongs to @@ -71,9 +71,9 @@ protected: T & add_component(game_object_id_t id, Args &&... args); /** * \brief Delete all components of a specific type and id - * + * * This method deletes all components of a specific type and id. - * + * * \tparam T The type of the component * \param id The id of the GameObject this component belongs to */ @@ -81,24 +81,24 @@ protected: void delete_components_by_id(game_object_id_t id); /** * \brief Delete all components of a specific type - * + * * This method deletes all components of a specific type. - * + * * \tparam T The type of the component */ template <typename T> void delete_components(); /** * \brief Delete all components of a specific id - * + * * This method deletes all components of a specific id. - * + * * \param id The id of the GameObject this component belongs to */ void delete_all_components_of_id(game_object_id_t id); /** * \brief Delete all components - * + * * This method deletes all components. */ void delete_all_components(); @@ -116,9 +116,9 @@ protected: public: /** * \brief Get all components of a specific type and id - * + * * This method gets all components of a specific type and id. - * + * * \tparam T The type of the component * \param id The id of the GameObject this component belongs to * \return A vector of all components of the specific type and id @@ -127,9 +127,9 @@ public: RefVector<T> get_components_by_id(game_object_id_t id) const; /** * \brief Get all components of a specific type - * + * * This method gets all components of a specific type. - * + * * \tparam T The type of the component * \return A vector of all components of the specific type */ @@ -171,7 +171,7 @@ private: using by_id_index = std::vector<T>; /** * \brief The components - * + * * This unordered_map stores all components. The key is the type of the component and the * value is a vector of vectors of unique pointers to the components. * diff --git a/src/crepe/manager/EventManager.h b/src/crepe/manager/EventManager.h index d634f54..ba5e98b 100644 --- a/src/crepe/manager/EventManager.h +++ b/src/crepe/manager/EventManager.h @@ -24,7 +24,7 @@ typedef size_t event_channel_t; /** * \class EventManager * \brief Manages event subscriptions, triggers, and queues, enabling decoupled event handling. - * + * * The `EventManager` acts as a centralized event system. It allows for registering callbacks * for specific event types, triggering events synchronously, queueing events for later * processing, and managing subscriptions via unique identifiers. @@ -35,20 +35,20 @@ public: /** * \brief Get the singleton instance of the EventManager. - * + * * This method returns the unique instance of the EventManager, creating it if it * doesn't already exist. Ensures only one instance is active in the program. - * + * * \return Reference to the singleton instance of the EventManager. */ static EventManager & get_instance(); /** * \brief Subscribe to a specific event type. - * + * * Registers a callback for a given event type and optional channel. Each callback * is assigned a unique subscription ID that can be used for later unsubscription. - * + * * \tparam EventType The type of the event to subscribe to. * \param callback The callback function to be invoked when the event is triggered. * \param channel The channel number to subscribe to (default is CHANNEL_ALL, which listens to all channels). @@ -60,18 +60,18 @@ public: /** * \brief Unsubscribe a previously registered callback. - * + * * Removes a callback from the subscription list based on its unique subscription ID. - * + * * \param event_id The unique subscription ID of the callback to remove. */ void unsubscribe(subscription_t event_id); /** * \brief Trigger an event immediately. - * + * * Synchronously invokes all registered callbacks for the given event type on the specified channel. - * + * * \tparam EventType The type of the event to trigger. * \param event The event instance to pass to the callbacks. * \param channel The channel to trigger the event on (default is CHANNEL_ALL, which triggers on all channels). @@ -81,9 +81,9 @@ public: /** * \brief Queue an event for later processing. - * + * * Adds an event to the event queue to be processed during the next call to `dispatch_events`. - * + * * \tparam EventType The type of the event to queue. * \param event The event instance to queue. * \param channel The channel to associate with the event (default is CHANNEL_ALL). @@ -93,7 +93,7 @@ public: /** * \brief Process all queued events. - * + * * Iterates through the event queue and triggers callbacks for each queued event. * Events are removed from the queue once processed. */ @@ -101,7 +101,7 @@ public: /** * \brief Clear all subscriptions. - * + * * Removes all registered event handlers and clears the subscription list. */ void clear(); @@ -109,7 +109,7 @@ public: private: /** * \brief Default constructor for the EventManager. - * + * * Constructor is private to enforce the singleton pattern. */ EventManager() = default; diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h index 71bd1c9..8094d80 100644 --- a/src/crepe/manager/Mediator.h +++ b/src/crepe/manager/Mediator.h @@ -3,8 +3,10 @@ #include "../util/OptionalRef.h" // TODO: remove these singletons: +#include "../facade/SDLContext.h" #include "EventManager.h" #include "SaveManager.h" +#include "api/LoopTimer.h" namespace crepe { @@ -28,6 +30,8 @@ struct Mediator { OptionalRef<SceneManager> scene_manager; OptionalRef<SaveManager> save_manager = SaveManager::get_instance(); OptionalRef<EventManager> event_manager = EventManager::get_instance(); + OptionalRef<SDLContext> sdl_context = SDLContext::get_instance(); + OptionalRef<LoopTimer> timer = LoopTimer::get_instance(); }; } // namespace crepe diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index 8bb6465..549c35d 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -1,8 +1,8 @@ -#include <cstdint> + #include "../api/Animator.h" -#include "../facade/SDLContext.h" #include "../manager/ComponentManager.h" +#include "api/LoopTimer.h" #include "AnimatorSystem.h" @@ -10,15 +10,30 @@ using namespace crepe; void AnimatorSystem::update() { ComponentManager & mgr = this->mediator.component_manager; - + LoopTimer & timer = this->mediator.timer; RefVector<Animator> animations = mgr.get_components_by_type<Animator>(); - uint64_t tick = SDLContext::get_instance().get_ticks(); + double elapsed_time = timer.get_current_time(); + for (Animator & a : animations) { if (!a.active) continue; - // (10 frames per second) - a.curr_row = (tick / 100) % a.row; - a.spritesheet.mask.x = (a.curr_row * a.spritesheet.mask.w) + a.curr_col; - a.spritesheet.mask = a.spritesheet.mask; + + Animator::Data & ctx = a.data; + float frame_duration = 1.0f / ctx.fps; + + int last_frame = ctx.row; + + int cycle_end = (ctx.cycle_end == -1) ? a.max_rows : ctx.cycle_end; + int total_frames = cycle_end - ctx.cycle_start; + + int curr_frame = static_cast<int>(elapsed_time / frame_duration) % total_frames; + + ctx.row = ctx.cycle_start + curr_frame; + a.spritesheet.mask.x = ctx.row * a.spritesheet.mask.w; + a.spritesheet.mask.y = (ctx.col * a.spritesheet.mask.h); + + if (!ctx.looping && curr_frame == ctx.cycle_start && last_frame == total_frames - 1) { + a.active = false; + } } } diff --git a/src/crepe/system/AnimatorSystem.h b/src/crepe/system/AnimatorSystem.h index f8179a9..7d3f565 100644 --- a/src/crepe/system/AnimatorSystem.h +++ b/src/crepe/system/AnimatorSystem.h @@ -2,9 +2,6 @@ #include "System.h" -//TODO: -// control if flip works with animation system - namespace crepe { /** diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp index c74ca1d..44a0431 100644 --- a/src/crepe/system/CollisionSystem.cpp +++ b/src/crepe/system/CollisionSystem.cpp @@ -1,5 +1,551 @@ +#include <algorithm> +#include <cmath> +#include <cstddef> +#include <functional> +#include <optional> +#include <utility> +#include <variant> + +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "api/BoxCollider.h" +#include "api/CircleCollider.h" +#include "api/Event.h" +#include "api/Metadata.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "api/Vector2.h" + +#include "Collider.h" #include "CollisionSystem.h" +#include "types.h" +#include "util/OptionalRef.h" using namespace crepe; -void CollisionSystem::update() {} +void CollisionSystem::update() { + std::vector<CollisionInternal> all_colliders; + game_object_id_t id = 0; + ComponentManager & mgr = this->mediator.component_manager; + RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>(); + // Collisions can only happen on object with a rigidbody + for (Rigidbody & rigidbody : rigidbodies) { + if (!rigidbody.active) continue; + id = rigidbody.game_object_id; + Transform & transform = mgr.get_components_by_id<Transform>(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) { + if (boxcollider.game_object_id != id) continue; + if (!boxcollider.active) continue; + all_colliders.push_back({.id = id, + .collider = collider_variant{boxcollider}, + .transform = transform, + .rigidbody = rigidbody}); + } + // Check if the circlecollider is active and has the same id as the rigidbody. + RefVector<CircleCollider> circlecolliders + = mgr.get_components_by_type<CircleCollider>(); + for (CircleCollider & circlecollider : circlecolliders) { + if (circlecollider.game_object_id != id) continue; + if (!circlecollider.active) continue; + all_colliders.push_back({.id = id, + .collider = collider_variant{circlecollider}, + .transform = transform, + .rigidbody = rigidbody}); + } + } + + // Check between all colliders if there is a collision + std::vector<std::pair<CollisionInternal, CollisionInternal>> collided + = this->gather_collisions(all_colliders); + + // For both objects call the collision handler + 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); + } +} + +void CollisionSystem::collision_handler_request(CollisionInternal & this_data, + CollisionInternal & other_data) { + + 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; + } + } + + // 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, + }; + + // Determine if static needs to be called + this->determine_collision_handler(collision_info); +} + +std::pair<vec2, CollisionSystem::Direction> +CollisionSystem::collision_handler(CollisionInternal & data1, CollisionInternal & data2, + 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); + 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); + 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); + 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); + break; + } + } + + 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; + if (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; + if (data1.rigidbody.data.linear_velocity.x != 0) + resolution.x = data1.rigidbody.data.linear_velocity.x + * (resolution.y / data1.rigidbody.data.linear_velocity.y); + } + + return std::make_pair(resolution, resolution_direction); +} + +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; + } + } + + 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; + + // Compute the distance between the two circle centers + float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y); + + // Compute the combined radii of the two circles + float combined_radius = circle_collider1.radius + circle_collider2.radius; + + // 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; +} + +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; + + // Compute half-dimensions of the box + float half_width = box_collider.dimensions.x / 2.0f; + float half_height = box_collider.dimensions.y / 2.0f; + + // 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); + + // Find the vector from the circle center to the closest point + vec2 closest_delta = delta - closest_point; + + // 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; + + // Compute penetration depth + float penetration_depth = circle_collider.radius - distance; + + // Compute the resolution vector + vec2 resolution = collision_normal * penetration_depth; + + return resolution; +} + +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); + }; + // 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); +} + +void CollisionSystem::static_collision_handler(CollisionInfo & info) { + // Move object back using calculate move back value + info.this_transform.position += info.resolution; + + // If bounce is enabled mirror velocity + if (info.this_rigidbody.data.elastisity_coefficient > 0) { + if (info.resolution_direction == Direction::BOTH) { + info.this_rigidbody.data.linear_velocity.y + = -info.this_rigidbody.data.linear_velocity.y + * info.this_rigidbody.data.elastisity_coefficient; + info.this_rigidbody.data.linear_velocity.x + = -info.this_rigidbody.data.linear_velocity.x + * info.this_rigidbody.data.elastisity_coefficient; + } else if (info.resolution_direction == Direction::Y_DIRECTION) { + info.this_rigidbody.data.linear_velocity.y + = -info.this_rigidbody.data.linear_velocity.y + * info.this_rigidbody.data.elastisity_coefficient; + } else if (info.resolution_direction == Direction::X_DIRECTION) { + info.this_rigidbody.data.linear_velocity.x + = -info.this_rigidbody.data.linear_velocity.x + * info.this_rigidbody.data.elastisity_coefficient; + } + } + // Stop movement if bounce is disabled + else { + info.this_rigidbody.data.linear_velocity = {0, 0}; + } +} + +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 + + // 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]); + } + } + + return collisions_ret; +} + +bool CollisionSystem::have_common_layer(const std::set<int> & layers1, + const std::set<int> & layers2) { + + // Check if any number is equal in the layers + for (int num : layers1) { + if (layers2.contains(num)) { + // Common layer found + return true; + break; + } + } + // No common layer found + 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; + } + } +} + +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); + } + } + 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)); +} diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h index c1a70d8..5b136c6 100644 --- a/src/crepe/system/CollisionSystem.h +++ b/src/crepe/system/CollisionSystem.h @@ -1,13 +1,311 @@ #pragma once +#include <optional> +#include <variant> +#include <vector> + +#include "api/BoxCollider.h" +#include "api/CircleCollider.h" +#include "api/Event.h" +#include "api/Metadata.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "api/Vector2.h" + +#include "Collider.h" #include "System.h" namespace crepe { +//! A system responsible for detecting and handling collisions between colliders. class CollisionSystem : public System { public: using System::System; + +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>>; + + //! Enum representing the types of collider pairs for collision detection. + enum class CollisionInternalType { + BOX_BOX, + CIRCLE_CIRCLE, + BOX_CIRCLE, + CIRCLE_BOX, + }; + + /** + * \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. + * 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. + */ + 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. + vec2 resolution; + //! The direction of movement for resolving the collision. + Direction resolution_direction = Direction::NONE; + }; + +public: + //! Updates the collision system by checking for collisions between colliders and handling them. void update() override; + +private: + /** + * \brief Determines the type of collider pair from two colliders. + * + * Uses std::holds_alternative to identify the types of the provided colliders. + * + * \param collider1 First collider variant (BoxCollider or CircleCollider). + * \param collider2 Second collider variant (BoxCollider or CircleCollider). + * \return The combined type of the two colliders. + */ + 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. + * + * Processes collision data and adjusts objects to resolve collisions and/or calls the user oncollision script function. + * + * \param data1 Collision data for the first collider. + * \param data2 Collision data for the second collider. + */ + void collision_handler_request(CollisionInternal & data1, CollisionInternal & data2); + + /** + * \brief Resolves collision between two colliders and calculates the movement required. + * + * Determines the displacement and direction needed to separate colliders based on their types. + * + * \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. + */ + std::pair<vec2, Direction> collision_handler(CollisionInternal & data1, + CollisionInternal & data2, + CollisionInternalType type); + + /** + * \brief Calculates the resolution vector for two BoxColliders. + * + * Computes the displacement required to separate two overlapping BoxColliders. + * + * \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. + */ + vec2 get_box_box_resolution(const BoxCollider & box_collider1, + const BoxCollider & box_collider2, const vec2 & position1, + const vec2 & position2) const; + + /** + * \brief Calculates the resolution vector for two CircleCollider. + * + * Computes the displacement required to separate two overlapping CircleCollider. + * + * \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. + */ + vec2 get_circle_circle_resolution(const CircleCollider & circle_collider1, + const CircleCollider & circle_collider2, + const vec2 & final_position1, + const vec2 & final_position2) const; + + /** + * \brief Calculates the resolution vector for two CircleCollider. + * + * Computes the displacement required to separate two overlapping CircleCollider. + * + * \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. + * + * Decides the correct resolution process based on the dynamic or static nature of the colliders involved. + * + * \param info Collision information containing data about both colliders. + */ + void determine_collision_handler(CollisionInfo & info); + + /** + * \brief Handles collisions involving static objects. + * + * Resolves collisions by adjusting positions and modifying velocities if bounce is enabled. + * + * \param info Collision information containing data about both colliders. + */ + void static_collision_handler(CollisionInfo & info); + +private: + /** + * \brief Checks for collisions between colliders. + * + * Identifies collisions and generates pairs of colliding objects for further processing. + * + * \param colliders A collection of all active colliders. + * \return A list of collision pairs with their associated data. + */ + std::vector<std::pair<CollisionInternal, CollisionInternal>> + gather_collisions(std::vector<CollisionInternal> & colliders); + + /** + * \brief Checks if two collision layers have at least one common layer. + * + * 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. + * + * \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. + */ + + bool have_common_layer(const std::set<int> & layers1, const std::set<int> & layers2); + + /** + * \brief Checks for collision between two colliders. + * + * Calls the appropriate collision detection function based on the collider types. + * + * \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; + + /** + * \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. + */ + bool get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, + const Transform & transform1, const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) 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. + */ + bool get_box_circle_collision(const BoxCollider & box1, const CircleCollider & circle2, + const Transform & transform1, const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) 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. + * + * \return status of collision + */ + bool get_circle_circle_collision(const CircleCollider & circle1, + const CircleCollider & circle2, + const Transform & transform1, + const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const; +}; + +/** + * \brief Event triggered during a collision between objects. + */ +class CollisionEvent : public Event { +public: + crepe::CollisionSystem::CollisionInfo info; + CollisionEvent(const crepe::CollisionSystem::CollisionInfo & collisionInfo) + : info(collisionInfo) {} }; } // namespace crepe diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp index 7cc8d30..aaa8bdf 100644 --- a/src/crepe/system/InputSystem.cpp +++ b/src/crepe/system/InputSystem.cpp @@ -24,10 +24,10 @@ void InputSystem::update() { RefVector<Transform> transform_vec = mgr.get_components_by_id<Transform>(current_cam.game_object_id); Transform & cam_transform = transform_vec.front().get(); - int camera_origin_x - = cam_transform.position.x + current_cam.offset.x - (current_cam.viewport_size.x / 2); - int camera_origin_y - = cam_transform.position.y + current_cam.offset.y - (current_cam.viewport_size.y / 2); + int camera_origin_x = cam_transform.position.x + current_cam.data.postion_offset.x + - (current_cam.viewport_size.x / 2); + int camera_origin_y = cam_transform.position.y + current_cam.data.postion_offset.y + - (current_cam.viewport_size.y / 2); for (const SDLContext::EventData & event : event_list) { int world_mouse_x = event.mouse_position.x + camera_origin_x; diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index c647284..068f01c 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -25,7 +25,7 @@ public: private: /** * \brief Emits a particle from the specified emitter based on its emission properties. - * + * * \param emitter Reference to the ParticleEmitter. * \param transform Const reference to the Transform component associated with the emitter. */ @@ -34,7 +34,7 @@ private: /** * \brief Calculates the number of times particles should be emitted based on emission rate * and update count. - * + * * \param count Current update count. * \param emission Emission rate. * \return The number of particles to emit. @@ -44,7 +44,7 @@ private: /** * \brief Checks whether particles are within the emitter’s boundary, resets or stops * particles if they exit. - * + * * \param emitter Reference to the ParticleEmitter. * \param transform Const reference to the Transform component associated with the emitter. */ @@ -52,7 +52,7 @@ private: /** * \brief Generates a random angle for particle emission within the specified range. - * + * * \param min_angle Minimum emission angle in degrees. * \param max_angle Maximum emission angle in degrees. * \return Random angle in degrees. @@ -61,7 +61,7 @@ private: /** * \brief Generates a random speed for particle emission within the specified range. - * + * * \param min_speed Minimum emission speed. * \param max_speed Maximum emission speed. * \return Random speed. diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index bebcf3d..ebf4439 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -25,17 +25,20 @@ void PhysicsSystem::update() { if (transform.game_object_id == rigidbody.game_object_id) { // Add gravity - if (rigidbody.data.use_gravity) { + if (rigidbody.data.gravity_scale > 0) { rigidbody.data.linear_velocity.y += (rigidbody.data.mass * rigidbody.data.gravity_scale * gravity); } // Add damping - if (rigidbody.data.angular_damping != 0) { - rigidbody.data.angular_velocity *= rigidbody.data.angular_damping; + if (rigidbody.data.angular_velocity_coefficient > 0) { + rigidbody.data.angular_velocity + *= rigidbody.data.angular_velocity_coefficient; } - if (rigidbody.data.linear_damping != vec2{0, 0}) { - rigidbody.data.linear_velocity *= rigidbody.data.linear_damping; + if (rigidbody.data.linear_velocity_coefficient.x > 0 + && rigidbody.data.linear_velocity_coefficient.y > 0) { + rigidbody.data.linear_velocity + *= rigidbody.data.linear_velocity_coefficient; } // Max velocity check diff --git a/src/crepe/system/PhysicsSystem.h b/src/crepe/system/PhysicsSystem.h index 227ab69..26152a5 100644 --- a/src/crepe/system/PhysicsSystem.h +++ b/src/crepe/system/PhysicsSystem.h @@ -6,7 +6,7 @@ namespace crepe { /** * \brief System that controls all physics - * + * * This class is a physics system that uses a rigidbody and transform to add physics to a game * object. */ @@ -15,7 +15,7 @@ public: using System::System; /** * \brief updates the physics system. - * + * * It calculates new velocties and changes the postion in the transform. */ void update() override; diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index 92dba43..26f2c85 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -17,13 +17,19 @@ using namespace crepe; using namespace std; -void RenderSystem::clear_screen() { this->context.clear_screen(); } +void RenderSystem::clear_screen() { + SDLContext & ctx = this->mediator.sdl_context; + ctx.clear_screen(); +} -void RenderSystem::present_screen() { this->context.present_screen(); } +void RenderSystem::present_screen() { + SDLContext & ctx = this->mediator.sdl_context; + ctx.present_screen(); +} -const Camera & RenderSystem::update_camera() { +SDLContext::CameraValues RenderSystem::update_camera() { ComponentManager & mgr = this->mediator.component_manager; - + SDLContext & ctx = this->mediator.sdl_context; RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); if (cameras.size() == 0) throw std::runtime_error("No cameras in current scene"); @@ -32,16 +38,18 @@ const Camera & RenderSystem::update_camera() { if (!cam.active) continue; const Transform & transform = mgr.get_components_by_id<Transform>(cam.game_object_id).front().get(); - this->context.set_camera(cam); - this->cam_pos = transform.position + cam.offset; - return cam; + SDLContext::CameraValues cam_val = ctx.set_camera(cam); + cam_val.cam_pos = transform.position + cam.data.postion_offset; + return cam_val; } throw std::runtime_error("No active cameras in current scene"); } bool sorting_comparison(const Sprite & a, const Sprite & b) { - if (a.sorting_in_layer < b.sorting_in_layer) return true; - if (a.sorting_in_layer == b.sorting_in_layer) return a.order_in_layer < b.order_in_layer; + if (a.data.sorting_in_layer != b.data.sorting_in_layer) + return a.data.sorting_in_layer < b.data.sorting_in_layer; + if (a.data.order_in_layer != b.data.order_in_layer) + return a.data.order_in_layer < b.data.order_in_layer; return false; } @@ -59,10 +67,11 @@ void RenderSystem::update() { this->present_screen(); } -bool RenderSystem::render_particle(const Sprite & sprite, const Camera & cam, +bool RenderSystem::render_particle(const Sprite & sprite, const SDLContext::CameraValues & cam, const double & scale) { ComponentManager & mgr = this->mediator.component_manager; + SDLContext & ctx = this->mediator.sdl_context; vector<reference_wrapper<ParticleEmitter>> emitters = mgr.get_components_by_id<ParticleEmitter>(sprite.game_object_id); @@ -77,10 +86,9 @@ bool RenderSystem::render_particle(const Sprite & sprite, const Camera & cam, for (const Particle & p : em.data.particles) { if (!p.active) continue; - this->context.draw(SDLContext::RenderContext{ + ctx.draw(SDLContext::RenderContext{ .sprite = sprite, .cam = cam, - .cam_pos = this->cam_pos, .pos = p.position, .angle = p.angle, .scale = scale, @@ -89,12 +97,12 @@ bool RenderSystem::render_particle(const Sprite & sprite, const Camera & cam, } return rendering_particles; } -void RenderSystem::render_normal(const Sprite & sprite, const Camera & cam, +void RenderSystem::render_normal(const Sprite & sprite, const SDLContext::CameraValues & cam, const Transform & tm) { - this->context.draw(SDLContext::RenderContext{ + SDLContext & ctx = this->mediator.sdl_context; + ctx.draw(SDLContext::RenderContext{ .sprite = sprite, .cam = cam, - .cam_pos = this->cam_pos, .pos = tm.position, .angle = tm.rotation, .scale = tm.scale, @@ -103,7 +111,7 @@ void RenderSystem::render_normal(const Sprite & sprite, const Camera & cam, void RenderSystem::render() { ComponentManager & mgr = this->mediator.component_manager; - const Camera & cam = this->update_camera(); + const SDLContext::CameraValues & cam = this->update_camera(); RefVector<Sprite> sprites = mgr.get_components_by_type<Sprite>(); RefVector<Sprite> sorted_sprites = this->sort(sprites); diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h index 096d058..e270a6b 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -18,7 +18,7 @@ class Transform; * \brief Manages rendering operations for all game objects. * * RenderSystem is responsible for rendering, clearing and presenting the screen, and - * managing the active camera. + * managing the active camera. */ class RenderSystem : public System { public: @@ -37,7 +37,7 @@ private: void present_screen(); //! Updates the active camera used for rendering. - const Camera & update_camera(); + SDLContext::CameraValues update_camera(); //! Renders the whole screen void render(); @@ -52,20 +52,22 @@ private: * constructor is now protected i cannot make tmp inside * \return true if particles have been rendered */ - bool render_particle(const Sprite & sprite, const Camera & cam, const double & scale); + bool render_particle(const Sprite & sprite, const SDLContext::CameraValues & cam, + const double & scale); /** - * \brief renders a sprite with a Transform component on the screen + * \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 tm the Transform component that holds the position,rotation and scale */ - void render_normal(const Sprite & sprite, const Camera & cam, const Transform & tm); + void render_normal(const Sprite & sprite, const SDLContext::CameraValues & cam, + const Transform & tm); /** * \brief sort a vector sprite objects with * - * \param objs the vector that will do a sorting algorithm on + * \param objs the vector that will do a sorting algorithm on * \return returns a sorted reference vector */ RefVector<Sprite> sort(RefVector<Sprite> & objs) const; @@ -75,13 +77,6 @@ private: * \todo Implement a text component and a button component. * \todo Consider adding text input functionality. */ - -private: - // FIXME: retrieve sdlcontext via mediator after #PR57 - SDLContext & context = SDLContext::get_instance(); - - //! camera postion in the current scene - vec2 cam_pos; }; } // namespace crepe diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h index 936e9ca..3db1b1e 100644 --- a/src/crepe/system/ScriptSystem.h +++ b/src/crepe/system/ScriptSystem.h @@ -8,7 +8,7 @@ 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. */ |