diff options
Diffstat (limited to 'src')
118 files changed, 3669 insertions, 1819 deletions
| diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt index da9d492..6cbb9fe 100644 --- a/src/crepe/CMakeLists.txt +++ b/src/crepe/CMakeLists.txt @@ -2,6 +2,7 @@ target_sources(crepe PUBLIC  	Particle.cpp  	Component.cpp  	Collider.cpp +	Resource.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -9,6 +10,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	Collider.h  	ValueBroker.h  	ValueBroker.hpp +	Resource.h  )  add_subdirectory(api) diff --git a/src/crepe/Collider.h b/src/crepe/Collider.h index a08a68e..42ccfd4 100644 --- a/src/crepe/Collider.h +++ b/src/crepe/Collider.h @@ -15,7 +15,7 @@ public:  	*  	* 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;  }; diff --git a/src/crepe/Component.h b/src/crepe/Component.h index dc17721..eff5a58 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.   */ @@ -16,7 +16,12 @@ class Component {  public:  	//! Whether the component is active  	bool active = true; -	//! The id of the GameObject this component belongs to +	/** +	 * \brief The id of the GameObject this component belongs to +	 * +	 * \note Only systems are supposed to use this member, but since friend +	 * relations aren't inherited this needs to be public. +	 */  	const game_object_id_t game_object_id;  protected: @@ -24,7 +29,7 @@ protected:  	 * \param id The id of the GameObject this component belongs to  	 */  	Component(game_object_id_t id); -	//! Only the ComponentManager can create components +	//! Only ComponentManager can create components  	friend class ComponentManager;  	Component(const Component &) = delete; diff --git a/src/crepe/Resource.cpp b/src/crepe/Resource.cpp new file mode 100644 index 0000000..85913ed --- /dev/null +++ b/src/crepe/Resource.cpp @@ -0,0 +1,6 @@ +#include "Resource.h" +#include "manager/Mediator.h" + +using namespace crepe; + +Resource::Resource(const Asset & asset, Mediator & mediator) {} diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h new file mode 100644 index 0000000..d65206b --- /dev/null +++ b/src/crepe/Resource.h @@ -0,0 +1,30 @@ +#pragma once + +namespace crepe { + +class ResourceManager; +class Asset; +class Mediator; + +/** + * \brief Resource interface + * + * Resource is an interface class used to represent a (deserialized) game resource (e.g. + * textures, sounds). Resources are always created from \ref Asset "assets" by ResourceManager. + * + * The game programmer has the ability to use the ResourceManager to keep instances of concrete + * resources between scenes, preventing them from being reinstantiated during a scene + * transition. + */ +class Resource { +public: +	Resource(const Asset & src, Mediator & mediator); +	virtual ~Resource() = default; + +	Resource(const Resource &) = delete; +	Resource(Resource &&) = delete; +	Resource & operator=(const Resource &) = delete; +	Resource & operator=(Resource &&) = delete; +}; + +} // namespace crepe diff --git a/src/crepe/api/AI.cpp b/src/crepe/api/AI.cpp new file mode 100644 index 0000000..2195249 --- /dev/null +++ b/src/crepe/api/AI.cpp @@ -0,0 +1,89 @@ +#include <stdexcept> +#include <type_traits> + +#include "AI.h" +#include "types.h" + +namespace crepe { + +AI::AI(game_object_id_t id, float max_force) : Component(id), max_force(max_force) {} + +void AI::make_circle_path(float radius, const vec2 & center, float start_angle, +						  bool clockwise) { +	if (radius <= 0) { +		throw std::runtime_error("Radius must be greater than 0"); +	} + +	// The step size is determined by the radius (step size is in radians) +	float step = RADIUS_TO_STEP / radius; +	// Force at least MIN_STEP steps (in case of a small radius) +	if (step > 2 * M_PI / MIN_STEP) { +		step = 2 * M_PI / MIN_STEP; +	} +	// The path node distance is determined by the step size and the radius +	this->path_node_distance = radius * step * PATH_NODE_DISTANCE_FACTOR; + +	if (clockwise) { +		for (float i = start_angle; i < 2 * M_PI + start_angle; i += step) { +			path.push_back(vec2{static_cast<float>(center.x + radius * cos(i)), +								static_cast<float>(center.y + radius * sin(i))}); +		} +	} else { +		for (float i = start_angle; i > start_angle - 2 * M_PI; i -= step) { +			path.push_back(vec2{static_cast<float>(center.x + radius * cos(i)), +								static_cast<float>(center.y + radius * sin(i))}); +		} +	} +} + +void AI::make_oval_path(float radius_x, float radius_y, const vec2 & center, float start_angle, +						bool clockwise, float rotation) { +	if (radius_x <= 0 && radius_y <= 0) { +		throw std::runtime_error("Radius must be greater than 0"); +	} + +	float max_radius = std::max(radius_x, radius_y); +	// The step size is determined by the radius (step size is in radians) +	float step = RADIUS_TO_STEP / max_radius; +	// Force at least MIN_STEP steps (in case of a small radius) +	if (step > 2 * M_PI / MIN_STEP) { +		step = 2 * M_PI / MIN_STEP; +	} +	// The path node distance is determined by the step size and the radius +	this->path_node_distance = max_radius * step * PATH_NODE_DISTANCE_FACTOR; + +	std::function<vec2(vec2, vec2)> rotate_point = [rotation](vec2 point, vec2 center) { +		float s = sin(rotation); +		float c = cos(rotation); + +		// Translate point back to origin +		point.x -= center.x; +		point.y -= center.y; + +		// Rotate point +		float xnew = point.x * c - point.y * s; +		float ynew = point.x * s + point.y * c; + +		// Translate point back +		point.x = xnew + center.x; +		point.y = ynew + center.y; + +		return point; +	}; + +	if (clockwise) { +		for (float i = start_angle; i < 2 * M_PI + start_angle; i += step) { +			vec2 point = {static_cast<float>(center.x + radius_x * cos(i)), +						  static_cast<float>(center.y + radius_y * sin(i))}; +			path.push_back(rotate_point(point, center)); +		} +	} else { +		for (float i = start_angle; i > start_angle - 2 * M_PI; i -= step) { +			vec2 point = {static_cast<float>(center.x + radius_x * cos(i)), +						  static_cast<float>(center.y + radius_y * sin(i))}; +			path.push_back(rotate_point(point, center)); +		} +	} +} + +} // namespace crepe diff --git a/src/crepe/api/AI.h b/src/crepe/api/AI.h new file mode 100644 index 0000000..c780a91 --- /dev/null +++ b/src/crepe/api/AI.h @@ -0,0 +1,128 @@ +#pragma once + +#include "Component.h" +#include "types.h" + +namespace crepe { + +/** + * \brief The AI component is used to control the movement of an entity using AI. + * + * The AI component can be used to control the movement of an entity. The AI component can be used + * to implement different behaviors such as seeking, fleeing, arriving, and path following. + */ +class AI : public Component { +public: +	//! The different types of behaviors that can be used +	enum BehaviorTypeMask { +		SEEK = 0x00002, +		FLEE = 0x00004, +		ARRIVE = 0x00008, +		PATH_FOLLOW = 0x00010, +	}; + +public: +	/** +	 * \param id The id of the game object +	 * \param max_force The maximum force that can be applied to the entity +	 */ +	AI(game_object_id_t id, float max_force); + +	/** +	 * \brief Check if a behavior is on (aka activated) +	 * +	 * \param behavior The behavior to check +	 * \return true if the behavior is on, false otherwise +	 */ +	bool on(BehaviorTypeMask behavior) const { return (flags & behavior); } +	//! Turn on the seek behavior +	void seek_on() { flags |= SEEK; } +	//! Turn off the seek behavior +	void seek_off() { flags &= ~SEEK; } +	//! Turn on the flee behavior +	void flee_on() { flags |= FLEE; } +	//! Turn off the flee behavior +	void flee_off() { flags &= ~FLEE; } +	//! Turn on the arrive behavior +	void arrive_on() { flags |= ARRIVE; } +	//! Turn off the arrive behavior +	void arrive_off() { flags &= ~ARRIVE; } +	//! Turn on the path follow behavior +	void path_follow_on() { flags |= PATH_FOLLOW; } +	//! Turn off the path follow behavior +	void path_follow_off() { flags &= ~PATH_FOLLOW; } + +	/** +	 * \brief Add a path node (for the path following behavior) +	 * +	 * \note The path is not relative to the entity's position (it is an absolute path) +	 * +	 * \param node The path node to add +	 */ +	void add_path_node(const vec2 & node) { path.push_back(node); } +	/** +	 * \brief Make a circle path (for the path following behavior) +	 * +	 * \note The path is not relative to the entity's position (it is an absolute path) +	 * +	 * \param radius The radius of the circle (in game units) +	 * \param center The center of the circle (in game units) +	 * \param start_angle The start angle of the circle (in radians) +	 * \param clockwise The direction of the circle +	 */ +	void make_circle_path(float radius, const vec2 & center = {0, 0}, float start_angle = 0, +						  bool clockwise = true); +	/** +	 * \brief Make an oval path (for the path following behavior) +	 * +	 * \note The path is not relative to the entity's position (it is an absolute path) +	 * +	 * \param radius_x The x radius of the oval (in game units) +	 * \param radius_y The y radius of the oval (in game units) +	 * \param center The center of the oval (in game units) +	 * \param start_angle The start angle of the oval (in radians) +	 * \param clockwise The direction of the oval +	 * \param rotation The rotation of the oval (in radians) +	 */ +	void make_oval_path(float radius_x, float radius_y, const vec2 & center = {0, 0}, +						float start_angle = 0, bool clockwise = true, float rotation = 0); + +public: +	//! The maximum force that can be applied to the entity (higher values will make the entity adjust faster) +	float max_force; + +	//! The target to seek at +	vec2 seek_target; +	//! The target to arrive at +	vec2 arrive_target; +	//! The target to flee from +	vec2 flee_target; +	//! The distance at which the entity will start to flee from the target +	float square_flee_panic_distance = 200.0f * 200.0f; +	//! The deceleration rate for the arrive behavior (higher values will make the entity decelerate faster (less overshoot)) +	float arrive_deceleration = 40.0f; +	//! The path to follow (for the path following behavior) +	std::vector<vec2> path; +	//! The distance from the path node at which the entity will move to the next node (automatically set by make_circle_path()) +	float path_node_distance = 400.0f; +	//! Looping behavior for the path +	bool path_loop = true; + +private: +	//! The flags for the behaviors +	int flags = 0; +	//! The current path index +	size_t path_index = 0; + +	//! The AISystem is the only class that should access the flags and path_index variables +	friend class AISystem; + +	//! The minimum amount of steps for the path following behavior +	static constexpr int MIN_STEP = 16; +	//! The radius to step size ratio for the path following behavior +	static constexpr float RADIUS_TO_STEP = 400.0f; +	//! The path node distance factor for the path following behavior +	static constexpr float PATH_NODE_DISTANCE_FACTOR = 0.75f; +}; + +} // namespace crepe diff --git a/src/crepe/api/Animator.cpp b/src/crepe/api/Animator.cpp index 45f67f6..4ce4bf0 100644 --- a/src/crepe/api/Animator.cpp +++ b/src/crepe/api/Animator.cpp @@ -7,21 +7,51 @@  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, const ivec2 & single_frame_size, +				   const uvec2 & grid_size, const Animator::Data & data)  	: Component(id), -	  spritesheet(ss), -	  row(row), -	  col(col) { +	  spritesheet(spritesheet), +	  grid_size(grid_size), +	  data(data) {  	dbg_trace(); -	this->spritesheet.mask.h /= col; -	this->spritesheet.mask.w /= row; +	this->spritesheet.mask.w = single_frame_size.x; +	this->spritesheet.mask.h = single_frame_size.y;  	this->spritesheet.mask.x = 0; -	this->spritesheet.mask.y = col_animator * this->spritesheet.mask.h; -	this->active = false; +	this->spritesheet.mask.y = 0; -	// 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; +		= static_cast<float>(single_frame_size.x) / single_frame_size.y;  } +  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->grid_size.x; +	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..5918800 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,87 @@ 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 single_frame_size the width and height in pixels of a single frame inside the +	 * spritesheet +	 * \param grid_size the max rows and 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, const ivec2 & single_frame_size, +			 const uvec2 & grid_size, const Animator::Data & data);  	~Animator(); // dbg_trace +public: +	Animator::Data data; +  private: -	//! A reference to the Sprite sheet containing the animation frames. +	//! A reference to the Sprite sheet containing.  	Sprite & spritesheet; -	//! The maximum number of columns in the sprite sheet. -	const int col; - -	//! The maximum number of rows in the sprite sheet. -	const int row; +	//! The maximum number of rows and columns inside the spritesheet +	const uvec2 grid_size; -	//! The current col being animated. -	int curr_col = 0; - -	//! The current row being animated. -	int curr_row = 0; - -	//TODO: Is this necessary? -	//int fps; - -private: -	//! AnimatorSystem adjust the private member parameters of Animator; -	friend class AnimatorSystem; - -	//! SDLContext reads the Animator member var's -	friend class SDLContext; +	//! Uses the spritesheet +	friend AnimatorSystem;  }; +  } // namespace crepe -// diff --git a/src/crepe/api/AssetManager.cpp b/src/crepe/api/AssetManager.cpp deleted file mode 100644 index 3925758..0000000 --- a/src/crepe/api/AssetManager.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "util/Log.h" - -#include "AssetManager.h" - -using namespace crepe; - -AssetManager & AssetManager::get_instance() { -	static AssetManager instance; -	return instance; -} - -AssetManager::~AssetManager() { -	dbg_trace(); -	this->asset_cache.clear(); -} - -AssetManager::AssetManager() { dbg_trace(); } diff --git a/src/crepe/api/AssetManager.h b/src/crepe/api/AssetManager.h deleted file mode 100644 index fee6780..0000000 --- a/src/crepe/api/AssetManager.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include <any> -#include <memory> -#include <string> -#include <unordered_map> - -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 - * cleared. - */ -class AssetManager { - -private: -	//! A cache that holds all the assets, accessible by their file path, over multiple scenes. -	std::unordered_map<std::string, std::any> asset_cache; - -private: -	AssetManager(); -	virtual ~AssetManager(); - -public: -	AssetManager(const AssetManager &) = delete; -	AssetManager(AssetManager &&) = delete; -	AssetManager & operator=(const AssetManager &) = delete; -	AssetManager & operator=(AssetManager &&) = delete; - -	/** -	 * \brief Retrieves the singleton instance of the AssetManager. -	 * -	 * \return A reference to the single instance of the AssetManager. -	 */ -	static AssetManager & get_instance(); - -public: -	/** -	 * \brief Caches an asset by loading it from the given file path. -	 * -	 * \param file_path The path to the asset file to load. -	 * \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. -	 */ -	template <typename T> -	std::shared_ptr<T> cache(const std::string & file_path, bool reload = false); -}; - -} // namespace crepe - -#include "AssetManager.hpp" diff --git a/src/crepe/api/AssetManager.hpp b/src/crepe/api/AssetManager.hpp deleted file mode 100644 index 1c0e978..0000000 --- a/src/crepe/api/AssetManager.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "AssetManager.h" - -namespace crepe { - -template <typename asset> -std::shared_ptr<asset> AssetManager::cache(const std::string & file_path, bool reload) { -	auto it = asset_cache.find(file_path); - -	if (!reload && it != asset_cache.end()) { -		return std::any_cast<std::shared_ptr<asset>>(it->second); -	} - -	std::shared_ptr<asset> new_asset = std::make_shared<asset>(file_path.c_str()); - -	asset_cache[file_path] = new_asset; - -	return new_asset; -} - -} // namespace crepe diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp new file mode 100644 index 0000000..7b05cb1 --- /dev/null +++ b/src/crepe/api/AudioSource.cpp @@ -0,0 +1,15 @@ +#include "AudioSource.h" + +using namespace crepe; +using namespace std; + +AudioSource::AudioSource(game_object_id_t id, const Asset & src) +	: Component(id), +	  source(src) {} + +void AudioSource::play(bool looping) { +	this->loop = looping; +	this->oneshot_play = true; +} + +void AudioSource::stop() { this->oneshot_stop = true; } diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h new file mode 100644 index 0000000..b20e490 --- /dev/null +++ b/src/crepe/api/AudioSource.h @@ -0,0 +1,74 @@ +#pragma once + +#include "../Component.h" +#include "../facade/SoundHandle.h" +#include "../types.h" + +#include "Asset.h" +#include "GameObject.h" + +namespace crepe { + +class AudioSystem; + +//! Audio source component +class AudioSource : public Component { +	//! AudioSource components are handled by AudioSystem +	friend class AudioSystem; + +protected: +	/** +	 * \param source Sound sample to load +	 */ +	AudioSource(game_object_id_t id, const Asset & source); +	//! Only ComponentManager creates components +	friend class ComponentManager; + +public: +	// std::unique_ptr needs to be able to destoy this component +	virtual ~AudioSource() = default; + +public: +	//! Start this audio source +	void play(bool looping = false); +	//! Stop this audio source +	void stop(); + +public: +	//! Play when this component becomes active +	bool play_on_awake = false; +	//! Repeat the current audio clip during playback +	bool loop = false; +	//! Normalized volume (0.0 - 1.0) +	float volume = 1.0; + +private: +	//! This audio source's clip +	const Asset source; + +	/** +	 * \name One-shot state variables +	 * +	 * These variables trigger function calls when set to true, and are unconditionally reset on +	 * every system update. +	 * +	 * \{ +	 */ +	//! Play this sample +	bool oneshot_play = false; +	//! Stop this sample +	bool oneshot_stop = false; +	//! \} +	/** +	 * \name State diffing variables +	 * \{ +	 */ +	typeof(active) last_active = false; +	typeof(volume) last_volume = volume; +	typeof(loop) last_loop = loop; +	//! \} +	//! This source's voice handle +	SoundHandle voice{}; +}; + +} // namespace crepe diff --git a/src/crepe/api/BoxCollider.h b/src/crepe/api/BoxCollider.h index 89e43d8..1ac4d46 100644 --- a/src/crepe/api/BoxCollider.h +++ b/src/crepe/api/BoxCollider.h @@ -8,7 +8,7 @@ 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 { diff --git a/src/crepe/api/Button.h b/src/crepe/api/Button.h index 26e7526..61b18d7 100644 --- a/src/crepe/api/Button.h +++ b/src/crepe/api/Button.h @@ -11,7 +11,7 @@ class Button : public UIObject {  public:  	/**  	 * \brief Constructs a Button with the specified game object ID and dimensions. -	 *  +	 *  	 * \param id The unique ID of the game object associated with this button.  	 * \param dimensions The width and height of the UIObject  	 * \param offset The offset relative this GameObjects Transform @@ -23,7 +23,7 @@ public:  	/**  	 * \brief Indicates if the button is a toggle button (can be pressed and released). -	 *  +	 *  	 * A toggle button allows for a pressed/released state, whereas a regular button  	 * typically only has an on-click state.  	 */ @@ -31,7 +31,7 @@ public:  	// TODO: create separate toggle button class  	/**  	 * \brief The callback function to be executed when the button is clicked. -	 *  +	 *  	 * This function is invoked whenever the button is clicked. It can be set to any  	 * function that matches the signature `void()`.  	 */ diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index b2e3df8..8f84f06 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -1,13 +1,11 @@  target_sources(crepe PUBLIC -	# AudioSource.cpp +	AudioSource.cpp  	BehaviorScript.cpp  	GameObject.cpp  	Rigidbody.cpp  	ParticleEmitter.cpp  	Transform.cpp  	Color.cpp -	Texture.cpp -	AssetManager.cpp  	Sprite.cpp  	Config.cpp  	Metadata.cpp @@ -15,19 +13,18 @@ target_sources(crepe PUBLIC  	Animator.cpp  	BoxCollider.cpp  	CircleCollider.cpp -	IKeyListener.cpp -	IMouseListener.cpp  	LoopManager.cpp -	LoopTimer.cpp  	Asset.cpp  	EventHandler.cpp  	Script.cpp  	Button.cpp  	UIObject.cpp +	AI.cpp +	Scene.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES -	# AudioSource.h +	AudioSource.h  	BehaviorScript.h  	Config.h  	Script.h @@ -39,10 +36,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	Vector2.h  	Vector2.hpp  	Color.h -	Texture.h  -	AssetManager.h  -	AssetManager.hpp  	Scene.h +	Scene.hpp  	Metadata.h  	Camera.h  	Animator.h @@ -51,11 +46,9 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	EventHandler.h  	EventHandler.hpp  	Event.h -	IKeyListener.h -	IMouseListener.h  	LoopManager.h -	LoopTimer.h  	Asset.h  	Button.h  	UIObject.h +	AI.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.h b/src/crepe/api/CircleCollider.h index ebd1cb2..c7bf66e 100644 --- a/src/crepe/api/CircleCollider.h +++ b/src/crepe/api/CircleCollider.h @@ -8,7 +8,7 @@ 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 { diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h index 225e9b9..ca2d3f1 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 { @@ -13,20 +15,10 @@ namespace crepe {   * modified *before* execution is handed over from the game programmer to the engine (i.e. the   * main loop is started).   */ -class Config final { -public: +struct Config final {  	//! Retrieve handle to global Config instance  	static Config & get_instance(); -private: -	Config() = default; -	~Config() = default; -	Config(const Config &) = default; -	Config(Config &&) = default; -	Config & operator=(const Config &) = default; -	Config & operator=(Config &&) = default; - -public:  	//! Logging-related settings  	struct {  		/** @@ -61,15 +53,14 @@ public:  		 *  		 * Gravity value of game.  		 */ -		double gravity = 1; +		float gravity = 10;  	} physics;  	//! 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 @@ -85,6 +76,12 @@ public:  		 */  		std::string root_pattern = ".crepe-root";  	} asset; + +	//! Audio system settings +	struct { +		//! Max amount of simultanious voices +		unsigned int voices = 32; +	} audio;  };  } // namespace crepe diff --git a/src/crepe/api/EventHandler.h b/src/crepe/api/EventHandler.h index 7bdd9a3..7bb501b 100644 --- a/src/crepe/api/EventHandler.h +++ b/src/crepe/api/EventHandler.h @@ -8,12 +8,12 @@  namespace crepe {  /**   * \brief A type alias for an event handler function. - *  - * The EventHandler is a std::function that takes an EventType reference and returns a boolean value  + * + * The EventHandler is a std::function that takes an EventType reference and returns a boolean value   * indicating whether the event is handled. - *  + *   * \tparam EventType The type of event this handler will handle. - *  + *   * Returning \c false from an event handler results in the event being propogated to other listeners for the same event type, while returning \c true stops propogation altogether.   */  template <typename EventType> @@ -22,7 +22,7 @@ using EventHandler = std::function<bool(const EventType & e)>;  /**   * \class IEventHandlerWrapper   * \brief An abstract base class for event handler wrappers. - *  + *   * This class provides the interface for handling events. Derived classes must implement the   * `call()` method to process events   */ @@ -35,9 +35,9 @@ public:  	/**  	 * \brief Executes the handler with the given event. -	 *  +	 *  	 * This method calls the `call()` method of the derived class, passing the event to the handler. -	 *  +	 *  	 * \param e The event to be processed.  	 * \return A boolean value indicating whether the event is handled.  	 */ @@ -46,9 +46,9 @@ public:  private:  	/**  	 * \brief The method responsible for handling the event. -	 *  +	 *  	 * This method is implemented by derived classes to process the event. -	 *  +	 *  	 * \param e The event to be processed.  	 * \return A boolean value indicating whether the event is handled.  	 */ @@ -58,11 +58,11 @@ private:  /**   * \class EventHandlerWrapper   * \brief A wrapper for event handler functions. - *  - * This class wraps an event handler function of a specific event type. It implements the  - * `call()` and `get_type()` methods to allow the handler to be executed and its type to be  + * + * This class wraps an event handler function of a specific event type. It implements the + * `call()` and `get_type()` methods to allow the handler to be executed and its type to be   * queried. - *  + *   * \tparam EventType The type of event this handler will handle.   */  template <typename EventType> @@ -70,9 +70,9 @@ class EventHandlerWrapper : public IEventHandlerWrapper {  public:  	/**  	 * \brief Constructs an EventHandlerWrapper with a given handler. -	 *  +	 *  	 * The constructor takes an event handler function and stores it in the wrapper. -	 *  +	 *  	 * \param handler The event handler function.  	 */  	explicit EventHandlerWrapper(const EventHandler<EventType> & handler); @@ -80,9 +80,9 @@ public:  private:  	/**  	 * \brief Calls the stored event handler with the event. -	 *  +	 *  	 * This method casts the event to the appropriate type and calls the handler. -	 *  +	 *  	 * \param e The event to be handled.  	 * \return A boolean value indicating whether the event is handled.  	 */ diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h index 4cd2bc0..ff80f49 100644 --- a/src/crepe/api/GameObject.h +++ b/src/crepe/api/GameObject.h @@ -10,7 +10,7 @@ class ComponentManager;  /**   * \brief Represents a GameObject - *  + *   * This class represents a GameObject. The GameObject class is only used as an interface for   * the game programmer. The actual implementation is done in the ComponentManager.   */ @@ -19,7 +19,7 @@ private:  	/**  	 * This constructor creates a new GameObject. It creates a new Transform and Metadata  	 * component and adds them to the ComponentManager. -	 *  +	 *  	 * \param component_manager Reference to component_manager  	 * \param id The id of the GameObject  	 * \param name The name of the GameObject @@ -37,20 +37,20 @@ private:  public:  	/**  	 * \brief Set the parent of this GameObject -	 *  +	 *  	 * This method sets the parent of this GameObject. It sets the parent in the Metadata  	 * component of this GameObject and adds this GameObject to the children list of the parent  	 * GameObject. -	 *  +	 *  	 * \param parent The parent GameObject  	 */  	void set_parent(const GameObject & parent);  	/**  	 * \brief Add a component to the GameObject -	 *  +	 *  	 * This method adds a component to the GameObject. It forwards the arguments to the  	 * ComponentManager. -	 *  +	 *  	 * \tparam T The type of the component  	 * \tparam Args The types of the arguments  	 * \param args The arguments to create the component diff --git a/src/crepe/api/IKeyListener.cpp b/src/crepe/api/IKeyListener.cpp deleted file mode 100644 index 8642655..0000000 --- a/src/crepe/api/IKeyListener.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "IKeyListener.h" - -using namespace crepe; - -// Constructor with specified channel -IKeyListener::IKeyListener(event_channel_t channel) -	: event_manager(EventManager::get_instance()) { -	this->press_id = event_manager.subscribe<KeyPressEvent>( -		[this](const KeyPressEvent & event) { return this->on_key_pressed(event); }, channel); -	this->release_id = event_manager.subscribe<KeyReleaseEvent>( -		[this](const KeyReleaseEvent & event) { return this->on_key_released(event); }, -		channel); -} - -// Destructor, unsubscribe events -IKeyListener::~IKeyListener() { -	event_manager.unsubscribe(this->press_id); -	event_manager.unsubscribe(this->release_id); -} diff --git a/src/crepe/api/IKeyListener.h b/src/crepe/api/IKeyListener.h deleted file mode 100644 index 180a0a6..0000000 --- a/src/crepe/api/IKeyListener.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include "../manager/EventManager.h" - -#include "Event.h" -#include "EventHandler.h" - -namespace crepe { - -/** - * \class IKeyListener - * \brief Interface for keyboard event handling in the application. - */ -class IKeyListener { -public: -	/** -	 * \brief Constructs an IKeyListener with a specified channel. -	 * \param channel The channel ID for event handling. -	 */ -	IKeyListener(event_channel_t channel = EventManager::CHANNEL_ALL); -	virtual ~IKeyListener(); -	IKeyListener(const IKeyListener &) = delete; -	IKeyListener & operator=(const IKeyListener &) = delete; -	IKeyListener & operator=(IKeyListener &&) = delete; -	IKeyListener(IKeyListener &&) = delete; - -	/** -	 * \brief Pure virtual function to handle key press events. -	 * \param event The key press event to handle. -	 * \return True if the event was handled, false otherwise. -	 */ -	virtual bool on_key_pressed(const KeyPressEvent & event) = 0; - -	/** -	 * \brief Pure virtual function to handle key release events. -	 * \param event The key release event to handle. -	 * \return True if the event was handled, false otherwise. -	 */ -	virtual bool on_key_released(const KeyReleaseEvent & event) = 0; - -private: -	//! Key press event id -	subscription_t press_id = -1; -	//! Key release event id -	subscription_t release_id = -1; -	//! EventManager reference -	EventManager & event_manager; -}; - -} // namespace crepe diff --git a/src/crepe/api/IMouseListener.cpp b/src/crepe/api/IMouseListener.cpp deleted file mode 100644 index 989aeb3..0000000 --- a/src/crepe/api/IMouseListener.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "IMouseListener.h" - -using namespace crepe; - -IMouseListener::IMouseListener(event_channel_t channel) -	: event_manager(EventManager::get_instance()) { -	this->click_id = event_manager.subscribe<MouseClickEvent>( -		[this](const MouseClickEvent & event) { return this->on_mouse_clicked(event); }, -		channel); - -	this->press_id = event_manager.subscribe<MousePressEvent>( -		[this](const MousePressEvent & event) { return this->on_mouse_pressed(event); }, -		channel); - -	this->release_id = event_manager.subscribe<MouseReleaseEvent>( -		[this](const MouseReleaseEvent & event) { return this->on_mouse_released(event); }, -		channel); - -	this->move_id = event_manager.subscribe<MouseMoveEvent>( -		[this](const MouseMoveEvent & event) { return this->on_mouse_moved(event); }, channel); -} - -IMouseListener::~IMouseListener() { -	// Unsubscribe event handlers -	event_manager.unsubscribe(this->click_id); -	event_manager.unsubscribe(this->press_id); -	event_manager.unsubscribe(this->release_id); -	event_manager.unsubscribe(this->move_id); -} diff --git a/src/crepe/api/IMouseListener.h b/src/crepe/api/IMouseListener.h deleted file mode 100644 index e19897d..0000000 --- a/src/crepe/api/IMouseListener.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include "../manager/EventManager.h" - -#include "Event.h" -#include "EventHandler.h" - -namespace crepe { - -/** - * \class IMouseListener - * \brief Interface for mouse event handling in the application. - */ -class IMouseListener { -public: -	/** -	 * \brief Constructs an IMouseListener with a specified channel. -	 * \param channel The channel ID for event handling. -	 */ -	IMouseListener(event_channel_t channel = EventManager::CHANNEL_ALL); -	virtual ~IMouseListener(); -	IMouseListener & operator=(const IMouseListener &) = delete; -	IMouseListener(const IMouseListener &) = delete; -	IMouseListener & operator=(const IMouseListener &&) = delete; -	IMouseListener(IMouseListener &&) = delete; - -	/** -	 * \brief Move assignment operator (deleted). -	 */ -	IMouseListener & operator=(IMouseListener &&) = delete; - -	/** -	 * \brief Handles a mouse click event. -	 * \param event The mouse click event to handle. -	 * \return True if the event was handled, false otherwise. -	 */ -	virtual bool on_mouse_clicked(const MouseClickEvent & event) = 0; - -	/** -	 * \brief Handles a mouse press event. -	 * \param event The mouse press event to handle. -	 * \return True if the event was handled, false otherwise. -	 */ -	virtual bool on_mouse_pressed(const MousePressEvent & event) = 0; - -	/** -	 * \brief Handles a mouse release event. -	 * \param event The mouse release event to handle. -	 * \return True if the event was handled, false otherwise. -	 */ -	virtual bool on_mouse_released(const MouseReleaseEvent & event) = 0; - -	/** -	 * \brief Handles a mouse move event. -	 * \param event The mouse move event to handle. -	 * \return True if the event was handled, false otherwise. -	 */ -	virtual bool on_mouse_moved(const MouseMoveEvent & event) = 0; - -private: -	//! Mouse click event id -	subscription_t click_id = -1; -	//! Mouse press event id -	subscription_t press_id = -1; -	//! Mouse release event id -	subscription_t release_id = -1; -	//! Mouse move event id -	subscription_t move_id = -1; -	//! EventManager reference -	EventManager & event_manager; -}; - -} //namespace crepe diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index a76c167..b5e5ff7 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -1,11 +1,16 @@ +#include "../facade/SDLContext.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h" +#include "../system/AISystem.h"  #include "../system/AnimatorSystem.h" +#include "../system/AudioSystem.h"  #include "../system/CollisionSystem.h"  #include "../system/InputSystem.h"  #include "../system/ParticleSystem.h"  #include "../system/PhysicsSystem.h"  #include "../system/RenderSystem.h"  #include "../system/ScriptSystem.h" -#include "manager/EventManager.h" +#include "../util/Log.h"  #include "LoopManager.h" @@ -13,9 +18,6 @@ using namespace crepe;  using namespace std;  LoopManager::LoopManager() { -	this->mediator.component_manager = this->component_manager; -	this->mediator.scene_manager = this->scene_manager; -  	this->load_system<AnimatorSystem>();  	this->load_system<CollisionSystem>();  	this->load_system<ParticleSystem>(); @@ -23,57 +25,62 @@ LoopManager::LoopManager() {  	this->load_system<RenderSystem>();  	this->load_system<ScriptSystem>();  	this->load_system<InputSystem>(); +	this->event_manager.subscribe<ShutDownEvent>( +		[this](const ShutDownEvent & event) { return this->on_shutdown(event); }); +	this->load_system<AudioSystem>(); +	this->load_system<AISystem>();  } - -void LoopManager::process_input() { this->get_system<InputSystem>().update(); } -  void LoopManager::start() {  	this->setup();  	this->loop();  } -void LoopManager::set_running(bool running) { this->game_running = running; } -void LoopManager::fixed_update() { -	// TODO: retrieve EventManager from direct member after singleton refactor -	EventManager & ev = this->mediator.event_manager; -	ev.dispatch_events(); -	this->get_system<ScriptSystem>().update(); -	this->get_system<PhysicsSystem>().update(); -	this->get_system<CollisionSystem>().update(); +void LoopManager::setup() { +	this->game_running = true; +	this->loop_timer.start(); +	this->scene_manager.load_next_scene();  }  void LoopManager::loop() { -	LoopTimer & timer = this->loop_timer; -	timer.start(); +	try { +		while (game_running) { +			this->loop_timer.update(); -	while (game_running) { -		timer.update(); +			while (this->loop_timer.get_lag() >= this->loop_timer.get_fixed_delta_time()) { +				this->fixed_update(); +				this->loop_timer.advance_fixed_elapsed_time(); +			} -		while (timer.get_lag() >= timer.get_fixed_delta_time()) { -			this->process_input(); -			this->fixed_update(); -			timer.advance_fixed_update(); +			this->frame_update(); +			this->loop_timer.enforce_frame_rate();  		} - -		this->update(); -		this->render(); - -		timer.enforce_frame_rate(); +	} catch (const exception & e) { +		Log::logf(Log::Level::ERROR, "Exception caught in main loop: {}", e.what()); +		this->event_manager.trigger_event<ShutDownEvent>(ShutDownEvent{});  	}  } -void LoopManager::setup() { -	LoopTimer & timer = this->loop_timer; -	this->game_running = true; -	this->scene_manager.load_next_scene(); -	timer.start(); -	timer.set_fps(200); +// will be called at a fixed interval +void LoopManager::fixed_update() { +	this->get_system<InputSystem>().update(); +	this->event_manager.dispatch_events(); +	this->get_system<ScriptSystem>().update(); +	this->get_system<AISystem>().update(); +	this->get_system<PhysicsSystem>().update(); +	this->get_system<CollisionSystem>().update(); +	this->get_system<AudioSystem>().update();  } -void LoopManager::render() { -	if (!this->game_running) return; - +// will be called every frame +void LoopManager::frame_update() { +	this->scene_manager.load_next_scene(); +	this->get_system<AnimatorSystem>().update(); +	//render  	this->get_system<RenderSystem>().update();  } -void LoopManager::update() {} +bool LoopManager::on_shutdown(const ShutDownEvent & e) { +	this->game_running = false; +	// propagate to possible user ShutDownEvent listeners +	return false; +} diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h index d8910a0..40e6b38 100644 --- a/src/crepe/api/LoopManager.h +++ b/src/crepe/api/LoopManager.h @@ -4,13 +4,15 @@  #include "../facade/SDLContext.h"  #include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/Mediator.h" +#include "../manager/ResourceManager.h" +#include "../manager/SaveManager.h"  #include "../manager/SceneManager.h"  #include "../system/System.h" -#include "LoopTimer.h" -  namespace crepe { -  /**   * \brief Main game loop manager   * @@ -18,8 +20,14 @@ namespace crepe {   */  class LoopManager {  public: -	void start();  	LoopManager(); +	/** +	 * \brief Start the gameloop +	 * +	 * This is the start of the engine where the setup is called and then the loop keeps running until the game stops running. +	 * The Game programmer needs to call this function to run the game. This should be done after creating and adding all scenes. +	 */ +	void start();  	/**  	 * \brief Add a new concrete scene to the scene manager @@ -44,47 +52,20 @@ private:  	void loop();  	/** -	 * \brief Function for handling input-related system calls. -	 * -	 * Processes user inputs from keyboard and mouse. -	 */ -	void process_input(); - -	/**  	 * \brief Per-frame update.  	 *  	 * Updates the game state based on the elapsed time since the last frame.  	 */ -	void update(); - -	/** -	 * \brief Late update which is called after update(). -	 * -	 * This function can be used for final adjustments before rendering. -	 */ -	void late_update(); +	virtual void frame_update();  	/**  	 * \brief Fixed update executed at a fixed rate.  	 *  	 * This function updates physics and game logic based on LoopTimer's fixed_delta_time.  	 */ -	void fixed_update(); - -	/** -	 * \brief Set game running variable -	 * -	 * \param running running (false = game shutdown, true = game running) -	 */ -	void set_running(bool running); - -	/** -	 * \brief Function for executing render-related systems. -	 * -	 * Renders the current state of the game to the screen. -	 */ -	void render(); +	virtual void fixed_update(); +	//! Indicates whether the game is running.  	bool game_running = false;  private: @@ -95,18 +76,29 @@ private:  	ComponentManager component_manager{mediator};  	//! Scene manager instance  	SceneManager scene_manager{mediator}; - -	//! SDL context \todo no more singletons! -	SDLContext & sdl_context = SDLContext::get_instance(); -	//! Loop timer \todo no more singletons! -	LoopTimer & loop_timer = LoopTimer::get_instance(); +	//! LoopTimerManager instance +	LoopTimerManager loop_timer{mediator}; +	//! EventManager instance +	EventManager event_manager{mediator}; +	//! Resource manager instance +	ResourceManager resource_manager{mediator}; +	//! Save manager instance +	SaveManager save_manager{mediator}; +	//! SDLContext instance +	SDLContext sdl_context{mediator};  private:  	/** +	 * \brief Callback function for ShutDownEvent +	 * +	 * This function sets the game_running variable to false, stopping the gameloop and therefor quitting the game. +	 */ +	bool on_shutdown(const ShutDownEvent & e); +	/**  	 * \brief Collection of System instances  	 *  	 * This map holds System instances indexed by the system's class typeid. It is filled in the -	 * constructor of \c LoopManager using LoopManager::load_system. +	 * constructor of LoopManager using LoopManager::load_system.  	 */  	std::unordered_map<std::type_index, std::unique_ptr<System>> systems;  	/** diff --git a/src/crepe/api/LoopTimer.cpp b/src/crepe/api/LoopTimer.cpp deleted file mode 100644 index 15a0e3a..0000000 --- a/src/crepe/api/LoopTimer.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include <chrono> - -#include "../facade/SDLContext.h" -#include "../util/Log.h" - -#include "LoopTimer.h" - -using namespace crepe; - -LoopTimer::LoopTimer() { dbg_trace(); } - -LoopTimer & LoopTimer::get_instance() { -	static LoopTimer instance; -	return instance; -} - -void LoopTimer::start() { -	this->last_frame_time = std::chrono::steady_clock::now(); -	this->elapsed_time = std::chrono::milliseconds(0); -	this->elapsed_fixed_time = std::chrono::milliseconds(0); -	this->delta_time = std::chrono::milliseconds(0); -} - -void LoopTimer::update() { -	auto current_frame_time = std::chrono::steady_clock::now(); -	// Convert to duration in seconds for delta time -	this->delta_time = std::chrono::duration_cast<std::chrono::duration<double>>( -		current_frame_time - last_frame_time); - -	if (this->delta_time > this->maximum_delta_time) { -		this->delta_time = this->maximum_delta_time; -	} - -	this->delta_time *= this->game_scale; -	this->elapsed_time += this->delta_time; -	this->last_frame_time = current_frame_time; -} - -double LoopTimer::get_delta_time() const { return this->delta_time.count(); } - -double LoopTimer::get_current_time() const { return this->elapsed_time.count(); } - -void LoopTimer::advance_fixed_update() { this->elapsed_fixed_time += this->fixed_delta_time; } - -double LoopTimer::get_fixed_delta_time() const { return this->fixed_delta_time.count(); } - -void LoopTimer::set_fps(int fps) { -	this->fps = fps; -	// target time per frame in seconds -	this->frame_target_time = std::chrono::duration<double>(1.0) / fps; -} - -int LoopTimer::get_fps() const { return this->fps; } - -void LoopTimer::set_game_scale(double value) { this->game_scale = value; } - -double LoopTimer::get_game_scale() const { return this->game_scale; } -void LoopTimer::enforce_frame_rate() { -	std::chrono::steady_clock::time_point current_frame_time -		= std::chrono::steady_clock::now(); -	std::chrono::milliseconds frame_duration -		= std::chrono::duration_cast<std::chrono::milliseconds>(current_frame_time -																- this->last_frame_time); - -	if (frame_duration < this->frame_target_time) { -		std::chrono::milliseconds delay_time -			= std::chrono::duration_cast<std::chrono::milliseconds>(this->frame_target_time -																	- frame_duration); -		if (delay_time.count() > 0) { -			SDLContext::get_instance().delay(delay_time.count()); -		} -	} - -	this->last_frame_time = current_frame_time; -} - -double LoopTimer::get_lag() const { -	return (this->elapsed_time - this->elapsed_fixed_time).count(); -} diff --git a/src/crepe/api/LoopTimer.h b/src/crepe/api/LoopTimer.h deleted file mode 100644 index 9393439..0000000 --- a/src/crepe/api/LoopTimer.h +++ /dev/null @@ -1,144 +0,0 @@ -#pragma once - -#include <chrono> - -namespace crepe { - -class LoopTimer { -public: -	/** -	 * \brief Get the singleton instance of LoopTimer. -	 * -	 * \return A reference to the LoopTimer instance. -	 */ -	static LoopTimer & get_instance(); - -	/** -	 * \brief Get the current delta time for the current frame. -	 * -	 * \return Delta time in seconds since the last frame. -	 */ -	double get_delta_time() const; - -	/** -	 * \brief Get the current game time. -	 * -	 * \note The current game time may vary from real-world elapsed time. It is the cumulative -	 * sum of each frame's delta time. -	 * -	 * \return Elapsed game time in seconds. -	 */ -	double get_current_time() const; - -	/** -	 * \brief Set the target frames per second (FPS). -	 * -	 * \param fps The desired frames rendered per second. -	 */ -	void set_fps(int fps); - -	/** -	 * \brief Get the current frames per second (FPS). -	 * -	 * \return Current FPS. -	 */ -	int get_fps() const; - -	/** -	 * \brief Get the current game scale. -	 * -	 * \return The current game scale, where 0 = paused, 1 = normal speed, and values > 1 speed -	 * up the game. -	 */ -	double get_game_scale() const; - -	/** -	 * \brief Set the game scale. -	 * -	 * \param game_scale The desired game scale (0 = pause, 1 = normal speed, > 1 = speed up). -	 */ -	void set_game_scale(double game_scale); - -private: -	friend class LoopManager; - -	/** -	 * \brief Start the loop timer. -	 * -	 * Initializes the timer to begin tracking frame times. -	 */ -	void start(); - -	/** -	 * \brief Enforce the frame rate limit. -	 * -	 * Ensures that the game loop does not exceed the target FPS by delaying frame updates as -	 * necessary. -	 */ -	void enforce_frame_rate(); - -	/** -	 * \brief Get the fixed delta time for consistent updates. -	 * -	 * Fixed delta time is used for operations that require uniform time steps, such as physics -	 * calculations. -	 * -	 * \return Fixed delta time in seconds. -	 */ -	double get_fixed_delta_time() const; - -	/** -	 * \brief Get the accumulated lag in the game loop. -	 * -	 * Lag represents the difference between the target frame time and the actual frame time, -	 * useful for managing fixed update intervals. -	 * -	 * \return Accumulated lag in seconds. -	 */ -	double get_lag() const; - -	/** -	 * \brief Construct a new LoopTimer object. -	 * -	 * Private constructor for singleton pattern to restrict instantiation outside the class. -	 */ -	LoopTimer(); - -	/** -	 * \brief Update the timer to the current frame. -	 * -	 * Calculates and updates the delta time for the current frame and adds it to the cumulative -	 * game time. -	 */ -	void update(); - -	/** -	 * \brief Advance the game loop by a fixed update interval. -	 * -	 * This method progresses the game state by a consistent, fixed time step, allowing for -	 * stable updates independent of frame rate fluctuations. -	 */ -	void advance_fixed_update(); - -private: -	//! Current frames per second -	int fps = 50; -	//! Current game scale -	double game_scale = 1; -	//! Maximum delta time in seconds to avoid large jumps -	std::chrono::duration<double> maximum_delta_time{0.25}; -	//! Delta time for the current frame in seconds -	std::chrono::duration<double> delta_time{0.0}; -	//! Target time per frame in seconds -	std::chrono::duration<double> frame_target_time = std::chrono::duration<double>(1.0) / fps; -	//! Fixed delta time for fixed updates in seconds -	std::chrono::duration<double> fixed_delta_time = std::chrono::duration<double>(1.0) / 50.0; -	//! Total elapsed game time in seconds -	std::chrono::duration<double> elapsed_time{0.0}; -	//! Total elapsed time for fixed updates in seconds -	std::chrono::duration<double> elapsed_fixed_time{0.0}; -	//! Time of the last frame -	std::chrono::steady_clock::time_point last_frame_time; -}; - -} // namespace crepe 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.h b/src/crepe/api/Rigidbody.h index 8265ba5..b08c8db 100644 --- a/src/crepe/api/Rigidbody.h +++ b/src/crepe/api/Rigidbody.h @@ -11,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.   */ @@ -19,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 { @@ -32,7 +32,7 @@ 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.  	 */ @@ -46,20 +46,20 @@ public:  	};  public: -	/**  +	/**  	 * \brief struct for Rigidbody data -	 *  +	 *  	 * This struct holds the data for the Rigidbody.  	 */  	struct Data {  		//! objects mass -		float mass = 0.0; +		float mass = 1;  		/**  		* \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; @@ -79,7 +79,7 @@ public:  		//! Linear velocity of the object (speed and direction).  		vec2 linear_velocity;  		//! Maximum linear velocity of the object. This limits the object's speed. -		vec2 max_linear_velocity = {INFINITY, INFINITY}; +		float max_linear_velocity = INFINITY;  		//! Linear velocity coefficient. This scales the object's velocity for adjustment or damping.  		vec2 linear_velocity_coefficient = {1, 1};  		//! \} @@ -108,7 +108,7 @@ public:  		* 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; @@ -128,7 +128,7 @@ public:  		* 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; @@ -136,14 +136,14 @@ public:  		 * \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  +		 * 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.  	 */ @@ -152,15 +152,15 @@ 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(float force); diff --git a/src/crepe/api/Scene.cpp b/src/crepe/api/Scene.cpp new file mode 100644 index 0000000..ad729d2 --- /dev/null +++ b/src/crepe/api/Scene.cpp @@ -0,0 +1,15 @@ +#include "Scene.h" + +using namespace crepe; + +SaveManager & Scene::get_save_manager() const { return mediator->save_manager; } + +GameObject Scene::new_object(const std::string & name, const std::string & tag, +							 const vec2 & position, double rotation, double scale) { +	// Forward the call to ComponentManager's new_object method +	return mediator->component_manager->new_object(name, tag, position, rotation, scale); +} + +void Scene::set_persistent(const Asset & asset, bool persistent) { +	mediator->resource_manager->set_persistent(asset, persistent); +} diff --git a/src/crepe/api/Scene.h b/src/crepe/api/Scene.h index 9f1e8ce..dcca9d4 100644 --- a/src/crepe/api/Scene.h +++ b/src/crepe/api/Scene.h @@ -2,17 +2,23 @@  #include <string> +#include "../manager/ComponentManager.h"  #include "../manager/Mediator.h" +#include "../manager/ResourceManager.h" +#include "../util/Log.h"  #include "../util/OptionalRef.h" +#include "GameObject.h" +  namespace crepe {  class SceneManager;  class ComponentManager; +class Asset;  /**   * \brief Represents a Scene - *  + *   * This class represents a Scene. The Scene class is only used as an interface for the game   * programmer.   */ @@ -38,10 +44,10 @@ public:  	// TODO: Late references should ALWAYS be private! This is currently kept as-is so unit tests  	// keep passing, but this reference should not be directly accessible by the user!!! -protected: +private:  	/**  	 * \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 @@ -53,6 +59,36 @@ protected:  	//! Mediator reference  	OptionalRef<Mediator> mediator;  	//! \} + +protected: +	/** +	* \brief Retrieve the reference to the SaveManager instance +	* +	* \returns A reference to the SaveManager instance held by the Mediator. +	*/ +	SaveManager & get_save_manager() const; + +	//! \copydoc ComponentManager::new_object +	GameObject new_object(const std::string & name, const std::string & tag = "", +						  const vec2 & position = {0, 0}, double rotation = 0, +						  double scale = 1); + +	//! \copydoc ResourceManager::set_persistent +	void set_persistent(const Asset & asset, bool persistent); +	/** +	* \name Logging functions +	* \see Log +	* \{ +	*/ +	//! \copydoc Log::logf +	template <class... Args> +	void logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args); +	//! \copydoc Log::logf +	template <class... Args> +	void logf(std::format_string<Args...> fmt, Args &&... args); +	//! \}  };  } // namespace crepe + +#include "Scene.hpp" diff --git a/src/crepe/api/Scene.hpp b/src/crepe/api/Scene.hpp new file mode 100644 index 0000000..14635df --- /dev/null +++ b/src/crepe/api/Scene.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "../util/Log.h" + +#include "Scene.h" + +namespace crepe { + +template <class... Args> +void Scene::logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args) { +	Log::logf(level, fmt, std::forward<Args>(args)...); +} + +template <class... Args> +void Scene::logf(std::format_string<Args...> fmt, Args &&... args) { +	Log::logf(fmt, std::forward<Args>(args)...); +} + +} // namespace crepe diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp index 4091fd4..753a9e3 100644 --- a/src/crepe/api/Script.cpp +++ b/src/crepe/api/Script.cpp @@ -8,8 +8,7 @@ using namespace crepe;  using namespace std;  Script::~Script() { -	Mediator & mediator = this->mediator; -	EventManager & mgr = mediator.event_manager; +	EventManager & mgr = this->mediator->event_manager;  	for (auto id : this->listeners) {  		mgr.unsubscribe(id);  	} @@ -21,7 +20,8 @@ void Script::subscribe(const EventHandler<CollisionEvent> & callback) {  }  void Script::set_next_scene(const string & name) { -	Mediator & mediator = this->mediator; -	SceneManager & mgr = mediator.scene_manager; +	SceneManager & mgr = this->mediator->scene_manager;  	mgr.set_next_scene(name);  } + +SaveManager & Script::get_save_manager() const { return this->mediator->save_manager; } diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index d1be1dc..668e5d1 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -20,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 @@ -86,6 +86,25 @@ protected:  	RefVector<T> get_components() const;  	/** +	 * \copydoc ComponentManager::get_components_by_id +	 * \see ComponentManager::get_components_by_id +	 */ +	template <typename T> +	RefVector<T> get_components_by_id(game_object_id_t id) const; +	/** +	 * \copydoc ComponentManager::get_components_by_name +	 * \see ComponentManager::get_components_by_name +	 */ +	template <typename T> +	RefVector<T> get_components_by_name(const std::string & name) const; +	/** +	 * \copydoc ComponentManager::get_components_by_tag +	 * \see ComponentManager::get_components_by_tag +	 */ +	template <typename T> +	RefVector<T> get_components_by_tag(const std::string & tag) const; + +	/**  	 * \brief Log a message using Log::logf  	 *  	 * \tparam Args Log::logf parameters @@ -113,6 +132,9 @@ protected:  	 */  	void set_next_scene(const std::string & name); +	//! Retrieve SaveManager reference +	SaveManager & get_save_manager() const; +  	//! \}  private: diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index 45f1ff1..225a51c 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -20,10 +20,7 @@ T & Script::get_component() const {  template <typename T>  RefVector<T> Script::get_components() const { -	Mediator & mediator = this->mediator; -	ComponentManager & mgr = mediator.component_manager; - -	return mgr.get_components_by_id<T>(this->game_object_id); +	return this->get_components_by_id<T>(this->game_object_id);  }  template <typename... Args> @@ -34,8 +31,7 @@ void Script::logf(Args &&... args) {  template <typename EventType>  void Script::subscribe_internal(const EventHandler<EventType> & callback,  								event_channel_t channel) { -	Mediator & mediator = this->mediator; -	EventManager & mgr = mediator.event_manager; +	EventManager & mgr = this->mediator->event_manager;  	subscription_t listener = mgr.subscribe<EventType>(  		[this, callback](const EventType & data) -> bool {  			bool & active = this->active; @@ -56,4 +52,26 @@ void Script::subscribe(const EventHandler<EventType> & callback) {  	this->subscribe_internal(callback, EventManager::CHANNEL_ALL);  } +template <typename T> +RefVector<T> Script::get_components_by_id(game_object_id_t id) const { +	Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; + +	return mgr.get_components_by_id<T>(id); +} +template <typename T> +RefVector<T> Script::get_components_by_name(const std::string & name) const { +	Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; + +	return mgr.get_components_by_name<T>(name); +} +template <typename T> +RefVector<T> Script::get_components_by_tag(const std::string & tag) const { +	Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; + +	return mgr.get_components_by_tag<T>(tag); +} +  } // namespace crepe diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp index 0a2ad4c..ba684ba 100644 --- a/src/crepe/api/Sprite.cpp +++ b/src/crepe/api/Sprite.cpp @@ -1,30 +1,21 @@  #include <cmath> -#include <utility>  #include "../util/Log.h" +#include "api/Asset.h"  #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, const Asset & 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) { +	  source(texture), +	  data(data) {  	dbg_trace(); - -	this->mask.w = sprite_image.get_size().x; -	this->mask.h = sprite_image.get_size().y; -	this->aspect_ratio = static_cast<double>(this->mask.w) / this->mask.h;  }  Sprite::~Sprite() { dbg_trace(); } diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h index a0e90a0..a2409c2 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 "api/Asset.h"  #include "Color.h" -#include "Texture.h" +#include "types.h"  namespace crepe { @@ -20,58 +19,68 @@ 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, const Asset & 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 Asset source; -	//! 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; - -	/** -	 * \aspect_ratio ratio of the img so that scaling will not become weird -	 * -	 * cannot be const because if Animator component is addded then ratio becomes scuffed and -	 * does it need to be calculated again in the Animator -	 */ -	double aspect_ratio; +	Data data;  private:  	//! Reads the mask of sprite @@ -83,6 +92,14 @@ private:  	//! Reads the all the variables plus the  mask  	friend class AnimatorSystem; +	/** +	 * \aspect_ratio the ratio of the sprite image +	 * +	 * - this value will only be set by the \c Animator component for the ratio of the Animation +	 * - if \c Animator component is not added it will not use this ratio (because 0) and will use aspect_ratio of the Asset. +	 */ +	float aspect_ratio = 0; +  	struct Rect {  		int w = 0;  		int h = 0; diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp deleted file mode 100644 index 2b56271..0000000 --- a/src/crepe/api/Texture.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../facade/SDLContext.h" -#include "../util/Log.h" - -#include "Asset.h" -#include "Texture.h" -#include "types.h" - -using namespace crepe; -using namespace std; - -Texture::Texture(const Asset & src) { -	dbg_trace(); -	this->load(src); -} - -Texture::~Texture() { -	dbg_trace(); -	this->texture.reset(); -} - -Texture::Texture(Texture && other) noexcept : texture(std::move(other.texture)) {} - -Texture & Texture::operator=(Texture && other) noexcept { -	if (this != &other) { -		texture = std::move(other.texture); -	} -	return *this; -} - -void Texture::load(const Asset & res) { -	SDLContext & ctx = SDLContext::get_instance(); -	this->texture = ctx.texture_from_path(res.get_path()); -} - -ivec2 Texture::get_size() const { -	if (this->texture == nullptr) return {}; -	return SDLContext::get_instance().get_size(*this); -} diff --git a/src/crepe/api/Texture.h b/src/crepe/api/Texture.h deleted file mode 100644 index 1817910..0000000 --- a/src/crepe/api/Texture.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -// FIXME: this header can't be included because this is an API header, and SDL2 development -// headers won't be bundled with crepe. Why is this facade in the API namespace? - -#include <SDL2/SDL_render.h> -#include <functional> -#include <memory> - -#include "Asset.h" -#include "types.h" - -namespace crepe { - -class SDLContext; -class Animator; - -/** - * \class Texture - * \brief Manages texture loading and properties. - * - * The Texture class is responsible for loading an image from a source and providing access to - * its dimensions. Textures can be used for rendering. - */ -class Texture { - -public: -	/** -	 * \brief Constructs a Texture from an Asset resource. -	 * \param src Asset with texture data to load. -	 */ -	Texture(const Asset & src); - -	/** -	 * \brief Destroys the Texture instance, freeing associated resources. -	 */ -	~Texture(); -	// FIXME: this constructor shouldn't be necessary because this class doesn't manage memory - -	Texture(Texture && other) noexcept; -	Texture & operator=(Texture && other) noexcept; -	Texture(const Texture &) = delete; -	Texture & operator=(const Texture &) = delete; - -	/** -	 * \brief Gets the width and height of the texture. -	 * \return Width and height of the texture in pixels. -	 */ -	ivec2 get_size() const; - -private: -	/** -	 * \brief Loads the texture from an Asset resource. -	 * \param res Unique pointer to an Asset resource to load the texture from. -	 */ -	void load(const Asset & res); - -private: -	//! The texture of the class from the library -	std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> texture; - -	//! Grants SDLContext access to private members. -	friend class SDLContext; - -	//! Grants Animator access to private members. -	friend class Animator; -}; - -} // namespace crepe 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/Vector2.h b/src/crepe/api/Vector2.h index c278c87..bf9d124 100644 --- a/src/crepe/api/Vector2.h +++ b/src/crepe/api/Vector2.h @@ -66,6 +66,30 @@ struct Vector2 {  	//! Checks if this vector is not equal to another vector.  	bool operator!=(const Vector2<T> & other) const; + +	//! Truncates the vector to a maximum length. +	void truncate(T max); + +	//! Normalizes the vector (resulting in vector with a length of 1). +	void normalize(); + +	//! Returns the length of the vector. +	T length() const; + +	//! Returns the squared length of the vector. +	T length_squared() const; + +	//! Returns the dot product (inwendig product) of this vector and another vector. +	T dot(const Vector2<T> & other) const; + +	//! Returns the distance between this vector and another vector. +	T distance(const Vector2<T> & other) const; + +	//! Returns the squared distance between this vector and another vector. +	T distance_squared(const Vector2<T> & other) const; + +	//! Returns the perpendicular vector to this vector. +	Vector2 perpendicular() const;  };  } // namespace crepe diff --git a/src/crepe/api/Vector2.hpp b/src/crepe/api/Vector2.hpp index cad15f8..ff53cb0 100644 --- a/src/crepe/api/Vector2.hpp +++ b/src/crepe/api/Vector2.hpp @@ -1,5 +1,7 @@  #pragma once +#include <cmath> +  #include "Vector2.h"  namespace crepe { @@ -115,4 +117,50 @@ bool Vector2<T>::operator!=(const Vector2<T> & other) const {  	return !(*this == other);  } +template <class T> +void Vector2<T>::truncate(T max) { +	if (length() > max) { +		normalize(); +		*this *= max; +	} +} + +template <class T> +void Vector2<T>::normalize() { +	T len = length(); +	if (len > 0) { +		*this /= len; +	} +} + +template <class T> +T Vector2<T>::length() const { +	return std::sqrt(x * x + y * y); +} + +template <class T> +T Vector2<T>::length_squared() const { +	return x * x + y * y; +} + +template <class T> +T Vector2<T>::dot(const Vector2<T> & other) const { +	return x * other.x + y * other.y; +} + +template <class T> +T Vector2<T>::distance(const Vector2<T> & other) const { +	return (*this - other).length(); +} + +template <class T> +T Vector2<T>::distance_squared(const Vector2<T> & other) const { +	return (*this - other).length_squared(); +} + +template <class T> +Vector2<T> Vector2<T>::perpendicular() const { +	return {-y, x}; +} +  } // namespace crepe diff --git a/src/crepe/facade/CMakeLists.txt b/src/crepe/facade/CMakeLists.txt index 4cc53bc..0598e16 100644 --- a/src/crepe/facade/CMakeLists.txt +++ b/src/crepe/facade/CMakeLists.txt @@ -1,5 +1,6 @@  target_sources(crepe PUBLIC  	Sound.cpp +	Texture.cpp  	SoundContext.cpp  	SDLContext.cpp  	DB.cpp @@ -7,6 +8,7 @@ target_sources(crepe PUBLIC  target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	Sound.h +	Texture.h  	SoundContext.h  	SDLContext.h  	DB.h diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index ad9f1f0..20bb030 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -2,6 +2,7 @@  #include <SDL2/SDL_blendmode.h>  #include <SDL2/SDL_image.h>  #include <SDL2/SDL_keycode.h> +#include <SDL2/SDL_pixels.h>  #include <SDL2/SDL_rect.h>  #include <SDL2/SDL_render.h>  #include <SDL2/SDL_surface.h> @@ -11,32 +12,25 @@  #include <cstddef>  #include <cstdint>  #include <functional> -#include <iostream>  #include <memory>  #include <stdexcept>  #include "../api/Camera.h" +#include "../api/Color.h"  #include "../api/Config.h"  #include "../api/Sprite.h" -#include "../api/Texture.h" -#include "../manager/EventManager.h"  #include "../util/Log.h" +#include "manager/Mediator.h"  #include "SDLContext.h" -#include "api/Color.h" +#include "Texture.h"  #include "types.h"  using namespace crepe;  using namespace std; -SDLContext & SDLContext::get_instance() { -	static SDLContext instance; -	return instance; -} - -SDLContext::SDLContext() { +SDLContext::SDLContext(Mediator & mediator) {  	dbg_trace(); -  	if (SDL_Init(SDL_INIT_VIDEO) != 0) {  		throw runtime_error(format("SDLContext: SDL_Init error: {}", SDL_GetError()));  	} @@ -64,6 +58,8 @@ SDLContext::SDLContext() {  	if (!(IMG_Init(img_flags) & img_flags)) {  		throw runtime_error("SDLContext: SDL_image could not initialize!");  	} + +	mediator.sdl_context = *this;  }  SDLContext::~SDLContext() { @@ -215,54 +211,76 @@ MouseButton SDLContext::sdl_to_mousebutton(Uint8 sdl_button) {  	return MOUSE_BUTTON_LOOKUP_TABLE[sdl_button];  } -void SDLContext::clear_screen() { +void SDLContext::clear_screen() { SDL_RenderClear(this->game_renderer.get()); } +void SDLContext::present_screen() {  	SDL_SetRenderDrawColor(this->game_renderer.get(), 0, 0, 0, 255); -	SDL_RenderClear(this->game_renderer.get()); -} -void SDLContext::present_screen() { SDL_RenderPresent(this->game_renderer.get()); } - -SDL_Rect SDLContext::get_src_rect(const Sprite & sprite) const { -	return SDL_Rect{ -		.x = sprite.mask.x, -		.y = sprite.mask.y, -		.w = sprite.mask.w, -		.h = sprite.mask.h, -	}; +	SDL_RenderFillRectF(this->game_renderer.get(), &black_bars[0]); +	SDL_RenderFillRectF(this->game_renderer.get(), &black_bars[1]); +	SDL_RenderPresent(this->game_renderer.get());  } -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, +	float aspect_ratio +		= (ctx.sprite.aspect_ratio == 0) ? ctx.texture.get_ratio() : ctx.sprite.aspect_ratio; + +	vec2 size = data.size; +	if (data.size.x == 0 && data.size.y != 0) { +		size.x = data.size.y * aspect_ratio; +	} +	if (data.size.y == 0 && data.size.x != 0) { +		size.y = data.size.x / aspect_ratio; +	} +	size *= cam_aux_data.render_scale * ctx.img_scale * data.scale_offset; + +	vec2 screen_pos = (ctx.pos + data.position_offset - cam_aux_data.cam_pos +					   + (cam_aux_data.zoomed_viewport) / 2) +						  * cam_aux_data.render_scale +					  - size / 2 + cam_aux_data.bar_size; + +	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; +	SDL_Rect * srcrect_ptr = NULL; +	if (ctx.sprite.mask.w != 0 || ctx.sprite.mask.h != 0) { +		srcrect.w = ctx.sprite.mask.w; +		srcrect.h = ctx.sprite.mask.h; +		srcrect.x = ctx.sprite.mask.x; +		srcrect.y = ctx.sprite.mask.y; +		srcrect_ptr = &srcrect; +	} -	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); +	SDL_FRect dstrect = this->get_dst_rect(SDLContext::DestinationRectangleData{ +		.sprite = ctx.sprite, +		.texture = ctx.texture, +		.pos = ctx.pos, +		.img_scale = 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); +	double angle = ctx.angle + data.angle_offset; + +	this->set_color_texture(ctx.texture, ctx.sprite.data.color); +	SDL_RenderCopyExF(this->game_renderer.get(), ctx.texture.get_img(), srcrect_ptr, &dstrect, +					  angle, NULL, render_flip);  } -void SDLContext::set_camera(const Camera & cam) { +void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) { +	const Camera::Data & cam_data = cam.data;  	// resize window  	int w, h;  	SDL_GetWindowSize(this->game_window.get(), &w, &h); @@ -270,46 +288,53 @@ 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 = this->cam_aux_data.zoomed_viewport; +	vec2 & bar_size = this->cam_aux_data.bar_size; +	vec2 & render_scale = this->cam_aux_data.render_scale; +	this->cam_aux_data.cam_pos = new_pos; + +	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);  } -uint64_t SDLContext::get_ticks() const { return SDL_GetTicks64(); } -  std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>>  SDLContext::texture_from_path(const std::string & path) { @@ -336,16 +361,18 @@ SDLContext::texture_from_path(const std::string & path) {  ivec2 SDLContext::get_size(const Texture & ctx) {  	ivec2 size; -	SDL_QueryTexture(ctx.texture.get(), NULL, NULL, &size.x, &size.y); +	SDL_QueryTexture(ctx.get_img(), NULL, NULL, &size.x, &size.y);  	return size;  } -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; +	const CameraAuxiliaryData & cam = this->cam_aux_data;  	while (SDL_PollEvent(&event)) { +		ivec2 mouse_pos; +		mouse_pos.x = (event.button.x - cam.bar_size.x) / cam.render_scale.x; +		mouse_pos.y = (event.button.y - cam.bar_size.y) / cam.render_scale.y;  		switch (event.type) {  			case SDL_QUIT:  				event_list.push_back(EventData{ @@ -369,7 +396,7 @@ std::vector<SDLContext::EventData> SDLContext::get_events() {  				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}, +					.mouse_position = mouse_pos,  				});  				break;  			case SDL_MOUSEBUTTONUP: { @@ -378,21 +405,21 @@ std::vector<SDLContext::EventData> SDLContext::get_events() {  				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}, +					.mouse_position = mouse_pos,  				});  			} break;  			case SDL_MOUSEMOTION: {  				event_list.push_back(  					EventData{.event_type = SDLContext::EventType::MOUSEMOVE, -							  .mouse_position = {event.motion.x, event.motion.y}, +							  .mouse_position = mouse_pos,  							  .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}, +					.mouse_position = mouse_pos,  					// TODO: why is this needed?  					.scroll_direction = event.wheel.y < 0 ? -1 : 1,  					.scroll_delta = event.wheel.preciseY, @@ -403,6 +430,6 @@ std::vector<SDLContext::EventData> SDLContext::get_events() {  	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); +	SDL_SetTextureColorMod(texture.get_img(), color.r, color.g, color.b); +	SDL_SetTextureAlphaMod(texture.get_img(), color.a);  } diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index a2b34c1..bcadf87 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -9,34 +9,60 @@  #include <functional>  #include <memory>  #include <string> -#include <utility>  #include "api/Camera.h"  #include "api/Color.h" -#include "api/Event.h"  #include "api/KeyCodes.h"  #include "api/Sprite.h" -#include "api/Texture.h"  #include "api/Transform.h" +  #include "types.h"  namespace crepe { -class LoopManager; -class InputSystem; +class Texture; +class Mediator; +  /** - * \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 CameraAuxiliaryData { + +		//! 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 Texture & texture;  		const vec2 & pos;  		const double & angle;  		const double & scale; @@ -66,37 +92,44 @@ public:  		float scroll_delta = INFINITY;  		ivec2 rel_mouse_move = {-1, -1};  	}; -	/** -	 * \brief Gets the singleton instance of SDLContext. -	 * \return Reference to the SDLContext instance. -	 */ -	static SDLContext & get_instance(); +public:  	SDLContext(const SDLContext &) = delete;  	SDLContext(SDLContext &&) = delete;  	SDLContext & operator=(const SDLContext &) = delete;  	SDLContext & operator=(SDLContext &&) = delete; -private: -	//! will only use get_events -	friend class InputSystem; +public: +	/** +	 * \brief Constructs an SDLContext instance. +	 * Initializes SDL, creates a window and renderer. +	 */ +	SDLContext(Mediator & mediator); + +	/** +	 * \brief Destroys the SDLContext instance. +	 * Cleans up SDL resources, including the window and renderer. +	 */ +	~SDLContext(); + +public:  	/**  	 * \brief Retrieves a list of all events from the SDL context. -	 *  +	 *  	 * This method retrieves all the events from the SDL context that are currently  	 * available. It is primarily used by the InputSystem to process various  	 * input events such as mouse clicks, mouse movements, and keyboard presses. -	 *  +	 *  	 * \return Events that occurred since last call to `get_events()`  	 */  	std::vector<SDLContext::EventData> get_events();  	/**  	 * \brief Converts an SDL key code to the custom Keycode type. -	 *  +	 *  	 * This method maps an SDL key code to the corresponding `Keycode` enum value,  	 * which is used internally by the system to identify the keys. -	 *  +	 *  	 * \param sdl_key The SDL key code to convert.  	 * \return The corresponding `Keycode` value or `Keycode::NONE` if the key is unrecognized.  	 */ @@ -104,20 +137,16 @@ private:  	/**  	 * \brief Converts an SDL mouse button code to the custom MouseButton type. -	 *  -	 * This method maps an SDL mouse button code to the corresponding `MouseButton`  +	 * +	 * This method maps an SDL mouse button code to the corresponding `MouseButton`  	 * enum value, which is used internally by the system to identify mouse buttons. -	 *  +	 *  	 * \param sdl_button The SDL mouse button code to convert.  	 * \return The corresponding `MouseButton` value or `MouseButton::NONE` if the key is unrecognized  	 */  	MouseButton sdl_to_mousebutton(Uint8 sdl_button); -private: -	//! Will only use get_ticks -	friend class AnimatorSystem; -	//! Will only use delay -	friend class LoopTimer; +public:  	/**  	 * \brief Gets the current SDL ticks since the program started.  	 * \return Current ticks in milliseconds as a constant uint64_t. @@ -133,23 +162,7 @@ private:  	 */  	void delay(int ms) const; -private: -	/** -	 * \brief Constructs an SDLContext instance. -	 * Initializes SDL, creates a window and renderer. -	 */ -	SDLContext(); - -	/** -	 * \brief Destroys the SDLContext instance. -	 * Cleans up SDL resources, including the window and renderer. -	 */ -	~SDLContext(); - -private: -	//! Will use the funtions: texture_from_path, get_width,get_height. -	friend class Texture; - +public:  	/**  	 * \brief Loads a texture from a file path.  	 * \param path Path to the image file. @@ -160,17 +173,14 @@ private:  	/**  	 * \brief Gets the size of a texture.  	 * \param texture Reference to the Texture object. -	 * \return Width and height of the texture as an integer. +	 * \return Width and height of the texture as an integer in pixels.  	 */  	ivec2 get_size(const Texture & ctx); -private: -	//! Will use draw,clear_screen, present_screen, camera. -	friend class RenderSystem; - +public:  	/**  	 * \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); @@ -181,36 +191,35 @@ private:  	void present_screen();  	/** -	 * \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); - -private: -	/** -	 * \brief calculates the sqaure size of the image +	 * \brief calculates camera view settings. such as black_bars, zoomed_viewport, scaling and +	 * adjusting window size.  	 * -	 * \param sprite Reference to the sprite to calculate the rectangle -	 * \return sdl rectangle to draw a src image +	 * \note only supports windowed mode. +	 * \param camera Reference to the current Camera object in the scene. +	 * \param new_pos new camera position from transform and offset  	 */ -	SDL_Rect get_src_rect(const Sprite & sprite) const; +	void update_camera_view(const Camera & camera, const vec2 & new_pos); + +public: +	//! the data needed to construct a sdl dst rectangle +	struct DestinationRectangleData { +		const Sprite & sprite; +		const Texture & texture; +		const vec2 & pos; +		const double & img_scale; +	};  	/**  	 * \brief calculates the sqaure size of the image for destination  	 * -	 * \param sprite Reference to the sprite to calculate rectangle -	 * \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 data needed to calculate a destination rectangle  	 * \return sdl rectangle to draw a dst image to draw on the screen  	 */ -	SDL_Rect get_dst_rect(const Sprite & sprite, const vec2 & pos, const Camera & cam, -						  const vec2 & cam_pos, const double & img_scale) const; +	SDL_FRect get_dst_rect(const DestinationRectangleData & data) const;  	/**  	 * \brief Set an additional color value multiplied into render copy operations.  	 * -	 * \param  texture the given texture to adjust  +	 * \param  texture the given texture to adjust  	 * \param  color the color data for the texture  	 */  	void set_color_texture(const Texture & texture, const Color & color); @@ -221,6 +230,16 @@ 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] = {}; + +	/** +	 * \cam_aux_data extra data that the component cannot hold. +	 * +	 * - this is defined in this class because get_events() needs this information aswell +	 */ +	CameraAuxiliaryData cam_aux_data;  };  } // namespace crepe diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index 4d3abf5..97e455e 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -1,59 +1,13 @@ +#include "../api/Asset.h"  #include "../util/Log.h"  #include "Sound.h" -#include "SoundContext.h"  using namespace crepe;  using namespace std; -Sound::Sound(unique_ptr<Asset> res) { +Sound::Sound(const Asset & src, Mediator & mediator) : Resource(src, mediator) { +	this->sample.load(src.get_path().c_str());  	dbg_trace(); -	this->load(std::move(res)); -} - -Sound::Sound(const char * src) { -	dbg_trace(); -	this->load(make_unique<Asset>(src)); -} - -void Sound::load(unique_ptr<Asset> res) { this->sample.load(res->get_path().c_str()); } - -void Sound::play() { -	SoundContext & ctx = SoundContext::get_instance(); -	if (ctx.engine.getPause(this->handle)) { -		// resume if paused -		ctx.engine.setPause(this->handle, false); -	} else { -		// or start new sound -		this->handle = ctx.engine.play(this->sample, this->volume); -		ctx.engine.setLooping(this->handle, this->looping); -	} -} - -void Sound::pause() { -	SoundContext & ctx = SoundContext::get_instance(); -	if (ctx.engine.getPause(this->handle)) return; -	ctx.engine.setPause(this->handle, true); -} - -void Sound::rewind() { -	SoundContext & ctx = SoundContext::get_instance(); -	if (!ctx.engine.isValidVoiceHandle(this->handle)) return; -	ctx.engine.seek(this->handle, 0); -} - -void Sound::set_volume(float volume) { -	this->volume = volume; - -	SoundContext & ctx = SoundContext::get_instance(); -	if (!ctx.engine.isValidVoiceHandle(this->handle)) return; -	ctx.engine.setVolume(this->handle, this->volume); -} - -void Sound::set_looping(bool looping) { -	this->looping = looping; - -	SoundContext & ctx = SoundContext::get_instance(); -	if (!ctx.engine.isValidVoiceHandle(this->handle)) return; -	ctx.engine.setLooping(this->handle, this->looping);  } +Sound::~Sound() { dbg_trace(); } diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index 4c68f32..4a5d692 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -1,84 +1,31 @@  #pragma once -#include <memory>  #include <soloud/soloud.h>  #include <soloud/soloud_wav.h> -#include "../api/Asset.h" +#include "../Resource.h"  namespace crepe { +class SoundContext; +class Mediator; +  /**   * \brief Sound resource facade   * - * This class is a wrapper around a \c SoLoud::Wav instance, which holds a - * single sample. It is part of the sound facade. + * This class is a wrapper around a \c SoLoud::Wav instance, which holds a single sample. It is + * part of the sound facade.   */ -class Sound { -public: -	/** -	 * \brief Pause this sample -	 * -	 * Pauses this sound if it is playing, or does nothing if it is already paused. The playhead -	 * position is saved, such that calling \c play() after this function makes the sound resume. -	 */ -	void pause(); -	/** -	 * \brief Play this sample -	 * -	 * Resume playback if this sound is paused, or start from the beginning of the sample. -	 * -	 * \note This class only saves a reference to the most recent 'voice' of this sound. Calling -	 * \c play() while the sound is already playing causes multiple instances of the sample to -	 * play simultaniously. The sample started last is the one that is controlled afterwards. -	 */ -	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. -	 */ -	void rewind(); -	/** -	 * \brief Set playback volume / gain -	 * -	 * \param volume  Volume (0 = muted, 1 = full volume) -	 */ -	void set_volume(float volume); -	/** -	 * \brief Get playback volume / gain -	 * -	 * \return Volume -	 */ -	float get_volume() const { return this->volume; } -	/** -	 * \brief Set looping behavior for this sample -	 * -	 * \param looping  Looping behavior (false = one-shot, true = loop) -	 */ -	void set_looping(bool looping); -	/** -	 * \brief Get looping behavior -	 * -	 * \return true if looping, false if one-shot -	 */ -	bool get_looping() const { return this->looping; } - +class Sound : public Resource {  public: -	Sound(const char * src); -	Sound(std::unique_ptr<Asset> res); - -private: -	void load(std::unique_ptr<Asset> res); +	Sound(const Asset & src, Mediator & mediator); +	~Sound(); // dbg_trace  private: +	//! Deserialized resource (soloud)  	SoLoud::Wav sample; -	SoLoud::handle handle; - -	float volume = 1.0f; -	bool looping = false; +	//! SoundContext uses \c sample +	friend class SoundContext;  };  } // namespace crepe diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp index deb2b62..b1f8cb3 100644 --- a/src/crepe/facade/SoundContext.cpp +++ b/src/crepe/facade/SoundContext.cpp @@ -4,17 +4,33 @@  using namespace crepe; -SoundContext & SoundContext::get_instance() { -	static SoundContext instance; -	return instance; -} -  SoundContext::SoundContext() {  	dbg_trace(); -	engine.init(); +	this->engine.init(); +	this->engine.setMaxActiveVoiceCount(this->config.audio.voices);  }  SoundContext::~SoundContext() {  	dbg_trace(); -	engine.deinit(); +	this->engine.deinit(); +} + +SoundHandle SoundContext::play(Sound & resource) { +	SoLoud::handle real_handle = this->engine.play(resource.sample, 1.0f); +	SoundHandle handle = this->next_handle; +	this->registry[handle] = real_handle; +	this->next_handle++; +	return handle; +} + +void SoundContext::stop(const SoundHandle & handle) { +	this->engine.stop(this->registry[handle]); +} + +void SoundContext::set_volume(const SoundHandle & handle, float volume) { +	this->engine.setVolume(this->registry[handle], volume); +} + +void SoundContext::set_loop(const SoundHandle & handle, bool loop) { +	this->engine.setLooping(this->registry[handle], loop);  } diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h index d703c16..d986c59 100644 --- a/src/crepe/facade/SoundContext.h +++ b/src/crepe/facade/SoundContext.h @@ -2,30 +2,80 @@  #include <soloud/soloud.h> +#include "../api/Config.h" +  #include "Sound.h" +#include "SoundHandle.h"  namespace crepe {  /**   * \brief Sound engine facade   * - * This class is a wrapper around a \c SoLoud::Soloud instance, which provides - * the methods for playing \c Sound instances. It is part of the sound facade. + * This class is a wrapper around a \c SoLoud::Soloud instance, which provides the methods for + * playing \c Sound instances. It is part of the sound facade.   */  class SoundContext { -private: -	// singleton +public:  	SoundContext();  	virtual ~SoundContext(); +  	SoundContext(const SoundContext &) = delete;  	SoundContext(SoundContext &&) = delete;  	SoundContext & operator=(const SoundContext &) = delete;  	SoundContext & operator=(SoundContext &&) = delete; +	/** +	 * \brief Play a sample +	 * +	 * Plays a Sound from the beginning of the sample and returns a handle to control it later. +	 * +	 * \param resource Sound instance to play +	 * +	 * \returns Handle to control this voice +	 */ +	virtual SoundHandle play(Sound & resource); +	/** +	 * \brief Stop a voice immediately if it is still playing +	 * +	 * \note This function does nothing if the handle is invalid or if the sound is already +	 * stopped / finished playing. +	 * +	 * \param handle Voice handle returned by SoundContext::play +	 */ +	virtual void stop(const SoundHandle & handle); +	/** +	 * \brief Change the volume of a voice +	 * +	 * \note This function does nothing if the handle is invalid or if the sound is already +	 * stopped / finished playing. +	 * +	 * \param handle Voice handle returned by SoundContext::play +	 * \param volume New gain value (0=silent, 1=default) +	 */ +	virtual void set_volume(const SoundHandle & handle, float volume); +	/** +	 * \brief Set the looping behavior of a voice +	 * +	 * \note This function does nothing if the handle is invalid or if the sound is already +	 * stopped / finished playing. +	 * +	 * \param handle Voice handle returned by SoundContext::play +	 * \param loop Looping behavior (false=oneshot, true=loop) +	 */ +	virtual void set_loop(const SoundHandle & handle, bool loop); +  private: -	static SoundContext & get_instance(); +	//! Abstracted class  	SoLoud::Soloud engine; -	friend class Sound; + +	//! Config reference +	Config & config = Config::get_instance(); + +	//! Sound handle registry +	std::unordered_map<SoundHandle, SoLoud::handle> registry; +	//! Unique handle counter +	SoundHandle next_handle = 0;  };  } // namespace crepe diff --git a/src/crepe/facade/SoundHandle.h b/src/crepe/facade/SoundHandle.h new file mode 100644 index 0000000..b7925fc --- /dev/null +++ b/src/crepe/facade/SoundHandle.h @@ -0,0 +1,12 @@ +#pragma once + +#include <cstddef> + +namespace crepe { + +/** + * \brief Voice handle returned by + */ +typedef size_t SoundHandle; + +} // namespace crepe diff --git a/src/crepe/facade/Texture.cpp b/src/crepe/facade/Texture.cpp new file mode 100644 index 0000000..b63403d --- /dev/null +++ b/src/crepe/facade/Texture.cpp @@ -0,0 +1,28 @@ +#include "../util/Log.h" +#include "facade/SDLContext.h" +#include "manager/Mediator.h" + +#include "Resource.h" +#include "Texture.h" +#include "types.h" + +using namespace crepe; +using namespace std; + +Texture::Texture(const Asset & src, Mediator & mediator) : Resource(src, mediator) { +	dbg_trace(); +	SDLContext & ctx = mediator.sdl_context; +	this->texture = ctx.texture_from_path(src.get_path()); +	this->size = ctx.get_size(*this); +	this->aspect_ratio = static_cast<float>(this->size.x) / this->size.y; +} + +Texture::~Texture() { +	dbg_trace(); +	this->texture.reset(); +} + +const ivec2 & Texture::get_size() const noexcept { return this->size; } +const float & Texture::get_ratio() const noexcept { return this->aspect_ratio; } + +SDL_Texture * Texture::get_img() const noexcept { return this->texture.get(); } diff --git a/src/crepe/facade/Texture.h b/src/crepe/facade/Texture.h new file mode 100644 index 0000000..cdacac4 --- /dev/null +++ b/src/crepe/facade/Texture.h @@ -0,0 +1,69 @@ +#pragma once + +#include <SDL2/SDL_render.h> +#include <memory> + +#include "../Resource.h" + +#include "types.h" + +namespace crepe { + +class Mediator; +class Asset; + +/** + * \class Texture + * \brief Manages texture loading and properties. + * + * The Texture class is responsible for loading an image from a source and providing access to + * its dimensions. Textures can be used for rendering. + */ +class Texture : public Resource { + +public: +	/** +	 * \brief Constructs a Texture from an Asset resource. +	 * \param src Asset with texture data to load. +	 * \param mediator use the SDLContext reference to load the image +	 */ +	Texture(const Asset & src, Mediator & mediator); + +	/** +	 * \brief Destroys the Texture instance +	 */ +	~Texture(); + +	/** +	 * \brief get width and height of image in pixels +	 * \return pixel size width and height +	 * +	 */ +	const ivec2 & get_size() const noexcept; + +	/** +	 * \brief aspect_ratio of image +	 * \return ratio +	 * +	 */ +	const float & get_ratio() const noexcept; + +	/** +	 * \brief get the image texture +	 * \return SDL_Texture +	 * +	 */ +	SDL_Texture * get_img() const noexcept; + +private: +	//! The texture of the class from the library +	std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> texture; + +	// texture size in pixel +	ivec2 size; + +	//! ratio of image +	float aspect_ratio; +}; + +} // namespace crepe diff --git a/src/crepe/manager/CMakeLists.txt b/src/crepe/manager/CMakeLists.txt index 517b8a2..f73e165 100644 --- a/src/crepe/manager/CMakeLists.txt +++ b/src/crepe/manager/CMakeLists.txt @@ -4,6 +4,8 @@ target_sources(crepe PUBLIC  	Manager.cpp  	SaveManager.cpp  	SceneManager.cpp +	LoopTimerManager.cpp +	ResourceManager.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -16,5 +18,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	SaveManager.h  	SceneManager.h  	SceneManager.hpp +	LoopTimerManager.h +	ResourceManager.h +	ResourceManager.hpp  ) diff --git a/src/crepe/manager/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp index 80cf8b4..df30d27 100644 --- a/src/crepe/manager/ComponentManager.cpp +++ b/src/crepe/manager/ComponentManager.cpp @@ -1,4 +1,5 @@  #include "../api/GameObject.h" +#include "../api/Metadata.h"  #include "../types.h"  #include "../util/Log.h" @@ -61,3 +62,13 @@ GameObject ComponentManager::new_object(const string & name, const string & tag,  void ComponentManager::set_persistent(game_object_id_t id, bool persistent) {  	this->persistent[id] = persistent;  } + +set<game_object_id_t> ComponentManager::get_objects_by_name(const string & name) const { +	return this->get_objects_by_predicate<Metadata>( +		[name](const Metadata & data) { return data.name == name; }); +} + +set<game_object_id_t> ComponentManager::get_objects_by_tag(const string & tag) const { +	return this->get_objects_by_predicate<Metadata>( +		[tag](const Metadata & data) { return data.tag == tag; }); +} diff --git a/src/crepe/manager/ComponentManager.h b/src/crepe/manager/ComponentManager.h index ad37586..19a8e81 100644 --- a/src/crepe/manager/ComponentManager.h +++ b/src/crepe/manager/ComponentManager.h @@ -1,6 +1,7 @@  #pragma once  #include <memory> +#include <set>  #include <typeindex>  #include <unordered_map>  #include <vector> @@ -16,7 +17,7 @@ class GameObject;  /**   * \brief Manages all components - *  + *   * This class manages all components. It provides methods to add, delete and get components.   */  class ComponentManager : public Manager { @@ -56,10 +57,10 @@ protected:  	friend class GameObject;  	/**  	 * \brief Add a component to the ComponentManager -	 *  +	 *  	 * This method adds a component to the ComponentManager. The component is created with the  	 * given arguments and added to the ComponentManager. -	 *  +	 *  	 * \tparam T The type of the component  	 * \tparam Args The types of the arguments  	 * \param id The id of the GameObject this component belongs to @@ -70,9 +71,9 @@ protected:  	T & add_component(game_object_id_t id, Args &&... args);  	/**  	 * \brief Delete all components of a specific type and id -	 *  +	 *  	 * This method deletes all components of a specific type and id. -	 *  +	 *  	 * \tparam T The type of the component  	 * \param id The id of the GameObject this component belongs to  	 */ @@ -80,24 +81,24 @@ protected:  	void delete_components_by_id(game_object_id_t id);  	/**  	 * \brief Delete all components of a specific type -	 *  +	 *  	 * This method deletes all components of a specific type. -	 *  +	 *  	 * \tparam T The type of the component  	 */  	template <typename T>  	void delete_components();  	/**  	 * \brief Delete all components of a specific id -	 *  +	 *  	 * This method deletes all components of a specific id. -	 *  +	 *  	 * \param id The id of the GameObject this component belongs to  	 */  	void delete_all_components_of_id(game_object_id_t id);  	/**  	 * \brief Delete all components -	 *  +	 *  	 * This method deletes all components.  	 */  	void delete_all_components(); @@ -115,9 +116,9 @@ protected:  public:  	/**  	 * \brief Get all components of a specific type and id -	 *  +	 *  	 * This method gets all components of a specific type and id. -	 *  +	 *  	 * \tparam T The type of the component  	 * \param id The id of the GameObject this component belongs to  	 * \return A vector of all components of the specific type and id @@ -126,19 +127,88 @@ 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  	 */  	template <typename T>  	RefVector<T> get_components_by_type() const; +	/** +	 * \brief Get all components of a specific type on a GameObject with name \c name +	 *  +	 * \tparam T The type of the component +	 * \param name Metadata::name for the same game_object_id as the returned components +	 * \return Components matching criteria +	 */ +	template <typename T> +	RefVector<T> get_components_by_name(const std::string & name) const; +	/** +	 * \brief Get all components of a specific type on a GameObject with tag \c tag +	 *  +	 * \tparam T The type of the component +	 * \param name Metadata::tag for the same game_object_id as the returned components +	 * \return Components matching criteria +	 */ +	template <typename T> +	RefVector<T> get_components_by_tag(const std::string & tag) const;  private:  	/** +	 * \brief Get object IDs by predicate function +	 * +	 * This function calls the predicate function \c pred for all components matching type \c T, +	 * and adds their parent game_object_id to a \c std::set if the predicate returns true. +	 * +	 * \tparam T The type of the component to check the predicate against +	 * \param pred Predicate function +	 * +	 * \note The predicate function may be called for multiple components with the same \c +	 * game_object_id. In this case, the ID is added if *any* call returns \c true. +	 * +	 * \returns game_object_id for all components where the predicate returned true +	 */ +	template <typename T> +	std::set<game_object_id_t> +	get_objects_by_predicate(const std::function<bool(const T &)> & pred) const; + +	/** +	 * \brief Get components of type \c T for multiple game object IDs +	 * +	 * \tparam T The type of the components to return +	 * \param ids The object IDs +	 * +	 * \return All components matching type \c T and one of the IDs in \c ids +	 */ +	template <typename T> +	RefVector<T> get_components_by_ids(const std::set<game_object_id_t> & ids) const; + +	/** +	 * \brief Get object IDs for objects with name \c name +	 * +	 * \param name Object name to match +	 * \returns Object IDs where Metadata::name is equal to \c name +	 */ +	std::set<game_object_id_t> get_objects_by_name(const std::string & name) const; +	/** +	 * \brief Get object IDs for objects with tag \c tag +	 * +	 * \param tag Object tag to match +	 * \returns Object IDs where Metadata::tag is equal to \c tag +	 */ +	std::set<game_object_id_t> get_objects_by_tag(const std::string & tag) const; + +private: +	//! By Component \c std::type_index (readability helper type) +	template <typename T> +	using by_type = std::unordered_map<std::type_index, T>; +	//! By \c game_object_id index (readability helper type) +	template <typename T> +	using by_id_index = std::vector<T>; +	/**  	 * \brief The components -	 *  +	 *  	 * This unordered_map stores all components. The key is the type of the component and the  	 * value is a vector of vectors of unique pointers to the components.  	 * @@ -146,8 +216,7 @@ private:  	 * The first vector is for the ids of the GameObjects and the second vector is for the  	 * components (because a GameObject might have multiple components).  	 */ -	std::unordered_map<std::type_index, std::vector<std::vector<std::unique_ptr<Component>>>> -		components; +	by_type<by_id_index<std::vector<std::unique_ptr<Component>>>> components;  	//! Persistent flag for each GameObject  	std::unordered_map<game_object_id_t, bool> persistent; diff --git a/src/crepe/manager/ComponentManager.hpp b/src/crepe/manager/ComponentManager.hpp index ffb38ec..9e70865 100644 --- a/src/crepe/manager/ComponentManager.hpp +++ b/src/crepe/manager/ComponentManager.hpp @@ -95,32 +95,25 @@ template <typename T>  RefVector<T> ComponentManager::get_components_by_id(game_object_id_t id) const {  	using namespace std; -	// Determine the type of T (this is used as the key of the unordered_map<>) -	type_index type = typeid(T); - -	// Create an empty vector<> -	RefVector<T> component_vector; - -	if (this->components.find(type) == this->components.end()) return component_vector; - -	// Get the correct vector<> -	const vector<vector<unique_ptr<Component>>> & component_array = this->components.at(type); - -	// Make sure that the id (that we are looking for) is within the boundaries of the vector<> -	if (id >= component_array.size()) return component_vector; - -	// Loop trough the whole vector<> -	for (const unique_ptr<Component> & component_ptr : component_array[id]) { -		// Cast the unique_ptr to a raw pointer -		T * casted_component = static_cast<T *>(component_ptr.get()); - -		if (casted_component == nullptr) continue; +	static_assert(is_base_of<Component, T>::value, +				  "get_components_by_id must recieve a derivative class of Component"); -		// Add the dereferenced raw pointer to the vector<> -		component_vector.push_back(*casted_component); +	type_index type = typeid(T); +	if (!this->components.contains(type)) return {}; + +	const by_id_index<vector<unique_ptr<Component>>> & components_by_id +		= this->components.at(type); +	if (id >= components_by_id.size()) return {}; + +	RefVector<T> out = {}; +	const vector<unique_ptr<Component>> & components = components_by_id.at(id); +	for (auto & component_ptr : components) { +		if (component_ptr == nullptr) continue; +		Component & component = *component_ptr.get(); +		out.push_back(static_cast<T &>(component));  	} -	return component_vector; +	return out;  }  template <typename T> @@ -158,4 +151,46 @@ RefVector<T> ComponentManager::get_components_by_type() const {  	return component_vector;  } +template <typename T> +std::set<game_object_id_t> +ComponentManager::get_objects_by_predicate(const std::function<bool(const T &)> & pred) const { +	using namespace std; + +	set<game_object_id_t> objects = {}; +	RefVector<T> components = this->get_components_by_type<T>(); + +	for (const T & component : components) { +		game_object_id_t id = dynamic_cast<const Component &>(component).game_object_id; +		if (objects.contains(id)) continue; +		if (!pred(component)) continue; +		objects.insert(id); +	} + +	return objects; +} + +template <typename T> +RefVector<T> +ComponentManager::get_components_by_ids(const std::set<game_object_id_t> & ids) const { +	using namespace std; + +	RefVector<T> out = {}; +	for (game_object_id_t id : ids) { +		RefVector<T> components = get_components_by_id<T>(id); +		out.insert(out.end(), components.begin(), components.end()); +	} + +	return out; +} + +template <typename T> +RefVector<T> ComponentManager::get_components_by_name(const std::string & name) const { +	return this->get_components_by_ids<T>(this->get_objects_by_name(name)); +} + +template <typename T> +RefVector<T> ComponentManager::get_components_by_tag(const std::string & tag) const { +	return this->get_components_by_ids<T>(this->get_objects_by_tag(tag)); +} +  } // namespace crepe diff --git a/src/crepe/manager/EventManager.cpp b/src/crepe/manager/EventManager.cpp index 20f0dd3..6aa49ee 100644 --- a/src/crepe/manager/EventManager.cpp +++ b/src/crepe/manager/EventManager.cpp @@ -3,11 +3,9 @@  using namespace crepe;  using namespace std; -EventManager & EventManager::get_instance() { -	static EventManager instance; -	return instance; +EventManager::EventManager(Mediator & mediator) : Manager(mediator) { +	this->mediator.event_manager = *this;  } -  void EventManager::dispatch_events() {  	for (auto & event : this->events_queue) {  		this->handle_event(event.type, event.channel, *event.event.get()); diff --git a/src/crepe/manager/EventManager.h b/src/crepe/manager/EventManager.h index d634f54..639e37f 100644 --- a/src/crepe/manager/EventManager.h +++ b/src/crepe/manager/EventManager.h @@ -8,6 +8,8 @@  #include "../api/Event.h"  #include "../api/EventHandler.h" +#include "Manager.h" +  namespace crepe {  //! Event listener unique ID @@ -22,33 +24,25 @@ typedef size_t subscription_t;  typedef size_t event_channel_t;  /** - * \class EventManager   * \brief Manages event subscriptions, triggers, and queues, enabling decoupled event handling. - *  + *   * The `EventManager` acts as a centralized event system. It allows for registering callbacks   * for specific event types, triggering events synchronously, queueing events for later   * processing, and managing subscriptions via unique identifiers.   */ -class EventManager { +class EventManager : public Manager {  public:  	static constexpr const event_channel_t CHANNEL_ALL = -1; -  	/** -	 * \brief Get the singleton instance of the EventManager. -	 *  -	 * This method returns the unique instance of the EventManager, creating it if it -	 * doesn't already exist. Ensures only one instance is active in the program. -	 *  -	 * \return Reference to the singleton instance of the EventManager. +	 * \param mediator A reference to a Mediator object used for transfering managers.  	 */ -	static EventManager & get_instance(); - +	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). @@ -60,18 +54,18 @@ public:  	/**  	 * \brief Unsubscribe a previously registered callback. -	 *  +	 *  	 * Removes a callback from the subscription list based on its unique subscription ID. -	 *  +	 *  	 * \param event_id The unique subscription ID of the callback to remove.  	 */  	void unsubscribe(subscription_t event_id);  	/**  	 * \brief Trigger an event immediately. -	 *  +	 *  	 * Synchronously invokes all registered callbacks for the given event type on the specified channel. -	 *  +	 *  	 * \tparam EventType The type of the event to trigger.  	 * \param event The event instance to pass to the callbacks.  	 * \param channel The channel to trigger the event on (default is CHANNEL_ALL, which triggers on all channels). @@ -81,9 +75,9 @@ public:  	/**  	 * \brief Queue an event for later processing. -	 *  +	 *  	 * Adds an event to the event queue to be processed during the next call to `dispatch_events`. -	 *  +	 *  	 * \tparam EventType The type of the event to queue.  	 * \param event The event instance to queue.  	 * \param channel The channel to associate with the event (default is CHANNEL_ALL). @@ -93,7 +87,7 @@ public:  	/**  	 * \brief Process all queued events. -	 *  +	 *  	 * Iterates through the event queue and triggers callbacks for each queued event.  	 * Events are removed from the queue once processed.  	 */ @@ -101,20 +95,13 @@ public:  	/**  	 * \brief Clear all subscriptions. -	 *  +	 *  	 * Removes all registered event handlers and clears the subscription list.  	 */  	void clear();  private:  	/** -	 * \brief Default constructor for the EventManager. -	 *  -	 * Constructor is private to enforce the singleton pattern. -	 */ -	EventManager() = default; - -	/**  	 * \struct QueueEntry  	 * \brief Represents an entry in the event queue.  	 */ diff --git a/src/crepe/manager/LoopTimerManager.cpp b/src/crepe/manager/LoopTimerManager.cpp new file mode 100644 index 0000000..a6e4788 --- /dev/null +++ b/src/crepe/manager/LoopTimerManager.cpp @@ -0,0 +1,91 @@ +#include <chrono> +#include <thread> + +#include "../util/Log.h" + +#include "LoopTimerManager.h" + +using namespace crepe; +using namespace std::chrono; +using namespace std::chrono_literals; + +LoopTimerManager::LoopTimerManager(Mediator & mediator) : Manager(mediator) { +	this->mediator.loop_timer = *this; +	dbg_trace(); +} + +void LoopTimerManager::start() { +	this->last_frame_time = std::chrono::steady_clock::now(); + +	this->elapsed_time = elapsed_time_t{0}; +	this->elapsed_fixed_time = elapsed_time_t{0}; +	this->delta_time = duration_t{0}; +} + +void LoopTimerManager::update() { +	time_point_t current_frame_time = std::chrono::steady_clock::now(); +	// Convert to duration in seconds for delta time +	this->delta_time = current_frame_time - last_frame_time; + +	if (this->delta_time > this->maximum_delta_time) { +		this->delta_time = this->maximum_delta_time; +	} +	if (this->delta_time > 0s) { +		this->actual_fps = static_cast<unsigned>(1.0 / this->delta_time.count()); +	} else { +		this->actual_fps = 0; +	} +	this->elapsed_time += duration_cast<elapsed_time_t>(this->delta_time); +	this->last_frame_time = current_frame_time; +} + +duration_t LoopTimerManager::get_delta_time() const { +	return this->delta_time * this->time_scale; +} + +elapsed_time_t LoopTimerManager::get_elapsed_time() const { return this->elapsed_time; } + +void LoopTimerManager::advance_fixed_elapsed_time() { +	this->elapsed_fixed_time +		+= std::chrono::duration_cast<elapsed_time_t>(this->fixed_delta_time); +} + +void LoopTimerManager::set_target_framerate(unsigned fps) { +	this->target_fps = fps; +	//check if fps is lower or equals 0 +	if (fps <= 0) return; +	// target time per frame in seconds +	this->frame_target_time = duration_t(1s) / this->target_fps; +} + +unsigned LoopTimerManager::get_fps() const { return this->actual_fps; } + +void LoopTimerManager::set_time_scale(double value) { this->time_scale = value; } + +float LoopTimerManager::get_time_scale() const { return this->time_scale; } + +void LoopTimerManager::enforce_frame_rate() { +	time_point_t current_frame_time = std::chrono::steady_clock::now(); +	duration_t frame_duration = current_frame_time - this->last_frame_time; +	// Check if frame duration is less than the target frame time +	if (frame_duration < this->frame_target_time) { +		duration_t delay_time = this->frame_target_time - frame_duration; +		if (delay_time > 0s) { +			std::this_thread::sleep_for(delay_time); +		} +	} +} + +duration_t LoopTimerManager::get_lag() const { +	return this->elapsed_time - this->elapsed_fixed_time; +} + +duration_t LoopTimerManager::get_scaled_fixed_delta_time() const { +	return this->fixed_delta_time * this->time_scale; +} + +void LoopTimerManager::set_fixed_delta_time(float seconds) { +	this->fixed_delta_time = duration_t(seconds); +} + +duration_t LoopTimerManager::get_fixed_delta_time() const { return this->fixed_delta_time; } diff --git a/src/crepe/manager/LoopTimerManager.h b/src/crepe/manager/LoopTimerManager.h new file mode 100644 index 0000000..76b02d3 --- /dev/null +++ b/src/crepe/manager/LoopTimerManager.h @@ -0,0 +1,175 @@ +#pragma once + +#include <chrono> + +#include "Manager.h" + +namespace crepe { + +typedef std::chrono::duration<float> duration_t; +typedef std::chrono::duration<unsigned long long, std::micro> elapsed_time_t; + +/** + * \brief Manages timing and frame rate for the game loop. + *  + * The LoopTimerManager class is responsible for calculating and managing timing functions  + * such as delta time, frames per second (FPS), fixed time steps, and time scaling. It ensures  + * consistent frame updates and supports game loop operations, such as handling fixed updates  + * for physics and other time-sensitive operations. + */ +class LoopTimerManager : public Manager { +public: +	/** +	 * \param mediator A reference to a Mediator object used for transfering managers. +	 */ +	LoopTimerManager(Mediator & mediator); +	/** +	 * \brief Get the current delta time for the current frame. +	 *	 +	 * This value represents the estimated frame duration of the current frame. +	 * This value can be used in the frame_update to convert pixel based values to time based values. +	 *  +	 * \return Delta time in seconds since the last frame. +	 */ +	duration_t get_delta_time() const; + +	/** +	 * \brief Get the current elapsed time (total time passed ) +	 * +	 * \note The current game time may vary from real-world elapsed time. It is the cumulative +	 * sum of each frame's delta time. +	 * +	 * \return Elapsed game time in seconds. +	 */ +	elapsed_time_t get_elapsed_time() const; + +	/** +	 * \brief Set the target frames per second (FPS). +	 * +	 * \param fps The desired frames rendered per second. +	 */ +	void set_target_framerate(unsigned fps); + +	/** +	 * \brief Get the current frames per second (FPS). +	 * +	 * \return Current FPS. +	 */ +	unsigned int get_fps() const; + +	/** +	 * \brief Get the current time scale. +	 * +	 * \return The current time scale, where (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up). +	 * up the game. +	 */ +	float get_time_scale() const; + +	/** +	 * \brief Set the time scale. +	 * +	 * time_scale is a value that changes the delta time that can be retrieved using get_delta_time function.  +	 *  +	 * \param time_scale The desired time scale (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up). +	 */ +	void set_time_scale(double time_scale); + +	/** +	 * \brief Get the fixed delta time in seconds without scaling by the time scale. +	 * +	 * This value is used in the LoopManager to determine how many times  +	 * the fixed_update should be called within a given interval. +	 * +	 * \return The unscaled fixed delta time in seconds. +	 */ +	duration_t get_fixed_delta_time() const; + +	/** +	 * \brief Set the fixed_delta_time in seconds. +	 *  +	 * \param seconds fixed_delta_time in seconds. +	 *  +	 * The fixed_delta_time value is used to determine how many times per second the fixed_update and process_input functions are called. +	 *  +	 */ +	void set_fixed_delta_time(float seconds); + +	/** +	 * \brief Retrieves the scaled fixed delta time in seconds. +	 * +	 * The scaled fixed delta time is the timing value used within the `fixed_update` function.  +	 * It is adjusted by the time_scale to account for any changes in the simulation's  +	 * speed. +	 * +	 * \return The fixed delta time, scaled by the current time scale, in seconds. +	 */ +	duration_t get_scaled_fixed_delta_time() const; + +private: +	//! Friend relation to use start,enforce_frame_rate,get_lag,update,advance_fixed_update. +	friend class LoopManager; +	/** +	 * \brief Start the loop timer. +	 * +	 * Initializes the timer to begin tracking frame times. +	 */ +	void start(); +	/** +	 * \brief Enforce the frame rate limit. +	 * +	 * Ensures that the game loop does not exceed the target FPS by delaying frame updates as +	 * necessary. +	 */ +	void enforce_frame_rate(); +	/** +	 * \brief Get the accumulated lag in the game loop. +	 * +	 * Lag represents the difference between the target frame time and the actual frame time, +	 * useful for managing fixed update intervals. +	 * +	 * \return Accumulated lag in seconds. +	 */ +	duration_t get_lag() const; + +	/** +	 * \brief Update the timer to the current frame. +	 * +	 * Calculates and updates the delta time for the current frame and adds it to the cumulative +	 * game time. +	 */ +	void update(); + +	/** +	 * \brief Progress the elapsed fixed time by the fixed delta time interval. +	 * +	 * This method advances the game's fixed update loop by adding the fixed_delta_time  +	 * to elapsed_fixed_time, ensuring the fixed update catches up with the elapsed time. +	 */ +	void advance_fixed_elapsed_time(); + +private: +	//! Target frames per second. +	unsigned int target_fps = 60; +	//! Actual frames per second. +	unsigned int actual_fps = 0; +	//! Time scale for speeding up or slowing down the game (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up). +	float time_scale = 1; +	//! Maximum delta time in seconds to avoid large jumps. +	duration_t maximum_delta_time{0.25}; +	//! Delta time for the current frame in seconds. +	duration_t delta_time{0.0}; +	//! Target time per frame in seconds +	duration_t frame_target_time{1.0 / target_fps}; +	//! Fixed delta time for fixed updates in seconds. +	duration_t fixed_delta_time{1.0 / 50.0}; +	//! Total elapsed game time in microseconds. +	elapsed_time_t elapsed_time{0}; +	//! Total elapsed time for fixed updates in microseconds. +	elapsed_time_t elapsed_fixed_time{0}; + +	typedef std::chrono::steady_clock::time_point time_point_t; +	//! Time of the last frame. +	time_point_t last_frame_time; +}; + +} // namespace crepe diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h index 71bd1c9..a336410 100644 --- a/src/crepe/manager/Mediator.h +++ b/src/crepe/manager/Mediator.h @@ -2,14 +2,15 @@  #include "../util/OptionalRef.h" -// TODO: remove these singletons: -#include "EventManager.h" -#include "SaveManager.h" -  namespace crepe {  class ComponentManager;  class SceneManager; +class EventManager; +class LoopTimerManager; +class SaveManager; +class ResourceManager; +class SDLContext;  /**   * Struct to pass references to classes that would otherwise need to be singletons down to @@ -24,10 +25,13 @@ class SceneManager;   * \warning This class should never be directly accessible from the API   */  struct Mediator { +	OptionalRef<SDLContext> sdl_context;  	OptionalRef<ComponentManager> component_manager;  	OptionalRef<SceneManager> scene_manager; -	OptionalRef<SaveManager> save_manager = SaveManager::get_instance(); -	OptionalRef<EventManager> event_manager = EventManager::get_instance(); +	OptionalRef<EventManager> event_manager; +	OptionalRef<LoopTimerManager> loop_timer; +	OptionalRef<SaveManager> save_manager; +	OptionalRef<ResourceManager> resource_manager;  };  } // namespace crepe diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp new file mode 100644 index 0000000..a141a46 --- /dev/null +++ b/src/crepe/manager/ResourceManager.cpp @@ -0,0 +1,30 @@ +#include "util/Log.h" + +#include "ResourceManager.h" + +using namespace crepe; +using namespace std; + +ResourceManager::ResourceManager(Mediator & mediator) : Manager(mediator) { +	dbg_trace(); +	mediator.resource_manager = *this; +} +ResourceManager::~ResourceManager() { dbg_trace(); } + +void ResourceManager::clear() { +	std::erase_if(this->resources, [](const pair<const Asset, CacheEntry> & pair) { +		const CacheEntry & entry = pair.second; +		return entry.persistent == false; +	}); +} + +void ResourceManager::clear_all() { this->resources.clear(); } + +void ResourceManager::set_persistent(const Asset & asset, bool persistent) { +	this->get_entry(asset).persistent = persistent; +} + +ResourceManager::CacheEntry & ResourceManager::get_entry(const Asset & asset) { +	if (!this->resources.contains(asset)) this->resources[asset] = {}; +	return this->resources.at(asset); +} diff --git a/src/crepe/manager/ResourceManager.h b/src/crepe/manager/ResourceManager.h new file mode 100644 index 0000000..84b275d --- /dev/null +++ b/src/crepe/manager/ResourceManager.h @@ -0,0 +1,78 @@ +#pragma once + +#include <memory> +#include <unordered_map> + +#include "../Resource.h" +#include "../api/Asset.h" + +#include "Manager.h" + +namespace crepe { + +/** + * \brief Owner of concrete Resource instances + *  + * ResourceManager caches concrete Resource instances per Asset. Concrete resources are + * destroyed at the end of scenes by default, unless the game programmer marks them as + * persistent. + */ +class ResourceManager : public Manager { +public: +	ResourceManager(Mediator & mediator); +	virtual ~ResourceManager(); // dbg_trace + +private: +	//! Cache entry +	struct CacheEntry { +		//! Concrete resource instance +		std::unique_ptr<Resource> resource = nullptr; +		//! Prevent ResourceManager::clear from removing this entry +		bool persistent = false; +	}; +	//! Internal cache +	std::unordered_map<const Asset, CacheEntry> resources; +	/** +	 * \brief Ensure a cache entry exists for this asset and return a mutable reference to it +	 * +	 * \param asset Asset the concrete resource is instantiated from +	 * +	 * \returns Mutable reference to cache entry +	 */ +	CacheEntry & get_entry(const Asset & asset); + +public: +	/** +	 * \brief Mark a resource as persistent (i.e. used across multiple scenes) +	 * +	 * \param asset Asset the concrete resource is instantiated from +	 * \param persistent Whether this resource is persistent (true=keep, false=destroy) +	 */ +	void set_persistent(const Asset & asset, bool persistent); + +	/** +	 * \brief Retrieve reference to concrete Resource by Asset +	 * +	 * \param asset Asset the concrete resource is instantiated from +	 * \tparam Resource Concrete derivative of Resource +	 * +	 * This class instantiates the concrete resource if it is not yet stored in the internal +	 * cache, or returns a reference to the cached resource if it already exists. +	 * +	 * \returns Reference to concrete resource +	 * +	 * \throws std::runtime_error if the \c Resource parameter does not match with the actual +	 * type of the resource stored in the cache for this Asset +	 */ +	template <typename Resource> +	Resource & get(const Asset & asset); + +	//! Clear non-persistent resources from cache +	void clear(); +	//! Clear all resources from cache regardless of persistence +	void clear_all(); +}; + +} // namespace crepe + +#include "ResourceManager.hpp" diff --git a/src/crepe/manager/ResourceManager.hpp b/src/crepe/manager/ResourceManager.hpp new file mode 100644 index 0000000..cf5c949 --- /dev/null +++ b/src/crepe/manager/ResourceManager.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include <format> + +#include "ResourceManager.h" + +namespace crepe { + +template <typename T> +T & ResourceManager::get(const Asset & asset) { +	using namespace std; +	static_assert(is_base_of<Resource, T>::value, +				  "cache must recieve a derivative class of Resource"); + +	CacheEntry & entry = this->get_entry(asset); +	if (entry.resource == nullptr) entry.resource = make_unique<T>(asset, this->mediator); + +	T * concrete_resource = dynamic_cast<T *>(entry.resource.get()); +	if (concrete_resource == nullptr) +		throw runtime_error(format("ResourceManager: mismatch between requested type and " +								   "actual type of resource ({})", +								   asset.get_path())); + +	return *concrete_resource; +} + +} // namespace crepe diff --git a/src/crepe/manager/SaveManager.cpp b/src/crepe/manager/SaveManager.cpp index d4ed1c1..691ea2f 100644 --- a/src/crepe/manager/SaveManager.cpp +++ b/src/crepe/manager/SaveManager.cpp @@ -1,13 +1,25 @@  #include "../ValueBroker.h"  #include "../api/Config.h"  #include "../facade/DB.h" -#include "../util/Log.h"  #include "SaveManager.h"  using namespace std;  using namespace crepe; +SaveManager::SaveManager(Mediator & mediator) : Manager(mediator) { +	mediator.save_manager = *this; +} + +DB & SaveManager::get_db() { +	if (this->db == nullptr) { +		Config & cfg = Config::get_instance(); +		this->db +			= {new DB(cfg.savemgr.location), [](void * db) { delete static_cast<DB *>(db); }}; +	} +	return *static_cast<DB *>(this->db.get()); +} +  template <>  string SaveManager::serialize(const string & value) const noexcept {  	return value; @@ -90,22 +102,6 @@ int32_t SaveManager::deserialize(const string & value) const noexcept {  	return deserialize<int64_t>(value);  } -SaveManager::SaveManager() { dbg_trace(); } - -SaveManager & SaveManager::get_instance() { -	dbg_trace(); -	static SaveManager instance; -	return instance; -} - -DB & SaveManager::get_db() { -	Config & cfg = Config::get_instance(); -	// TODO: make this path relative to XDG_DATA_HOME on Linux and whatever the -	// default equivalent is on Windows using some third party library -	static DB db(cfg.savemgr.location); -	return db; -} -  bool SaveManager::has(const string & key) {  	DB & db = this->get_db();  	return db.has(key); @@ -155,7 +151,8 @@ ValueBroker<T> SaveManager::get(const string & key) {  	return {  		[this, key](const T & target) { this->set<T>(key, target); },  		[this, key, value]() mutable -> const T & { -			value = this->deserialize<T>(this->get_db().get(key)); +			DB & db = this->get_db(); +			value = this->deserialize<T>(db.get(key));  			return value;  		},  	}; diff --git a/src/crepe/manager/SaveManager.h b/src/crepe/manager/SaveManager.h index 3d8c852..61a978d 100644 --- a/src/crepe/manager/SaveManager.h +++ b/src/crepe/manager/SaveManager.h @@ -1,9 +1,12 @@  #pragma once +#include <functional>  #include <memory>  #include "../ValueBroker.h" +#include "Manager.h" +  namespace crepe {  class DB; @@ -18,7 +21,7 @@ class DB;   *   * The underlying database is a key-value store.   */ -class SaveManager { +class SaveManager : public Manager {  public:  	/**  	 * \brief Get a read/write reference to a value and initialize it if it does not yet exist @@ -63,8 +66,8 @@ public:  	 */  	bool has(const std::string & key); -private: -	SaveManager(); +public: +	SaveManager(Mediator & mediator);  	virtual ~SaveManager() = default;  private: @@ -89,26 +92,13 @@ private:  	template <typename T>  	T deserialize(const std::string & value) const noexcept; -public: -	// singleton -	static SaveManager & get_instance(); -	SaveManager(const SaveManager &) = delete; -	SaveManager(SaveManager &&) = delete; -	SaveManager & operator=(const SaveManager &) = delete; -	SaveManager & operator=(SaveManager &&) = delete; +protected: +	//! Create or return DB +	virtual DB & get_db();  private: -	/** -	 * \brief Create an instance of DB and return its reference -	 * -	 * \returns DB instance -	 * -	 * This function exists because DB is a facade class, which can't directly be used in the API -	 * without workarounds -	 * -	 * TODO: better solution -	 */ -	static DB & get_db(); +	//! Database +	std::unique_ptr<void, std::function<void(void *)>> db = nullptr;  };  } // namespace crepe diff --git a/src/crepe/manager/SceneManager.cpp b/src/crepe/manager/SceneManager.cpp index 50a9fbb..d4ca90b 100644 --- a/src/crepe/manager/SceneManager.cpp +++ b/src/crepe/manager/SceneManager.cpp @@ -32,4 +32,7 @@ void SceneManager::load_next_scene() {  	// Load the new scene  	scene->load_scene(); + +	//clear the next scene +	next_scene.clear();  } diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp new file mode 100644 index 0000000..680dbb8 --- /dev/null +++ b/src/crepe/system/AISystem.cpp @@ -0,0 +1,186 @@ +#include <algorithm> +#include <cmath> + +#include "manager/ComponentManager.h" +#include "manager/LoopTimerManager.h" +#include "manager/Mediator.h" + +#include "AISystem.h" + +using namespace crepe; +using namespace std::chrono; + +void AISystem::update() { +	const Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; +	LoopTimerManager & loop_timer = mediator.loop_timer; +	RefVector<AI> ai_components = mgr.get_components_by_type<AI>(); + +	float dt = loop_timer.get_scaled_fixed_delta_time().count(); + +	// Loop through all AI components +	for (AI & ai : ai_components) { +		if (!ai.active) { +			continue; +		} + +		RefVector<Rigidbody> rigidbodies +			= mgr.get_components_by_id<Rigidbody>(ai.game_object_id); +		if (rigidbodies.empty()) { +			throw std::runtime_error( +				"AI component must be attached to a GameObject with a Rigidbody component"); +		} +		Rigidbody & rigidbody = rigidbodies.front().get(); +		if (!rigidbody.active) { +			continue; +		} +		if (rigidbody.data.mass <= 0) { +			throw std::runtime_error("Mass must be greater than 0"); +		} + +		// Calculate the force to apply to the entity +		vec2 force = this->calculate(ai, rigidbody); +		// Calculate the acceleration (using the above calculated force) +		vec2 acceleration = force / rigidbody.data.mass; +		// Finally, update Rigidbody's velocity +		rigidbody.data.linear_velocity += acceleration * dt; +	} +} + +vec2 AISystem::calculate(AI & ai, const Rigidbody & rigidbody) { +	const Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; +	RefVector<Transform> transforms = mgr.get_components_by_id<Transform>(ai.game_object_id); +	Transform & transform = transforms.front().get(); + +	vec2 force; + +	// Run all the behaviors that are on, and stop if the force gets too high +	if (ai.on(AI::BehaviorTypeMask::FLEE)) { +		vec2 force_to_add = this->flee(ai, rigidbody, transform); + +		if (!this->accumulate_force(ai, force, force_to_add)) { +			return force; +		} +	} +	if (ai.on(AI::BehaviorTypeMask::ARRIVE)) { +		vec2 force_to_add = this->arrive(ai, rigidbody, transform); + +		if (!this->accumulate_force(ai, force, force_to_add)) { +			return force; +		} +	} +	if (ai.on(AI::BehaviorTypeMask::SEEK)) { +		vec2 force_to_add = this->seek(ai, rigidbody, transform); + +		if (!this->accumulate_force(ai, force, force_to_add)) { +			return force; +		} +	} +	if (ai.on(AI::BehaviorTypeMask::PATH_FOLLOW)) { +		vec2 force_to_add = this->path_follow(ai, rigidbody, transform); + +		if (!this->accumulate_force(ai, force, force_to_add)) { +			return force; +		} +	} + +	return force; +} + +bool AISystem::accumulate_force(const AI & ai, vec2 & running_total, vec2 & force_to_add) { +	float magnitude = running_total.length(); +	float magnitude_remaining = ai.max_force - magnitude; + +	if (magnitude_remaining <= 0.0f) { +		// If the force is already at/above the max force, return false +		return false; +	} + +	float magnitude_to_add = force_to_add.length(); +	if (magnitude_to_add < magnitude_remaining) { +		// If the force to add is less than the remaining force, add it +		running_total += force_to_add; +	} else { +		// If the force to add is greater than the remaining force, add a fraction of it +		force_to_add.normalize(); +		running_total += force_to_add * magnitude_remaining; +	} + +	return true; +} + +vec2 AISystem::seek(const AI & ai, const Rigidbody & rigidbody, +					const Transform & transform) const { +	// Calculate the desired velocity +	vec2 desired_velocity = ai.seek_target - transform.position; +	desired_velocity.normalize(); +	desired_velocity *= rigidbody.data.max_linear_velocity; + +	return desired_velocity - rigidbody.data.linear_velocity; +} + +vec2 AISystem::flee(const AI & ai, const Rigidbody & rigidbody, +					const Transform & transform) const { +	// Calculate the desired velocity if the entity is within the panic distance +	vec2 desired_velocity = transform.position - ai.flee_target; +	if (desired_velocity.length_squared() > ai.square_flee_panic_distance) { +		return vec2{0, 0}; +	} +	desired_velocity.normalize(); +	desired_velocity *= rigidbody.data.max_linear_velocity; + +	return desired_velocity - rigidbody.data.linear_velocity; +} + +vec2 AISystem::arrive(const AI & ai, const Rigidbody & rigidbody, +					  const Transform & transform) const { +	// Calculate the desired velocity (taking into account the deceleration rate) +	vec2 to_target = ai.arrive_target - transform.position; +	float distance = to_target.length(); +	if (distance > 0.0f) { +		if (ai.arrive_deceleration <= 0.0f) { +			throw std::runtime_error("Deceleration rate must be greater than 0"); +		} + +		float speed = distance / ai.arrive_deceleration; +		speed = std::min(speed, rigidbody.data.max_linear_velocity); +		vec2 desired_velocity = to_target * (speed / distance); + +		return desired_velocity - rigidbody.data.linear_velocity; +	} + +	return vec2{0, 0}; +} + +vec2 AISystem::path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform) { +	if (ai.path.empty()) { +		return vec2{0, 0}; +	} + +	// Get the target node +	vec2 target = ai.path.at(ai.path_index); +	// Calculate the force to apply to the entity +	vec2 to_target = target - transform.position; +	if (to_target.length_squared() > ai.path_node_distance * ai.path_node_distance) { +		// If the entity is not close enough to the target node, seek it +		ai.seek_target = target; +		ai.arrive_target = target; +	} else { +		// If the entity is close enough to the target node, move to the next node +		ai.path_index++; +		if (ai.path_index >= ai.path.size()) { +			if (ai.path_loop) { +				// If the path is looping, reset the path index +				ai.path_index = 0; +			} else { +				// If the path is not looping, arrive at the last node +				ai.path_index = ai.path.size() - 1; +				return this->arrive(ai, rigidbody, transform); +			} +		} +	} + +	// Seek the target node +	return this->seek(ai, rigidbody, transform); +} diff --git a/src/crepe/system/AISystem.h b/src/crepe/system/AISystem.h new file mode 100644 index 0000000..d5f8a8e --- /dev/null +++ b/src/crepe/system/AISystem.h @@ -0,0 +1,81 @@ +#pragma once + +#include "api/AI.h" +#include "api/Rigidbody.h" + +#include "System.h" +#include "api/Transform.h" +#include "types.h" + +namespace crepe { + +/** + * \brief The AISystem is used to control the movement of entities using AI. + * + * The AISystem is used to control the movement of entities using AI. The AISystem can be used to + * implement different behaviors such as seeking, fleeing, arriving, and path following. + */ +class AISystem : public System { +public: +	using System::System; + +	//! Update the AI system +	void update() override; + +private: +	/** +	 * \brief Calculate the total force to apply to the entity +	 * +	 * \param ai The AI component +	 * \param rigidbody The Rigidbody component +	 */ +	vec2 calculate(AI & ai, const Rigidbody & rigidbody); +	/** +	 * \brief Accumulate the force to apply to the entity +	 * +	 * \param ai The AI component +	 * \param running_total The running total of the force +	 * \param force_to_add The force to add +	 * \return true if the force was added, false otherwise +	 */ +	bool accumulate_force(const AI & ai, vec2 & running_total, vec2 & force_to_add); + +	/** +	 * \brief Calculate the seek force +	 * +	 * \param ai The AI component +	 * \param rigidbody The Rigidbody component +	 * \param transform The Transform component +	 * \return The seek force +	 */ +	vec2 seek(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; +	/** +	 * \brief Calculate the flee force +	 * +	 * \param ai The AI component +	 * \param rigidbody The Rigidbody component +	 * \param transform The Transform component +	 * \return The flee force +	 */ +	vec2 flee(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; +	/** +	 * \brief Calculate the arrive force +	 * +	 * \param ai The AI component +	 * \param rigidbody The Rigidbody component +	 * \param transform The Transform component +	 * \return The arrive force +	 */ +	vec2 arrive(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; +	/** +	 * \brief Calculate the path follow force +	 * +	 * \param ai The AI component +	 * \param rigidbody The Rigidbody component +	 * \param transform The Transform component +	 * \return The path follow force +	 */ +	vec2 path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform); +}; + +} // namespace crepe diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index 8bb6465..107b25d 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -1,24 +1,42 @@ -#include <cstdint> +  #include "../api/Animator.h" -#include "../facade/SDLContext.h"  #include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h" +#include <chrono>  #include "AnimatorSystem.h"  using namespace crepe; +using namespace std::chrono;  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(); +	float elapsed_time = duration_cast<duration<float>>(timer.get_elapsed_time()).count(); +  	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; +		if (a.data.fps == 0) continue; + +		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.grid_size.x : 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/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp new file mode 100644 index 0000000..b1aa0f8 --- /dev/null +++ b/src/crepe/system/AudioSystem.cpp @@ -0,0 +1,62 @@ +#include "AudioSystem.h" + +#include "../manager/ComponentManager.h" +#include "../manager/ResourceManager.h" +#include "../types.h" + +using namespace crepe; +using namespace std; + +void AudioSystem::update() { +	ComponentManager & component_manager = this->mediator.component_manager; +	ResourceManager & resource_manager = this->mediator.resource_manager; +	RefVector<AudioSource> components +		= component_manager.get_components_by_type<AudioSource>(); + +	for (AudioSource & component : components) { +		Sound & resource = resource_manager.get<Sound>(component.source); + +		this->diff_update(component, resource); + +		this->update_last(component); +	} +} + +void AudioSystem::diff_update(AudioSource & component, Sound & resource) { +	SoundContext & context = this->get_context(); + +	if (component.active != component.last_active) { +		if (!component.active) { +			context.stop(component.voice); +			return; +		} +		if (component.play_on_awake) component.oneshot_play = true; +	} +	if (!component.active) return; + +	if (component.oneshot_play) { +		component.voice = context.play(resource); +		component.oneshot_play = false; +	} +	if (component.oneshot_stop) { +		context.stop(component.voice); +		component.oneshot_stop = false; +	} +	if (component.volume != component.last_volume) { +		context.set_volume(component.voice, component.volume); +	} +	if (component.loop != component.last_loop) { +		context.set_loop(component.voice, component.loop); +	} +} + +void AudioSystem::update_last(AudioSource & component) { +	component.last_active = component.active; +	component.last_loop = component.loop; +	component.last_volume = component.volume; +} + +SoundContext & AudioSystem::get_context() { +	if (this->context == nullptr) this->context = make_unique<SoundContext>(); +	return *this->context.get(); +} diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h new file mode 100644 index 0000000..2ddc443 --- /dev/null +++ b/src/crepe/system/AudioSystem.h @@ -0,0 +1,51 @@ +#pragma once + +#include "../api/AudioSource.h" +#include "../facade/Sound.h" +#include "../facade/SoundContext.h" + +#include "System.h" + +namespace crepe { + +class AudioSystem : public System { +public: +	using System::System; +	void update() override; + +private: +	/** +	 * \brief Update `last_*` members of \c component +	 * +	 * Copies all component properties stored for comparison between AudioSystem::update() calls +	 * +	 * \param component AudioSource component to update +	 */ +	void update_last(AudioSource & component); + +	/** +	 * \brief Compare update component +	 * +	 * Compares properties of \c component and \c data, and calls SoundContext functions where +	 * applicable. +	 * +	 * \param component AudioSource component to update +	 * \param resource Sound instance for AudioSource's Asset +	 */ +	void diff_update(AudioSource & component, Sound & resource); + +protected: +	/** +	 * \brief Get SoundContext +	 * +	 * SoundContext is retrieved through this function instead of being a direct member of +	 * AudioSystem to aid with testability. +	 */ +	virtual SoundContext & get_context(); + +private: +	//! SoundContext +	std::unique_ptr<SoundContext> context = nullptr; +}; + +} // namespace crepe diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index 95f6e33..0e2db76 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -5,8 +5,10 @@ target_sources(crepe PUBLIC  	PhysicsSystem.cpp  	CollisionSystem.cpp  	RenderSystem.cpp +	AudioSystem.cpp  	AnimatorSystem.cpp  	InputSystem.cpp +	AISystem.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -15,6 +17,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	PhysicsSystem.h  	CollisionSystem.h  	RenderSystem.h +	AudioSystem.h  	AnimatorSystem.h  	InputSystem.h +	AISystem.h  ) diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h index 7e893c8..5b136c6 100644 --- a/src/crepe/system/CollisionSystem.h +++ b/src/crepe/system/CollisionSystem.h @@ -37,7 +37,7 @@ private:  	/**  		* \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, @@ -65,7 +65,7 @@ private:  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 { @@ -90,9 +90,9 @@ public:  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. @@ -102,9 +102,9 @@ private:  	/**  		* \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. @@ -116,9 +116,9 @@ private:  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.  		*/ @@ -126,9 +126,9 @@ private:  	/**  		* \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. @@ -140,9 +140,9 @@ private:  	/**  		* \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. @@ -155,13 +155,13 @@ private:  	/**  		* \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 position1 The position of the first CircleCollider. -		* \param position2 The position of 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, @@ -171,14 +171,13 @@ private:  	/**  		* \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. -		* \param inverse Inverted true if box circle collision, false if circle box collision (inverts the direction).  		* \return The resolution vector for the collision.  		*/  	vec2 get_circle_box_resolution(const CircleCollider & circle_collider, @@ -188,18 +187,18 @@ private:  	/**  		* \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); @@ -207,9 +206,9 @@ private:  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.  		*/ @@ -218,13 +217,13 @@ private:  	/**  	 * \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  +	 * 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. @@ -234,9 +233,9 @@ private:  	/**  		* \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. @@ -248,7 +247,7 @@ private:  	/**  		* \brief Detects collisions between two BoxColliders. -		*  +		*  		* \param box1 The first BoxCollider.  		* \param box2 The second BoxCollider.  		* \param transform1 Transform of the first object. diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp index 7cc8d30..a710ae2 100644 --- a/src/crepe/system/InputSystem.cpp +++ b/src/crepe/system/InputSystem.cpp @@ -1,6 +1,8 @@  #include "../api/Button.h"  #include "../manager/ComponentManager.h"  #include "../manager/EventManager.h" +#include "facade/SDLContext.h" +#include "util/Log.h"  #include "InputSystem.h" @@ -9,7 +11,8 @@ 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(); +	SDLContext & context = this->mediator.sdl_context; +	std::vector<SDLContext::EventData> event_list = context.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; @@ -24,10 +27,10 @@ void InputSystem::update() {  	RefVector<Transform> transform_vec  		= mgr.get_components_by_id<Transform>(current_cam.game_object_id);  	Transform & cam_transform = transform_vec.front().get(); -	int camera_origin_x -		= cam_transform.position.x + current_cam.offset.x - (current_cam.viewport_size.x / 2); -	int camera_origin_y -		= cam_transform.position.y + current_cam.offset.y - (current_cam.viewport_size.y / 2); +	int camera_origin_x = cam_transform.position.x + current_cam.data.postion_offset.x +						  - (current_cam.viewport_size.x / 2); +	int camera_origin_y = cam_transform.position.y + current_cam.data.postion_offset.y +						  - (current_cam.viewport_size.y / 2);  	for (const SDLContext::EventData & event : event_list) {  		int world_mouse_x = event.mouse_position.x + camera_origin_x; diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index c647284..068f01c 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -25,7 +25,7 @@ public:  private:  	/**  	 * \brief Emits a particle from the specified emitter based on its emission properties. -	 *  +	 *  	 * \param emitter Reference to the ParticleEmitter.  	 * \param transform Const reference to the Transform component associated with the emitter.  	 */ @@ -34,7 +34,7 @@ private:  	/**  	 * \brief Calculates the number of times particles should be emitted based on emission rate  	 * and update count. -	 *  +	 *  	 * \param count Current update count.  	 * \param emission Emission rate.  	 * \return The number of particles to emit. @@ -44,7 +44,7 @@ private:  	/**  	 * \brief Checks whether particles are within the emitter’s boundary, resets or stops  	 * particles if they exit. -	 *  +	 *  	 * \param emitter Reference to the ParticleEmitter.  	 * \param transform Const reference to the Transform component associated with the emitter.  	 */ @@ -52,7 +52,7 @@ private:  	/**  	 * \brief Generates a random angle for particle emission within the specified range. -	 *  +	 *  	 * \param min_angle Minimum emission angle in degrees.  	 * \param max_angle Maximum emission angle in degrees.  	 * \return Random angle in degrees. @@ -61,7 +61,7 @@ private:  	/**  	 * \brief Generates a random speed for particle emission within the specified range. -	 *  +	 *  	 * \param min_speed Minimum emission speed.  	 * \param max_speed Maximum emission speed.  	 * \return Random speed. diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index ebf4439..3b3b8ab 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -5,88 +5,95 @@  #include "../api/Transform.h"  #include "../api/Vector2.h"  #include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/Mediator.h"  #include "PhysicsSystem.h"  using namespace crepe;  void PhysicsSystem::update() { -	ComponentManager & mgr = this->mediator.component_manager; + +	const Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; +	LoopTimerManager & loop_timer = mediator.loop_timer;  	RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>(); -	RefVector<Transform> transforms = mgr.get_components_by_type<Transform>(); +	float dt = loop_timer.get_scaled_fixed_delta_time().count(); -	double gravity = Config::get_instance().physics.gravity; +	float gravity = Config::get_instance().physics.gravity;  	for (Rigidbody & rigidbody : rigidbodies) {  		if (!rigidbody.active) continue; +		Transform & transform +			= mgr.get_components_by_id<Transform>(rigidbody.game_object_id).front().get();  		switch (rigidbody.data.body_type) {  			case Rigidbody::BodyType::DYNAMIC: -				for (Transform & transform : transforms) { -					if (transform.game_object_id == rigidbody.game_object_id) { +				if (transform.game_object_id == rigidbody.game_object_id) { +					// Add gravity -						// Add 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_velocity_coefficient > 0) { -							rigidbody.data.angular_velocity -								*= rigidbody.data.angular_velocity_coefficient; -						} -						if (rigidbody.data.linear_velocity_coefficient.x > 0 -							&& rigidbody.data.linear_velocity_coefficient.y > 0) { -							rigidbody.data.linear_velocity -								*= rigidbody.data.linear_velocity_coefficient; -						} +					if (rigidbody.data.mass <= 0) { +						throw std::runtime_error("Mass must be greater than 0"); +					} -						// Max velocity check -						if (rigidbody.data.angular_velocity -							> rigidbody.data.max_angular_velocity) { -							rigidbody.data.angular_velocity -								= rigidbody.data.max_angular_velocity; -						} else if (rigidbody.data.angular_velocity -								   < -rigidbody.data.max_angular_velocity) { -							rigidbody.data.angular_velocity -								= -rigidbody.data.max_angular_velocity; -						} +					if (gravity <= 0) { +						throw std::runtime_error("Config Gravity must be greater than 0"); +					} -						if (rigidbody.data.linear_velocity.x -							> rigidbody.data.max_linear_velocity.x) { -							rigidbody.data.linear_velocity.x -								= rigidbody.data.max_linear_velocity.x; -						} else if (rigidbody.data.linear_velocity.x -								   < -rigidbody.data.max_linear_velocity.x) { -							rigidbody.data.linear_velocity.x -								= -rigidbody.data.max_linear_velocity.x; -						} +					if (rigidbody.data.gravity_scale > 0 && !rigidbody.data.constraints.y) { +						rigidbody.data.linear_velocity.y +							+= (rigidbody.data.mass * rigidbody.data.gravity_scale * gravity +								* dt); +					} +					// Add coefficient rotation +					if (rigidbody.data.angular_velocity_coefficient > 0) { +						rigidbody.data.angular_velocity +							*= std::pow(rigidbody.data.angular_velocity_coefficient, dt); +					} -						if (rigidbody.data.linear_velocity.y -							> rigidbody.data.max_linear_velocity.y) { -							rigidbody.data.linear_velocity.y -								= rigidbody.data.max_linear_velocity.y; -						} else if (rigidbody.data.linear_velocity.y -								   < -rigidbody.data.max_linear_velocity.y) { -							rigidbody.data.linear_velocity.y -								= -rigidbody.data.max_linear_velocity.y; -						} +					// Add coefficient movement horizontal +					if (rigidbody.data.linear_velocity_coefficient.x > 0 +						&& !rigidbody.data.constraints.x) { +						rigidbody.data.linear_velocity.x +							*= std::pow(rigidbody.data.linear_velocity_coefficient.x, dt); +					} -						// Move object -						if (!rigidbody.data.constraints.rotation) { -							transform.rotation += rigidbody.data.angular_velocity; -							transform.rotation = std::fmod(transform.rotation, 360.0); -							if (transform.rotation < 0) { -								transform.rotation += 360.0; -							} -						} -						if (!rigidbody.data.constraints.x) { -							transform.position.x += rigidbody.data.linear_velocity.x; -						} -						if (!rigidbody.data.constraints.y) { -							transform.position.y += rigidbody.data.linear_velocity.y; +					// Add coefficient movement horizontal +					if (rigidbody.data.linear_velocity_coefficient.y > 0 +						&& !rigidbody.data.constraints.y) { +						rigidbody.data.linear_velocity.y +							*= std::pow(rigidbody.data.linear_velocity_coefficient.y, dt); +					} + +					// Max velocity check +					if (rigidbody.data.angular_velocity +						> rigidbody.data.max_angular_velocity) { +						rigidbody.data.angular_velocity = rigidbody.data.max_angular_velocity; +					} else if (rigidbody.data.angular_velocity +							   < -rigidbody.data.max_angular_velocity) { +						rigidbody.data.angular_velocity = -rigidbody.data.max_angular_velocity; +					} + +					// Set max velocity to maximum length +					if (rigidbody.data.linear_velocity.length() +						> rigidbody.data.max_linear_velocity) { +						rigidbody.data.linear_velocity.normalize(); +						rigidbody.data.linear_velocity *= rigidbody.data.max_linear_velocity; +					} + +					// Move object +					if (!rigidbody.data.constraints.rotation) { +						transform.rotation += rigidbody.data.angular_velocity * dt; +						transform.rotation = std::fmod(transform.rotation, 360.0); +						if (transform.rotation < 0) { +							transform.rotation += 360.0;  						}  					} +					if (!rigidbody.data.constraints.x) { +						transform.position.x += rigidbody.data.linear_velocity.x * dt; +					} +					if (!rigidbody.data.constraints.y) { +						transform.position.y += rigidbody.data.linear_velocity.y * dt; +					}  				}  				break;  			case Rigidbody::BodyType::KINEMATIC: 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..afd9548 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -10,20 +10,29 @@  #include "../api/Sprite.h"  #include "../api/Transform.h"  #include "../facade/SDLContext.h" +#include "../facade/Texture.h"  #include "../manager/ComponentManager.h" +#include "../manager/ResourceManager.h"  #include "RenderSystem.h" +#include "types.h"  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() { +void 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 +41,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; +		vec2 new_camera_pos = transform.position + cam.data.postion_offset; +		ctx.update_camera_view(cam, new_camera_pos); +		return;  	}  	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 +70,12 @@ void RenderSystem::update() {  	this->present_screen();  } -bool RenderSystem::render_particle(const Sprite & sprite, const Camera & cam, -								   const double & scale) { +bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) {  	ComponentManager & mgr = this->mediator.component_manager; +	SDLContext & ctx = this->mediator.sdl_context; +	ResourceManager & resource_manager = this->mediator.resource_manager; +	Texture & res = resource_manager.get<Texture>(sprite.source);  	vector<reference_wrapper<ParticleEmitter>> emitters  		= mgr.get_components_by_id<ParticleEmitter>(sprite.game_object_id); @@ -77,10 +90,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, +				.texture = res,  				.pos = p.position,  				.angle = p.angle,  				.scale = scale, @@ -89,12 +101,14 @@ bool RenderSystem::render_particle(const Sprite & sprite, const Camera & cam,  	}  	return rendering_particles;  } -void RenderSystem::render_normal(const Sprite & sprite, const Camera & cam, -								 const Transform & tm) { -	this->context.draw(SDLContext::RenderContext{ +void RenderSystem::render_normal(const Sprite & sprite, const Transform & tm) { +	SDLContext & ctx = this->mediator.sdl_context; +	ResourceManager & resource_manager = this->mediator.resource_manager; +	const Texture & res = resource_manager.get<Texture>(sprite.source); + +	ctx.draw(SDLContext::RenderContext{  		.sprite = sprite, -		.cam = cam, -		.cam_pos = this->cam_pos, +		.texture = res,  		.pos = tm.position,  		.angle = tm.rotation,  		.scale = tm.scale, @@ -103,7 +117,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(); +	this->update_camera();  	RefVector<Sprite> sprites = mgr.get_components_by_type<Sprite>();  	RefVector<Sprite> sorted_sprites = this->sort(sprites); @@ -113,10 +127,10 @@ void RenderSystem::render() {  		const Transform & transform  			= mgr.get_components_by_id<Transform>(sprite.game_object_id).front().get(); -		bool rendered_particles = this->render_particle(sprite, cam, transform.scale); +		bool rendered_particles = this->render_particle(sprite, transform.scale);  		if (rendered_particles) continue; -		this->render_normal(sprite, cam, transform); +		this->render_normal(sprite, transform);  	}  } diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h index 096d058..fc7b46e 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -2,8 +2,6 @@  #include <cmath> -#include "facade/SDLContext.h" -  #include "System.h"  #include "types.h" @@ -14,11 +12,10 @@ class Sprite;  class Transform;  /** - * \class RenderSystem   * \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 +34,7 @@ private:  	void present_screen();  	//! Updates the active camera used for rendering. -	const Camera & update_camera(); +	void update_camera();  	//! Renders the whole screen  	void render(); @@ -52,20 +49,20 @@ 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 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 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 +72,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/crepe/system/System.cpp b/src/crepe/system/System.cpp index f68549b..ecc740d 100644 --- a/src/crepe/system/System.cpp +++ b/src/crepe/system/System.cpp @@ -1,7 +1,5 @@ -#include "../util/Log.h" -  #include "System.h"  using namespace crepe; -System::System(const Mediator & mediator) : mediator(mediator) { dbg_trace(); } +System::System(const Mediator & mediator) : mediator(mediator) {} diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h index 3201667..1b2cb3f 100644 --- a/src/crepe/util/OptionalRef.h +++ b/src/crepe/util/OptionalRef.h @@ -25,7 +25,7 @@ public:  	 */  	OptionalRef<T> & operator=(T & ref);  	/** -	 * \brief Retrieve this reference +	 * \brief Retrieve this reference (cast)  	 *  	 * \returns Internal reference if it is set  	 * @@ -33,6 +33,14 @@ public:  	 */  	operator T &() const;  	/** +	 * \brief Retrieve this reference (member access) +	 * +	 * \returns Internal reference if it is set +	 * +	 * \throws std::runtime_error if this function is called while the reference it not set +	 */ +	T * operator->() const; +	/**  	 * \brief Check if this reference is not empty  	 *  	 * \returns `true` if reference is set, or `false` if it is not diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp index 4608c9e..5e36b3a 100644 --- a/src/crepe/util/OptionalRef.hpp +++ b/src/crepe/util/OptionalRef.hpp @@ -19,6 +19,13 @@ OptionalRef<T>::operator T &() const {  }  template <typename T> +T * OptionalRef<T>::operator->() const { +	if (this->ref == nullptr) +		throw std::runtime_error("OptionalRef: attempt to dereference nullptr"); +	return this->ref; +} + +template <typename T>  OptionalRef<T> & OptionalRef<T>::operator=(T & ref) {  	this->ref = &ref;  	return *this; 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/AITest.cpp b/src/example/AITest.cpp new file mode 100644 index 0000000..93ba500 --- /dev/null +++ b/src/example/AITest.cpp @@ -0,0 +1,90 @@ +#include <crepe/api/AI.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Camera.h> +#include <crepe/api/Color.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/LoopManager.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Script.h> +#include <crepe/api/Sprite.h> +#include <crepe/manager/Mediator.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +class Script1 : public Script { +	bool shutdown(const ShutDownEvent & event) { +		// Very dirty way of shutting down the game +		throw "ShutDownEvent"; +		return true; +	} + +	bool mousemove(const MouseMoveEvent & event) { +		/*RefVector<AI> aivec = this->get_components<AI>(); +		AI & ai = aivec.front().get(); +		ai.flee_target +			= vec2{static_cast<float>(event.mouse_x), static_cast<float>(event.mouse_y)};*/ +		return true; +	} + +	void init() { +		subscribe<ShutDownEvent>( +			[this](const ShutDownEvent & ev) -> bool { return this->shutdown(ev); }); +		subscribe<MouseMoveEvent>( +			[this](const MouseMoveEvent & ev) -> bool { return this->mousemove(ev); }); +	} +}; + +class Scene1 : public Scene { +public: +	void load_scene() override { +		Mediator & mediator = this->mediator; +		ComponentManager & mgr = mediator.component_manager; + +		GameObject game_object1 = mgr.new_object("", "", vec2{0, 0}, 0, 1); +		GameObject game_object2 = mgr.new_object("", "", vec2{0, 0}, 0, 1); + +		Asset img{"asset/texture/test_ap43.png"}; + +		Sprite & test_sprite = game_object1.add_component<Sprite>( +			img, Sprite::Data{ +					 .color = Color::MAGENTA, +					 .flip = Sprite::FlipSettings{false, false}, +					 .sorting_in_layer = 2, +					 .order_in_layer = 2, +					 .size = {0, 100}, +					 .angle_offset = 0, +					 .position_offset = {0, 0}, +				 }); + +		AI & ai = game_object1.add_component<AI>(3000); +		// ai.arrive_on(); +		// ai.flee_on(); +		ai.path_follow_on(); +		ai.make_oval_path(500, 1000, {0, -1000}, 1.5708, true); +		ai.make_oval_path(1000, 500, {0, 500}, 4.7124, false); +		game_object1.add_component<Rigidbody>(Rigidbody::Data{ +			.mass = 0.1f, +			.max_linear_velocity = 40, +		}); +		game_object1.add_component<BehaviorScript>().set_script<Script1>(); + +		game_object2.add_component<Camera>(ivec2{1080, 720}, vec2{5000, 5000}, +										   Camera::Data{ +											   .bg_color = Color::WHITE, +											   .zoom = 1, +										   }); +	} + +	string get_name() const override { return "Scene1"; } +}; + +int main() { +	LoopManager engine; +	engine.add_scene<Scene1>(); +	engine.start(); + +	return 0; +} diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 8ef71bb..187ed46 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -16,8 +16,7 @@ function(add_example target_name)  	add_dependencies(examples ${target_name})  endfunction() -add_example(asset_manager) -add_example(savemgr)  add_example(rendering_particle)  add_example(game)  add_example(button) +add_example(AITest) diff --git a/src/example/asset_manager.cpp b/src/example/asset_manager.cpp deleted file mode 100644 index 917b547..0000000 --- a/src/example/asset_manager.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include <crepe/api/AssetManager.h> -#include <crepe/api/Texture.h> -#include <crepe/facade/Sound.h> - -using namespace crepe; - -int main() { - -	// this needs to be called before the asset manager otherwise the destructor of sdl is not in -	// the right order -	{ Texture test("../asset/texture/img.png"); } -	// FIXME: make it so the issue described by the above comment is not possible (i.e. the order -	// in which internal classes are instantiated should not impact the way the engine works). - -	auto & mgr = AssetManager::get_instance(); - -	{ -		// TODO: [design] the Sound class can't be directly included by the user as it includes -		// SoLoud headers. -		auto bgm = mgr.cache<Sound>("../mwe/audio/bgm.ogg"); -		auto sfx1 = mgr.cache<Sound>("../mwe/audio/sfx1.wav"); -		auto sfx2 = mgr.cache<Sound>("../mwe/audio/sfx2.wav"); - -		auto img = mgr.cache<Texture>("../asset/texture/img.png"); -		auto img1 = mgr.cache<Texture>("../asset/texture/second.png"); -	} - -	{ -		auto bgm = mgr.cache<Sound>("../mwe/audio/bgm.ogg"); -		auto sfx1 = mgr.cache<Sound>("../mwe/audio/sfx1.wav"); -		auto sfx2 = mgr.cache<Sound>("../mwe/audio/sfx2.wav"); - -		auto img = mgr.cache<Texture>("../asset/texture/img.png"); -		auto img1 = mgr.cache<Texture>("../asset/texture/second.png"); -	} -} diff --git a/src/example/game.cpp b/src/example/game.cpp index dd5b968..8ea50ea 100644 --- a/src/example/game.cpp +++ b/src/example/game.cpp @@ -2,6 +2,7 @@  #include "api/Scene.h"  #include "manager/ComponentManager.h"  #include "manager/Mediator.h" +#include "types.h"  #include <crepe/api/BoxCollider.h>  #include <crepe/api/Camera.h>  #include <crepe/api/Color.h> @@ -11,7 +12,6 @@  #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> @@ -66,6 +66,11 @@ class MyScript1 : public Script {  				//add collider switch  				break;  			} +			case Keycode::Q: { +				Rigidbody & rg = this->get_component<Rigidbody>(); +				rg.data.angular_velocity = 1; +				break; +			}  			default:  				break;  		} @@ -158,15 +163,13 @@ public:  	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( +		GameObject world = new_object(  			"Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1);  		world.add_component<Rigidbody>(Rigidbody::Data{  			.mass = 0, @@ -187,15 +190,18 @@ public:  		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); +			vec2{screen_size_width, screen_size_height}, +			Camera::Data{ +				.bg_color = Color::WHITE, +				.zoom = 1, +			}); -		GameObject game_object1 = mgr.new_object( +		GameObject game_object1 = new_object(  			"Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1);  		game_object1.add_component<Rigidbody>(Rigidbody::Data{  			.mass = 1, -			.gravity_scale = 0, +			.gravity_scale = 1,  			.body_type = Rigidbody::BodyType::DYNAMIC,  			.linear_velocity = {0, 1},  			.constraints = {0, 0, 0}, @@ -206,19 +212,24 @@ public:  		// 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); + +		Asset img1{"asset/texture/square.png"}; +		game_object1.add_component<Sprite>(img1, Sprite::Data{ +													 .size = {20, 20}, +												 });  		//add circle with cirlcecollider deactiveated  		game_object1.add_component<CircleCollider>(vec2{0, 0}, 10).active = false; -		auto img2 = Texture("asset/texture/circle.png"); +		Asset img2{"asset/texture/circle.png"};  		game_object1 -			.add_component<Sprite>(img2, color, Sprite::FlipSettings{false, false}, 1, 1, 20) +			.add_component<Sprite>(img2, +								   Sprite::Data{ +									   .size = {20, 20}, +								   })  			.active  			= false; -		GameObject game_object2 = mgr.new_object( +		GameObject game_object2 = new_object(  			"Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1);  		game_object2.add_component<Rigidbody>(Rigidbody::Data{  			.mass = 1, @@ -233,15 +244,19 @@ public:  		// 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); + +		game_object2.add_component<Sprite>(img1, Sprite::Data{ +													 .size = {20, 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) +			.add_component<Sprite>(img2, +								   Sprite::Data{ +									   .size = {20, 20}, +								   })  			.active  			= false;  	} diff --git a/src/example/rendering_particle.cpp b/src/example/rendering_particle.cpp index 349d11e..13e625f 100644 --- a/src/example/rendering_particle.cpp +++ b/src/example/rendering_particle.cpp @@ -1,43 +1,24 @@ -#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 "api/Asset.h"  #include <crepe/Component.h> +#include <crepe/api/Animator.h> +#include <crepe/api/Button.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> +#include <iostream>  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 +40,56 @@ 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() { -	/* -	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; -	*/ +		cout << "TestScene" << endl; +		Mediator & mediator = this->mediator; +		ComponentManager & mgr = mediator.component_manager; +		GameObject game_object = mgr.new_object("", "", vec2{0, 0}, 0, 1); -	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); +		Color color(255, 255, 255, 255); + +		Asset img{"asset/spritesheet/spritesheet_test.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 = {0, 0}, +				 }); + +		//auto & anim = game_object.add_component<Animator>(test_sprite,ivec2{32, 64}, uvec2{4,1}, Animator::Data{}); +		//anim.set_anim(0); + +		auto & cam = game_object.add_component<Camera>(ivec2{720, 1280}, vec2{400, 400}, +													   Camera::Data{ +														   .bg_color = Color::WHITE, +													   }); + +		function<void()> on_click = [&]() { cout << "button clicked" << std::endl; }; +		function<void()> on_enter = [&]() { cout << "enter" << std::endl; }; +		function<void()> on_exit = [&]() { cout << "exit" << std::endl; }; + +		auto & button +			= game_object.add_component<Button>(vec2{200, 200}, vec2{0, 0}, on_click, false); +		button.on_mouse_enter = on_enter; +		button.on_mouse_exit = on_exit; +		button.is_toggle = true; +		button.active = true;  	} +	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/example/savemgr.cpp b/src/example/savemgr.cpp deleted file mode 100644 index 65c4a34..0000000 --- a/src/example/savemgr.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/** \file - *  - * Standalone example for usage of the save manager - */ - -#include <cassert> -#include <crepe/api/Config.h> -#include <crepe/api/SaveManager.h> -#include <crepe/util/Log.h> -#include <crepe/util/Proxy.h> - -using namespace crepe; - -// unrelated setup code -int _ = []() { -	// make sure all log messages get printed -	auto & cfg = Config::get_instance(); -	cfg.log.level = Log::Level::TRACE; - -	return 0; // satisfy compiler -}(); - -int main() { -	const char * key = "mygame.test"; - -	SaveManager & mgr = SaveManager::get_instance(); - -	dbg_logf("has key = {}", mgr.has(key)); -	ValueBroker<int> prop = mgr.get<int>(key, 0); -	Proxy<int> val = mgr.get<int>(key, 0); - -	dbg_logf("val = {}", mgr.get<int>(key).get()); -	prop.set(1); -	dbg_logf("val = {}", mgr.get<int>(key).get()); -	val = 2; -	dbg_logf("val = {}", mgr.get<int>(key).get()); -	mgr.set<int>(key, 3); -	dbg_logf("val = {}", mgr.get<int>(key).get()); - -	dbg_logf("has key = {}", mgr.has(key)); -	assert(true == mgr.has(key)); - -	return 0; -} diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp new file mode 100644 index 0000000..48bba1b --- /dev/null +++ b/src/test/AudioTest.cpp @@ -0,0 +1,161 @@ +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <crepe/api/AudioSource.h> +#include <crepe/api/GameObject.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/ResourceManager.h> +#include <crepe/system/AudioSystem.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; +using namespace testing; + +class AudioTest : public Test { +private: +	class TestSoundContext : public SoundContext { +	public: +		MOCK_METHOD(SoundHandle, play, (Sound & resource), (override)); +		MOCK_METHOD(void, stop, (const SoundHandle &), (override)); +		MOCK_METHOD(void, set_volume, (const SoundHandle &, float), (override)); +		MOCK_METHOD(void, set_loop, (const SoundHandle &, bool), (override)); +	}; + +	class TestAudioSystem : public AudioSystem { +	public: +		using AudioSystem::AudioSystem; +		StrictMock<TestSoundContext> context; +		virtual SoundContext & get_context() { return this->context; } +	}; + +private: +	Mediator mediator; +	ComponentManager component_manager{mediator}; +	ResourceManager resource_manager{mediator}; + +public: +	TestAudioSystem system{mediator}; +	TestSoundContext & context = system.context; + +private: +	GameObject entity = component_manager.new_object("name"); + +public: +	AudioSource & component = entity.add_component<AudioSource>("mwe/audio/bgm.ogg"); +}; + +TEST_F(AudioTest, Default) { +	EXPECT_CALL(context, play(_)).Times(0); +	EXPECT_CALL(context, stop(_)).Times(0); +	EXPECT_CALL(context, set_volume(_, _)).Times(0); +	EXPECT_CALL(context, set_loop(_, _)).Times(0); +	system.update(); +} + +TEST_F(AudioTest, Play) { +	system.update(); + +	{ +		InSequence seq; + +		EXPECT_CALL(context, play(_)).Times(0); +		component.play(); +	} + +	{ +		InSequence seq; + +		EXPECT_CALL(context, play(_)).Times(1); +		system.update(); +	} +} + +TEST_F(AudioTest, Stop) { +	system.update(); + +	{ +		InSequence seq; + +		EXPECT_CALL(context, stop(_)).Times(0); +		component.stop(); +	} + +	{ +		InSequence seq; + +		EXPECT_CALL(context, stop(_)).Times(1); +		system.update(); +	} +} + +TEST_F(AudioTest, Volume) { +	system.update(); + +	{ +		InSequence seq; + +		EXPECT_CALL(context, set_volume(_, _)).Times(0); +		component.volume += 0.2; +	} + +	{ +		InSequence seq; + +		EXPECT_CALL(context, set_volume(_, component.volume)).Times(1); +		system.update(); +	} +} + +TEST_F(AudioTest, Looping) { +	system.update(); + +	{ +		InSequence seq; + +		EXPECT_CALL(context, set_loop(_, _)).Times(0); +		component.loop = !component.loop; +	} + +	{ +		InSequence seq; + +		EXPECT_CALL(context, set_loop(_, component.loop)).Times(1); +		system.update(); +	} +} + +TEST_F(AudioTest, StopOnDeactivate) { +	system.update(); + +	{ +		InSequence seq; + +		EXPECT_CALL(context, stop(_)).Times(1); +		component.active = false; +		system.update(); +	} +} + +TEST_F(AudioTest, PlayOnActive) { +	component.active = false; +	component.play_on_awake = true; +	system.update(); + +	{ +		InSequence seq; + +		EXPECT_CALL(context, play(_)).Times(1); +		component.active = true; +		system.update(); +	} +} + +TEST_F(AudioTest, PlayImmediately) { +	component.play_on_awake = false; +	component.play(); + +	EXPECT_CALL(context, play(_)).Times(1); + +	system.update(); +} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 9a4dfe7..11b4ca9 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -4,7 +4,9 @@ target_sources(test_main PUBLIC  	PhysicsTest.cpp  	ScriptTest.cpp  	ParticleTest.cpp +	AudioTest.cpp  	AssetTest.cpp +	ResourceManagerTest.cpp  	OptionalRefTest.cpp  	RenderSystemTest.cpp  	EventTest.cpp @@ -13,8 +15,13 @@ target_sources(test_main PUBLIC  	ValueBrokerTest.cpp  	DBTest.cpp  	Vector2Test.cpp +	LoopManagerTest.cpp +	LoopTimerTest.cpp  	InputTest.cpp  	ScriptEventTest.cpp  	ScriptSceneTest.cpp -	# Profiling.cpp #disabled for saving time +	Profiling.cpp +	SaveManagerTest.cpp +	ScriptSaveManagerTest.cpp +	ScriptECSTest.cpp  ) diff --git a/src/test/CollisionTest.cpp b/src/test/CollisionTest.cpp index dd45eb6..5dbc670 100644 --- a/src/test/CollisionTest.cpp +++ b/src/test/CollisionTest.cpp @@ -50,6 +50,7 @@ public:  class CollisionTest : public Test {  public:  	Mediator m; +	EventManager event_mgr{m};  	ComponentManager mgr{m};  	CollisionSystem collision_sys{m};  	ScriptSystem script_sys{m}; diff --git a/src/test/ECSTest.cpp b/src/test/ECSTest.cpp index 3e6c61c..af2b7b0 100644 --- a/src/test/ECSTest.cpp +++ b/src/test/ECSTest.cpp @@ -1,6 +1,7 @@  #include <gtest/gtest.h>  #define protected public +#define private public  #include <crepe/api/GameObject.h>  #include <crepe/api/Metadata.h> @@ -16,6 +17,10 @@ class ECSTest : public ::testing::Test {  public:  	ComponentManager mgr{m}; + +	class TestComponent : public Component { +		using Component::Component; +	};  };  TEST_F(ECSTest, createGameObject) { @@ -387,3 +392,77 @@ TEST_F(ECSTest, resetPersistent) {  	EXPECT_EQ(metadata.size(), 0);  	EXPECT_EQ(transform.size(), 0);  } + +TEST_F(ECSTest, IDByName) { +	GameObject foo = mgr.new_object("foo"); +	GameObject bar = mgr.new_object("bar"); + +	{ +		auto objects = mgr.get_objects_by_name(""); +		EXPECT_EQ(objects.size(), 0); +	} + +	{ +		auto objects = mgr.get_objects_by_name("foo"); +		EXPECT_EQ(objects.size(), 1); +		EXPECT_TRUE(objects.contains(foo.id)); +	} +} + +TEST_F(ECSTest, IDByTag) { +	GameObject foo = mgr.new_object("foo", "common tag"); +	GameObject bar = mgr.new_object("bar", "common tag"); + +	{ +		auto objects = mgr.get_objects_by_tag(""); +		EXPECT_EQ(objects.size(), 0); +	} + +	{ +		auto objects = mgr.get_objects_by_tag("common tag"); +		EXPECT_EQ(objects.size(), 2); +		EXPECT_TRUE(objects.contains(foo.id)); +		EXPECT_TRUE(objects.contains(bar.id)); +	} +} + +TEST_F(ECSTest, ComponentsByName) { +	GameObject foo = mgr.new_object("foo"); +	foo.add_component<TestComponent>(); +	GameObject bar = mgr.new_object("bar"); +	bar.add_component<TestComponent>(); +	bar.add_component<TestComponent>(); + +	{ +		auto objects = mgr.get_components_by_name<TestComponent>(""); +		EXPECT_EQ(objects.size(), 0); +	} + +	{ +		auto objects = mgr.get_components_by_name<TestComponent>("foo"); +		EXPECT_EQ(objects.size(), 1); +	} + +	{ +		auto objects = mgr.get_components_by_name<TestComponent>("bar"); +		EXPECT_EQ(objects.size(), 2); +	} +} + +TEST_F(ECSTest, ComponentsByTag) { +	GameObject foo = mgr.new_object("foo", "common tag"); +	foo.add_component<TestComponent>(); +	GameObject bar = mgr.new_object("bar", "common tag"); +	bar.add_component<TestComponent>(); +	bar.add_component<TestComponent>(); + +	{ +		auto objects = mgr.get_components_by_tag<TestComponent>(""); +		EXPECT_EQ(objects.size(), 0); +	} + +	{ +		auto objects = mgr.get_components_by_tag<TestComponent>("common tag"); +		EXPECT_EQ(objects.size(), 3); +	} +} diff --git a/src/test/EventTest.cpp b/src/test/EventTest.cpp index 4a4872d..82272b5 100644 --- a/src/test/EventTest.cpp +++ b/src/test/EventTest.cpp @@ -1,56 +1,41 @@ -#include <gmock/gmock.h> -#include <gtest/gtest.h> -  #include <crepe/api/Event.h> -#include <crepe/api/IKeyListener.h> -#include <crepe/api/IMouseListener.h>  #include <crepe/manager/EventManager.h> - +#include <crepe/manager/Mediator.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h>  using namespace std;  using namespace std::chrono_literals;  using namespace crepe;  class EventManagerTest : public ::testing::Test {  protected: +	Mediator mediator; +	EventManager event_mgr{mediator};  	void SetUp() override {  		// Clear any existing subscriptions or events before each test -		EventManager::get_instance().clear(); +		event_mgr.clear();  	}  	void TearDown() override {  		// Ensure cleanup after each test -		EventManager::get_instance().clear(); +		event_mgr.clear();  	}  }; -class MockKeyListener : public IKeyListener { -public: -	MOCK_METHOD(bool, on_key_pressed, (const KeyPressEvent & event), (override)); -	MOCK_METHOD(bool, on_key_released, (const KeyReleaseEvent & event), (override)); -}; - -class MockMouseListener : public IMouseListener { -public: -	MOCK_METHOD(bool, on_mouse_clicked, (const MouseClickEvent & event), (override)); -	MOCK_METHOD(bool, on_mouse_pressed, (const MousePressEvent & event), (override)); -	MOCK_METHOD(bool, on_mouse_released, (const MouseReleaseEvent & event), (override)); -	MOCK_METHOD(bool, on_mouse_moved, (const MouseMoveEvent & event), (override)); -}; -  TEST_F(EventManagerTest, EventSubscription) {  	EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) { return true; };  	// Subscribe to KeyPressEvent -	EventManager::get_instance().subscribe<KeyPressEvent>(key_handler, 1); +	event_mgr.subscribe<KeyPressEvent>(key_handler, 1);  	// Verify subscription (not directly verifiable; test by triggering event) -	EventManager::get_instance().trigger_event<KeyPressEvent>( +	event_mgr.trigger_event<KeyPressEvent>(  		KeyPressEvent{  			.repeat = true,  			.key = Keycode::A,  		},  		1); -	EventManager::get_instance().trigger_event<KeyPressEvent>( +	event_mgr.trigger_event<KeyPressEvent>(  		KeyPressEvent{  			.repeat = true,  			.key = Keycode::A, @@ -68,13 +53,11 @@ TEST_F(EventManagerTest, EventManagerTest_trigger_all_channels) {  		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);  		return false;  	}; -	EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, -															EventManager::CHANNEL_ALL); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler, EventManager::CHANNEL_ALL);  	MouseClickEvent click_event{  		.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; -	EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, -																EventManager::CHANNEL_ALL); +	event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);  	EXPECT_TRUE(triggered);  } @@ -88,19 +71,17 @@ TEST_F(EventManagerTest, EventManagerTest_trigger_one_channel) {  		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);  		return false;  	}; -	EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, test_channel); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler, test_channel);  	MouseClickEvent click_event{  		.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; -	EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, -																EventManager::CHANNEL_ALL); +	event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);  	EXPECT_FALSE(triggered); -	EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, test_channel); +	event_mgr.trigger_event<MouseClickEvent>(click_event, test_channel);  }  TEST_F(EventManagerTest, EventManagerTest_callback_propagation) { -	EventManager & event_manager = EventManager::get_instance();  	// Flags to track handler calls  	bool triggered_true = false; @@ -126,11 +107,11 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) {  	// Test event  	MouseClickEvent click_event{  		.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; -	event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); -	event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL);  	// Trigger event -	event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); +	event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);  	// Check that only the true handler was triggered  	EXPECT_TRUE(triggered_true); @@ -139,12 +120,12 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) {  	// Reset and clear  	triggered_true = false;  	triggered_false = false; -	event_manager.clear(); -	event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); -	event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); +	event_mgr.clear(); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL);  	// Trigger event again -	event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); +	event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);  	// Check that both handlers were triggered  	EXPECT_TRUE(triggered_true); @@ -152,47 +133,37 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) {  }  TEST_F(EventManagerTest, EventManagerTest_queue_dispatch) { -	EventManager & event_manager = EventManager::get_instance();  	bool triggered1 = false;  	bool triggered2 = false;  	int test_channel = 1; - -	// Adjusted to use KeyPressEvent with repeat as the first variable -	EventHandler<KeyPressEvent> key_handler1 = [&](const KeyPressEvent & e) { +	EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) {  		triggered1 = true; -		EXPECT_EQ(e.repeat, false); // Expecting repeat to be false -		EXPECT_EQ(e.key, Keycode::A); // Adjust expected key code +		EXPECT_EQ(e.mouse_x, 100); +		EXPECT_EQ(e.mouse_y, 200); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);  		return false; // Allows propagation  	}; - -	EventHandler<KeyPressEvent> key_handler2 = [&](const KeyPressEvent & e) { +	EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) {  		triggered2 = true; -		EXPECT_EQ(e.repeat, false); // Expecting repeat to be false -		EXPECT_EQ(e.key, Keycode::A); // Adjust expected key code +		EXPECT_EQ(e.mouse_x, 100); +		EXPECT_EQ(e.mouse_y, 200); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);  		return false; // Allows propagation  	}; +	event_mgr.subscribe<MouseClickEvent>(mouse_handler1); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler2, test_channel); -	// Subscribe handlers to KeyPressEvent -	event_manager.subscribe<KeyPressEvent>(key_handler1); -	event_manager.subscribe<KeyPressEvent>(key_handler2, test_channel); - -	// Queue a KeyPressEvent instead of KeyDownEvent -	event_manager.queue_event<KeyPressEvent>(KeyPressEvent{ -		.repeat = false, .key = Keycode::A}); // Adjust event with repeat flag first - -	event_manager.queue_event<KeyPressEvent>( -		KeyPressEvent{.repeat = false, -					  .key = Keycode::A}, // Adjust event for second subscription +	event_mgr.queue_event<MouseClickEvent>( +		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); +	event_mgr.queue_event<MouseClickEvent>( +		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE},  		test_channel); - -	event_manager.dispatch_events(); - +	event_mgr.dispatch_events();  	EXPECT_TRUE(triggered1);  	EXPECT_TRUE(triggered2);  }  TEST_F(EventManagerTest, EventManagerTest_unsubscribe) { -	EventManager & event_manager = EventManager::get_instance();  	// Flags to track if handlers are triggered  	bool triggered1 = false; @@ -215,15 +186,15 @@ TEST_F(EventManagerTest, EventManagerTest_unsubscribe) {  		return false; // Allows propagation  	};  	// Subscribe handlers -	subscription_t handler1_id = event_manager.subscribe<MouseClickEvent>(mouse_handler1); -	subscription_t handler2_id = event_manager.subscribe<MouseClickEvent>(mouse_handler2); +	subscription_t handler1_id = event_mgr.subscribe<MouseClickEvent>(mouse_handler1); +	subscription_t handler2_id = event_mgr.subscribe<MouseClickEvent>(mouse_handler2);  	// Queue events -	event_manager.queue_event<MouseClickEvent>( +	event_mgr.queue_event<MouseClickEvent>(  		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE});  	// Dispatch events - both handlers should be triggered -	event_manager.dispatch_events(); +	event_mgr.dispatch_events();  	EXPECT_TRUE(triggered1); // Handler 1 should be triggered  	EXPECT_TRUE(triggered2); // Handler 2 should be triggered @@ -232,14 +203,14 @@ TEST_F(EventManagerTest, EventManagerTest_unsubscribe) {  	triggered2 = false;  	// Unsubscribe handler1 -	event_manager.unsubscribe(handler1_id); +	event_mgr.unsubscribe(handler1_id);  	// Queue the same event again -	event_manager.queue_event<MouseClickEvent>( +	event_mgr.queue_event<MouseClickEvent>(  		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE});  	// Dispatch events - only handler 2 should be triggered, handler 1 should NOT -	event_manager.dispatch_events(); +	event_mgr.dispatch_events();  	EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered  	EXPECT_TRUE(triggered2); // Handler 2 should be triggered @@ -247,14 +218,14 @@ TEST_F(EventManagerTest, EventManagerTest_unsubscribe) {  	triggered2 = false;  	// Unsubscribe handler2 -	event_manager.unsubscribe(handler2_id); +	event_mgr.unsubscribe(handler2_id);  	// Queue the event again -	event_manager.queue_event<MouseClickEvent>( +	event_mgr.queue_event<MouseClickEvent>(  		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE});  	// Dispatch events - no handler should be triggered -	event_manager.dispatch_events(); +	event_mgr.dispatch_events();  	EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered  	EXPECT_FALSE(triggered2); // Handler 2 should NOT be triggered  } diff --git a/src/test/InputTest.cpp b/src/test/InputTest.cpp index cb9833f..8b40cea 100644 --- a/src/test/InputTest.cpp +++ b/src/test/InputTest.cpp @@ -1,3 +1,4 @@ +#include "system/RenderSystem.h"  #include <gtest/gtest.h>  #define protected public  #define private public @@ -24,17 +25,23 @@ class InputTest : public ::testing::Test {  public:  	Mediator mediator;  	ComponentManager mgr{mediator}; +	SDLContext sdl_context{mediator};  	InputSystem input_system{mediator}; - -	EventManager & event_manager = EventManager::get_instance(); +	RenderSystem render{mediator}; +	EventManager event_manager{mediator};  	//GameObject camera;  protected:  	void SetUp() override { -		mediator.event_manager = event_manager; -		mediator.component_manager = mgr; -		event_manager.clear(); +		GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +		auto & camera +			= obj.add_component<Camera>(ivec2{500, 500}, vec2{500, 500}, +										Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); +		render.update(); +		//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) { @@ -59,10 +66,6 @@ protected:  };  TEST_F(InputTest, MouseDown) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, -											  vec2{0, 0}); -	camera.active = true;  	bool mouse_triggered = false;  	EventHandler<MousePressEvent> on_mouse_down = [&](const MousePressEvent & event) {  		mouse_triggered = true; @@ -89,10 +92,6 @@ TEST_F(InputTest, MouseDown) {  }  TEST_F(InputTest, MouseUp) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, -											  vec2{0, 0}); -	camera.active = true;  	bool function_triggered = false;  	EventHandler<MouseReleaseEvent> on_mouse_release = [&](const MouseReleaseEvent & e) {  		function_triggered = true; @@ -117,10 +116,6 @@ TEST_F(InputTest, MouseUp) {  }  TEST_F(InputTest, MouseMove) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, -											  vec2{0, 0}); -	camera.active = true;  	bool function_triggered = false;  	EventHandler<MouseMoveEvent> on_mouse_move = [&](const MouseMoveEvent & e) {  		function_triggered = true; @@ -147,10 +142,6 @@ TEST_F(InputTest, MouseMove) {  }  TEST_F(InputTest, KeyDown) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, -											  vec2{0, 0}); -	camera.active = true;  	bool function_triggered = false;  	// Define event handler for KeyPressEvent @@ -178,10 +169,6 @@ TEST_F(InputTest, KeyDown) {  }  TEST_F(InputTest, KeyUp) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, -											  vec2{0, 0}); -	camera.active = true;  	bool function_triggered = false;  	EventHandler<KeyReleaseEvent> on_key_release = [&](const KeyReleaseEvent & event) {  		function_triggered = true; @@ -202,10 +189,6 @@ TEST_F(InputTest, KeyUp) {  }  TEST_F(InputTest, MouseClick) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, -											  vec2{0, 0}); -	camera.active = true;  	bool on_click_triggered = false;  	EventHandler<MouseClickEvent> on_mouse_click = [&](const MouseClickEvent & event) {  		on_click_triggered = true; @@ -223,10 +206,6 @@ TEST_F(InputTest, MouseClick) {  }  TEST_F(InputTest, testButtonClick) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, -											  vec2{0, 0}); -	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; }; @@ -250,10 +229,6 @@ TEST_F(InputTest, testButtonClick) {  }  TEST_F(InputTest, testButtonHover) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>(Color::BLACK, ivec2{0, 0}, vec2{500, 500}, 0.0, -											  vec2{0, 0}); -	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; }; diff --git a/src/test/LoopManagerTest.cpp b/src/test/LoopManagerTest.cpp new file mode 100644 index 0000000..df132ae --- /dev/null +++ b/src/test/LoopManagerTest.cpp @@ -0,0 +1,78 @@ +#include <chrono> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <thread> +#define private public +#define protected public +#include <crepe/api/LoopManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/manager/LoopTimerManager.h> +using namespace std::chrono; +using namespace crepe; + +class DISABLED_LoopManagerTest : public ::testing::Test { +protected: +	class TestGameLoop : public crepe::LoopManager { +	public: +		MOCK_METHOD(void, fixed_update, (), (override)); +		MOCK_METHOD(void, frame_update, (), (override)); +	}; + +	TestGameLoop test_loop; +	void SetUp() override {} +}; + +TEST_F(DISABLED_LoopManagerTest, FixedUpdate) { +	// Arrange +	test_loop.loop_timer.set_target_framerate(60); + +	// Set expectations for the mock calls +	EXPECT_CALL(test_loop, frame_update).Times(::testing::Between(55, 65)); +	EXPECT_CALL(test_loop, fixed_update).Times(::testing::Between(48, 52)); + +	// Start the loop in a separate thread +	std::thread loop_thread([&]() { test_loop.start(); }); + +	// Let the loop run for exactly 1 second +	std::this_thread::sleep_for(std::chrono::seconds(1)); + +	// Stop the game loop +	test_loop.game_running = false; +	// Wait for the loop thread to finish +	loop_thread.join(); + +	// Test finished +} + +TEST_F(DISABLED_LoopManagerTest, ScaledFixedUpdate) { +	// Arrange +	test_loop.loop_timer.set_target_framerate(60); + +	// Set expectations for the mock calls +	EXPECT_CALL(test_loop, frame_update).Times(::testing::Between(55, 65)); +	EXPECT_CALL(test_loop, fixed_update).Times(::testing::Between(48, 52)); + +	// Start the loop in a separate thread +	std::thread loop_thread([&]() { test_loop.start(); }); + +	// Let the loop run for exactly 1 second +	std::this_thread::sleep_for(std::chrono::seconds(1)); + +	// Stop the game loop +	test_loop.game_running = false; +	// Wait for the loop thread to finish +	loop_thread.join(); + +	// Test finished +} + +TEST_F(DISABLED_LoopManagerTest, ShutDown) { +	// Arrange +	test_loop.loop_timer.set_target_framerate(60); +	// Start the loop in a separate thread +	std::thread loop_thread([&]() { test_loop.start(); }); +	std::this_thread::sleep_for(std::chrono::milliseconds(1)); +	test_loop.event_manager.trigger_event<ShutDownEvent>(ShutDownEvent{}); +	// Wait for the loop thread to finish +	loop_thread.join(); +} diff --git a/src/test/LoopTimerTest.cpp b/src/test/LoopTimerTest.cpp new file mode 100644 index 0000000..7bd6305 --- /dev/null +++ b/src/test/LoopTimerTest.cpp @@ -0,0 +1,97 @@ +#include <chrono> +#include <gtest/gtest.h> +#include <thread> + +#define private public +#define protected public + +#include <crepe/manager/LoopTimerManager.h> +#include <crepe/manager/Mediator.h> + +using namespace std::chrono; +using namespace crepe; + +class LoopTimerTest : public ::testing::Test { +protected: +	Mediator mediator; +	LoopTimerManager loop_timer{mediator}; + +	void SetUp() override { loop_timer.start(); } +}; + +TEST_F(LoopTimerTest, EnforcesTargetFrameRate) { +	// Set the target FPS to 60 (which gives a target time per frame of ~16.67 ms) +	loop_timer.set_target_framerate(60); + +	auto start_time = steady_clock::now(); +	loop_timer.enforce_frame_rate(); + +	auto elapsed_time = steady_clock::now() - start_time; +	auto elapsed_ms = duration_cast<milliseconds>(elapsed_time).count(); + +	// For 60 FPS, the target frame time is around 16.67ms +	ASSERT_NEAR(elapsed_ms, 16.7, 5); +} + +TEST_F(LoopTimerTest, SetTargetFps) { +	// Set the target FPS to 120 +	loop_timer.set_target_framerate(120); + +	// Calculate the expected frame time (~8.33ms per frame) +	duration_t expected_frame_time = std::chrono::duration<float>(1.0 / 120.0); + +	ASSERT_NEAR(loop_timer.frame_target_time.count(), expected_frame_time.count(), 0.001); +} + +TEST_F(LoopTimerTest, DeltaTimeCalculation) { +	// Set the target FPS to 60 (16.67 ms per frame) +	loop_timer.set_target_framerate(60); + +	auto start_time = steady_clock::now(); +	loop_timer.update(); +	auto end_time = steady_clock::now(); + +	// Check the delta time +	duration_t delta_time = loop_timer.get_delta_time(); + +	auto elapsed_time = duration_cast<seconds>(end_time - start_time).count(); + +	// Assert that delta_time is close to the elapsed time +	ASSERT_NEAR(delta_time.count(), elapsed_time, 1); +} + +TEST_F(LoopTimerTest, DISABLED_getCurrentTime) { +	// Set the target FPS to 60 (16.67 ms per frame) +	loop_timer.set_target_framerate(60); + +	auto start_time = steady_clock::now(); + +	// Sleep +	std::this_thread::sleep_for(std::chrono::milliseconds(100)); + +	loop_timer.update(); + +	auto end_time = steady_clock::now(); + +	// Get the elapsed time in seconds as a double +	auto elapsed_time +		= std::chrono::duration_cast<elapsed_time_t>(end_time - start_time).count(); + +	ASSERT_NEAR(loop_timer.get_elapsed_time().count(), elapsed_time, 5); +} +TEST_F(LoopTimerTest, getFPS) { +	// Set the target FPS to 60 (which gives a target time per frame of ~16.67 ms) +	loop_timer.set_target_framerate(60); + +	auto start_time = steady_clock::now(); +	loop_timer.enforce_frame_rate(); + +	auto elapsed_time = steady_clock::now() - start_time; +	loop_timer.update(); +	unsigned int fps = loop_timer.get_fps(); +	auto elapsed_ms = duration_cast<milliseconds>(elapsed_time).count(); + +	// For 60 FPS, the target frame time is around 16.67ms +	ASSERT_NEAR(elapsed_ms, 16.7, 1); +	ASSERT_NEAR(fps, 60, 2); +} diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index a659fe5..9112a3f 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -1,10 +1,10 @@ +#include "api/Asset.h"  #include <crepe/Particle.h>  #include <crepe/api/Config.h>  #include <crepe/api/GameObject.h>  #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/manager/ComponentManager.h>  #include <crepe/system/ParticleSystem.h> @@ -30,9 +30,13 @@ public:  			GameObject game_object = mgr.new_object("", "", vec2{0, 0}, 0, 0);  			Color color(0, 0, 0, 0); -			auto s1 = Texture("asset/texture/img.png"); +			auto s1 = Asset("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 43d2931..3afb3c7 100644 --- a/src/test/PhysicsTest.cpp +++ b/src/test/PhysicsTest.cpp @@ -3,6 +3,8 @@  #include <crepe/api/Rigidbody.h>  #include <crepe/api/Transform.h>  #include <crepe/manager/ComponentManager.h> +#include <crepe/manager/LoopTimerManager.h> +#include <crepe/manager/Mediator.h>  #include <crepe/system/PhysicsSystem.h>  #include <gtest/gtest.h> @@ -16,6 +18,7 @@ class PhysicsTest : public ::testing::Test {  public:  	ComponentManager component_manager{m};  	PhysicsSystem system{m}; +	LoopTimerManager loop_timer{m};  	void SetUp() override {  		ComponentManager & mgr = this->component_manager; @@ -27,7 +30,7 @@ public:  				.mass = 1,  				.gravity_scale = 1,  				.body_type = Rigidbody::BodyType::DYNAMIC, -				.max_linear_velocity = vec2{10, 10}, +				.max_linear_velocity = 10,  				.max_angular_velocity = 10,  				.constraints = {0, 0},  			}); @@ -55,39 +58,40 @@ TEST_F(PhysicsTest, gravity) {  	EXPECT_EQ(transform.position.y, 0);  	system.update(); -	EXPECT_EQ(transform.position.y, 1); +	EXPECT_NEAR(transform.position.y, 0.0004, 0.0001);  	system.update(); -	EXPECT_EQ(transform.position.y, 3); +	EXPECT_NEAR(transform.position.y, 0.002, 0.001);  }  TEST_F(PhysicsTest, max_velocity) {  	ComponentManager & mgr = this->component_manager;  	vector<reference_wrapper<Rigidbody>> rigidbodies = mgr.get_components_by_id<Rigidbody>(0);  	Rigidbody & rigidbody = rigidbodies.front().get(); +	rigidbody.data.gravity_scale = 0;  	ASSERT_FALSE(rigidbodies.empty());  	EXPECT_EQ(rigidbody.data.linear_velocity.y, 0);  	rigidbody.add_force_linear({100, 100});  	rigidbody.add_force_angular(100);  	system.update(); -	EXPECT_EQ(rigidbody.data.linear_velocity.y, 10); -	EXPECT_EQ(rigidbody.data.linear_velocity.x, 10); +	EXPECT_NEAR(rigidbody.data.linear_velocity.y, 7.07, 0.01); +	EXPECT_NEAR(rigidbody.data.linear_velocity.x, 7.07, 0.01);  	EXPECT_EQ(rigidbody.data.angular_velocity, 10);  	rigidbody.add_force_linear({-100, -100});  	rigidbody.add_force_angular(-100);  	system.update(); -	EXPECT_EQ(rigidbody.data.linear_velocity.y, -10); -	EXPECT_EQ(rigidbody.data.linear_velocity.x, -10); +	EXPECT_NEAR(rigidbody.data.linear_velocity.y, -7.07, 0.01); +	EXPECT_NEAR(rigidbody.data.linear_velocity.x, -7.07, 0.01);  	EXPECT_EQ(rigidbody.data.angular_velocity, -10);  }  TEST_F(PhysicsTest, movement) { -	Config::get_instance().physics.gravity = 0;  	ComponentManager & mgr = this->component_manager;  	vector<reference_wrapper<Rigidbody>> rigidbodies = mgr.get_components_by_id<Rigidbody>(0);  	Rigidbody & rigidbody = rigidbodies.front().get(); +	rigidbody.data.gravity_scale = 0;  	vector<reference_wrapper<Transform>> transforms = mgr.get_components_by_id<Transform>(0);  	const Transform & transform = transforms.front().get();  	ASSERT_FALSE(rigidbodies.empty()); @@ -96,31 +100,33 @@ TEST_F(PhysicsTest, movement) {  	rigidbody.add_force_linear({1, 1});  	rigidbody.add_force_angular(1);  	system.update(); -	EXPECT_EQ(transform.position.x, 1); -	EXPECT_EQ(transform.position.y, 1); -	EXPECT_EQ(transform.rotation, 1); +	EXPECT_NEAR(transform.position.x, 0.02, 0.001); +	EXPECT_NEAR(transform.position.y, 0.02, 0.001); +	EXPECT_NEAR(transform.rotation, 0.02, 0.001);  	rigidbody.data.constraints = {1, 1, 1}; -	EXPECT_EQ(transform.position.x, 1); -	EXPECT_EQ(transform.position.y, 1); -	EXPECT_EQ(transform.rotation, 1); - +	EXPECT_NEAR(transform.position.x, 0.02, 0.001); +	EXPECT_NEAR(transform.position.y, 0.02, 0.001); +	EXPECT_NEAR(transform.rotation, 0.02, 0.001); +	rigidbody.data.constraints = {0, 0, 0};  	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); +	EXPECT_NEAR(rigidbody.data.linear_velocity.x, 0.98, 0.01); +	EXPECT_NEAR(rigidbody.data.linear_velocity.y, 0.98, 0.01); +	EXPECT_NEAR(rigidbody.data.angular_velocity, 0.98, 0.01);  	rigidbody.data.constraints = {1, 1, 0};  	rigidbody.data.angular_velocity_coefficient = 0;  	rigidbody.data.max_angular_velocity = 1000;  	rigidbody.data.angular_velocity = 360;  	system.update(); -	EXPECT_EQ(transform.rotation, 1); +	EXPECT_NEAR(transform.rotation, 7.24, 0.01);  	rigidbody.data.angular_velocity = -360;  	system.update(); -	EXPECT_EQ(transform.rotation, 1); +	EXPECT_NEAR(transform.rotation, 0.04, 0.001); +	system.update(); +	EXPECT_NEAR(transform.rotation, 352.84, 0.01);  } diff --git a/src/test/Profiling.cpp b/src/test/Profiling.cpp index 24ac494..35f52dc 100644 --- a/src/test/Profiling.cpp +++ b/src/test/Profiling.cpp @@ -1,9 +1,11 @@ -#include "manager/Mediator.h" -#include "system/ParticleSystem.h" -#include "system/PhysicsSystem.h" -#include "system/RenderSystem.h"  #include <chrono>  #include <cmath> +#include <crepe/api/Asset.h> +#include <crepe/manager/Mediator.h> +#include <crepe/manager/ResourceManager.h> +#include <crepe/system/ParticleSystem.h> +#include <crepe/system/PhysicsSystem.h> +#include <crepe/system/RenderSystem.h>  #include <gtest/gtest.h>  #define private public @@ -15,6 +17,7 @@  #include <crepe/api/Rigidbody.h>  #include <crepe/api/Script.h>  #include <crepe/api/Transform.h> +#include <crepe/facade/SDLContext.h>  #include <crepe/manager/ComponentManager.h>  #include <crepe/manager/EventManager.h>  #include <crepe/system/CollisionSystem.h> @@ -41,7 +44,7 @@ class TestScript : public Script {  	}  }; -class Profiling : public Test { +class DISABLED_ProfilingTest : public Test {  public:  	// Config for test  	// Minimum amount to let test pass @@ -54,6 +57,8 @@ public:  	const std::chrono::microseconds duration = 16000us;  	Mediator m; +	SDLContext sdl_context{m}; +	ResourceManager resman{m};  	ComponentManager mgr{m};  	// Add system used for profling tests  	CollisionSystem collision_sys{m}; @@ -70,8 +75,11 @@ public:  	void SetUp() override {  		GameObject do_not_use = mgr.new_object("DO_NOT_USE", "", {0, 0}); -		do_not_use.add_component<Camera>(Color::WHITE, ivec2{1080, 720}, vec2{2000, 2000}, -										 1.0f); +		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(); @@ -127,7 +135,7 @@ public:  	}  }; -TEST_F(Profiling, Profiling_1) { +TEST_F(DISABLED_ProfilingTest, Profiling_1) {  	while (this->total_time / this->average < this->duration) {  		{ @@ -150,7 +158,7 @@ TEST_F(Profiling, Profiling_1) {  	EXPECT_GE(this->game_object_count, this->min_gameobject_count);  } -TEST_F(Profiling, Profiling_2) { +TEST_F(DISABLED_ProfilingTest, Profiling_2) {  	while (this->total_time / this->average < this->duration) {  		{ @@ -164,10 +172,15 @@ TEST_F(Profiling, Profiling_2) {  			gameobject.add_component<BoxCollider>(vec2{0, 0}, vec2{1, 1});  			gameobject.add_component<BehaviorScript>().set_script<TestScript>(); -			Color color(0, 0, 0, 0); -			auto img = Texture("asset/texture/square.png");  			Sprite & test_sprite = gameobject.add_component<Sprite>( -				img, color, Sprite::FlipSettings{false, false}, 1, 1, 500); +				Asset{"asset/texture/square.png"}, +				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++; @@ -184,7 +197,7 @@ TEST_F(Profiling, Profiling_2) {  	EXPECT_GE(this->game_object_count, this->min_gameobject_count);  } -TEST_F(Profiling, Profiling_3) { +TEST_F(DISABLED_ProfilingTest, Profiling_3) {  	while (this->total_time / this->average < this->duration) {  		{ @@ -197,10 +210,15 @@ TEST_F(Profiling, Profiling_3) {  			});  			gameobject.add_component<BoxCollider>(vec2{0, 0}, vec2{1, 1});  			gameobject.add_component<BehaviorScript>().set_script<TestScript>(); -			Color color(0, 0, 0, 0); -			auto img = Texture("asset/texture/square.png");  			Sprite & test_sprite = gameobject.add_component<Sprite>( -				img, color, Sprite::FlipSettings{false, false}, 1, 1, 500); +				Asset{"asset/texture/square.png"}, +				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, diff --git a/src/test/RenderSystemTest.cpp b/src/test/RenderSystemTest.cpp index c105dcb..b4519cb 100644 --- a/src/test/RenderSystemTest.cpp +++ b/src/test/RenderSystemTest.cpp @@ -1,3 +1,7 @@ +#include "api/Asset.h" +#include "facade/SDLContext.h" +#include "manager/ResourceManager.h" +#include "types.h"  #include <functional>  #include <gtest/gtest.h>  #include <memory> @@ -10,7 +14,6 @@  #include <crepe/api/Color.h>  #include <crepe/api/GameObject.h>  #include <crepe/api/Sprite.h> -#include <crepe/api/Texture.h>  #include <crepe/manager/ComponentManager.h>  #include <crepe/system/RenderSystem.h> @@ -24,6 +27,8 @@ class RenderSystemTest : public Test {  public:  	ComponentManager mgr{m}; +	SDLContext ctx{m}; +	ResourceManager resource_manager{m};  	RenderSystem sys{m};  	GameObject entity1 = this->mgr.new_object("name");  	GameObject entity2 = this->mgr.new_object("name"); @@ -31,45 +36,54 @@ public:  	GameObject entity4 = this->mgr.new_object("name");  	void SetUp() override { -		auto s1 = Texture("asset/texture/img.png"); -		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 s1 = Asset("asset/texture/img.png"); +		auto s2 = Asset("asset/texture/img.png"); +		auto s3 = Asset("asset/texture/img.png"); +		auto s4 = Asset("asset/texture/img.png"); +		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}, +												}); + +		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, +												}); +		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, +												}); +		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, +												}); +		EXPECT_EQ(sprite4.data.sorting_in_layer, 1); +		EXPECT_EQ(sprite4.data.order_in_layer, 1);  	}  }; -TEST_F(RenderSystemTest, expected_throws) { -	GameObject entity1 = this->mgr.new_object("NAME"); - -	// 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); -	}); - +TEST_F(RenderSystemTest, NoCamera) {  	// No camera  	EXPECT_ANY_THROW({ this->sys.update(); });  } @@ -89,32 +103,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 +157,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 +167,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);  } diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp new file mode 100644 index 0000000..965eeab --- /dev/null +++ b/src/test/ResourceManagerTest.cpp @@ -0,0 +1,84 @@ +#include "manager/Mediator.h" +#include <gtest/gtest.h> + +#define private public +#define protected public + +#include <crepe/api/GameObject.h> +#include <crepe/manager/ResourceManager.h> +#include <crepe/util/Log.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class ResourceManagerTest : public Test { +	Mediator mediator; + +public: +	ResourceManager resource_manager{mediator}; + +	class Unrelated : public Resource { +		using Resource::Resource; +	}; + +	Asset asset_a{"asset/texture/img.png"}; +	Asset asset_b{"asset/texture/ERROR.png"}; + +	class TestResource : public Resource { +	public: +		static unsigned instances; + +	public: +		const unsigned instance; +		TestResource(const Asset & src, Mediator & mediator) +			: Resource(src, mediator), +			  instance(this->instances++) {} +		~TestResource() { this->instances--; } +		bool operator==(const TestResource & other) const { +			return this->instance == other.instance; +		} +	}; + +private: +	void SetUp() override { TestResource::instances = 0; } +}; +unsigned ResourceManagerTest::TestResource::instances = 0; + +TEST_F(ResourceManagerTest, Default) { +	TestResource & res_1 = resource_manager.get<TestResource>(asset_a); +	TestResource & res_2 = resource_manager.get<TestResource>(asset_a); +	TestResource & res_3 = resource_manager.get<TestResource>(asset_b); +	TestResource & res_4 = resource_manager.get<TestResource>(asset_b); + +	ASSERT_EQ(res_1, res_2); +	ASSERT_EQ(res_3, res_4); +	EXPECT_NE(res_1, res_3); + +	EXPECT_EQ(TestResource::instances, 2); + +	resource_manager.clear(); +} + +TEST_F(ResourceManagerTest, Persistent) { +	resource_manager.set_persistent(asset_a, true); +	EXPECT_EQ(TestResource::instances, 0); + +	resource_manager.get<TestResource>(asset_a); +	resource_manager.get<TestResource>(asset_a); +	resource_manager.get<TestResource>(asset_b); +	resource_manager.get<TestResource>(asset_b); +	EXPECT_EQ(TestResource::instances, 2); + +	resource_manager.clear(); +	EXPECT_EQ(TestResource::instances, 1); + +	resource_manager.clear_all(); +	EXPECT_EQ(TestResource::instances, 0); +} + +TEST_F(ResourceManagerTest, UnmatchedType) { +	EXPECT_NO_THROW({ resource_manager.get<TestResource>(asset_a); }); + +	EXPECT_THROW({ resource_manager.get<Unrelated>(asset_a); }, runtime_error); +} diff --git a/src/test/SaveManagerTest.cpp b/src/test/SaveManagerTest.cpp new file mode 100644 index 0000000..e9b0c29 --- /dev/null +++ b/src/test/SaveManagerTest.cpp @@ -0,0 +1,40 @@ +#include <gtest/gtest.h> + +#include <crepe/ValueBroker.h> +#include <crepe/facade/DB.h> +#include <crepe/manager/SaveManager.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class SaveManagerTest : public Test { +	Mediator m; +	class TestSaveManager : public SaveManager { +		using SaveManager::SaveManager; + +		// in-memory database for testing +		DB db{}; +		virtual DB & get_db() override { return this->db; } +	}; + +public: +	TestSaveManager mgr{m}; +}; + +TEST_F(SaveManagerTest, ReadWrite) { +	ASSERT_FALSE(mgr.has("foo")); +	mgr.set<string>("foo", "bar"); +	ASSERT_TRUE(mgr.has("foo")); + +	ValueBroker value = mgr.get<string>("foo"); +	EXPECT_EQ(value.get(), "bar"); +} + +TEST_F(SaveManagerTest, DefaultValue) { +	ValueBroker value = mgr.get<int>("foo", 3); + +	ASSERT_EQ(value.get(), 3); +	value.set(5); +	ASSERT_EQ(value.get(), 5); +} diff --git a/src/test/ScriptECSTest.cpp b/src/test/ScriptECSTest.cpp new file mode 100644 index 0000000..1ec33ba --- /dev/null +++ b/src/test/ScriptECSTest.cpp @@ -0,0 +1,41 @@ +#include <gtest/gtest.h> + +#define protected public + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Script.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/system/ScriptSystem.h> + +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptECSTest : public ScriptTest { +public: +	class TestComponent : public Component { +		using Component::Component; +	}; +}; + +TEST_F(ScriptECSTest, GetOwnComponent) { +	MyScript & script = this->script; +	Metadata & metadata = script.get_component<Metadata>(); + +	EXPECT_EQ(metadata.name, OBJ_NAME); +} + +TEST_F(ScriptECSTest, GetOwnComponents) { +	const unsigned COUNT = 4; + +	for (unsigned i = 0; i < COUNT; i++) entity.add_component<TestComponent>(); + +	MyScript & script = this->script; +	RefVector<TestComponent> components = script.get_components<TestComponent>(); + +	EXPECT_EQ(components.size(), COUNT); +} diff --git a/src/test/ScriptEventTest.cpp b/src/test/ScriptEventTest.cpp index 5da31e7..c1b4028 100644 --- a/src/test/ScriptEventTest.cpp +++ b/src/test/ScriptEventTest.cpp @@ -26,7 +26,7 @@ public:  	class MyEvent : public Event {};  }; -TEST_F(ScriptEventTest, Inactive) { +TEST_F(ScriptEventTest, Default) {  	BehaviorScript & behaviorscript = this->behaviorscript;  	MyScript & script = this->script;  	EventManager & evmgr = this->event_manager; diff --git a/src/test/ScriptSaveManagerTest.cpp b/src/test/ScriptSaveManagerTest.cpp new file mode 100644 index 0000000..64403c4 --- /dev/null +++ b/src/test/ScriptSaveManagerTest.cpp @@ -0,0 +1,35 @@ +#include <gtest/gtest.h> + +// stupid hack to allow access to private/protected members under test +#define private public +#define protected public + +#include <crepe/facade/DB.h> +#include <crepe/manager/SaveManager.h> + +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptSaveManagerTest : public ScriptTest { +public: +	class TestSaveManager : public SaveManager { +		using SaveManager::SaveManager; + +		// in-memory database for testing +		DB db{}; +		virtual DB & get_db() override { return this->db; } +	}; + +	TestSaveManager save_mgr{mediator}; +}; + +TEST_F(ScriptSaveManagerTest, GetSaveManager) { +	MyScript & script = this->script; + +	SaveManager & mgr = script.get_save_manager(); + +	EXPECT_EQ(&mgr, &save_mgr); +} diff --git a/src/test/ScriptSceneTest.cpp b/src/test/ScriptSceneTest.cpp index 9ee1e52..2568049 100644 --- a/src/test/ScriptSceneTest.cpp +++ b/src/test/ScriptSceneTest.cpp @@ -18,7 +18,7 @@ public:  	class MyScene : public Scene {};  }; -TEST_F(ScriptSceneTest, Inactive) { +TEST_F(ScriptSceneTest, Default) {  	BehaviorScript & behaviorscript = this->behaviorscript;  	MyScript & script = this->script; diff --git a/src/test/ScriptTest.cpp b/src/test/ScriptTest.cpp index 1d2d6dd..acdae70 100644 --- a/src/test/ScriptTest.cpp +++ b/src/test/ScriptTest.cpp @@ -6,7 +6,6 @@  #define protected public  #include "ScriptTest.h" -#include <crepe/api/GameObject.h>  using namespace std;  using namespace crepe; @@ -14,7 +13,6 @@ using namespace testing;  void ScriptTest::SetUp() {  	auto & mgr = this->component_manager; -	GameObject entity = mgr.new_object("name");  	BehaviorScript & component = entity.add_component<BehaviorScript>();  	this->behaviorscript = component; diff --git a/src/test/ScriptTest.h b/src/test/ScriptTest.h index 1bbfdd3..31fa7c9 100644 --- a/src/test/ScriptTest.h +++ b/src/test/ScriptTest.h @@ -6,15 +6,18 @@  #include <crepe/api/BehaviorScript.h>  #include <crepe/api/Script.h>  #include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h>  #include <crepe/system/ScriptSystem.h> -  class ScriptTest : public testing::Test {  protected:  	crepe::Mediator mediator; +	static constexpr const char * OBJ_NAME = "foo";  public:  	crepe::ComponentManager component_manager{mediator};  	crepe::ScriptSystem system{mediator}; +	crepe::EventManager event_mgr{mediator}; +	crepe::GameObject entity = component_manager.new_object(OBJ_NAME);  	class MyScript : public crepe::Script {  		// NOTE: explicitly stating `public:` is not required on actual scripts diff --git a/src/test/Vector2Test.cpp b/src/test/Vector2Test.cpp index 17bca41..1e21af9 100644 --- a/src/test/Vector2Test.cpp +++ b/src/test/Vector2Test.cpp @@ -382,3 +382,151 @@ TEST_F(Vector2Test, NotEquals) {  	EXPECT_FALSE(long_vec1 != long_vec1);  	EXPECT_TRUE(long_vec1 != long_vec2);  } + +TEST_F(Vector2Test, Truncate) { +	Vector2<int> vec = {3, 4}; +	vec.truncate(3); +	EXPECT_EQ(vec.x, 0); +	EXPECT_EQ(vec.y, 0); + +	Vector2<double> vec2 = {3.0, 4.0}; +	vec2.truncate(3.0); +	EXPECT_FLOAT_EQ(vec2.x, 1.8); +	EXPECT_FLOAT_EQ(vec2.y, 2.4); + +	Vector2<long> vec3 = {3, 4}; +	vec3.truncate(3); +	EXPECT_EQ(vec3.x, 0); +	EXPECT_EQ(vec3.y, 0); + +	Vector2<float> vec4 = {3.0f, 4.0f}; +	vec4.truncate(3.0f); +	EXPECT_FLOAT_EQ(vec4.x, 1.8f); +	EXPECT_FLOAT_EQ(vec4.y, 2.4f); +} + +TEST_F(Vector2Test, Normalize) { +	Vector2<int> vec = {3, 4}; +	vec.normalize(); +	EXPECT_EQ(vec.x, 0); +	EXPECT_EQ(vec.y, 0); + +	Vector2<double> vec2 = {3.0, 4.0}; +	vec2.normalize(); +	EXPECT_FLOAT_EQ(vec2.x, 0.6); +	EXPECT_FLOAT_EQ(vec2.y, 0.8); + +	Vector2<long> vec3 = {3, 4}; +	vec3.normalize(); +	EXPECT_EQ(vec3.x, 0); +	EXPECT_EQ(vec3.y, 0); + +	Vector2<float> vec4 = {3.0f, 4.0f}; +	vec4.normalize(); +	EXPECT_FLOAT_EQ(vec4.x, 0.6f); +	EXPECT_FLOAT_EQ(vec4.y, 0.8f); +} + +TEST_F(Vector2Test, Length) { +	Vector2<int> vec = {3, 4}; +	EXPECT_EQ(vec.length(), 5); + +	Vector2<double> vec2 = {3.0, 4.0}; +	EXPECT_FLOAT_EQ(vec2.length(), 5.0); + +	Vector2<long> vec3 = {3, 4}; +	EXPECT_EQ(vec3.length(), 5); + +	Vector2<float> vec4 = {3.0f, 4.0f}; +	EXPECT_FLOAT_EQ(vec4.length(), 5.0f); +} + +TEST_F(Vector2Test, LengthSquared) { +	Vector2<int> vec = {3, 4}; +	EXPECT_EQ(vec.length_squared(), 25); + +	Vector2<double> vec2 = {3.0, 4.0}; +	EXPECT_FLOAT_EQ(vec2.length_squared(), 25.0); + +	Vector2<long> vec3 = {3, 4}; +	EXPECT_EQ(vec3.length_squared(), 25); + +	Vector2<float> vec4 = {3.0f, 4.0f}; +	EXPECT_FLOAT_EQ(vec4.length_squared(), 25.0f); +} + +TEST_F(Vector2Test, Dot) { +	Vector2<int> vec1 = {3, 4}; +	Vector2<int> vec2 = {5, 6}; +	EXPECT_EQ(vec1.dot(vec2), 39); + +	Vector2<double> vec3 = {3.0, 4.0}; +	Vector2<double> vec4 = {5.0, 6.0}; +	EXPECT_FLOAT_EQ(vec3.dot(vec4), 39.0); + +	Vector2<long> vec5 = {3, 4}; +	Vector2<long> vec6 = {5, 6}; +	EXPECT_EQ(vec5.dot(vec6), 39); + +	Vector2<float> vec7 = {3.0f, 4.0f}; +	Vector2<float> vec8 = {5.0f, 6.0f}; +	EXPECT_FLOAT_EQ(vec7.dot(vec8), 39.0f); +} + +TEST_F(Vector2Test, Distance) { +	Vector2<int> vec1 = {1, 1}; +	Vector2<int> vec2 = {4, 5}; +	EXPECT_EQ(vec1.distance(vec2), 5); + +	Vector2<double> vec3 = {1.0, 1.0}; +	Vector2<double> vec4 = {4.0, 5.0}; +	EXPECT_FLOAT_EQ(vec3.distance(vec4), 5.0); + +	Vector2<long> vec5 = {1, 1}; +	Vector2<long> vec6 = {4, 5}; +	EXPECT_EQ(vec5.distance(vec6), 5); + +	Vector2<float> vec7 = {1.0f, 1.0f}; +	Vector2<float> vec8 = {4.0f, 5.0f}; +	EXPECT_FLOAT_EQ(vec7.distance(vec8), 5.0f); +} + +TEST_F(Vector2Test, DistanceSquared) { +	Vector2<int> vec1 = {3, 4}; +	Vector2<int> vec2 = {5, 6}; +	EXPECT_EQ(vec1.distance_squared(vec2), 8); + +	Vector2<double> vec3 = {3.0, 4.0}; +	Vector2<double> vec4 = {5.0, 6.0}; +	EXPECT_FLOAT_EQ(vec3.distance_squared(vec4), 8.0); + +	Vector2<long> vec5 = {3, 4}; +	Vector2<long> vec6 = {5, 6}; +	EXPECT_EQ(vec5.distance_squared(vec6), 8); + +	Vector2<float> vec7 = {3.0f, 4.0f}; +	Vector2<float> vec8 = {5.0f, 6.0f}; +	EXPECT_FLOAT_EQ(vec7.distance_squared(vec8), 8.0f); +} + +TEST_F(Vector2Test, Perpendicular) { +	Vector2<int> vec = {3, 4}; +	Vector2<int> result = vec.perpendicular(); +	EXPECT_EQ(result.x, -4); +	EXPECT_EQ(result.y, 3); + +	Vector2<double> vec2 = {3.0, 4.0}; +	Vector2<double> result2 = vec2.perpendicular(); +	EXPECT_FLOAT_EQ(result2.x, -4.0); +	EXPECT_FLOAT_EQ(result2.y, 3.0); + +	Vector2<long> vec3 = {3, 4}; +	Vector2<long> result3 = vec3.perpendicular(); +	EXPECT_EQ(result3.x, -4); +	EXPECT_EQ(result3.y, 3); + +	Vector2<float> vec4 = {3.0f, 4.0f}; +	Vector2<float> result4 = vec4.perpendicular(); +	EXPECT_FLOAT_EQ(result4.x, -4.0f); +	EXPECT_FLOAT_EQ(result4.y, 3.0f); +} diff --git a/src/test/main.cpp b/src/test/main.cpp index aece72d..ed2aed5 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -1,9 +1,5 @@ -#include <gtest/gtest.h> - -#define protected public -#define private public -  #include <crepe/api/Config.h> +#include <gtest/gtest.h>  using namespace crepe;  using namespace testing; @@ -11,12 +7,14 @@ using namespace testing;  class GlobalConfigReset : public EmptyTestEventListener {  public:  	Config & cfg = Config::get_instance(); -	Config cfg_default = Config();  	// This function is called before each test  	void OnTestStart(const TestInfo &) override { -		cfg = cfg_default; -		cfg.log.level = Log::Level::WARNING; +		cfg = { +			.log = { +				.level = Log::Level::WARNING, +			}, +		};  	}  }; |