diff options
95 files changed, 2887 insertions, 1360 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/Component.h b/src/crepe/Component.h index c30419d..eff5a58 100644 --- a/src/crepe/Component.h +++ b/src/crepe/Component.h @@ -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 b8a91dc..4ce4bf0 100644 --- a/src/crepe/api/Animator.cpp +++ b/src/crepe/api/Animator.cpp @@ -7,23 +7,21 @@  using namespace crepe; -Animator::Animator(game_object_id_t id, Sprite & spritesheet, unsigned int max_row, -				   unsigned int max_col, const Animator::Data & data) +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(spritesheet), -	  max_rows(max_row), -	  max_columns(max_col), +	  grid_size(grid_size),  	  data(data) {  	dbg_trace(); -	this->spritesheet.mask.h /= this->max_columns; -	this->spritesheet.mask.w /= this->max_rows; -	this->spritesheet.mask.x = this->data.row * this->spritesheet.mask.w; -	this->spritesheet.mask.y = this->data.col * this->spritesheet.mask.h; +	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 = 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(); } @@ -54,6 +52,6 @@ void Animator::set_anim(int col) {  void Animator::next_anim() {  	Animator::Data & ctx = this->data; -	ctx.row = ctx.row++ % this->max_rows; +	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 7c850b8..5918800 100644 --- a/src/crepe/api/Animator.h +++ b/src/crepe/api/Animator.h @@ -75,27 +75,28 @@ public:  	 *  	 * \param id The unique identifier for the component, typically assigned automatically.  	 * \param spritesheet the reference to the spritesheet -	 * \param max_row maximum of rows inside the given spritesheet -	 * \param max_col maximum of columns inside the given spritesheet +	 * \param 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(game_object_id_t id, Sprite & spritesheet, unsigned int max_row, -			 unsigned int max_col, const Animator::Data & data); +	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: -	//! The maximum number of columns in the sprite sheet. -	const unsigned int max_columns; -	//! The maximum number of rows in the sprite sheet. -	const unsigned int max_rows;  	Animator::Data data;  private:  	//! A reference to the Sprite sheet containing.  	Sprite & spritesheet; + +	//! The maximum number of rows and columns inside the spritesheet +	const uvec2 grid_size; +  	//! Uses the spritesheet  	friend AnimatorSystem;  }; 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 3b1cc4b..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/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 593c4e6..fb11c8d 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,17 @@ 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  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES -	# AudioSource.h +	AudioSource.h  	BehaviorScript.h  	Config.h  	Script.h @@ -39,9 +35,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	Vector2.h  	Vector2.hpp  	Color.h -	Texture.h -	AssetManager.h -	AssetManager.hpp  	Scene.h  	Metadata.h  	Camera.h @@ -51,11 +44,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/Config.h b/src/crepe/api/Config.h index 324e639..925ded8 100644 --- a/src/crepe/api/Config.h +++ b/src/crepe/api/Config.h @@ -15,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 {  		/** @@ -91,6 +81,12 @@ public:  		//! The maximum number of pixels the mouse can move between MouseDown and MouseUp events to be considered a click.  		int click_tolerance = 5;  	} input; + +	//! Audio system settings +	struct { +		//! Max amount of simultanious voices +		unsigned int voices = 32; +	} audio;  };  } // namespace crepe 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 88243c4..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" @@ -20,58 +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..2915315 100644 --- a/src/crepe/api/LoopManager.h +++ b/src/crepe/api/LoopManager.h @@ -4,13 +4,16 @@  #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 +21,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 +53,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 +77,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/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 d99ab0e..668e5d1 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -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 cc0e20a..ba684ba 100644 --- a/src/crepe/api/Sprite.cpp +++ b/src/crepe/api/Sprite.cpp @@ -1,26 +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 & texture, const Sprite::Data & data) +Sprite::Sprite(game_object_id_t id, const Asset & texture, const Sprite::Data & data)  	: Component(id), -	  texture(std::move(texture)), +	  source(texture),  	  data(data) {  	dbg_trace(); - -	this->mask.w = this->texture.get_size().x; -	this->mask.h = this->texture.get_size().y; -	this->aspect_ratio = static_cast<double>(this->mask.w) / this->mask.h;  }  Sprite::~Sprite() { dbg_trace(); } diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h index dbf41e4..a2409c2 100644 --- a/src/crepe/api/Sprite.h +++ b/src/crepe/api/Sprite.h @@ -1,9 +1,9 @@  #pragma once  #include "../Component.h" +#include "api/Asset.h"  #include "Color.h" -#include "Texture.h"  #include "types.h"  namespace crepe { @@ -74,24 +74,15 @@ public:  	 * \param texture asset of the image  	 * \param ctx all the sprite data  	 */ -	Sprite(game_object_id_t id, Texture & texture, const Data & data); +	Sprite(game_object_id_t id, const Asset & texture, const Data & data);  	~Sprite();  	//! Texture used for the sprite -	const Texture texture; +	const Asset source;  	Data data;  private: -	/** -	 * \brief ratio of the img -	 * -	 * - This will multiply one of \c size variable if it is 0. -	 * - Will be adjusted if \c Animator component is added to an GameObject that is why this -	 *   value cannot be const. -	 */ -	float aspect_ratio; -  	//! Reads the mask of sprite  	friend class SDLContext; @@ -101,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/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 8f6c02c..4552605 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> @@ -18,23 +19,18 @@  #include "../api/Color.h"  #include "../api/Config.h"  #include "../api/Sprite.h" -#include "../api/Texture.h"  #include "../util/Log.h" +#include "manager/Mediator.h"  #include "SDLContext.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()));  	} @@ -62,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() { @@ -236,35 +234,26 @@ 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_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const {  	const Sprite::Data & data = ctx.sprite.data; +	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 * ctx.sprite.aspect_ratio; +		size.x = data.size.y * aspect_ratio;  	}  	if (data.size.y == 0 && data.size.x != 0) { -		size.y = data.size.x / ctx.sprite.aspect_ratio; +		size.y = data.size.x / aspect_ratio;  	} +	size *= cam_aux_data.render_scale * ctx.img_scale * data.scale_offset; -	const CameraValues & cam = ctx.cam; - -	size *= cam.render_scale * ctx.img_scale * data.scale_offset; - -	vec2 screen_pos -		= (ctx.pos + data.position_offset - cam.cam_pos + (cam.zoomed_viewport) / 2) -			  * cam.render_scale -		  - size / 2 + cam.bar_size; +	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, @@ -275,31 +264,38 @@ SDL_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const {  }  void SDLContext::draw(const RenderContext & ctx) { -  	const Sprite::Data & data = ctx.sprite.data;  	SDL_RendererFlip render_flip  		= (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * data.flip.flip_x)  							  | (SDL_FLIP_VERTICAL * data.flip.flip_y)); -	SDL_Rect srcrect = this->get_src_rect(ctx.sprite); +	SDL_Rect 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_FRect dstrect = this->get_dst_rect(SDLContext::DestinationRectangleData{  		.sprite = ctx.sprite, -		.cam = ctx.cam, +		.texture = ctx.texture,  		.pos = ctx.pos,  		.img_scale = ctx.scale,  	});  	double angle = ctx.angle + data.angle_offset; -	this->set_color_texture(ctx.sprite.texture, ctx.sprite.data.color); -	SDL_RenderCopyExF(this->game_renderer.get(), ctx.sprite.texture.texture.get(), &srcrect, -					  &dstrect, angle, NULL, render_flip); +	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);  } -SDLContext::CameraValues 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; -	CameraValues ret_cam;  	// resize window  	int w, h;  	SDL_GetWindowSize(this->game_window.get(), &w, &h); @@ -307,9 +303,10 @@ SDLContext::CameraValues SDLContext::set_camera(const Camera & cam) {  		SDL_SetWindowSize(this->game_window.get(), cam.screen.x, cam.screen.y);  	} -	vec2 & zoomed_viewport = ret_cam.zoomed_viewport; -	vec2 & bar_size = ret_cam.bar_size; -	vec2 & render_scale = ret_cam.render_scale; +	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; @@ -351,12 +348,8 @@ SDLContext::CameraValues SDLContext::set_camera(const Camera & cam) {  	// fill bg color  	SDL_RenderFillRect(this->game_renderer.get(), &bg); - -	return ret_cam;  } -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) { @@ -383,18 +376,17 @@ 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; - -	// Handle general SDL events  	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({SDLContext::EventType::SHUTDOWN, {}, {}, {}}); @@ -413,41 +405,38 @@ std::vector<SDLContext::EventData> SDLContext::get_events() {  									  {}});  				break;  			case SDL_MOUSEBUTTONDOWN: -				event_list.push_back({SDLContext::EventType::MOUSEDOWN, -									  {}, -									  {sdl_to_mousebutton(event.button.button), -									   {event.button.x, event.button.y}}, -									  {}}); +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::MOUSEDOWN, +					.mouse_button = sdl_to_mousebutton(event.button.button), +					.mouse_position = {event.button.x, event.button.y}, +				});  				break; -			case SDL_MOUSEBUTTONUP: -				event_list.push_back({SDLContext::EventType::MOUSEUP, -									  {}, -									  {sdl_to_mousebutton(event.button.button), -									   {event.button.x, event.button.y}}, -									  {}}); -				break; -			case SDL_MOUSEMOTION: -				event_list.push_back({SDLContext::EventType::MOUSEMOVE, -									  {}, -									  {{}, -									   {event.motion.x, event.motion.y}, -									   -1, -									   INFINITY, -									   {event.motion.xrel, event.motion.yrel}}, -									  {}}); -				break; -			case SDL_MOUSEWHEEL: +			case SDL_MOUSEBUTTONUP: { +				int x, y; +				SDL_GetMouseState(&x, &y); +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::MOUSEUP, +					.mouse_button = sdl_to_mousebutton(event.button.button), +					.mouse_position = {event.button.x, event.button.y}, +				}); +			} break; + +			case SDL_MOUSEMOTION: {  				event_list.push_back( -					{SDLContext::EventType::MOUSEWHEEL, -					 {}, -					 {{}, {}, event.wheel.y < 0 ? -1 : 1, event.wheel.preciseY, {}}, -					 {}}); -				break; - -			// Forward window events for further processing -			case SDL_WINDOWEVENT: -				this->handle_window_event(event.window, event_list); -				break; +					EventData{.event_type = SDLContext::EventType::MOUSEMOVE, +							  .mouse_position = {event.motion.x, event.motion.y}, +							  .rel_mouse_move = {event.motion.xrel, event.motion.yrel}}); +			} break; + +			case SDL_MOUSEWHEEL: { +				event_list.push_back(EventData{ +					.event_type = SDLContext::EventType::MOUSEWHEEL, +					.mouse_position = {event.motion.x, event.motion.y}, +					// TODO: why is this needed? +					.scroll_direction = event.wheel.y < 0 ? -1 : 1, +					.scroll_delta = event.wheel.preciseY, +				}); +			} break;  		}  	} @@ -489,6 +478,6 @@ void SDLContext::handle_window_event(const SDL_WindowEvent & window_event,  }  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 053aa59..0a2456d 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -15,16 +15,16 @@  #include "api/Color.h"  #include "api/KeyCodes.h"  #include "api/Sprite.h" -#include "api/Texture.h"  #include "api/Transform.h" +  #include "types.h"  namespace crepe { -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 @@ -33,7 +33,7 @@ class InputSystem;  class SDLContext {  public:  	//! data that the camera component cannot hold -	struct CameraValues { +	struct CameraAuxiliaryData {  		//! zoomed in viewport in game_units  		vec2 zoomed_viewport; @@ -63,7 +63,7 @@ public:  	//! rendering data needed to render on screen  	struct RenderContext {  		const Sprite & sprite; -		const CameraValues & cam; +		const Texture & texture;  		const vec2 & pos;  		const double & angle;  		const double & scale; @@ -129,14 +129,26 @@ public:  	 */  	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.  	 * @@ -177,9 +189,7 @@ private:  	 */  	MouseButton sdl_to_mousebutton(Uint8 sdl_button); -private: -	//! 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. @@ -195,23 +205,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. @@ -222,14 +216,11 @@ 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 RenderContext Reference to rendering data to draw @@ -243,35 +234,28 @@ private:  	void present_screen();  	/** -	 * \brief sets the background of the camera (will be adjusted in future PR) -	 * \param camera Reference to the Camera object. +	 * \brief calculates camera view settings. such as black_bars, zoomed_viewport, scaling and +	 * adjusting window size. +	 * +	 * \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  	 */ -	CameraValues set_camera(const Camera & camera); +	void update_camera_view(const Camera & camera, const vec2 & new_pos); -private: +public:  	//! the data needed to construct a sdl dst rectangle  	struct DestinationRectangleData {  		const Sprite & sprite; -		const CameraValues & cam; +		const Texture & texture;  		const vec2 & pos;  		const double & img_scale;  	}; -	/** -	 * \brief calculates the sqaure size of the image -	 * -	 * \param sprite Reference to the sprite to calculate the rectangle -	 * \return sdl rectangle to draw a src image -	 */ -	SDL_Rect get_src_rect(const Sprite & sprite) const;  	/**  	 * \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_FRect get_dst_rect(const DestinationRectangleData & data) const; @@ -292,6 +276,13 @@ private:  	//! 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 ee43d94..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 44429d9..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> @@ -134,8 +135,77 @@ public:  	 */  	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  	 * @@ -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 ba5e98b..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,27 +24,19 @@ 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.  	 * @@ -108,13 +102,6 @@ public:  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..9819632 --- /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 = 1.0 / duration_cast<seconds>(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..91403e4 --- /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<double> 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 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 target_fps = 60; +	//! Actual frames per second. +	unsigned 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 8094d80..a336410 100644 --- a/src/crepe/manager/Mediator.h +++ b/src/crepe/manager/Mediator.h @@ -2,16 +2,15 @@  #include "../util/OptionalRef.h" -// TODO: remove these singletons: -#include "../facade/SDLContext.h" -#include "EventManager.h" -#include "SaveManager.h" -#include "api/LoopTimer.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 @@ -26,12 +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<SDLContext> sdl_context = SDLContext::get_instance(); -	OptionalRef<LoopTimer> timer = LoopTimer::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..d231c7c --- /dev/null +++ b/src/crepe/system/AISystem.cpp @@ -0,0 +1,188 @@ +#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 & timer = mediator.loop_timer; +	RefVector<AI> ai_components = mgr.get_components_by_type<AI>(); +	LoopTimerManager & loop_timer = mediator.loop_timer; + +	//TODO: Use fixed loop dt (this is not available at master at the moment) +	duration_t dt = loop_timer.get_delta_time(); + +	// 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 * duration_cast<seconds>(dt).count(); +	} +} + +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.length()); +		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 549c35d..31eb85c 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -2,7 +2,7 @@  #include "../api/Animator.h"  #include "../manager/ComponentManager.h" -#include "api/LoopTimer.h" +#include "../manager/LoopTimerManager.h"  #include "AnimatorSystem.h" @@ -10,10 +10,10 @@ using namespace crepe;  void AnimatorSystem::update() {  	ComponentManager & mgr = this->mediator.component_manager; -	LoopTimer & timer = this->mediator.timer; +	LoopTimerManager & timer = this->mediator.loop_timer;  	RefVector<Animator> animations = mgr.get_components_by_type<Animator>(); -	double elapsed_time = timer.get_current_time(); +	unsigned long long elapsed_time = timer.get_elapsed_time().count();  	for (Animator & a : animations) {  		if (!a.active) continue; @@ -23,7 +23,7 @@ void AnimatorSystem::update() {  		int last_frame = ctx.row; -		int cycle_end = (ctx.cycle_end == -1) ? a.max_rows : ctx.cycle_end; +		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; 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/InputSystem.cpp b/src/crepe/system/InputSystem.cpp index e76b1ec..459feb3 100644 --- a/src/crepe/system/InputSystem.cpp +++ b/src/crepe/system/InputSystem.cpp @@ -2,6 +2,9 @@  #include "../api/Button.h"  #include "../manager/ComponentManager.h"  #include "../manager/EventManager.h" +#include "facade/SDLContext.h" +#include "util/Log.h" +  #include "InputSystem.h"  using namespace crepe; @@ -9,7 +12,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; diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index 26f2c85..afd9548 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -10,9 +10,12 @@  #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; @@ -27,7 +30,7 @@ void RenderSystem::present_screen() {  	ctx.present_screen();  } -SDLContext::CameraValues 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>(); @@ -38,9 +41,9 @@ SDLContext::CameraValues RenderSystem::update_camera() {  		if (!cam.active) continue;  		const Transform & transform  			= mgr.get_components_by_id<Transform>(cam.game_object_id).front().get(); -		SDLContext::CameraValues cam_val = ctx.set_camera(cam); -		cam_val.cam_pos = transform.position + cam.data.postion_offset; -		return cam_val; +		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");  } @@ -67,11 +70,12 @@ void RenderSystem::update() {  	this->present_screen();  } -bool RenderSystem::render_particle(const Sprite & sprite, const SDLContext::CameraValues & 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); @@ -88,7 +92,7 @@ bool RenderSystem::render_particle(const Sprite & sprite, const SDLContext::Came  			ctx.draw(SDLContext::RenderContext{  				.sprite = sprite, -				.cam = cam, +				.texture = res,  				.pos = p.position,  				.angle = p.angle,  				.scale = scale, @@ -97,12 +101,14 @@ bool RenderSystem::render_particle(const Sprite & sprite, const SDLContext::Came  	}  	return rendering_particles;  } -void RenderSystem::render_normal(const Sprite & sprite, const SDLContext::CameraValues & cam, -								 const Transform & tm) { +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, +		.texture = res,  		.pos = tm.position,  		.angle = tm.rotation,  		.scale = tm.scale, @@ -111,7 +117,7 @@ void RenderSystem::render_normal(const Sprite & sprite, const SDLContext::Camera  void RenderSystem::render() {  	ComponentManager & mgr = this->mediator.component_manager; -	const SDLContext::CameraValues & cam = this->update_camera(); +	this->update_camera();  	RefVector<Sprite> sprites = mgr.get_components_by_type<Sprite>();  	RefVector<Sprite> sorted_sprites = this->sort(sprites); @@ -121,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 e270a6b..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,7 +12,6 @@ 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 @@ -37,7 +34,7 @@ private:  	void present_screen();  	//! Updates the active camera used for rendering. -	SDLContext::CameraValues update_camera(); +	void update_camera();  	//! Renders the whole screen  	void render(); @@ -52,8 +49,7 @@ private:  	 *  constructor is now protected i cannot make tmp inside  	 * \return true if particles have been rendered  	 */ -	bool render_particle(const Sprite & sprite, const SDLContext::CameraValues & cam, -						 const double & scale); +	bool render_particle(const Sprite & sprite, const double & scale);  	/**  	 * \brief renders a sprite with a Transform component on the screen @@ -61,8 +57,7 @@ private:  	 * \param sprite  the sprite component that holds all the data  	 * \param tm the Transform component that holds the position,rotation and scale  	 */ -	void render_normal(const Sprite & sprite, const SDLContext::CameraValues & cam, -					   const Transform & tm); +	void render_normal(const Sprite & sprite, const Transform & tm);  	/**  	 * \brief sort a vector sprite objects with 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/example/AITest.cpp b/src/example/AITest.cpp new file mode 100644 index 0000000..f4efc9f --- /dev/null +++ b/src/example/AITest.cpp @@ -0,0 +1,86 @@ +#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/api/Texture.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); + +		Texture img = Texture("asset/texture/test_ap43.png"); +		game_object1.add_component<Sprite>(img, Sprite::Data{ +													.color = Color::MAGENTA, +													.flip = Sprite::FlipSettings{false, false}, +													.sorting_in_layer = 1, +													.order_in_layer = 1, +													.size = {0, 195}, +												}); +		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, 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/rendering_particle.cpp b/src/example/rendering_particle.cpp index 29d475d..13e625f 100644 --- a/src/example/rendering_particle.cpp +++ b/src/example/rendering_particle.cpp @@ -1,5 +1,7 @@ +#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> @@ -7,11 +9,11 @@  #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/manager/Mediator.h>  #include <crepe/types.h> +#include <iostream>  using namespace crepe;  using namespace std; @@ -41,13 +43,15 @@ using namespace std;  class TestScene : public Scene {  public:  	void load_scene() { + +		cout << "TestScene" << endl;  		Mediator & mediator = this->mediator;  		ComponentManager & mgr = mediator.component_manager;  		GameObject game_object = mgr.new_object("", "", vec2{0, 0}, 0, 1);  		Color color(255, 255, 255, 255); -		auto img = Texture("asset/spritesheet/pokemon_spritesheet.png"); +		Asset img{"asset/spritesheet/spritesheet_test.png"};  		Sprite & test_sprite = game_object.add_component<Sprite>(  			img, Sprite::Data{ @@ -57,21 +61,27 @@ public:  					 .order_in_layer = 2,  					 .size = {0, 100},  					 .angle_offset = 0, -					 .position_offset = {100, 0}, +					 .position_offset = {0, 0},  				 }); -		auto & anim = game_object.add_component<Animator>(test_sprite, 4, 4, -														  Animator::Data{ -															  .fps = 1, -															  .looping = false, -														  }); -		anim.set_anim(2); -		anim.active = false; +		//auto & 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{1280, 720}, vec2{400, 400}, +		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"; }; 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 c9cbac5..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 +	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 ef7fc10..f57a6de 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,8 +53,7 @@ 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_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE};  	EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, @@ -87,18 +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_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE};  	EventManager::get_instance().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; @@ -127,7 +110,7 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) {  	event_manager.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); @@ -136,12 +119,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); @@ -149,47 +132,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; @@ -212,15 +185,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>(  		MouseClickEvent{.mouse_pos = {100, 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 @@ -229,14 +202,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>(  		MouseClickEvent{.mouse_pos = {100, 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 @@ -244,14 +217,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>(  		MouseClickEvent{.mouse_pos = {100, 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 a7058b2..a6e60b5 100644 --- a/src/test/InputTest.cpp +++ b/src/test/InputTest.cpp @@ -24,10 +24,10 @@ 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(); +	EventManager event_manager{mediator};  	//GameObject camera;  protected: diff --git a/src/test/LoopManagerTest.cpp b/src/test/LoopManagerTest.cpp new file mode 100644 index 0000000..af89d64 --- /dev/null +++ b/src/test/LoopManagerTest.cpp @@ -0,0 +1,76 @@ +#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 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(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(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(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..5e1eccf --- /dev/null +++ b/src/test/LoopTimerTest.cpp @@ -0,0 +1,78 @@ +#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, 1); +} + +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, 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); +} diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index 1409c4f..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,7 +30,7 @@ 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, Sprite::Data{  						.color = color, diff --git a/src/test/Profiling.cpp b/src/test/Profiling.cpp index c753bca..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> @@ -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}; @@ -167,15 +172,15 @@ TEST_F(DISABLED_ProfilingTest, Profiling_2) {  			gameobject.add_component<BoxCollider>(vec2{0, 0}, vec2{1, 1});  			gameobject.add_component<BehaviorScript>().set_script<TestScript>(); -			auto img = Texture("asset/texture/square.png");  			Sprite & test_sprite = gameobject.add_component<Sprite>( -				img, Sprite::Data{ -						 .color = {0, 0, 0, 0}, -						 .flip = {.flip_x = false, .flip_y = false}, -						 .sorting_in_layer = 1, -						 .order_in_layer = 1, -						 .size = {.y = 500}, -					 }); +				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++; @@ -205,15 +210,15 @@ TEST_F(DISABLED_ProfilingTest, Profiling_3) {  			});  			gameobject.add_component<BoxCollider>(vec2{0, 0}, vec2{1, 1});  			gameobject.add_component<BehaviorScript>().set_script<TestScript>(); -			auto img = Texture("asset/texture/square.png");  			Sprite & test_sprite = gameobject.add_component<Sprite>( -				img, Sprite::Data{ -						 .color = {0, 0, 0, 0}, -						 .flip = {.flip_x = false, .flip_y = false}, -						 .sorting_in_layer = 1, -						 .order_in_layer = 1, -						 .size = {.y = 500}, -					 }); +				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 205f534..b4519cb 100644 --- a/src/test/RenderSystemTest.cpp +++ b/src/test/RenderSystemTest.cpp @@ -1,3 +1,6 @@ +#include "api/Asset.h" +#include "facade/SDLContext.h" +#include "manager/ResourceManager.h"  #include "types.h"  #include <functional>  #include <gtest/gtest.h> @@ -11,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> @@ -25,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"); @@ -32,10 +36,10 @@ 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 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), @@ -45,7 +49,6 @@ public:  													.size = {10, 10},  												}); -		ASSERT_NE(sprite1.texture.texture.get(), nullptr);  		EXPECT_EQ(sprite1.data.order_in_layer, 5);  		EXPECT_EQ(sprite1.data.sorting_in_layer, 5);  		auto & sprite2 @@ -55,7 +58,6 @@ public:  													.sorting_in_layer = 2,  													.order_in_layer = 1,  												}); -		ASSERT_NE(sprite2.texture.texture.get(), nullptr);  		EXPECT_EQ(sprite2.data.sorting_in_layer, 2);  		EXPECT_EQ(sprite2.data.order_in_layer, 1); @@ -66,7 +68,6 @@ public:  													.sorting_in_layer = 1,  													.order_in_layer = 2,  												}); -		ASSERT_NE(sprite3.texture.texture.get(), nullptr);  		EXPECT_EQ(sprite3.data.sorting_in_layer, 1);  		EXPECT_EQ(sprite3.data.order_in_layer, 2); @@ -77,27 +78,12 @@ public:  													.sorting_in_layer = 1,  													.order_in_layer = 1,  												}); -		ASSERT_NE(sprite4.texture.texture.get(), nullptr);  		EXPECT_EQ(sprite4.data.sorting_in_layer, 1);  		EXPECT_EQ(sprite4.data.order_in_layer, 1);  	}  }; -TEST_F(RenderSystemTest, expected_throws) { -	GameObject entity1 = this->mgr.new_object("NAME"); - -	// no texture img -	EXPECT_ANY_THROW({ -		auto test = Texture(""); -		auto & sprite1 = entity1.add_component<Sprite>( -			test, Sprite::Data{ -					  .color = Color(0, 0, 0, 0), -					  .flip = Sprite::FlipSettings{false, false}, -					  .sorting_in_layer = 1, -					  .order_in_layer = 1, -				  }); -	}); - +TEST_F(RenderSystemTest, NoCamera) {  	// No camera  	EXPECT_ANY_THROW({ this->sys.update(); });  } @@ -185,7 +171,7 @@ TEST_F(RenderSystemTest, Color) {  								  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.texture.texture.get(), nullptr); +	//ASSERT_NE(sprite.texture.texture.get(), nullptr);  	sprite.data.color = Color::GREEN;  	EXPECT_EQ(sprite.data.color.r, Color::GREEN.r); 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, +			}, +		};  	}  }; |