diff options
Diffstat (limited to 'src')
64 files changed, 3504 insertions, 503 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.cpp b/src/crepe/api/Button.cpp new file mode 100644 index 0000000..76f74f0 --- /dev/null +++ b/src/crepe/api/Button.cpp @@ -0,0 +1,11 @@ +#include "Button.h" + +namespace crepe { + +Button::Button(game_object_id_t id, const vec2 & dimensions, const vec2 & offset, +			   const std::function<void()> & on_click, bool is_toggle) +	: UIObject(id, dimensions, offset), +	  is_toggle(is_toggle), +	  on_click(on_click) {} + +} // namespace crepe diff --git a/src/crepe/api/Button.h b/src/crepe/api/Button.h new file mode 100644 index 0000000..61b18d7 --- /dev/null +++ b/src/crepe/api/Button.h @@ -0,0 +1,67 @@ +#pragma once + +#include <functional> + +#include "UIObject.h" + +namespace crepe { + +//! Represents a clickable UI button, derived from the UiObject class. +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 +	 * \param is_toggle Optional flag to indicate if the button is a toggle button. Defaults to false. +	 * \param on_click callback function that will be invoked when the button is clicked. +	 */ +	Button(game_object_id_t id, const vec2 & dimensions, const vec2 & offset, +		   const std::function<void()> & on_click, bool is_toggle = false); + +	/** +	 * \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. +	 */ +	bool is_toggle = false; +	// 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()`. +	 */ +	std::function<void()> on_click = nullptr; + +	/** +	 * \brief Callback function to be executed when the mouse enters the button's boundaries. +	 * +	 * This function is triggered when the mouse cursor moves over the button, allowing +	 * custom actions like visual effects, highlighting, or sound effects. +	 */ +	std::function<void()> on_mouse_enter = nullptr; + +	/** +	 * \brief Callback function to be executed when the mouse exits the button's boundaries. +	 * +	 * This function is triggered when the mouse cursor moves out of the button's area, +	 * allowing custom actions like resetting visual effects or playing exit-related effects. +	 */ +	std::function<void()> on_mouse_exit = nullptr; + +private: +	//! friend relation for is_pressed and hover variables +	friend class InputSystem; +	//! Indicates whether the toggle button is pressed +	bool is_pressed = false; +	//! Indicates whether the mouse is currently hovering over the button +	bool hover = false; + +public: +}; + +} // namespace crepe diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 60d9dc5..ca4edec 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -13,10 +13,14 @@ target_sources(crepe PUBLIC  	Metadata.cpp  	Camera.cpp  	Animator.cpp +	BoxCollider.cpp +	CircleCollider.cpp  	LoopManager.cpp  	Asset.cpp  	EventHandler.cpp  	Script.cpp +	Button.cpp +	UIObject.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -32,16 +36,20 @@ 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  	LoopManager.h  	Asset.h +	Button.h +	UIObject.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 b267e3e..f2f3daf 100644 --- a/src/crepe/api/Event.h +++ b/src/crepe/api/Event.h @@ -88,12 +88,30 @@ public:  	//! Y-coordinate of the mouse position at the time of the event.  	int mouse_y = 0; + +	// Movement since last event in x +	int delta_x = 0; + +	// Movement since last event in y +	int delta_y = 0;  };  /** - * \brief Event triggered during a collision between objects. + * \brief Event triggered when the mouse is moved.   */ -class CollisionEvent : public Event {}; +class MouseScrollEvent : public Event { +public: +	//! X-coordinate of the mouse position at the time of the event. +	int mouse_x = 0; + +	//! Y-coordinate of the mouse position at the time of the event. +	int mouse_y = 0; + +	//! scroll direction (-1 = down, 1 = up) +	int scroll_direction = 0; +	//! scroll amount in y axis (from and away from the person). +	float scroll_delta = 0; +};  /**   * \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 ef659fd..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,70 +22,70 @@ 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   */  class IEventHandlerWrapper {  public:  	/** -     * \brief Virtual destructor for IEventHandlerWrapper. -     */ +	 * \brief Virtual destructor for IEventHandlerWrapper. +	 */  	virtual ~IEventHandlerWrapper() = default;  	/** -     * \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. -     */ +	 * \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. +	 */  	bool exec(const Event & e);  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. -     */ +	 * \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. +	 */  	virtual bool call(const Event & e) = 0;  };  /**   * \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>  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. -     */ +	 * \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);  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. -     */ +	 * \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. +	 */  	bool call(const Event & e) override;  	//! The event handler function.  	EventHandler<EventType> handler; 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/KeyCodes.h b/src/crepe/api/KeyCodes.h index 9e173e0..fcfc080 100644 --- a/src/crepe/api/KeyCodes.h +++ b/src/crepe/api/KeyCodes.h @@ -1,5 +1,5 @@  #pragma once - +namespace crepe {  //! Enumeration for mouse button inputs, including standard and extended buttons.  enum class MouseButton {  	NONE = 0, //!< No mouse button input. @@ -85,9 +85,9 @@ enum class Keycode {  	PRINT_SCREEN = 283, //!< Print Screen key.  	PAUSE = 284, //!< Pause key.  	/** -	 * \name Function keys (F1-F25). -	 * \{ -	 */ +		 * \name Function keys (F1-F25). +		 * \{ +		 */  	F1 = 290,  	F2 = 291,  	F3 = 292, @@ -115,9 +115,9 @@ enum class Keycode {  	F25 = 314,  	/// \}  	/** -	 * \name Keypad digits and operators. -	 * \{ -	 */ +		 * \name Keypad digits and operators. +		 * \{ +		 */  	KP0 = 320,  	KP1 = 321,  	KP2 = 322, @@ -137,9 +137,9 @@ enum class Keycode {  	KP_EQUAL = 336,  	/// \}  	/** -	 * \name Modifier keys. -	 * \{ -	 */ +		 * \name Modifier keys. +		 * \{ +		 */  	LEFT_SHIFT = 340,  	LEFT_CONTROL = 341,  	LEFT_ALT = 342, @@ -151,3 +151,4 @@ enum class Keycode {  	/// \}  	MENU = 348, //!< Menu key.  }; +} // namespace crepe diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index 42a1e77..c25e31e 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -3,10 +3,12 @@  #include "../manager/EventManager.h"  #include "../system/AnimatorSystem.h"  #include "../system/CollisionSystem.h" +#include "../system/InputSystem.h"  #include "../system/ParticleSystem.h"  #include "../system/PhysicsSystem.h"  #include "../system/RenderSystem.h"  #include "../system/ScriptSystem.h" +#include "manager/EventManager.h"  #include "LoopManager.h" @@ -20,20 +22,27 @@ LoopManager::LoopManager() {  	this->load_system<PhysicsSystem>();  	this->load_system<RenderSystem>();  	this->load_system<ScriptSystem>(); +	this->load_system<InputSystem>();  	this->event_manager.subscribe<ShutDownEvent>(  		[this](const ShutDownEvent & event) { return this->on_shutdown(event); });  }  void LoopManager::process_input() {  -	 -	this->sdl_context.handle_events(this->game_running); } +	this->get_system<InputSystem>().update(); +}  void LoopManager::start() {  	this->setup();  	this->loop();  } -void LoopManager::fixed_update() {} +void LoopManager::fixed_update() { +	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() { @@ -55,14 +64,15 @@ void LoopManager::loop() {  }  void LoopManager::setup() { -  	this->game_running = true;  	this->loop_timer.start(); +	this->scene_manager.load_next_scene();  }  void LoopManager::render() {  	if (!this->game_running) return; +	this->get_system<AnimatorSystem>().update();  	this->get_system<RenderSystem>().update();  }  bool LoopManager::on_shutdown(const ShutDownEvent & e) { diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h index d07ef66..9986aa5 100644 --- a/src/crepe/api/LoopManager.h +++ b/src/crepe/api/LoopManager.h @@ -8,6 +8,7 @@  #include "../manager/EventManager.h"  #include "../manager/LoopTimerManager.h"  #include "../system/System.h" +#include "../manager/Mediator.h"  #include "api/Event.h" @@ -98,7 +99,7 @@ private:  	LoopTimerManager loop_timer{mediator};  	//! EventManager instance  	EventManager event_manager{mediator}; -	 +  	//! SDL context \todo no more singletons!  	SDLContext & sdl_context = SDLContext::get_instance(); 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 1b339b0..d99ab0e 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/api/UIObject.cpp b/src/crepe/api/UIObject.cpp new file mode 100644 index 0000000..d239b89 --- /dev/null +++ b/src/crepe/api/UIObject.cpp @@ -0,0 +1,8 @@ +#include "UIObject.h" + +using namespace crepe; + +UIObject::UIObject(game_object_id_t id, const vec2 & dimensions, const vec2 & offset) +	: Component(id), +	  dimensions(dimensions), +	  offset(offset) {} diff --git a/src/crepe/api/UIObject.h b/src/crepe/api/UIObject.h new file mode 100644 index 0000000..f7f4fba --- /dev/null +++ b/src/crepe/api/UIObject.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../Component.h" + +namespace crepe { + +/** + * \brief Represents a UI object in the game, derived from the Component class. + */ +class UIObject : public Component { +public: +	/** +	 * \brief Constructs a UiObject with the specified game object ID. +	 * \param id The unique ID of the game object associated with this UI object. +	 * \param dimensions width and height of the UIObject +	 * \param offset Offset relative to the GameObject Transform +	 */ +	UIObject(game_object_id_t id, const vec2 & dimensions, const vec2 & offset); +	//! Width and height of the UIObject +	vec2 dimensions; +	//! Position offset relative to this GameObjects Transform +	vec2 offset; +}; + +} // namespace crepe diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index e8be7ca..4cc2206 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -6,22 +6,22 @@  #include <SDL2/SDL_render.h>  #include <SDL2/SDL_surface.h>  #include <SDL2/SDL_video.h> +#include <array>  #include <cmath>  #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 "../util/Log.h"  #include "SDLContext.h" -#include "api/Color.h"  #include "types.h"  using namespace crepe; @@ -76,32 +76,150 @@ SDLContext::~SDLContext() {  	IMG_Quit();  	SDL_Quit();  } -void SDLContext::handle_events(bool & running) { -	//TODO: wouter i need events -	/* -	SDL_Event event; -	SDL_PollEvent(&event); -	switch (event.type) { -		case SDL_QUIT: -			running = false; -			break; -		case SDL_KEYDOWN: -			triggerEvent(KeyPressedEvent(getCustomKey(event.key.keysym.sym))); -			break; -		case SDL_MOUSEBUTTONDOWN: -			int x, y; -			SDL_GetMouseState(&x, &y); -			triggerEvent(MousePressedEvent(x, y)); -			break; + +Keycode SDLContext::sdl_to_keycode(SDL_Keycode sdl_key) { +	static const std::array<Keycode, SDL_NUM_SCANCODES> LOOKUP_TABLE = [] { +		std::array<Keycode, SDL_NUM_SCANCODES> table{}; +		table.fill(Keycode::NONE); + +		table[SDL_SCANCODE_SPACE] = Keycode::SPACE; +		table[SDL_SCANCODE_APOSTROPHE] = Keycode::APOSTROPHE; +		table[SDL_SCANCODE_COMMA] = Keycode::COMMA; +		table[SDL_SCANCODE_MINUS] = Keycode::MINUS; +		table[SDL_SCANCODE_PERIOD] = Keycode::PERIOD; +		table[SDL_SCANCODE_SLASH] = Keycode::SLASH; +		table[SDL_SCANCODE_0] = Keycode::D0; +		table[SDL_SCANCODE_1] = Keycode::D1; +		table[SDL_SCANCODE_2] = Keycode::D2; +		table[SDL_SCANCODE_3] = Keycode::D3; +		table[SDL_SCANCODE_4] = Keycode::D4; +		table[SDL_SCANCODE_5] = Keycode::D5; +		table[SDL_SCANCODE_6] = Keycode::D6; +		table[SDL_SCANCODE_7] = Keycode::D7; +		table[SDL_SCANCODE_8] = Keycode::D8; +		table[SDL_SCANCODE_9] = Keycode::D9; +		table[SDL_SCANCODE_SEMICOLON] = Keycode::SEMICOLON; +		table[SDL_SCANCODE_EQUALS] = Keycode::EQUAL; +		table[SDL_SCANCODE_A] = Keycode::A; +		table[SDL_SCANCODE_B] = Keycode::B; +		table[SDL_SCANCODE_C] = Keycode::C; +		table[SDL_SCANCODE_D] = Keycode::D; +		table[SDL_SCANCODE_E] = Keycode::E; +		table[SDL_SCANCODE_F] = Keycode::F; +		table[SDL_SCANCODE_G] = Keycode::G; +		table[SDL_SCANCODE_H] = Keycode::H; +		table[SDL_SCANCODE_I] = Keycode::I; +		table[SDL_SCANCODE_J] = Keycode::J; +		table[SDL_SCANCODE_K] = Keycode::K; +		table[SDL_SCANCODE_L] = Keycode::L; +		table[SDL_SCANCODE_M] = Keycode::M; +		table[SDL_SCANCODE_N] = Keycode::N; +		table[SDL_SCANCODE_O] = Keycode::O; +		table[SDL_SCANCODE_P] = Keycode::P; +		table[SDL_SCANCODE_Q] = Keycode::Q; +		table[SDL_SCANCODE_R] = Keycode::R; +		table[SDL_SCANCODE_S] = Keycode::S; +		table[SDL_SCANCODE_T] = Keycode::T; +		table[SDL_SCANCODE_U] = Keycode::U; +		table[SDL_SCANCODE_V] = Keycode::V; +		table[SDL_SCANCODE_W] = Keycode::W; +		table[SDL_SCANCODE_X] = Keycode::X; +		table[SDL_SCANCODE_Y] = Keycode::Y; +		table[SDL_SCANCODE_Z] = Keycode::Z; +		table[SDL_SCANCODE_LEFTBRACKET] = Keycode::LEFT_BRACKET; +		table[SDL_SCANCODE_BACKSLASH] = Keycode::BACKSLASH; +		table[SDL_SCANCODE_RIGHTBRACKET] = Keycode::RIGHT_BRACKET; +		table[SDL_SCANCODE_GRAVE] = Keycode::GRAVE_ACCENT; +		table[SDL_SCANCODE_ESCAPE] = Keycode::ESCAPE; +		table[SDL_SCANCODE_RETURN] = Keycode::ENTER; +		table[SDL_SCANCODE_TAB] = Keycode::TAB; +		table[SDL_SCANCODE_BACKSPACE] = Keycode::BACKSPACE; +		table[SDL_SCANCODE_INSERT] = Keycode::INSERT; +		table[SDL_SCANCODE_DELETE] = Keycode::DELETE; +		table[SDL_SCANCODE_RIGHT] = Keycode::RIGHT; +		table[SDL_SCANCODE_LEFT] = Keycode::LEFT; +		table[SDL_SCANCODE_DOWN] = Keycode::DOWN; +		table[SDL_SCANCODE_UP] = Keycode::UP; +		table[SDL_SCANCODE_PAGEUP] = Keycode::PAGE_UP; +		table[SDL_SCANCODE_PAGEDOWN] = Keycode::PAGE_DOWN; +		table[SDL_SCANCODE_HOME] = Keycode::HOME; +		table[SDL_SCANCODE_END] = Keycode::END; +		table[SDL_SCANCODE_CAPSLOCK] = Keycode::CAPS_LOCK; +		table[SDL_SCANCODE_SCROLLLOCK] = Keycode::SCROLL_LOCK; +		table[SDL_SCANCODE_NUMLOCKCLEAR] = Keycode::NUM_LOCK; +		table[SDL_SCANCODE_PRINTSCREEN] = Keycode::PRINT_SCREEN; +		table[SDL_SCANCODE_PAUSE] = Keycode::PAUSE; +		table[SDL_SCANCODE_F1] = Keycode::F1; +		table[SDL_SCANCODE_F2] = Keycode::F2; +		table[SDL_SCANCODE_F3] = Keycode::F3; +		table[SDL_SCANCODE_F4] = Keycode::F4; +		table[SDL_SCANCODE_F5] = Keycode::F5; +		table[SDL_SCANCODE_F6] = Keycode::F6; +		table[SDL_SCANCODE_F7] = Keycode::F7; +		table[SDL_SCANCODE_F8] = Keycode::F8; +		table[SDL_SCANCODE_F9] = Keycode::F9; +		table[SDL_SCANCODE_F10] = Keycode::F10; +		table[SDL_SCANCODE_F11] = Keycode::F11; +		table[SDL_SCANCODE_F12] = Keycode::F12; +		table[SDL_SCANCODE_KP_0] = Keycode::KP0; +		table[SDL_SCANCODE_KP_1] = Keycode::KP1; +		table[SDL_SCANCODE_KP_2] = Keycode::KP2; +		table[SDL_SCANCODE_KP_3] = Keycode::KP3; +		table[SDL_SCANCODE_KP_4] = Keycode::KP4; +		table[SDL_SCANCODE_KP_5] = Keycode::KP5; +		table[SDL_SCANCODE_KP_6] = Keycode::KP6; +		table[SDL_SCANCODE_KP_7] = Keycode::KP7; +		table[SDL_SCANCODE_KP_8] = Keycode::KP8; +		table[SDL_SCANCODE_KP_9] = Keycode::KP9; +		table[SDL_SCANCODE_LSHIFT] = Keycode::LEFT_SHIFT; +		table[SDL_SCANCODE_LCTRL] = Keycode::LEFT_CONTROL; +		table[SDL_SCANCODE_LALT] = Keycode::LEFT_ALT; +		table[SDL_SCANCODE_LGUI] = Keycode::LEFT_SUPER; +		table[SDL_SCANCODE_RSHIFT] = Keycode::RIGHT_SHIFT; +		table[SDL_SCANCODE_RCTRL] = Keycode::RIGHT_CONTROL; +		table[SDL_SCANCODE_RALT] = Keycode::RIGHT_ALT; +		table[SDL_SCANCODE_RGUI] = Keycode::RIGHT_SUPER; +		table[SDL_SCANCODE_MENU] = Keycode::MENU; + +		return table; +	}(); + +	if (sdl_key < 0 || sdl_key >= SDL_NUM_SCANCODES) { +		return Keycode::NONE;  	} -	*/ + +	return LOOKUP_TABLE[sdl_key];  } -void SDLContext::clear_screen() { +MouseButton SDLContext::sdl_to_mousebutton(Uint8 sdl_button) { +	static const std::array<MouseButton, 5> MOUSE_BUTTON_LOOKUP_TABLE = [] { +		std::array<MouseButton, 5> table{}; +		table.fill(MouseButton::NONE); + +		table[SDL_BUTTON_LEFT] = MouseButton::LEFT_MOUSE; +		table[SDL_BUTTON_RIGHT] = MouseButton::RIGHT_MOUSE; +		table[SDL_BUTTON_MIDDLE] = MouseButton::MIDDLE_MOUSE; +		table[SDL_BUTTON_X1] = MouseButton::X1_MOUSE; +		table[SDL_BUTTON_X2] = MouseButton::X2_MOUSE; + +		return table; +	}(); + +	if (sdl_button >= MOUSE_BUTTON_LOOKUP_TABLE.size()) { +		// Return NONE for invalid or unmapped button +		return MouseButton::NONE; +	} + +	return MOUSE_BUTTON_LOOKUP_TABLE[sdl_button]; +} + +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{ @@ -111,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); @@ -152,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(); } @@ -224,6 +374,66 @@ ivec2 SDLContext::get_size(const Texture & ctx) {  void SDLContext::delay(int ms) const { SDL_Delay(ms); } +std::vector<SDLContext::EventData> SDLContext::get_events() { +	std::vector<SDLContext::EventData> event_list; +	SDL_Event event; +	while (SDL_PollEvent(&event)) { +		switch (event.type) { +			case SDL_QUIT: +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::SHUTDOWN, +				}); +				break; +			case SDL_KEYDOWN: +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::KEYDOWN, +					.key = sdl_to_keycode(event.key.keysym.scancode), +					.key_repeat = (event.key.repeat != 0), +				}); +				break; +			case SDL_KEYUP: +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::KEYUP, +					.key = sdl_to_keycode(event.key.keysym.scancode), +				}); +				break; +			case SDL_MOUSEBUTTONDOWN: +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::MOUSEDOWN, +					.mouse_button = sdl_to_mousebutton(event.button.button), +					.mouse_position = {event.button.x, event.button.y}, +				}); +				break; +			case SDL_MOUSEBUTTONUP: { +				int x, y; +				SDL_GetMouseState(&x, &y); +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::MOUSEUP, +					.mouse_button = sdl_to_mousebutton(event.button.button), +					.mouse_position = {event.button.x, event.button.y}, +				}); +			} break; + +			case SDL_MOUSEMOTION: { +				event_list.push_back( +					EventData{.event_type = SDLContext::EventType::MOUSEMOVE, +							  .mouse_position = {event.motion.x, event.motion.y}, +							  .rel_mouse_move = {event.motion.xrel, event.motion.yrel}}); +			} break; + +			case SDL_MOUSEWHEEL: { +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::MOUSEWHEEL, +					.mouse_position = {event.motion.x, event.motion.y}, +					// TODO: why is this needed? +					.scroll_direction = event.wheel.y < 0 ? -1 : 1, +					.scroll_delta = event.wheel.preciseY, +				}); +			} break; +		} +	} +	return event_list; +}  void SDLContext::set_color_texture(const Texture & texture, const Color & color) {  	SDL_SetTextureColorMod(texture.texture.get(), color.r, color.g, color.b);  	SDL_SetTextureAlphaMod(texture.texture.get(), color.a); diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index e49ca78..e232511 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -1,5 +1,6 @@  #pragma once +#include <SDL2/SDL.h>  #include <SDL2/SDL_keycode.h>  #include <SDL2/SDL_rect.h>  #include <SDL2/SDL_render.h> @@ -9,38 +10,88 @@  #include <memory>  #include <string> -#include "../api/Camera.h" -#include "../api/Sprite.h" - +#include "api/Camera.h"  #include "api/Color.h" +#include "api/KeyCodes.h" +#include "api/Sprite.h"  #include "api/Texture.h" +#include "api/Transform.h"  #include "types.h"  namespace crepe { -// TODO: SDL_Keycode is defined in a header not distributed with crepe, which means this -// typedef is unusable when crepe is packaged. Wouter will fix this later. -typedef SDL_Keycode CREPE_KEYCODES; - +class LoopManager; +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;  	};  public: +	//! EventType enum for passing eventType +	enum EventType { +		NONE = 0, +		MOUSEDOWN, +		MOUSEUP, +		MOUSEMOVE, +		MOUSEWHEEL, +		KEYUP, +		KEYDOWN, +		SHUTDOWN, + +	}; +	//! EventData struct for passing event data from facade +	struct EventData { +		SDLContext::EventType event_type = SDLContext::EventType::NONE; +		Keycode key = Keycode::NONE; +		bool key_repeat = false; +		MouseButton mouse_button = MouseButton::NONE; +		ivec2 mouse_position = {-1, -1}; +		int scroll_direction = -1; +		float scroll_delta = INFINITY; +		ivec2 rel_mouse_move = {-1, -1}; +	};  	/**  	 * \brief Gets the singleton instance of SDLContext.  	 * \return Reference to the SDLContext instance. @@ -53,17 +104,42 @@ public:  	SDLContext & operator=(SDLContext &&) = delete;  private: -	//! will only use handle_events -	friend class LoopManager; +	//! will only use get_events +	friend class InputSystem;  	/** -	 * \brief Handles SDL events such as window close and input. -	 * \param running Reference to a boolean flag that controls the main loop. +	 * \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()`  	 */ -	void handle_events(bool & running); +	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. +	 */ +	Keycode sdl_to_keycode(SDL_Keycode sdl_key); + +	/** +	 * \brief Converts an SDL mouse button code to the custom MouseButton type. +	 * +	 * 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;  	/** @@ -118,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); @@ -132,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  	 * @@ -150,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); @@ -169,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 ad37586..44429d9 100644 --- a/src/crepe/manager/ComponentManager.h +++ b/src/crepe/manager/ComponentManager.h @@ -16,7 +16,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 { @@ -56,10 +56,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 @@ -70,9 +70,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  	 */ @@ -80,24 +80,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(); @@ -115,9 +115,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 @@ -126,9 +126,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  	 */ @@ -138,7 +138,7 @@ public:  private:  	/**  	 * \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 5f8b107..30b929c 100644 --- a/src/crepe/manager/EventManager.h +++ b/src/crepe/manager/EventManager.h @@ -25,7 +25,7 @@ typedef size_t event_channel_t;  /**   * \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. @@ -36,10 +36,10 @@ public:  	EventManager(Mediator & mediator);  	/**  	 * \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). @@ -51,18 +51,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). @@ -72,9 +72,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). @@ -84,7 +84,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.  	 */ @@ -92,14 +92,13 @@ public:  	/**  	 * \brief Clear all subscriptions. -	 *  +	 *  	 * Removes all registered event handlers and clears the subscription list.  	 */  	void clear();  private: -  	/**  	 * \struct QueueEntry  	 * \brief Represents an entry in the event queue. diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h index c72af8e..987f4d4 100644 --- a/src/crepe/manager/Mediator.h +++ b/src/crepe/manager/Mediator.h @@ -3,7 +3,10 @@  #include "../util/OptionalRef.h"  // TODO: remove these singletons: +#include "../facade/SDLContext.h" +//#include "EventManager.h"  #include "SaveManager.h" +//#include "LoopTimerManager.h"  namespace crepe { @@ -26,9 +29,10 @@ class EventManager;  struct Mediator {  	OptionalRef<ComponentManager> component_manager;  	OptionalRef<SceneManager> scene_manager; -	OptionalRef<SaveManager> save_manager = SaveManager::get_instance();  	OptionalRef<EventManager> event_manager;  	OptionalRef<LoopTimerManager> loop_timer; +	OptionalRef<SaveManager> save_manager = SaveManager::get_instance(); +	OptionalRef<SDLContext> sdl_context = SDLContext::get_instance();  };  } // namespace crepe diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index 8bb6465..499f618 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 "../manager/LoopTimerManager.h"  #include "AnimatorSystem.h" @@ -10,15 +10,30 @@ using namespace crepe;  void AnimatorSystem::update() {  	ComponentManager & mgr = this->mediator.component_manager; - +	LoopTimerManager & timer = this->mediator.loop_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/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index d658b25..95f6e33 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(crepe PUBLIC  	CollisionSystem.cpp  	RenderSystem.cpp  	AnimatorSystem.cpp +	InputSystem.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -15,4 +16,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	CollisionSystem.h  	RenderSystem.h  	AnimatorSystem.h +	InputSystem.h  ) 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 new file mode 100644 index 0000000..aaa8bdf --- /dev/null +++ b/src/crepe/system/InputSystem.cpp @@ -0,0 +1,187 @@ +#include "../api/Button.h" +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" + +#include "InputSystem.h" + +using namespace crepe; + +void InputSystem::update() { +	ComponentManager & mgr = this->mediator.component_manager; +	EventManager & event_mgr = this->mediator.event_manager; +	std::vector<SDLContext::EventData> event_list = SDLContext::get_instance().get_events(); +	RefVector<Button> buttons = mgr.get_components_by_type<Button>(); +	RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); +	OptionalRef<Camera> curr_cam_ref; +	// Find the active camera +	for (Camera & cam : cameras) { +		if (!cam.active) continue; +		curr_cam_ref = cam; +		break; +	} +	if (!curr_cam_ref) return; +	Camera & current_cam = curr_cam_ref; +	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.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; +		int world_mouse_y = event.mouse_position.y + camera_origin_y; +		// check if the mouse is within the viewport +		bool mouse_in_viewport +			= !(world_mouse_x < camera_origin_x +				|| world_mouse_x > camera_origin_x + current_cam.viewport_size.x +				|| world_mouse_y < camera_origin_y +				|| world_mouse_y > camera_origin_y + current_cam.viewport_size.y); + +		switch (event.event_type) { +			case SDLContext::EventType::KEYDOWN: +				event_mgr.queue_event<KeyPressEvent>(KeyPressEvent{ +					.repeat = event.key_repeat, +					.key = event.key, +				}); +				break; +			case SDLContext::EventType::KEYUP: +				event_mgr.queue_event<KeyReleaseEvent>(KeyReleaseEvent{ +					.key = event.key, +				}); +				break; +			case SDLContext::EventType::MOUSEDOWN: +				if (!mouse_in_viewport) { +					break; +				} +				event_mgr.queue_event<MousePressEvent>(MousePressEvent{ +					.mouse_x = world_mouse_x, +					.mouse_y = world_mouse_y, +					.button = event.mouse_button, +				}); +				this->last_mouse_down_position = {world_mouse_x, world_mouse_y}; +				this->last_mouse_button = event.mouse_button; +				break; +			case SDLContext::EventType::MOUSEUP: { +				if (!mouse_in_viewport) { +					break; +				} +				event_mgr.queue_event<MouseReleaseEvent>(MouseReleaseEvent{ +					.mouse_x = world_mouse_x, +					.mouse_y = world_mouse_y, +					.button = event.mouse_button, +				}); +				//check if its a click by checking the last button down +				int delta_x = world_mouse_x - this->last_mouse_down_position.x; +				int delta_y = world_mouse_y - this->last_mouse_down_position.y; + +				if (this->last_mouse_button == event.mouse_button +					&& std::abs(delta_x) <= click_tolerance +					&& std::abs(delta_y) <= click_tolerance) { +					event_mgr.queue_event<MouseClickEvent>(MouseClickEvent{ +						.mouse_x = world_mouse_x, +						.mouse_y = world_mouse_y, +						.button = event.mouse_button, +					}); + +					this->handle_click(event.mouse_button, world_mouse_x, world_mouse_y); +				} +			} break; +			case SDLContext::EventType::MOUSEMOVE: +				if (!mouse_in_viewport) { +					break; +				} +				event_mgr.queue_event<MouseMoveEvent>(MouseMoveEvent{ +					.mouse_x = world_mouse_x, +					.mouse_y = world_mouse_y, +					.delta_x = event.rel_mouse_move.x, +					.delta_y = event.rel_mouse_move.y, +				}); +				this->handle_move(event, world_mouse_x, world_mouse_y); +				break; +			case SDLContext::EventType::MOUSEWHEEL: +				event_mgr.queue_event<MouseScrollEvent>(MouseScrollEvent{ +					.mouse_x = world_mouse_x, +					.mouse_y = world_mouse_y, +					.scroll_direction = event.scroll_direction, +					.scroll_delta = event.scroll_delta, +				}); +				break; +			case SDLContext::EventType::SHUTDOWN: +				event_mgr.queue_event<ShutDownEvent>(ShutDownEvent{}); +				break; +			default: +				break; +		} +	} +} +void InputSystem::handle_move(const SDLContext::EventData & event_data, +							  const int world_mouse_x, const int world_mouse_y) { +	ComponentManager & mgr = this->mediator.component_manager; + +	RefVector<Button> buttons = mgr.get_components_by_type<Button>(); + +	for (Button & button : buttons) { +		RefVector<Transform> transform_vec +			= mgr.get_components_by_id<Transform>(button.game_object_id); +		Transform & transform(transform_vec.front().get()); + +		bool was_hovering = button.hover; +		if (button.active +			&& this->is_mouse_inside_button(world_mouse_x, world_mouse_y, button, transform)) { +			button.hover = true; +			if (!was_hovering && button.on_mouse_enter) { +				button.on_mouse_enter(); +			} +		} else { +			button.hover = false; +			// Trigger the on_exit callback if the hover state just changed to false +			if (was_hovering && button.on_mouse_exit) { +				button.on_mouse_exit(); +			} +		} +	} +} + +void InputSystem::handle_click(const MouseButton & mouse_button, const int world_mouse_x, +							   const int world_mouse_y) { +	ComponentManager & mgr = this->mediator.component_manager; + +	RefVector<Button> buttons = mgr.get_components_by_type<Button>(); + +	for (Button & button : buttons) { +		RefVector<Transform> transform_vec +			= mgr.get_components_by_id<Transform>(button.game_object_id); +		Transform & transform = transform_vec.front().get(); + +		if (button.active +			&& this->is_mouse_inside_button(world_mouse_x, world_mouse_y, button, transform)) { +			this->handle_button_press(button); +		} +	} +} + +bool InputSystem::is_mouse_inside_button(const int mouse_x, const int mouse_y, +										 const Button & button, const Transform & transform) { +	int actual_x = transform.position.x + button.offset.x; +	int actual_y = transform.position.y + button.offset.y; + +	int half_width = button.dimensions.x / 2; +	int half_height = button.dimensions.y / 2; + +	// Check if the mouse is within the button's boundaries +	return mouse_x >= actual_x - half_width && mouse_x <= actual_x + half_width +		   && mouse_y >= actual_y - half_height && mouse_y <= actual_y + half_height; +} + +void InputSystem::handle_button_press(Button & button) { +	if (button.is_toggle) { +		if (!button.is_pressed && button.on_click) { +			button.on_click(); +		} +		button.is_pressed = !button.is_pressed; +	} else if (button.on_click) { +		button.on_click(); +	} +} diff --git a/src/crepe/system/InputSystem.h b/src/crepe/system/InputSystem.h index 0c42bd6..87e86f8 100644 --- a/src/crepe/system/InputSystem.h +++ b/src/crepe/system/InputSystem.h @@ -24,14 +24,15 @@ public:  	using System::System;  	/** -     * \brief Updates the system, processing all input events. -     * This method processes all events and triggers corresponding actions. -     */ +	 * \brief Updates the system, processing all input events. +	 * This method processes all events and triggers corresponding actions. +	 */  	void update() override;  private:  	//! Stores the last position of the mouse when the button was pressed.  	ivec2 last_mouse_down_position; +	// TODO: specify world/hud space and make regular `vec2`  	//! Stores the last mouse button pressed.  	MouseButton last_mouse_button = MouseButton::NONE; 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.   */ diff --git a/src/doc/feature/animator_creation.dox b/src/doc/feature/animator_creation.dox new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/doc/feature/animator_creation.dox diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 6f92d45..8ef71bb 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -19,3 +19,5 @@ endfunction()  add_example(asset_manager)  add_example(savemgr)  add_example(rendering_particle) +add_example(game) +add_example(button) diff --git a/src/example/button.cpp b/src/example/button.cpp new file mode 100644 index 0000000..00bdc28 --- /dev/null +++ b/src/example/button.cpp @@ -0,0 +1,54 @@ +#include <SDL2/SDL_timer.h> +#include <chrono> +#include <crepe/Component.h> +#include <crepe/ComponentManager.h> +#include <crepe/api/Animator.h> +#include <crepe/api/Button.h> +#include <crepe/api/Camera.h> +#include <crepe/api/Color.h> +#include <crepe/api/EventManager.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> +#include <crepe/api/Transform.h> +#include <crepe/system/AnimatorSystem.h> +#include <crepe/system/InputSystem.h> +#include <crepe/system/RenderSystem.h> +#include <crepe/types.h> +#include <iostream> +using namespace crepe; +using namespace std; + +int main(int argc, char * argv[]) { +	ComponentManager mgr; +	RenderSystem sys{mgr}; +	EventManager & event_mgr = EventManager::get_instance(); +	InputSystem input_sys{mgr}; +	AnimatorSystem asys{mgr}; +	GameObject camera_obj = mgr.new_object("", "", vec2{1000, 1000}, 0, 1); +	camera_obj.add_component<Camera>(Color::WHITE, ivec2{1080, 720}, vec2{2000, 2000}, 1.0f); + +	GameObject button_obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); +	auto s2 = Texture("asset/texture/test_ap43.png"); +	bool button_clicked = false; +	auto & sprite2 = button_obj.add_component<Sprite>( +		s2, Color::GREEN, Sprite::FlipSettings{false, false}, 2, 1, 100); +	std::function<void()> on_click = [&]() { std::cout << "button clicked" << std::endl; }; +	std::function<void()> on_enter = [&]() { std::cout << "enter" << std::endl; }; +	std::function<void()> on_exit = [&]() { std::cout << "exit" << std::endl; }; +	auto & button +		= button_obj.add_component<Button>(vec2{100, 100}, vec2{0, 0}, on_click, false); +	button.on_mouse_enter = on_enter; +	button.on_mouse_exit = on_exit; +	button.is_toggle = true; +	button.active = true; +	auto start = std::chrono::steady_clock::now(); +	while (true) { +		input_sys.update(); +		sys.update(); +		asys.update(); +		event_mgr.dispatch_events(); +		SDL_Delay(30); +	} +	return 0; +} diff --git a/src/example/game.cpp b/src/example/game.cpp new file mode 100644 index 0000000..4239c15 --- /dev/null +++ b/src/example/game.cpp @@ -0,0 +1,255 @@ +#include "api/CircleCollider.h" +#include "api/Scene.h" +#include "manager/ComponentManager.h" +#include "manager/Mediator.h" +#include <crepe/api/BoxCollider.h> +#include <crepe/api/Camera.h> +#include <crepe/api/Color.h> +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/LoopManager.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> +#include <crepe/api/Transform.h> +#include <crepe/api/Vector2.h> + +using namespace crepe; + +using namespace std; + +class MyScript1 : public Script { +	bool flip = false; +	bool oncollision(const CollisionEvent & test) { +		Log::logf("Box {} script on_collision()", test.info.this_collider.game_object_id); +		return true; +	} +	bool keypressed(const KeyPressEvent & test) { +		Log::logf("Box script keypressed()"); +		switch (test.key) { +			case Keycode::A: { +				Transform & tf = this->get_component<Transform>(); +				tf.position.x -= 1; +				break; +			} +			case Keycode::W: { +				Transform & tf = this->get_component<Transform>(); +				tf.position.y -= 1; +				break; +			} +			case Keycode::S: { +				Transform & tf = this->get_component<Transform>(); +				tf.position.y += 1; +				break; +			} +			case Keycode::D: { +				Transform & tf = this->get_component<Transform>(); +				tf.position.x += 1; +				break; +			} +			case Keycode::E: { +				if (flip) { +					flip = false; +					this->get_component<BoxCollider>().active = true; +					this->get_components<Sprite>()[0].get().active = true; +					this->get_component<CircleCollider>().active = false; +					this->get_components<Sprite>()[1].get().active = false; +				} else { +					flip = true; +					this->get_component<BoxCollider>().active = false; +					this->get_components<Sprite>()[0].get().active = false; +					this->get_component<CircleCollider>().active = true; +					this->get_components<Sprite>()[1].get().active = true; +				} + +				//add collider switch +				break; +			} +			default: +				break; +		} +		return false; +	} + +	void init() { +		Log::logf("init"); +		subscribe<CollisionEvent>( +			[this](const CollisionEvent & ev) -> bool { return this->oncollision(ev); }); +		subscribe<KeyPressEvent>( +			[this](const KeyPressEvent & ev) -> bool { return this->keypressed(ev); }); +	} +	void update() { +		// Retrieve component from the same GameObject this script is on +	} +}; + +class MyScript2 : public Script { +	bool flip = false; +	bool oncollision(const CollisionEvent & test) { +		Log::logf("Box {} script on_collision()", test.info.this_collider.game_object_id); +		return true; +	} +	bool keypressed(const KeyPressEvent & test) { +		Log::logf("Box script keypressed()"); +		switch (test.key) { +			case Keycode::LEFT: { +				Transform & tf = this->get_component<Transform>(); +				tf.position.x -= 1; +				break; +			} +			case Keycode::UP: { +				Transform & tf = this->get_component<Transform>(); +				tf.position.y -= 1; +				break; +			} +			case Keycode::DOWN: { +				Transform & tf = this->get_component<Transform>(); +				tf.position.y += 1; +				break; +			} +			case Keycode::RIGHT: { +				Transform & tf = this->get_component<Transform>(); +				tf.position.x += 1; +				break; +			} +			case Keycode::PAUSE: { +				if (flip) { +					flip = false; +					this->get_component<BoxCollider>().active = true; +					this->get_components<Sprite>()[0].get().active = true; +					this->get_component<CircleCollider>().active = false; +					this->get_components<Sprite>()[1].get().active = false; +				} else { +					flip = true; +					this->get_component<BoxCollider>().active = false; +					this->get_components<Sprite>()[0].get().active = false; +					this->get_component<CircleCollider>().active = true; +					this->get_components<Sprite>()[1].get().active = true; +				} + +				//add collider switch +				break; +			} +			default: +				break; +		} +		return false; +	} + +	void init() { +		Log::logf("init"); +		subscribe<CollisionEvent>( +			[this](const CollisionEvent & ev) -> bool { return this->oncollision(ev); }); +		subscribe<KeyPressEvent>( +			[this](const KeyPressEvent & ev) -> bool { return this->keypressed(ev); }); +	} +	void update() { +		// Retrieve component from the same GameObject this script is on +	} +}; + +class ConcreteScene1 : public Scene { +public: +	using Scene::Scene; + +	void load_scene() { + +		Mediator & m = this->mediator; +		ComponentManager & mgr = m.component_manager; +		Color color(0, 0, 0, 255); + +		float screen_size_width = 320; +		float screen_size_height = 240; +		float world_collider = 1000; +		//define playable world +		GameObject world = mgr.new_object( +			"Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1); +		world.add_component<Rigidbody>(Rigidbody::Data{ +			.mass = 0, +			.gravity_scale = 0, +			.body_type = Rigidbody::BodyType::STATIC, +			.offset = {0, 0}, +			.collision_layers = {0}, +		}); +		world.add_component<BoxCollider>( +			vec2{0, 0 - (screen_size_height / 2 + world_collider / 2)}, +			vec2{world_collider, world_collider}); +		; // Top +		world.add_component<BoxCollider>(vec2{0, screen_size_height / 2 + world_collider / 2}, +										 vec2{world_collider, world_collider}); // Bottom +		world.add_component<BoxCollider>( +			vec2{0 - (screen_size_width / 2 + world_collider / 2), 0}, +			vec2{world_collider, world_collider}); // Left +		world.add_component<BoxCollider>(vec2{screen_size_width / 2 + world_collider / 2, 0}, +										 vec2{world_collider, world_collider}); // right +		world.add_component<Camera>( +			Color::WHITE, +			ivec2{static_cast<int>(screen_size_width), static_cast<int>(screen_size_height)}, +			vec2{screen_size_width, screen_size_height}, 1.0f); + +		GameObject game_object1 = mgr.new_object( +			"Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1); +		game_object1.add_component<Rigidbody>(Rigidbody::Data{ +			.mass = 1, +			.gravity_scale = 0, +			.body_type = Rigidbody::BodyType::DYNAMIC, +			.linear_velocity = {0, 0}, +			.constraints = {0, 0, 0}, +			.elastisity_coefficient = 1, +			.offset = {0, 0}, +			.collision_layers = {0}, +		}); +		// add box with boxcollider +		game_object1.add_component<BoxCollider>(vec2{0, 0}, vec2{20, 20}); +		game_object1.add_component<BehaviorScript>().set_script<MyScript1>(); +		auto img1 = Texture("asset/texture/square.png"); +		game_object1.add_component<Sprite>(img1, color, Sprite::FlipSettings{false, false}, 1, +										   1, 20); + +		//add circle with cirlcecollider deactiveated +		game_object1.add_component<CircleCollider>(vec2{0, 0}, 10).active = false; +		auto img2 = Texture("asset/texture/circle.png"); +		game_object1 +			.add_component<Sprite>(img2, color, Sprite::FlipSettings{false, false}, 1, 1, 20) +			.active +			= false; + +		GameObject game_object2 = mgr.new_object( +			"Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1); +		game_object2.add_component<Rigidbody>(Rigidbody::Data{ +			.mass = 1, +			.gravity_scale = 0, +			.body_type = Rigidbody::BodyType::STATIC, +			.linear_velocity = {0, 0}, +			.constraints = {0, 0, 0}, +			.elastisity_coefficient = 1, +			.offset = {0, 0}, +			.collision_layers = {0}, +		}); +		// add box with boxcollider +		game_object2.add_component<BoxCollider>(vec2{0, 0}, vec2{20, 20}); +		game_object2.add_component<BehaviorScript>().set_script<MyScript2>(); +		auto img3 = Texture("asset/texture/square.png"); +		game_object2.add_component<Sprite>(img3, color, Sprite::FlipSettings{false, false}, 1, +										   1, 20); + +		//add circle with cirlcecollider deactiveated +		game_object2.add_component<CircleCollider>(vec2{0, 0}, 10).active = false; +		auto img4 = Texture("asset/texture/circle.png"); +		game_object2 +			.add_component<Sprite>(img4, color, Sprite::FlipSettings{false, false}, 1, 1, 20) +			.active +			= false; +	} + +	string get_name() const { return "scene1"; } +}; + +int main(int argc, char * argv[]) { + +	LoopManager gameloop; +	gameloop.add_scene<ConcreteScene1>(); +	gameloop.start(); +	return 0; +} diff --git a/src/example/rendering_particle.cpp b/src/example/rendering_particle.cpp index 349d11e..29d475d 100644 --- a/src/example/rendering_particle.cpp +++ b/src/example/rendering_particle.cpp @@ -1,43 +1,22 @@ -#include "api/Animator.h" -#include "api/Camera.h" -#include "system/AnimatorSystem.h" -#include "system/ParticleSystem.h" -#include <SDL2/SDL_timer.h> -#include <crepe/ComponentManager.h> -  #include <crepe/Component.h> +#include <crepe/api/Animator.h> +#include <crepe/api/Camera.h>  #include <crepe/api/Color.h>  #include <crepe/api/GameObject.h> +#include <crepe/api/LoopManager.hpp>  #include <crepe/api/ParticleEmitter.h>  #include <crepe/api/Rigidbody.h>  #include <crepe/api/Sprite.h>  #include <crepe/api/Texture.h>  #include <crepe/api/Transform.h> -#include <crepe/system/RenderSystem.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/Mediator.h>  #include <crepe/types.h> -#include <chrono> -  using namespace crepe;  using namespace std; -int main(int argc, char * argv[]) { -	ComponentManager mgr; -	GameObject game_object = mgr.new_object("", "", vec2{0, 0}, 0, 1); -	RenderSystem sys{mgr}; -	ParticleSystem psys{mgr}; -	AnimatorSystem asys{mgr}; - -	Color color(255, 255, 255, 100); - -	auto img = Texture("asset/texture/test_ap43.png"); -	Sprite & test_sprite = game_object.add_component<Sprite>( -		img, color, Sprite::FlipSettings{true, true}, 1, 1, 500); - -	//game_object.add_component<Animator>(test_sprite, 4, 1, 0).active = true; -	game_object.add_component<Animator>(test_sprite, 1, 1, 0).active = true; - -	/* +/*  	auto & test = game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{  		.position = {0, 0},  		.max_particles = 10, @@ -59,25 +38,48 @@ int main(int argc, char * argv[]) {  	});  	*/ -	auto & cam = game_object.add_component<Camera>(Color::RED, ivec2{1080, 720}, -												   vec2{2000, 2000}, 1.0f); +class TestScene : public Scene { +public: +	void load_scene() { +		Mediator & mediator = this->mediator; +		ComponentManager & mgr = mediator.component_manager; +		GameObject game_object = mgr.new_object("", "", vec2{0, 0}, 0, 1); -	/* -	game_object -		.add_component<Sprite>(make_shared<Texture>("asset/texture/img.png"), color, -		.add_component<Sprite>(make_shared<Texture>("asset/texture/img.png"), color, -							   FlipSettings{false, false}) -		.order_in_layer -		= 6; -	*/ +		Color color(255, 255, 255, 255); + +		auto img = Texture("asset/spritesheet/pokemon_spritesheet.png"); + +		Sprite & test_sprite = game_object.add_component<Sprite>( +			img, Sprite::Data{ +					 .color = color, +					 .flip = Sprite::FlipSettings{false, false}, +					 .sorting_in_layer = 2, +					 .order_in_layer = 2, +					 .size = {0, 100}, +					 .angle_offset = 0, +					 .position_offset = {100, 0}, +				 }); -	auto start = std::chrono::steady_clock::now(); -	while (std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) { -		psys.update(); -		asys.update(); -		sys.update(); -		SDL_Delay(10); +		auto & anim = game_object.add_component<Animator>(test_sprite, 4, 4, +														  Animator::Data{ +															  .fps = 1, +															  .looping = false, +														  }); +		anim.set_anim(2); +		anim.active = false; + +		auto & cam = game_object.add_component<Camera>(ivec2{1280, 720}, vec2{400, 400}, +													   Camera::Data{ +														   .bg_color = Color::WHITE, +													   });  	} +	string get_name() const { return "TestScene"; }; +}; + +int main(int argc, char * argv[]) { +	LoopManager engine; +	engine.add_scene<TestScene>(); +	engine.start();  	return 0;  } diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 232c763..1c963d8 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,5 +1,6 @@  target_sources(test_main PUBLIC  	main.cpp +	CollisionTest.cpp  	PhysicsTest.cpp  	ScriptTest.cpp  	ParticleTest.cpp @@ -15,4 +16,8 @@ target_sources(test_main PUBLIC  	LoopManagerTest.cpp  	LoopTimerTest.cpp +	InputTest.cpp +	ScriptEventTest.cpp +	ScriptSceneTest.cpp +	Profiling.cpp  ) diff --git a/src/test/CollisionTest.cpp b/src/test/CollisionTest.cpp new file mode 100644 index 0000000..5dbc670 --- /dev/null +++ b/src/test/CollisionTest.cpp @@ -0,0 +1,391 @@ +#include "api/BoxCollider.h" +#include "manager/Mediator.h" +#include <cmath> +#include <cstddef> +#include <gtest/gtest.h> + +#define private public +#define protected public + +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Transform.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/manager/Mediator.h> +#include <crepe/system/CollisionSystem.h> +#include <crepe/system/ScriptSystem.h> +#include <crepe/types.h> +#include <crepe/util/Log.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; +using namespace testing; + +class CollisionHandler : public Script { +public: +	int box_id; +	function<void(const CollisionEvent & ev)> test_fn = [](const CollisionEvent & ev) {}; + +	CollisionHandler(int box_id) { this->box_id = box_id; } + +	bool on_collision(const CollisionEvent & ev) { +		//Log::logf("Box {} script on_collision()", box_id); +		test_fn(ev); +		return true; +	} + +	void init() { +		subscribe<CollisionEvent>( +			[this](const CollisionEvent & ev) -> bool { return this->on_collision(ev); }); +	} +	void update() { +		// Retrieve component from the same GameObject this script is on +	} +}; + +class CollisionTest : public Test { +public: +	Mediator m; +	EventManager event_mgr{m}; +	ComponentManager mgr{m}; +	CollisionSystem collision_sys{m}; +	ScriptSystem script_sys{m}; + +	GameObject world = mgr.new_object("world", "", {50, 50}); +	GameObject game_object1 = mgr.new_object("object1", "", {50, 50}); +	GameObject game_object2 = mgr.new_object("object2", "", {50, 30}); + +	CollisionHandler * script_object1_ref = nullptr; +	CollisionHandler * script_object2_ref = nullptr; + +	void SetUp() override { +		world.add_component<Rigidbody>(Rigidbody::Data{ +			// TODO: remove unrelated properties: +			.body_type = Rigidbody::BodyType::STATIC, +			.offset = {0, 0}, +		}); +		// Create a box with an inner size of 10x10 units +		world.add_component<BoxCollider>(vec2{0, -100}, vec2{100, 100}); // Top +		world.add_component<BoxCollider>(vec2{0, 100}, vec2{100, 100}); // Bottom +		world.add_component<BoxCollider>(vec2{-100, 0}, vec2{100, 100}); // Left +		world.add_component<BoxCollider>(vec2{100, 0}, vec2{100, 100}); // right + +		game_object1.add_component<Rigidbody>(Rigidbody::Data{ +			.mass = 1, +			.gravity_scale = 0.01, +			.body_type = Rigidbody::BodyType::DYNAMIC, +			.linear_velocity = {0, 0}, +			.constraints = {0, 0, 0}, +			.elastisity_coefficient = 1, +			.offset = {0, 0}, +			.collision_layers = {0}, +		}); +		game_object1.add_component<BoxCollider>(vec2{0, 0}, vec2{10, 10}); +		BehaviorScript & script_object1 +			= game_object1.add_component<BehaviorScript>().set_script<CollisionHandler>(1); +		script_object1_ref = static_cast<CollisionHandler *>(script_object1.script.get()); +		ASSERT_NE(script_object1_ref, nullptr); + +		game_object2.add_component<Rigidbody>(Rigidbody::Data{ +			.mass = 1, +			.gravity_scale = 0.01, +			.body_type = Rigidbody::BodyType::DYNAMIC, +			.linear_velocity = {0, 0}, +			.constraints = {0, 0, 0}, +			.elastisity_coefficient = 1, +			.offset = {0, 0}, +			.collision_layers = {0}, +		}); +		game_object2.add_component<BoxCollider>(vec2{0, 0}, vec2{10, 10}); +		BehaviorScript & script_object2 +			= game_object2.add_component<BehaviorScript>().set_script<CollisionHandler>(2); +		script_object2_ref = static_cast<CollisionHandler *>(script_object2.script.get()); +		ASSERT_NE(script_object2_ref, nullptr); + +		// Ensure Script::init() is called on all BehaviorScript instances +		script_sys.update(); +	} +}; + +TEST_F(CollisionTest, collision_example) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +	}; +	EXPECT_FALSE(collision_happend); +	collision_sys.update(); +	EXPECT_FALSE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_both_no_velocity) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, 10); +		EXPECT_EQ(ev.info.resolution.y, 10); +		EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +		EXPECT_EQ(ev.info.resolution.x, 10); +		EXPECT_EQ(ev.info.resolution.y, 10); +		EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {50, 30}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_x_direction_no_velocity) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, -5); +		EXPECT_EQ(ev.info.resolution.y, 0); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::X_DIRECTION); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +		EXPECT_EQ(ev.info.resolution.x, 5); +		EXPECT_EQ(ev.info.resolution.y, 0); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::X_DIRECTION); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {45, 30}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_y_direction_no_velocity) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, 0); +		EXPECT_EQ(ev.info.resolution.y, -5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::Y_DIRECTION); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +		EXPECT_EQ(ev.info.resolution.x, 0); +		EXPECT_EQ(ev.info.resolution.y, 5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::Y_DIRECTION); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {50, 25}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_both) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, 10); +		EXPECT_EQ(ev.info.resolution.y, 10); +		EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +		EXPECT_EQ(ev.info.resolution.x, 10); +		EXPECT_EQ(ev.info.resolution.y, 10); +		EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {50, 30}; +	Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); +	rg1.data.linear_velocity = {10, 10}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.linear_velocity = {10, 10}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_x_direction) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, -5); +		EXPECT_EQ(ev.info.resolution.y, -5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::X_DIRECTION); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +		EXPECT_EQ(ev.info.resolution.x, 5); +		EXPECT_EQ(ev.info.resolution.y, 5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::X_DIRECTION); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {45, 30}; +	Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); +	rg1.data.linear_velocity = {10, 10}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.linear_velocity = {10, 10}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_y_direction) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, -5); +		EXPECT_EQ(ev.info.resolution.y, -5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::Y_DIRECTION); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +		EXPECT_EQ(ev.info.resolution.x, 5); +		EXPECT_EQ(ev.info.resolution.y, 5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::Y_DIRECTION); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {50, 25}; +	Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); +	rg1.data.linear_velocity = {10, 10}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.linear_velocity = {10, 10}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_both) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, 10); +		EXPECT_EQ(ev.info.resolution.y, 10); +		EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		// is static should not be called +		FAIL(); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {50, 30}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_x_direction) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, -5); +		EXPECT_EQ(ev.info.resolution.y, -5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::X_DIRECTION); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		// is static should not be called +		FAIL(); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {45, 30}; +	Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); +	rg1.data.linear_velocity = {10, 10}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_y_direction) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, -5); +		EXPECT_EQ(ev.info.resolution.y, -5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::Y_DIRECTION); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		// is static should not be called +		FAIL(); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {50, 25}; +	Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); +	rg1.data.linear_velocity = {10, 10}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_multiple) { //todo check visually +	bool collision_happend = false; +	float offset_value = 0; +	float resolution = 0; +	script_object1_ref->test_fn = [&](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.this_collider.offset.x, offset_value); +		EXPECT_EQ(ev.info.resolution.x, resolution); +	}; +	script_object2_ref->test_fn = [&](const CollisionEvent & ev) { +		// is static should not be called +		FAIL(); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {45, 30}; +	Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); +	rg1.data.linear_velocity = {10, 10}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; +	BoxCollider & bxc = this->mgr.get_components_by_id<BoxCollider>(1).front().get(); +	bxc.offset = {5, 0}; +	this->game_object1.add_component<BoxCollider>(vec2{-5, 0}, vec2{10, 10}); +	offset_value = 5; +	resolution = 10; +	collision_sys.update(); +	offset_value = -5; +	resolution = 10; +	tf.position = {55, 30}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} diff --git a/src/test/InputTest.cpp b/src/test/InputTest.cpp new file mode 100644 index 0000000..bf08a04 --- /dev/null +++ b/src/test/InputTest.cpp @@ -0,0 +1,292 @@ +#include <gtest/gtest.h> +#define protected public +#define private public +#include "api/KeyCodes.h" +#include "manager/ComponentManager.h" +#include "manager/EventManager.h" +#include "manager/Mediator.h" +#include "system/InputSystem.h" +#include <SDL2/SDL.h> +#include <SDL2/SDL_keycode.h> +#include <crepe/api/Button.h> +#include <crepe/api/Camera.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Transform.h> +#include <crepe/api/Vector2.h> +#include <gmock/gmock.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; + +class InputTest : public ::testing::Test { +public: +	Mediator mediator; +	ComponentManager mgr{mediator}; + +	InputSystem input_system{mediator}; +	EventManager event_manager{mediator}; +	//GameObject camera; + +protected: +	void SetUp() override { +		mediator.event_manager = event_manager; +		mediator.component_manager = mgr; +		event_manager.clear(); +	} + +	void simulate_mouse_click(int mouse_x, int mouse_y, Uint8 mouse_button) { +		SDL_Event event; + +		// Simulate Mouse Button Down event +		SDL_zero(event); +		event.type = SDL_MOUSEBUTTONDOWN; +		event.button.x = mouse_x; +		event.button.y = mouse_y; +		event.button.button = mouse_button; +		SDL_PushEvent(&event); + +		// Simulate Mouse Button Up event +		SDL_zero(event); +		event.type = SDL_MOUSEBUTTONUP; +		event.button.x = mouse_x; +		event.button.y = mouse_y; +		event.button.button = mouse_button; +		SDL_PushEvent(&event); +	} +}; + +TEST_F(InputTest, MouseDown) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>( +		ivec2{100, 100}, vec2{100, 100}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); +	camera.active = true; +	bool mouse_triggered = false; +	EventHandler<MousePressEvent> on_mouse_down = [&](const MousePressEvent & event) { +		mouse_triggered = true; +		//middle of the screen = 0,0 +		EXPECT_EQ(event.mouse_x, 0); +		EXPECT_EQ(event.mouse_y, 0); +		EXPECT_EQ(event.button, MouseButton::LEFT_MOUSE); +		return false; +	}; +	event_manager.subscribe<MousePressEvent>(on_mouse_down); + +	SDL_Event event; +	SDL_zero(event); +	event.type = SDL_MOUSEBUTTONDOWN; +	// middle of the screen of a 500*500 camera = 250*250 +	event.button.x = 250; +	event.button.y = 250; +	event.button.button = SDL_BUTTON_LEFT; +	SDL_PushEvent(&event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(mouse_triggered); +} + +TEST_F(InputTest, MouseUp) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>( +		ivec2{100, 100}, vec2{100, 100}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); +	camera.active = true; +	bool function_triggered = false; +	EventHandler<MouseReleaseEvent> on_mouse_release = [&](const MouseReleaseEvent & e) { +		function_triggered = true; +		EXPECT_EQ(e.mouse_x, 0); +		EXPECT_EQ(e.mouse_y, 0); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); +		return false; +	}; +	event_manager.subscribe<MouseReleaseEvent>(on_mouse_release); + +	SDL_Event event; +	SDL_zero(event); +	event.type = SDL_MOUSEBUTTONUP; +	event.button.x = 250; +	event.button.y = 250; +	event.button.button = SDL_BUTTON_LEFT; +	SDL_PushEvent(&event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, MouseMove) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>( +		ivec2{100, 100}, vec2{100, 100}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); +	camera.active = true; +	bool function_triggered = false; +	EventHandler<MouseMoveEvent> on_mouse_move = [&](const MouseMoveEvent & e) { +		function_triggered = true; +		EXPECT_EQ(e.mouse_x, 0); +		EXPECT_EQ(e.mouse_y, 0); +		EXPECT_EQ(e.delta_x, 10); +		EXPECT_EQ(e.delta_y, 10); +		return false; +	}; +	event_manager.subscribe<MouseMoveEvent>(on_mouse_move); + +	SDL_Event event; +	SDL_zero(event); +	event.type = SDL_MOUSEMOTION; +	event.motion.x = 250; +	event.motion.y = 250; +	event.motion.xrel = 10; +	event.motion.yrel = 10; +	SDL_PushEvent(&event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, KeyDown) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>( +		ivec2{100, 100}, vec2{100, 100}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); +	camera.active = true; +	bool function_triggered = false; + +	// Define event handler for KeyPressEvent +	EventHandler<KeyPressEvent> on_key_press = [&](const KeyPressEvent & event) { +		function_triggered = true; +		EXPECT_EQ(event.key, Keycode::B); // Validate the key is 'B' +		EXPECT_EQ(event.repeat, true); // Validate repeat flag +		return false; +	}; + +	event_manager.subscribe<KeyPressEvent>(on_key_press); + +	// Simulate SDL_KEYDOWN event +	SDL_Event test_event; +	SDL_zero(test_event); +	test_event.type = SDL_KEYDOWN; // Key down event +	test_event.key.keysym.scancode = SDL_SCANCODE_B; // Set scancode for 'B' +	test_event.key.repeat = 1; // Set repeat flag +	SDL_PushEvent(&test_event); + +	input_system.update(); // Process the event +	event_manager.dispatch_events(); // Dispatch events to handlers + +	EXPECT_TRUE(function_triggered); // Check if the handler was triggered +} + +TEST_F(InputTest, KeyUp) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>( +		ivec2{100, 100}, vec2{100, 100}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); +	camera.active = true; +	bool function_triggered = false; +	EventHandler<KeyReleaseEvent> on_key_release = [&](const KeyReleaseEvent & event) { +		function_triggered = true; +		EXPECT_EQ(event.key, Keycode::B); +		return false; +	}; +	event_manager.subscribe<KeyReleaseEvent>(on_key_release); + +	SDL_Event event; +	SDL_zero(event); +	event.type = SDL_KEYUP; +	event.key.keysym.scancode = SDL_SCANCODE_B; +	SDL_PushEvent(&event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, MouseClick) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>( +		ivec2{100, 100}, vec2{100, 100}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); +	camera.active = true; +	bool on_click_triggered = false; +	EventHandler<MouseClickEvent> on_mouse_click = [&](const MouseClickEvent & event) { +		on_click_triggered = true; +		EXPECT_EQ(event.button, MouseButton::LEFT_MOUSE); +		EXPECT_EQ(event.mouse_x, 0); +		EXPECT_EQ(event.mouse_y, 0); +		return false; +	}; +	event_manager.subscribe<MouseClickEvent>(on_mouse_click); + +	this->simulate_mouse_click(250, 250, SDL_BUTTON_LEFT); +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(on_click_triggered); +} + +TEST_F(InputTest, testButtonClick) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>( +		ivec2{100, 100}, vec2{100, 100}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); +	camera.active = true; +	GameObject button_obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); +	bool button_clicked = false; +	std::function<void()> on_click = [&]() { button_clicked = true; }; +	auto & button +		= button_obj.add_component<Button>(vec2{100, 100}, vec2{0, 0}, on_click, false); + +	bool hover = false; +	button.active = true; + +	button.is_pressed = false; +	button.is_toggle = false; +	this->simulate_mouse_click(999, 999, SDL_BUTTON_LEFT); +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_FALSE(button_clicked); + +	this->simulate_mouse_click(250, 250, SDL_BUTTON_LEFT); +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(button_clicked); +} + +TEST_F(InputTest, testButtonHover) { +	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +	auto & camera = obj.add_component<Camera>( +		ivec2{100, 100}, vec2{100, 100}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); +	camera.active = true; +	GameObject button_obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); +	bool button_clicked = false; +	std::function<void()> on_click = [&]() { button_clicked = true; }; +	auto & button +		= button_obj.add_component<Button>(vec2{100, 100}, vec2{0, 0}, on_click, false); +	button.active = true; +	button.is_pressed = false; +	button.is_toggle = false; + +	// Mouse not on button +	SDL_Event event; +	SDL_zero(event); +	event.type = SDL_MOUSEMOTION; +	event.motion.x = 700; +	event.motion.y = 700; +	event.motion.xrel = 10; +	event.motion.yrel = 10; +	SDL_PushEvent(&event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_FALSE(button.hover); + +	// Mouse on button +	SDL_Event hover_event; +	SDL_zero(hover_event); +	hover_event.type = SDL_MOUSEMOTION; +	hover_event.motion.x = 250; +	hover_event.motion.y = 250; +	hover_event.motion.xrel = 10; +	hover_event.motion.yrel = 10; +	SDL_PushEvent(&hover_event); + +	input_system.update(); +	event_manager.dispatch_events(); +	EXPECT_TRUE(button.hover); +} diff --git a/src/test/LoopManagerTest.cpp b/src/test/LoopManagerTest.cpp index 57f7a2e..13a0ada 100644 --- a/src/test/LoopManagerTest.cpp +++ b/src/test/LoopManagerTest.cpp @@ -20,7 +20,6 @@ protected:      };      TestGameLoop test_loop; -	// LoopManager test_loop;      void SetUp() override {      } diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index a659fe5..1409c4f 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -32,7 +32,11 @@ public:  			Color color(0, 0, 0, 0);  			auto s1 = Texture("asset/texture/img.png");  			Sprite & test_sprite = game_object.add_component<Sprite>( -				s1, color, Sprite::FlipSettings{true, true}, 1, 1, 100); +				s1, Sprite::Data{ +						.color = color, +						.flip = Sprite::FlipSettings{true, true}, +						.size = {10, 10}, +					});  			game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{  				.position = {0, 0}, diff --git a/src/test/PhysicsTest.cpp b/src/test/PhysicsTest.cpp index 43af8e4..43d2931 100644 --- a/src/test/PhysicsTest.cpp +++ b/src/test/PhysicsTest.cpp @@ -30,8 +30,6 @@ public:  				.max_linear_velocity = vec2{10, 10},  				.max_angular_velocity = 10,  				.constraints = {0, 0}, -				.use_gravity = true, -				.bounce = false,  			});  		}  		transforms = mgr.get_components_by_id<Transform>(0); @@ -107,16 +105,16 @@ TEST_F(PhysicsTest, movement) {  	EXPECT_EQ(transform.position.y, 1);  	EXPECT_EQ(transform.rotation, 1); -	rigidbody.data.linear_damping.x = 0.5; -	rigidbody.data.linear_damping.y = 0.5; -	rigidbody.data.angular_damping = 0.5; +	rigidbody.data.linear_velocity_coefficient.x = 0.5; +	rigidbody.data.linear_velocity_coefficient.y = 0.5; +	rigidbody.data.angular_velocity_coefficient = 0.5;  	system.update();  	EXPECT_EQ(rigidbody.data.linear_velocity.x, 0.5);  	EXPECT_EQ(rigidbody.data.linear_velocity.y, 0.5);  	EXPECT_EQ(rigidbody.data.angular_velocity, 0.5);  	rigidbody.data.constraints = {1, 1, 0}; -	rigidbody.data.angular_damping = 0; +	rigidbody.data.angular_velocity_coefficient = 0;  	rigidbody.data.max_angular_velocity = 1000;  	rigidbody.data.angular_velocity = 360;  	system.update(); diff --git a/src/test/Profiling.cpp b/src/test/Profiling.cpp new file mode 100644 index 0000000..c753bca --- /dev/null +++ b/src/test/Profiling.cpp @@ -0,0 +1,243 @@ +#include "manager/Mediator.h" +#include "system/ParticleSystem.h" +#include "system/PhysicsSystem.h" +#include "system/RenderSystem.h" +#include <chrono> +#include <cmath> +#include <gtest/gtest.h> + +#define private public +#define protected public + +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Transform.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/system/CollisionSystem.h> +#include <crepe/system/ScriptSystem.h> +#include <crepe/types.h> +#include <crepe/util/Log.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; +using namespace testing; + +class TestScript : public Script { +	bool oncollision(const CollisionEvent & test) { +		Log::logf("Box {} script on_collision()", test.info.this_collider.game_object_id); +		return true; +	} +	void init() { +		subscribe<CollisionEvent>( +			[this](const CollisionEvent & ev) -> bool { return this->oncollision(ev); }); +	} +	void update() { +		// Retrieve component from the same GameObject this script is on +	} +}; + +class DISABLED_ProfilingTest : public Test { +public: +	// Config for test +	// Minimum amount to let test pass +	const int min_gameobject_count = 100; +	// Maximum amount to stop test +	const int max_gameobject_count = 150; +	// Amount of times a test runs to calculate average +	const int average = 5; +	// Maximum duration to stop test +	const std::chrono::microseconds duration = 16000us; + +	Mediator m; +	ComponentManager mgr{m}; +	// Add system used for profling tests +	CollisionSystem collision_sys{m}; +	PhysicsSystem physics_sys{m}; +	ParticleSystem particle_sys{m}; +	RenderSystem render_sys{m}; +	ScriptSystem script_sys{m}; + +	// Test data +	std::map<std::string, std::chrono::microseconds> timings; +	int game_object_count = 0; +	std::chrono::microseconds total_time = 0us; + +	void SetUp() override { + +		GameObject do_not_use = mgr.new_object("DO_NOT_USE", "", {0, 0}); +		do_not_use.add_component<Camera>(ivec2{1080, 720}, vec2{2000, 2000}, +										 Camera::Data{ +											 .bg_color = Color::WHITE, +											 .zoom = 1.0f, +										 }); +		// initialize systems here: +		//calls init +		script_sys.update(); +		//creates window +		render_sys.update(); +	} + +	// Helper function to time an update call and store its duration +	template <typename Func> +	std::chrono::microseconds time_function(const std::string & name, Func && func) { +		auto start = std::chrono::steady_clock::now(); +		func(); +		auto end = std::chrono::steady_clock::now(); +		std::chrono::microseconds duration +			= std::chrono::duration_cast<std::chrono::microseconds>(end - start); +		timings[name] += duration; +		return duration; +	} + +	// Run and profile all systems, return the total time in milliseconds +	std::chrono::microseconds run_all_systems() { +		std::chrono::microseconds total_microseconds = 0us; +		total_microseconds += time_function("PhysicsSystem", [&]() { physics_sys.update(); }); +		total_microseconds +			+= time_function("CollisionSystem", [&]() { collision_sys.update(); }); +		total_microseconds +			+= time_function("ParticleSystem", [&]() { particle_sys.update(); }); +		total_microseconds += time_function("RenderSystem", [&]() { render_sys.update(); }); +		return total_microseconds; +	} + +	// Print timings of all functions +	void log_timings() const { +		std::string result = "\nFunction timings:\n"; + +		for (const auto & [name, duration] : timings) { +			result += name + " took " + std::to_string(duration.count() / 1000.0 / average) +					  + " ms (" + std::to_string(duration.count() / average) + " µs).\n"; +		} + +		result += "Total time: " + std::to_string(this->total_time.count() / 1000.0 / average) +				  + " ms (" + std::to_string(this->total_time.count() / average) + " µs)\n"; + +		result += "Amount of gameobjects: " + std::to_string(game_object_count) + "\n"; + +		GTEST_LOG_(INFO) << result; +	} + +	void clear_timings() { +		for (auto & [key, value] : timings) { +			value = std::chrono::microseconds(0); +		} +	} +}; + +TEST_F(DISABLED_ProfilingTest, Profiling_1) { +	while (this->total_time / this->average < this->duration) { + +		{ +			//define gameobject used for testing +			GameObject gameobject = mgr.new_object("gameobject", "", {0, 0}); +		} + +		this->game_object_count++; + +		this->total_time = 0us; +		clear_timings(); + +		for (int amount = 0; amount < this->average; amount++) { +			this->total_time += run_all_systems(); +		} + +		if (this->game_object_count >= this->max_gameobject_count) break; +	} +	log_timings(); +	EXPECT_GE(this->game_object_count, this->min_gameobject_count); +} + +TEST_F(DISABLED_ProfilingTest, Profiling_2) { +	while (this->total_time / this->average < this->duration) { + +		{ +			//define gameobject used for testing +			GameObject gameobject = mgr.new_object( +				"gameobject", "", {static_cast<float>(game_object_count * 2), 0}); +			gameobject.add_component<Rigidbody>(Rigidbody::Data{ +				.gravity_scale = 0.0, +				.body_type = Rigidbody::BodyType::STATIC, +			}); +			gameobject.add_component<BoxCollider>(vec2{0, 0}, vec2{1, 1}); + +			gameobject.add_component<BehaviorScript>().set_script<TestScript>(); +			auto img = Texture("asset/texture/square.png"); +			Sprite & test_sprite = gameobject.add_component<Sprite>( +				img, Sprite::Data{ +						 .color = {0, 0, 0, 0}, +						 .flip = {.flip_x = false, .flip_y = false}, +						 .sorting_in_layer = 1, +						 .order_in_layer = 1, +						 .size = {.y = 500}, +					 }); +		} + +		this->game_object_count++; + +		this->total_time = 0us; +		clear_timings(); +		for (int amount = 0; amount < this->average; amount++) { +			this->total_time += run_all_systems(); +		} + +		if (this->game_object_count >= this->max_gameobject_count) break; +	} +	log_timings(); +	EXPECT_GE(this->game_object_count, this->min_gameobject_count); +} + +TEST_F(DISABLED_ProfilingTest, Profiling_3) { +	while (this->total_time / this->average < this->duration) { + +		{ +			//define gameobject used for testing +			GameObject gameobject = mgr.new_object( +				"gameobject", "", {static_cast<float>(game_object_count * 2), 0}); +			gameobject.add_component<Rigidbody>(Rigidbody::Data{ +				.gravity_scale = 0, +				.body_type = Rigidbody::BodyType::STATIC, +			}); +			gameobject.add_component<BoxCollider>(vec2{0, 0}, vec2{1, 1}); +			gameobject.add_component<BehaviorScript>().set_script<TestScript>(); +			auto img = Texture("asset/texture/square.png"); +			Sprite & test_sprite = gameobject.add_component<Sprite>( +				img, Sprite::Data{ +						 .color = {0, 0, 0, 0}, +						 .flip = {.flip_x = false, .flip_y = false}, +						 .sorting_in_layer = 1, +						 .order_in_layer = 1, +						 .size = {.y = 500}, +					 }); +			auto & test = gameobject.add_component<ParticleEmitter>(ParticleEmitter::Data{ +				.max_particles = 10, +				.emission_rate = 100, +				.end_lifespan = 100000, +				.boundary{ +					.width = 1000, +					.height = 1000, +					.offset = vec2{0, 0}, +					.reset_on_exit = false, +				}, +				.sprite = test_sprite, +			}); +		} +		render_sys.update(); +		this->game_object_count++; + +		this->total_time = 0us; +		clear_timings(); +		for (int amount = 0; amount < this->average; amount++) { +			this->total_time += run_all_systems(); +		} + +		if (this->game_object_count >= this->max_gameobject_count) break; +	} +	log_timings(); +	EXPECT_GE(this->game_object_count, this->min_gameobject_count); +} diff --git a/src/test/RenderSystemTest.cpp b/src/test/RenderSystemTest.cpp index c105dcb..205f534 100644 --- a/src/test/RenderSystemTest.cpp +++ b/src/test/RenderSystemTest.cpp @@ -1,3 +1,4 @@ +#include "types.h"  #include <functional>  #include <gtest/gtest.h>  #include <memory> @@ -35,28 +36,50 @@ public:  		auto s2 = Texture("asset/texture/img.png");  		auto s3 = Texture("asset/texture/img.png");  		auto s4 = Texture("asset/texture/img.png"); -		auto & sprite1 = entity1.add_component<Sprite>( -			s1, Color(0, 0, 0, 0), Sprite::FlipSettings{false, false}, 5, 5, 100); -		ASSERT_NE(sprite1.sprite_image.texture.get(), nullptr); -		EXPECT_EQ(sprite1.order_in_layer, 5); -		EXPECT_EQ(sprite1.sorting_in_layer, 5); -		auto & sprite2 = entity2.add_component<Sprite>( -			s2, Color(0, 0, 0, 0), Sprite::FlipSettings{false, false}, 2, 1, 100); -		ASSERT_NE(sprite2.sprite_image.texture.get(), nullptr); -		EXPECT_EQ(sprite2.sorting_in_layer, 2); -		EXPECT_EQ(sprite2.order_in_layer, 1); - -		auto & sprite3 = entity3.add_component<Sprite>( -			s3, Color(0, 0, 0, 0), Sprite::FlipSettings{false, false}, 1, 2, 100); -		ASSERT_NE(sprite3.sprite_image.texture.get(), nullptr); -		EXPECT_EQ(sprite3.sorting_in_layer, 1); -		EXPECT_EQ(sprite3.order_in_layer, 2); - -		auto & sprite4 = entity4.add_component<Sprite>( -			s4, Color(0, 0, 0, 0), Sprite::FlipSettings{false, false}, 1, 1, 100); -		ASSERT_NE(sprite4.sprite_image.texture.get(), nullptr); -		EXPECT_EQ(sprite4.sorting_in_layer, 1); -		EXPECT_EQ(sprite4.order_in_layer, 1); +		auto & sprite1 +			= entity1.add_component<Sprite>(s1, Sprite::Data{ +													.color = Color(0, 0, 0, 0), +													.flip = Sprite::FlipSettings{false, false}, +													.sorting_in_layer = 5, +													.order_in_layer = 5, +													.size = {10, 10}, +												}); + +		ASSERT_NE(sprite1.texture.texture.get(), nullptr); +		EXPECT_EQ(sprite1.data.order_in_layer, 5); +		EXPECT_EQ(sprite1.data.sorting_in_layer, 5); +		auto & sprite2 +			= entity2.add_component<Sprite>(s2, Sprite::Data{ +													.color = Color(0, 0, 0, 0), +													.flip = Sprite::FlipSettings{false, false}, +													.sorting_in_layer = 2, +													.order_in_layer = 1, +												}); +		ASSERT_NE(sprite2.texture.texture.get(), nullptr); +		EXPECT_EQ(sprite2.data.sorting_in_layer, 2); +		EXPECT_EQ(sprite2.data.order_in_layer, 1); + +		auto & sprite3 +			= entity3.add_component<Sprite>(s3, Sprite::Data{ +													.color = Color(0, 0, 0, 0), +													.flip = Sprite::FlipSettings{false, false}, +													.sorting_in_layer = 1, +													.order_in_layer = 2, +												}); +		ASSERT_NE(sprite3.texture.texture.get(), nullptr); +		EXPECT_EQ(sprite3.data.sorting_in_layer, 1); +		EXPECT_EQ(sprite3.data.order_in_layer, 2); + +		auto & sprite4 +			= entity4.add_component<Sprite>(s4, Sprite::Data{ +													.color = Color(0, 0, 0, 0), +													.flip = Sprite::FlipSettings{false, false}, +													.sorting_in_layer = 1, +													.order_in_layer = 1, +												}); +		ASSERT_NE(sprite4.texture.texture.get(), nullptr); +		EXPECT_EQ(sprite4.data.sorting_in_layer, 1); +		EXPECT_EQ(sprite4.data.order_in_layer, 1);  	}  }; @@ -66,8 +89,13 @@ TEST_F(RenderSystemTest, expected_throws) {  	// no texture img  	EXPECT_ANY_THROW({  		auto test = Texture(""); -		entity1.add_component<Sprite>(test, Color(0, 0, 0, 0), -									  Sprite::FlipSettings{false, false}, 1, 1, 100); +		auto & sprite1 = entity1.add_component<Sprite>( +			test, Sprite::Data{ +					  .color = Color(0, 0, 0, 0), +					  .flip = Sprite::FlipSettings{false, false}, +					  .sorting_in_layer = 1, +					  .order_in_layer = 1, +				  });  	});  	// No camera @@ -89,32 +117,33 @@ TEST_F(RenderSystemTest, sorting_sprites) {  	// 3. sorting_in_layer: 2, order_in_layer: 1 (entity2)  	// 4. sorting_in_layer: 5, order_in_layer: 5 (entity1) -	EXPECT_EQ(sorted_sprites[0].get().sorting_in_layer, 1); -	EXPECT_EQ(sorted_sprites[0].get().order_in_layer, 1); +	EXPECT_EQ(sorted_sprites[0].get().data.sorting_in_layer, 1); +	EXPECT_EQ(sorted_sprites[0].get().data.order_in_layer, 1); -	EXPECT_EQ(sorted_sprites[1].get().sorting_in_layer, 1); -	EXPECT_EQ(sorted_sprites[1].get().order_in_layer, 2); +	EXPECT_EQ(sorted_sprites[1].get().data.sorting_in_layer, 1); +	EXPECT_EQ(sorted_sprites[1].get().data.order_in_layer, 2); -	EXPECT_EQ(sorted_sprites[2].get().sorting_in_layer, 2); -	EXPECT_EQ(sorted_sprites[2].get().order_in_layer, 1); +	EXPECT_EQ(sorted_sprites[2].get().data.sorting_in_layer, 2); +	EXPECT_EQ(sorted_sprites[2].get().data.order_in_layer, 1); -	EXPECT_EQ(sorted_sprites[3].get().sorting_in_layer, 5); -	EXPECT_EQ(sorted_sprites[3].get().order_in_layer, 5); +	EXPECT_EQ(sorted_sprites[3].get().data.sorting_in_layer, 5); +	EXPECT_EQ(sorted_sprites[3].get().data.order_in_layer, 5);  	for (size_t i = 1; i < sorted_sprites.size(); ++i) {  		const Sprite & prev = sorted_sprites[i - 1].get();  		const Sprite & curr = sorted_sprites[i].get(); -		if (prev.sorting_in_layer == curr.sorting_in_layer) { -			EXPECT_LE(prev.order_in_layer, curr.order_in_layer); +		if (prev.data.sorting_in_layer == curr.data.sorting_in_layer) { +			EXPECT_LE(prev.data.order_in_layer, curr.data.order_in_layer);  		} else { -			EXPECT_LE(prev.sorting_in_layer, curr.sorting_in_layer); +			EXPECT_LE(prev.data.sorting_in_layer, curr.data.sorting_in_layer);  		}  	}  }  TEST_F(RenderSystemTest, Update) { -	entity1.add_component<Camera>(Color::WHITE, ivec2{1080, 720}, vec2{2000, 2000}, 1.0f); +	entity1.add_component<Camera>(ivec2{100, 100}, vec2{100, 100}, +								  Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f});  	{  		vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>();  		ASSERT_EQ(sprites.size(), 4); @@ -142,7 +171,9 @@ TEST_F(RenderSystemTest, Camera) {  		EXPECT_NE(cameras.size(), 1);  	}  	{ -		entity1.add_component<Camera>(Color::WHITE, ivec2{1080, 720}, vec2{2000, 2000}, 1.0f); +		entity1.add_component<Camera>(ivec2{100, 100}, vec2{100, 100}, +									  Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); +  		auto cameras = this->mgr.get_components_by_type<Camera>();  		EXPECT_EQ(cameras.size(), 1);  	} @@ -150,18 +181,20 @@ TEST_F(RenderSystemTest, Camera) {  	//TODO improve with newer version  }  TEST_F(RenderSystemTest, Color) { -	entity1.add_component<Camera>(Color::WHITE, ivec2{1080, 720}, vec2{2000, 2000}, 1.0f); +	entity1.add_component<Camera>(ivec2{100, 100}, vec2{100, 100}, +								  Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); +  	auto & sprite = this->mgr.get_components_by_id<Sprite>(entity1.id).front().get(); -	ASSERT_NE(sprite.sprite_image.texture.get(), nullptr); +	ASSERT_NE(sprite.texture.texture.get(), nullptr); -	sprite.color = Color::GREEN; -	EXPECT_EQ(sprite.color.r, Color::GREEN.r); -	EXPECT_EQ(sprite.color.g, Color::GREEN.g); -	EXPECT_EQ(sprite.color.b, Color::GREEN.b); -	EXPECT_EQ(sprite.color.a, Color::GREEN.a); +	sprite.data.color = Color::GREEN; +	EXPECT_EQ(sprite.data.color.r, Color::GREEN.r); +	EXPECT_EQ(sprite.data.color.g, Color::GREEN.g); +	EXPECT_EQ(sprite.data.color.b, Color::GREEN.b); +	EXPECT_EQ(sprite.data.color.a, Color::GREEN.a);  	this->sys.update(); -	EXPECT_EQ(sprite.color.r, Color::GREEN.r); -	EXPECT_EQ(sprite.color.g, Color::GREEN.g); -	EXPECT_EQ(sprite.color.b, Color::GREEN.b); -	EXPECT_EQ(sprite.color.a, Color::GREEN.a); +	EXPECT_EQ(sprite.data.color.r, Color::GREEN.r); +	EXPECT_EQ(sprite.data.color.g, Color::GREEN.g); +	EXPECT_EQ(sprite.data.color.b, Color::GREEN.b); +	EXPECT_EQ(sprite.data.color.a, Color::GREEN.a);  } |