diff options
| author | Loek Le Blansch <loek@pipeframe.xyz> | 2024-12-14 12:06:13 +0100 | 
|---|---|---|
| committer | Loek Le Blansch <loek@pipeframe.xyz> | 2024-12-14 12:06:13 +0100 | 
| commit | 332fd8a0df85062e8474acab84b469afeb89f72b (patch) | |
| tree | a06cf5beaadb721b18c1add881282e12b4a38a99 | |
| parent | 8e72da5b2fec93be40f0c0a7f3199fc12f7681c9 (diff) | |
| parent | b9fc66f6922b1f40f2dbe14e8dfc4caa469654bc (diff) | |
merge master
42 files changed, 947 insertions, 839 deletions
| diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 46deb67..8f84f06 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -13,16 +13,14 @@ target_sources(crepe PUBLIC  	Animator.cpp  	BoxCollider.cpp  	CircleCollider.cpp -	IKeyListener.cpp -	IMouseListener.cpp  	LoopManager.cpp -	LoopTimer.cpp  	Asset.cpp  	EventHandler.cpp  	Script.cpp  	Button.cpp  	UIObject.cpp  	AI.cpp +	Scene.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -39,6 +37,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	Vector2.hpp  	Color.h  	Scene.h +	Scene.hpp  	Metadata.h  	Camera.h  	Animator.h @@ -47,10 +46,7 @@ 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 diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h index fae31f4..d11f3f2 100644 --- a/src/crepe/api/Config.h +++ b/src/crepe/api/Config.h @@ -45,8 +45,12 @@ struct Config final {  	//! Physics-related settings  	struct physics { // NOLINT -		//! Gravity value of physics system -		double gravity = 1; +		/** +		 * \brief gravity value of physics system +		 * +		 * Gravity value of game. +		 */ +		float gravity = 10;  	} physics;  	//! Default window settings 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 1f0ba72..b5e5ff7 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -1,3 +1,6 @@ +#include "../facade/SDLContext.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h"  #include "../system/AISystem.h"  #include "../system/AnimatorSystem.h"  #include "../system/AudioSystem.h" @@ -7,7 +10,7 @@  #include "../system/PhysicsSystem.h"  #include "../system/RenderSystem.h"  #include "../system/ScriptSystem.h" -#include "manager/EventManager.h" +#include "../util/Log.h"  #include "LoopManager.h" @@ -22,62 +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<AISystem>().update(); -	this->get_system<PhysicsSystem>().update(); -	this->get_system<CollisionSystem>().update(); -	this->get_system<AudioSystem>().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 1bafa56..40e6b38 100644 --- a/src/crepe/api/LoopManager.h +++ b/src/crepe/api/LoopManager.h @@ -4,16 +4,15 @@  #include "../facade/SDLContext.h"  #include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/Mediator.h"  #include "../manager/ResourceManager.h"  #include "../manager/SaveManager.h"  #include "../manager/SceneManager.h"  #include "../system/System.h" -#include "LoopTimer.h" -#include "manager/ResourceManager.h" -  namespace crepe { -  /**   * \brief Main game loop manager   * @@ -21,8 +20,14 @@ namespace crepe {   */  class LoopManager {  public: -	void start();  	LoopManager(); +	/** +	 * \brief Start the gameloop +	 * +	 * This is the start of the engine where the setup is called and then the loop keeps running until the game stops running. +	 * The Game programmer needs to call this function to run the game. This should be done after creating and adding all scenes. +	 */ +	void start();  	/**  	 * \brief Add a new concrete scene to the scene manager @@ -47,47 +52,20 @@ private:  	void loop();  	/** -	 * \brief Function for handling input-related system calls. -	 * -	 * Processes user inputs from keyboard and mouse. -	 */ -	void process_input(); - -	/**  	 * \brief Per-frame update.  	 *  	 * Updates the game state based on the elapsed time since the last frame.  	 */ -	void update(); - -	/** -	 * \brief Late update which is called after update(). -	 * -	 * This function can be used for final adjustments before rendering. -	 */ -	void late_update(); +	virtual void frame_update();  	/**  	 * \brief Fixed update executed at a fixed rate.  	 *  	 * This function updates physics and game logic based on LoopTimer's fixed_delta_time.  	 */ -	void fixed_update(); - -	/** -	 * \brief Set game running variable -	 * -	 * \param running running (false = game shutdown, true = game running) -	 */ -	void set_running(bool running); - -	/** -	 * \brief Function for executing render-related systems. -	 * -	 * Renders the current state of the game to the screen. -	 */ -	void render(); +	virtual void fixed_update(); +	//! Indicates whether the game is running.  	bool game_running = false;  private: @@ -98,21 +76,29 @@ private:  	ComponentManager component_manager{mediator};  	//! Scene manager instance  	SceneManager scene_manager{mediator}; +	//! 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}; -	//! LoopTimer instance -	LoopTimer loop_timer{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 56e48d3..0000000 --- a/src/crepe/api/LoopTimer.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include <chrono> - -#include "../util/Log.h" -#include "facade/SDLContext.h" -#include "manager/Manager.h" - -#include "LoopTimer.h" - -using namespace crepe; - -LoopTimer::LoopTimer(Mediator & mediator) : Manager(mediator) { -	dbg_trace(); -	mediator.timer = *this; -} - -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 & ctx = this->mediator.sdl_context; -			ctx.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 0a73a4c..0000000 --- a/src/crepe/api/LoopTimer.h +++ /dev/null @@ -1,138 +0,0 @@ -#pragma once - -#include "manager/Manager.h" -#include <chrono> - -namespace crepe { - -class LoopTimer : public Manager { -public: -	/** -	 * \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(Mediator & mediator); - -	/** -	 * \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/Rigidbody.h b/src/crepe/api/Rigidbody.h index 40c6bf1..b08c8db 100644 --- a/src/crepe/api/Rigidbody.h +++ b/src/crepe/api/Rigidbody.h @@ -53,7 +53,7 @@ public:  	 */  	struct Data {  		//! objects mass -		float mass = 0.0; +		float mass = 1;  		/**  		* \brief Gravity scale factor.  		* @@ -79,7 +79,7 @@ public:  		//! Linear velocity of the object (speed and direction).  		vec2 linear_velocity;  		//! Maximum linear velocity of the object. This limits the object's speed. -		vec2 max_linear_velocity = {INFINITY, INFINITY}; +		float max_linear_velocity = INFINITY;  		//! Linear velocity coefficient. This scales the object's velocity for adjustment or damping.  		vec2 linear_velocity_coefficient = {1, 1};  		//! \} diff --git a/src/crepe/api/Scene.cpp b/src/crepe/api/Scene.cpp new file mode 100644 index 0000000..ad729d2 --- /dev/null +++ b/src/crepe/api/Scene.cpp @@ -0,0 +1,15 @@ +#include "Scene.h" + +using namespace crepe; + +SaveManager & Scene::get_save_manager() const { return mediator->save_manager; } + +GameObject Scene::new_object(const std::string & name, const std::string & tag, +							 const vec2 & position, double rotation, double scale) { +	// Forward the call to ComponentManager's new_object method +	return mediator->component_manager->new_object(name, tag, position, rotation, scale); +} + +void Scene::set_persistent(const Asset & asset, bool persistent) { +	mediator->resource_manager->set_persistent(asset, persistent); +} diff --git a/src/crepe/api/Scene.h b/src/crepe/api/Scene.h index ba9bb76..dcca9d4 100644 --- a/src/crepe/api/Scene.h +++ b/src/crepe/api/Scene.h @@ -2,13 +2,19 @@  #include <string> +#include "../manager/ComponentManager.h"  #include "../manager/Mediator.h" +#include "../manager/ResourceManager.h" +#include "../util/Log.h"  #include "../util/OptionalRef.h" +#include "GameObject.h" +  namespace crepe {  class SceneManager;  class ComponentManager; +class Asset;  /**   * \brief Represents a Scene @@ -38,7 +44,7 @@ public:  	// TODO: Late references should ALWAYS be private! This is currently kept as-is so unit tests  	// keep passing, but this reference should not be directly accessible by the user!!! -protected: +private:  	/**  	 * \name Late references  	 * @@ -53,6 +59,36 @@ protected:  	//! Mediator reference  	OptionalRef<Mediator> mediator;  	//! \} + +protected: +	/** +	* \brief Retrieve the reference to the SaveManager instance +	* +	* \returns A reference to the SaveManager instance held by the Mediator. +	*/ +	SaveManager & get_save_manager() const; + +	//! \copydoc ComponentManager::new_object +	GameObject new_object(const std::string & name, const std::string & tag = "", +						  const vec2 & position = {0, 0}, double rotation = 0, +						  double scale = 1); + +	//! \copydoc ResourceManager::set_persistent +	void set_persistent(const Asset & asset, bool persistent); +	/** +	* \name Logging functions +	* \see Log +	* \{ +	*/ +	//! \copydoc Log::logf +	template <class... Args> +	void logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args); +	//! \copydoc Log::logf +	template <class... Args> +	void logf(std::format_string<Args...> fmt, Args &&... args); +	//! \}  };  } // namespace crepe + +#include "Scene.hpp" diff --git a/src/crepe/api/Scene.hpp b/src/crepe/api/Scene.hpp new file mode 100644 index 0000000..14635df --- /dev/null +++ b/src/crepe/api/Scene.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "../util/Log.h" + +#include "Scene.h" + +namespace crepe { + +template <class... Args> +void Scene::logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args) { +	Log::logf(level, fmt, std::forward<Args>(args)...); +} + +template <class... Args> +void Scene::logf(std::format_string<Args...> fmt, Args &&... args) { +	Log::logf(fmt, std::forward<Args>(args)...); +} + +} // namespace crepe diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index b95ba6a..68a144e 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> @@ -232,15 +233,12 @@ SDL_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const {  	if (data.size.y == 0 && data.size.x != 0) {  		size.y = data.size.x / aspect_ratio;  	} +	size *= cam_aux_data.render_scale * ctx.img_scale * data.scale_offset; -	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, @@ -269,7 +267,6 @@ void SDLContext::draw(const RenderContext & ctx) {  	SDL_FRect dstrect = this->get_dst_rect(SDLContext::DestinationRectangleData{  		.sprite = ctx.sprite,  		.texture = ctx.texture, -		.cam = ctx.cam,  		.pos = ctx.pos,  		.img_scale = ctx.scale,  	}); @@ -281,10 +278,9 @@ void SDLContext::draw(const RenderContext & ctx) {  					  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); @@ -292,9 +288,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; @@ -336,12 +333,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) { @@ -372,12 +365,14 @@ ivec2 SDLContext::get_size(const Texture & ctx) {  	return size;  } -void SDLContext::delay(int ms) const { SDL_Delay(ms); } -  std::vector<SDLContext::EventData> SDLContext::get_events() {  	std::vector<SDLContext::EventData> event_list;  	SDL_Event event; +	const CameraAuxiliaryData & cam = this->cam_aux_data;  	while (SDL_PollEvent(&event)) { +		ivec2 mouse_pos; +		mouse_pos.x = (event.button.x - cam.bar_size.x) / cam.render_scale.x; +		mouse_pos.y = (event.button.y - cam.bar_size.y) / cam.render_scale.y;  		switch (event.type) {  			case SDL_QUIT:  				event_list.push_back(EventData{ @@ -401,7 +396,7 @@ std::vector<SDLContext::EventData> SDLContext::get_events() {  				event_list.push_back(EventData{  					.event_type = SDLContext::EventType::MOUSEDOWN,  					.mouse_button = sdl_to_mousebutton(event.button.button), -					.mouse_position = {event.button.x, event.button.y}, +					.mouse_position = mouse_pos,  				});  				break;  			case SDL_MOUSEBUTTONUP: { @@ -410,21 +405,21 @@ std::vector<SDLContext::EventData> SDLContext::get_events() {  				event_list.push_back(EventData{  					.event_type = SDLContext::EventType::MOUSEUP,  					.mouse_button = sdl_to_mousebutton(event.button.button), -					.mouse_position = {event.button.x, event.button.y}, +					.mouse_position = mouse_pos,  				});  			} break;  			case SDL_MOUSEMOTION: {  				event_list.push_back(  					EventData{.event_type = SDLContext::EventType::MOUSEMOVE, -							  .mouse_position = {event.motion.x, event.motion.y}, +							  .mouse_position = mouse_pos,  							  .rel_mouse_move = {event.motion.xrel, event.motion.yrel}});  			} break;  			case SDL_MOUSEWHEEL: {  				event_list.push_back(EventData{  					.event_type = SDLContext::EventType::MOUSEWHEEL, -					.mouse_position = {event.motion.x, event.motion.y}, +					.mouse_position = mouse_pos,  					// TODO: why is this needed?  					.scroll_direction = event.wheel.y < 0 ? -1 : 1,  					.scroll_delta = event.wheel.preciseY, diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index 46b779f..bcadf87 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -24,7 +24,6 @@ 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 +32,7 @@ class Mediator;  class SDLContext {  public:  	//! data that the camera component cannot hold -	struct CameraValues { +	struct CameraAuxiliaryData {  		//! zoomed in viewport in game_units  		vec2 zoomed_viewport; @@ -64,7 +63,6 @@ public:  	struct RenderContext {  		const Sprite & sprite;  		const Texture & texture; -		const CameraValues & cam;  		const vec2 & pos;  		const double & angle;  		const double & scale; @@ -193,18 +191,20 @@ public:  	void present_screen();  	/** -	 * \brief sets the background of the camera (will be adjusted in future PR) -	 * \param camera Reference to the Camera object. -	 * \return camera data the component cannot store +	 * \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);  public:  	//! the data needed to construct a sdl dst rectangle  	struct DestinationRectangleData {  		const Sprite & sprite;  		const Texture & texture; -		const CameraValues & cam;  		const vec2 & pos;  		const double & img_scale;  	}; @@ -233,6 +233,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/manager/CMakeLists.txt b/src/crepe/manager/CMakeLists.txt index 480c8ee..f73e165 100644 --- a/src/crepe/manager/CMakeLists.txt +++ b/src/crepe/manager/CMakeLists.txt @@ -4,6 +4,7 @@ target_sources(crepe PUBLIC  	Manager.cpp  	SaveManager.cpp  	SceneManager.cpp +	LoopTimerManager.cpp  	ResourceManager.cpp  ) @@ -17,6 +18,7 @@ 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/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..a6e4788 --- /dev/null +++ b/src/crepe/manager/LoopTimerManager.cpp @@ -0,0 +1,91 @@ +#include <chrono> +#include <thread> + +#include "../util/Log.h" + +#include "LoopTimerManager.h" + +using namespace crepe; +using namespace std::chrono; +using namespace std::chrono_literals; + +LoopTimerManager::LoopTimerManager(Mediator & mediator) : Manager(mediator) { +	this->mediator.loop_timer = *this; +	dbg_trace(); +} + +void LoopTimerManager::start() { +	this->last_frame_time = std::chrono::steady_clock::now(); + +	this->elapsed_time = elapsed_time_t{0}; +	this->elapsed_fixed_time = elapsed_time_t{0}; +	this->delta_time = duration_t{0}; +} + +void LoopTimerManager::update() { +	time_point_t current_frame_time = std::chrono::steady_clock::now(); +	// Convert to duration in seconds for delta time +	this->delta_time = current_frame_time - last_frame_time; + +	if (this->delta_time > this->maximum_delta_time) { +		this->delta_time = this->maximum_delta_time; +	} +	if (this->delta_time > 0s) { +		this->actual_fps = static_cast<unsigned>(1.0 / this->delta_time.count()); +	} else { +		this->actual_fps = 0; +	} +	this->elapsed_time += duration_cast<elapsed_time_t>(this->delta_time); +	this->last_frame_time = current_frame_time; +} + +duration_t LoopTimerManager::get_delta_time() const { +	return this->delta_time * this->time_scale; +} + +elapsed_time_t LoopTimerManager::get_elapsed_time() const { return this->elapsed_time; } + +void LoopTimerManager::advance_fixed_elapsed_time() { +	this->elapsed_fixed_time +		+= std::chrono::duration_cast<elapsed_time_t>(this->fixed_delta_time); +} + +void LoopTimerManager::set_target_framerate(unsigned fps) { +	this->target_fps = fps; +	//check if fps is lower or equals 0 +	if (fps <= 0) return; +	// target time per frame in seconds +	this->frame_target_time = duration_t(1s) / this->target_fps; +} + +unsigned LoopTimerManager::get_fps() const { return this->actual_fps; } + +void LoopTimerManager::set_time_scale(double value) { this->time_scale = value; } + +float LoopTimerManager::get_time_scale() const { return this->time_scale; } + +void LoopTimerManager::enforce_frame_rate() { +	time_point_t current_frame_time = std::chrono::steady_clock::now(); +	duration_t frame_duration = current_frame_time - this->last_frame_time; +	// Check if frame duration is less than the target frame time +	if (frame_duration < this->frame_target_time) { +		duration_t delay_time = this->frame_target_time - frame_duration; +		if (delay_time > 0s) { +			std::this_thread::sleep_for(delay_time); +		} +	} +} + +duration_t LoopTimerManager::get_lag() const { +	return this->elapsed_time - this->elapsed_fixed_time; +} + +duration_t LoopTimerManager::get_scaled_fixed_delta_time() const { +	return this->fixed_delta_time * this->time_scale; +} + +void LoopTimerManager::set_fixed_delta_time(float seconds) { +	this->fixed_delta_time = duration_t(seconds); +} + +duration_t LoopTimerManager::get_fixed_delta_time() const { return this->fixed_delta_time; } diff --git a/src/crepe/manager/LoopTimerManager.h b/src/crepe/manager/LoopTimerManager.h new file mode 100644 index 0000000..76b02d3 --- /dev/null +++ b/src/crepe/manager/LoopTimerManager.h @@ -0,0 +1,175 @@ +#pragma once + +#include <chrono> + +#include "Manager.h" + +namespace crepe { + +typedef std::chrono::duration<float> duration_t; +typedef std::chrono::duration<unsigned long long, std::micro> elapsed_time_t; + +/** + * \brief Manages timing and frame rate for the game loop. + *  + * The LoopTimerManager class is responsible for calculating and managing timing functions  + * such as delta time, frames per second (FPS), fixed time steps, and time scaling. It ensures  + * consistent frame updates and supports game loop operations, such as handling fixed updates  + * for physics and other time-sensitive operations. + */ +class LoopTimerManager : public Manager { +public: +	/** +	 * \param mediator A reference to a Mediator object used for transfering managers. +	 */ +	LoopTimerManager(Mediator & mediator); +	/** +	 * \brief Get the current delta time for the current frame. +	 *	 +	 * This value represents the estimated frame duration of the current frame. +	 * This value can be used in the frame_update to convert pixel based values to time based values. +	 *  +	 * \return Delta time in seconds since the last frame. +	 */ +	duration_t get_delta_time() const; + +	/** +	 * \brief Get the current elapsed time (total time passed ) +	 * +	 * \note The current game time may vary from real-world elapsed time. It is the cumulative +	 * sum of each frame's delta time. +	 * +	 * \return Elapsed game time in seconds. +	 */ +	elapsed_time_t get_elapsed_time() const; + +	/** +	 * \brief Set the target frames per second (FPS). +	 * +	 * \param fps The desired frames rendered per second. +	 */ +	void set_target_framerate(unsigned fps); + +	/** +	 * \brief Get the current frames per second (FPS). +	 * +	 * \return Current FPS. +	 */ +	unsigned int get_fps() const; + +	/** +	 * \brief Get the current time scale. +	 * +	 * \return The current time scale, where (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up). +	 * up the game. +	 */ +	float get_time_scale() const; + +	/** +	 * \brief Set the time scale. +	 * +	 * time_scale is a value that changes the delta time that can be retrieved using get_delta_time function.  +	 *  +	 * \param time_scale The desired time scale (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up). +	 */ +	void set_time_scale(double time_scale); + +	/** +	 * \brief Get the fixed delta time in seconds without scaling by the time scale. +	 * +	 * This value is used in the LoopManager to determine how many times  +	 * the fixed_update should be called within a given interval. +	 * +	 * \return The unscaled fixed delta time in seconds. +	 */ +	duration_t get_fixed_delta_time() const; + +	/** +	 * \brief Set the fixed_delta_time in seconds. +	 *  +	 * \param seconds fixed_delta_time in seconds. +	 *  +	 * The fixed_delta_time value is used to determine how many times per second the fixed_update and process_input functions are called. +	 *  +	 */ +	void set_fixed_delta_time(float seconds); + +	/** +	 * \brief Retrieves the scaled fixed delta time in seconds. +	 * +	 * The scaled fixed delta time is the timing value used within the `fixed_update` function.  +	 * It is adjusted by the time_scale to account for any changes in the simulation's  +	 * speed. +	 * +	 * \return The fixed delta time, scaled by the current time scale, in seconds. +	 */ +	duration_t get_scaled_fixed_delta_time() const; + +private: +	//! Friend relation to use start,enforce_frame_rate,get_lag,update,advance_fixed_update. +	friend class LoopManager; +	/** +	 * \brief Start the loop timer. +	 * +	 * Initializes the timer to begin tracking frame times. +	 */ +	void start(); +	/** +	 * \brief Enforce the frame rate limit. +	 * +	 * Ensures that the game loop does not exceed the target FPS by delaying frame updates as +	 * necessary. +	 */ +	void enforce_frame_rate(); +	/** +	 * \brief Get the accumulated lag in the game loop. +	 * +	 * Lag represents the difference between the target frame time and the actual frame time, +	 * useful for managing fixed update intervals. +	 * +	 * \return Accumulated lag in seconds. +	 */ +	duration_t get_lag() const; + +	/** +	 * \brief Update the timer to the current frame. +	 * +	 * Calculates and updates the delta time for the current frame and adds it to the cumulative +	 * game time. +	 */ +	void update(); + +	/** +	 * \brief Progress the elapsed fixed time by the fixed delta time interval. +	 * +	 * This method advances the game's fixed update loop by adding the fixed_delta_time  +	 * to elapsed_fixed_time, ensuring the fixed update catches up with the elapsed time. +	 */ +	void advance_fixed_elapsed_time(); + +private: +	//! Target frames per second. +	unsigned int target_fps = 60; +	//! Actual frames per second. +	unsigned int actual_fps = 0; +	//! Time scale for speeding up or slowing down the game (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up). +	float time_scale = 1; +	//! Maximum delta time in seconds to avoid large jumps. +	duration_t maximum_delta_time{0.25}; +	//! Delta time for the current frame in seconds. +	duration_t delta_time{0.0}; +	//! Target time per frame in seconds +	duration_t frame_target_time{1.0 / target_fps}; +	//! Fixed delta time for fixed updates in seconds. +	duration_t fixed_delta_time{1.0 / 50.0}; +	//! Total elapsed game time in microseconds. +	elapsed_time_t elapsed_time{0}; +	//! Total elapsed time for fixed updates in microseconds. +	elapsed_time_t elapsed_fixed_time{0}; + +	typedef std::chrono::steady_clock::time_point time_point_t; +	//! Time of the last frame. +	time_point_t last_frame_time; +}; + +} // namespace crepe diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h index 628154a..a336410 100644 --- a/src/crepe/manager/Mediator.h +++ b/src/crepe/manager/Mediator.h @@ -2,17 +2,15 @@  #include "../util/OptionalRef.h" -// TODO: remove these singletons: -#include "EventManager.h" -  namespace crepe {  class ComponentManager;  class SceneManager; +class EventManager; +class LoopTimerManager;  class SaveManager;  class ResourceManager;  class SDLContext; -class LoopTimer;  /**   * Struct to pass references to classes that would otherwise need to be singletons down to @@ -30,10 +28,10 @@ struct Mediator {  	OptionalRef<SDLContext> sdl_context;  	OptionalRef<ComponentManager> component_manager;  	OptionalRef<SceneManager> scene_manager; +	OptionalRef<EventManager> event_manager; +	OptionalRef<LoopTimerManager> loop_timer;  	OptionalRef<SaveManager> save_manager; -	OptionalRef<EventManager> event_manager = EventManager::get_instance();  	OptionalRef<ResourceManager> resource_manager; -	OptionalRef<LoopTimer> timer;  };  } // 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 index 7f04432..680dbb8 100644 --- a/src/crepe/system/AISystem.cpp +++ b/src/crepe/system/AISystem.cpp @@ -1,22 +1,22 @@  #include <algorithm>  #include <cmath> -#include "api/LoopTimer.h"  #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; -	LoopTimer & timer = mediator.timer; +	LoopTimerManager & loop_timer = mediator.loop_timer;  	RefVector<AI> ai_components = mgr.get_components_by_type<AI>(); -	//TODO: Use fixed loop dt (this is not available at master at the moment) -	double dt = timer.get_delta_time(); +	float dt = loop_timer.get_scaled_fixed_delta_time().count();  	// Loop through all AI components  	for (AI & ai : ai_components) { @@ -144,7 +144,7 @@ vec2 AISystem::arrive(const AI & ai, const Rigidbody & rigidbody,  		}  		float speed = distance / ai.arrive_deceleration; -		speed = std::min(speed, rigidbody.data.max_linear_velocity.length()); +		speed = std::min(speed, rigidbody.data.max_linear_velocity);  		vec2 desired_velocity = to_target * (speed / distance);  		return desired_velocity - rigidbody.data.linear_velocity; diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index d61ba35..107b25d 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -2,21 +2,24 @@  #include "../api/Animator.h"  #include "../manager/ComponentManager.h" -#include "api/LoopTimer.h" +#include "../manager/LoopTimerManager.h" +#include <chrono>  #include "AnimatorSystem.h"  using namespace crepe; +using namespace std::chrono;  void AnimatorSystem::update() {  	ComponentManager & mgr = this->mediator.component_manager; -	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(); +	float elapsed_time = duration_cast<duration<float>>(timer.get_elapsed_time()).count();  	for (Animator & a : animations) {  		if (!a.active) continue; +		if (a.data.fps == 0) continue;  		Animator::Data & ctx = a.data;  		float frame_duration = 1.0f / ctx.fps; diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp index 44a0431..496224e 100644 --- a/src/crepe/system/CollisionSystem.cpp +++ b/src/crepe/system/CollisionSystem.cpp @@ -192,13 +192,15 @@ CollisionSystem::collision_handler(CollisionInternal & data1, CollisionInternal  		resolution_direction = Direction::BOTH;  	} else if (resolution.x != 0) {  		resolution_direction = Direction::X_DIRECTION; -		if (data1.rigidbody.data.linear_velocity.y != 0) -			resolution.y = data1.rigidbody.data.linear_velocity.y +		//checks if the other velocity has a value and if this object moved +		if (data1.rigidbody.data.linear_velocity.x != 0 && data1.rigidbody.data.linear_velocity.y != 0) +			resolution.y = -data1.rigidbody.data.linear_velocity.y  						   * (resolution.x / data1.rigidbody.data.linear_velocity.x);  	} else if (resolution.y != 0) {  		resolution_direction = Direction::Y_DIRECTION; -		if (data1.rigidbody.data.linear_velocity.x != 0) -			resolution.x = data1.rigidbody.data.linear_velocity.x +		//checks if the other velocity has a value and if this object moved +		if (data1.rigidbody.data.linear_velocity.x != 0 && data1.rigidbody.data.linear_velocity.y != 0) +			resolution.x = -data1.rigidbody.data.linear_velocity.x  						   * (resolution.y / data1.rigidbody.data.linear_velocity.y);  	} @@ -314,28 +316,46 @@ void CollisionSystem::static_collision_handler(CollisionInfo & info) {  	// Move object back using calculate move back value  	info.this_transform.position += info.resolution; -	// If bounce is enabled mirror velocity -	if (info.this_rigidbody.data.elastisity_coefficient > 0) { -		if (info.resolution_direction == Direction::BOTH) { -			info.this_rigidbody.data.linear_velocity.y -				= -info.this_rigidbody.data.linear_velocity.y -				  * info.this_rigidbody.data.elastisity_coefficient; -			info.this_rigidbody.data.linear_velocity.x -				= -info.this_rigidbody.data.linear_velocity.x -				  * info.this_rigidbody.data.elastisity_coefficient; -		} else if (info.resolution_direction == Direction::Y_DIRECTION) { -			info.this_rigidbody.data.linear_velocity.y -				= -info.this_rigidbody.data.linear_velocity.y -				  * info.this_rigidbody.data.elastisity_coefficient; -		} else if (info.resolution_direction == Direction::X_DIRECTION) { -			info.this_rigidbody.data.linear_velocity.x -				= -info.this_rigidbody.data.linear_velocity.x -				  * info.this_rigidbody.data.elastisity_coefficient; -		} -	} -	// Stop movement if bounce is disabled -	else { -		info.this_rigidbody.data.linear_velocity = {0, 0}; +	switch (info.resolution_direction) { +		case Direction::BOTH: +			//bounce +			if (info.this_rigidbody.data.elastisity_coefficient > 0) { +				info.this_rigidbody.data.linear_velocity = -info.this_rigidbody.data.linear_velocity * info.this_rigidbody.data.elastisity_coefficient; +			} +			//stop movement +			else { +				info.this_rigidbody.data.linear_velocity = {0, 0}; +			} +			break; +		case Direction::Y_DIRECTION: +			// Bounce +			if (info.this_rigidbody.data.elastisity_coefficient > 0) { +				info.this_rigidbody.data.linear_velocity.y +					= -info.this_rigidbody.data.linear_velocity.y +					  * info.this_rigidbody.data.elastisity_coefficient; +			} +			// Stop movement +			else { +				info.this_rigidbody.data.linear_velocity.y = 0; +				info.this_transform.position.x -= info.resolution.x; +			} +			break; +		case Direction::X_DIRECTION: +			// Bounce +			if (info.this_rigidbody.data.elastisity_coefficient > 0) { +				info.this_rigidbody.data.linear_velocity.x +					= -info.this_rigidbody.data.linear_velocity.x +					  * info.this_rigidbody.data.elastisity_coefficient; +			} +			// Stop movement +			else { +				info.this_rigidbody.data.linear_velocity.x = 0; +				info.this_transform.position.y -= info.resolution.y; +			} +			break; +		case Direction::NONE: +			// Not possible +			break;  	}  } diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index ebf4439..3b3b8ab 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -5,88 +5,95 @@  #include "../api/Transform.h"  #include "../api/Vector2.h"  #include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/Mediator.h"  #include "PhysicsSystem.h"  using namespace crepe;  void PhysicsSystem::update() { -	ComponentManager & mgr = this->mediator.component_manager; + +	const Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; +	LoopTimerManager & loop_timer = mediator.loop_timer;  	RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>(); -	RefVector<Transform> transforms = mgr.get_components_by_type<Transform>(); +	float dt = loop_timer.get_scaled_fixed_delta_time().count(); -	double gravity = Config::get_instance().physics.gravity; +	float gravity = Config::get_instance().physics.gravity;  	for (Rigidbody & rigidbody : rigidbodies) {  		if (!rigidbody.active) continue; +		Transform & transform +			= mgr.get_components_by_id<Transform>(rigidbody.game_object_id).front().get();  		switch (rigidbody.data.body_type) {  			case Rigidbody::BodyType::DYNAMIC: -				for (Transform & transform : transforms) { -					if (transform.game_object_id == rigidbody.game_object_id) { +				if (transform.game_object_id == rigidbody.game_object_id) { +					// Add gravity -						// Add gravity -						if (rigidbody.data.gravity_scale > 0) { -							rigidbody.data.linear_velocity.y -								+= (rigidbody.data.mass * rigidbody.data.gravity_scale -									* gravity); -						} -						// Add damping -						if (rigidbody.data.angular_velocity_coefficient > 0) { -							rigidbody.data.angular_velocity -								*= rigidbody.data.angular_velocity_coefficient; -						} -						if (rigidbody.data.linear_velocity_coefficient.x > 0 -							&& rigidbody.data.linear_velocity_coefficient.y > 0) { -							rigidbody.data.linear_velocity -								*= rigidbody.data.linear_velocity_coefficient; -						} +					if (rigidbody.data.mass <= 0) { +						throw std::runtime_error("Mass must be greater than 0"); +					} -						// Max velocity check -						if (rigidbody.data.angular_velocity -							> rigidbody.data.max_angular_velocity) { -							rigidbody.data.angular_velocity -								= rigidbody.data.max_angular_velocity; -						} else if (rigidbody.data.angular_velocity -								   < -rigidbody.data.max_angular_velocity) { -							rigidbody.data.angular_velocity -								= -rigidbody.data.max_angular_velocity; -						} +					if (gravity <= 0) { +						throw std::runtime_error("Config Gravity must be greater than 0"); +					} -						if (rigidbody.data.linear_velocity.x -							> rigidbody.data.max_linear_velocity.x) { -							rigidbody.data.linear_velocity.x -								= rigidbody.data.max_linear_velocity.x; -						} else if (rigidbody.data.linear_velocity.x -								   < -rigidbody.data.max_linear_velocity.x) { -							rigidbody.data.linear_velocity.x -								= -rigidbody.data.max_linear_velocity.x; -						} +					if (rigidbody.data.gravity_scale > 0 && !rigidbody.data.constraints.y) { +						rigidbody.data.linear_velocity.y +							+= (rigidbody.data.mass * rigidbody.data.gravity_scale * gravity +								* dt); +					} +					// Add coefficient rotation +					if (rigidbody.data.angular_velocity_coefficient > 0) { +						rigidbody.data.angular_velocity +							*= std::pow(rigidbody.data.angular_velocity_coefficient, dt); +					} -						if (rigidbody.data.linear_velocity.y -							> rigidbody.data.max_linear_velocity.y) { -							rigidbody.data.linear_velocity.y -								= rigidbody.data.max_linear_velocity.y; -						} else if (rigidbody.data.linear_velocity.y -								   < -rigidbody.data.max_linear_velocity.y) { -							rigidbody.data.linear_velocity.y -								= -rigidbody.data.max_linear_velocity.y; -						} +					// Add coefficient movement horizontal +					if (rigidbody.data.linear_velocity_coefficient.x > 0 +						&& !rigidbody.data.constraints.x) { +						rigidbody.data.linear_velocity.x +							*= std::pow(rigidbody.data.linear_velocity_coefficient.x, dt); +					} -						// Move object -						if (!rigidbody.data.constraints.rotation) { -							transform.rotation += rigidbody.data.angular_velocity; -							transform.rotation = std::fmod(transform.rotation, 360.0); -							if (transform.rotation < 0) { -								transform.rotation += 360.0; -							} -						} -						if (!rigidbody.data.constraints.x) { -							transform.position.x += rigidbody.data.linear_velocity.x; -						} -						if (!rigidbody.data.constraints.y) { -							transform.position.y += rigidbody.data.linear_velocity.y; +					// Add coefficient movement horizontal +					if (rigidbody.data.linear_velocity_coefficient.y > 0 +						&& !rigidbody.data.constraints.y) { +						rigidbody.data.linear_velocity.y +							*= std::pow(rigidbody.data.linear_velocity_coefficient.y, dt); +					} + +					// Max velocity check +					if (rigidbody.data.angular_velocity +						> rigidbody.data.max_angular_velocity) { +						rigidbody.data.angular_velocity = rigidbody.data.max_angular_velocity; +					} else if (rigidbody.data.angular_velocity +							   < -rigidbody.data.max_angular_velocity) { +						rigidbody.data.angular_velocity = -rigidbody.data.max_angular_velocity; +					} + +					// Set max velocity to maximum length +					if (rigidbody.data.linear_velocity.length() +						> rigidbody.data.max_linear_velocity) { +						rigidbody.data.linear_velocity.normalize(); +						rigidbody.data.linear_velocity *= rigidbody.data.max_linear_velocity; +					} + +					// Move object +					if (!rigidbody.data.constraints.rotation) { +						transform.rotation += rigidbody.data.angular_velocity * dt; +						transform.rotation = std::fmod(transform.rotation, 360.0); +						if (transform.rotation < 0) { +							transform.rotation += 360.0;  						}  					} +					if (!rigidbody.data.constraints.x) { +						transform.position.x += rigidbody.data.linear_velocity.x * dt; +					} +					if (!rigidbody.data.constraints.y) { +						transform.position.y += rigidbody.data.linear_velocity.y * dt; +					}  				}  				break;  			case Rigidbody::BodyType::KINEMATIC: diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index 51340fb..afd9548 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -15,6 +15,7 @@  #include "../manager/ResourceManager.h"  #include "RenderSystem.h" +#include "types.h"  using namespace crepe;  using namespace std; @@ -29,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>(); @@ -40,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");  } @@ -69,8 +70,7 @@ 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; @@ -93,7 +93,6 @@ bool RenderSystem::render_particle(const Sprite & sprite, const SDLContext::Came  			ctx.draw(SDLContext::RenderContext{  				.sprite = sprite,  				.texture = res, -				.cam = cam,  				.pos = p.position,  				.angle = p.angle,  				.scale = scale, @@ -102,8 +101,7 @@ 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); @@ -111,7 +109,6 @@ void RenderSystem::render_normal(const Sprite & sprite, const SDLContext::Camera  	ctx.draw(SDLContext::RenderContext{  		.sprite = sprite,  		.texture = res, -		.cam = cam,  		.pos = tm.position,  		.angle = tm.rotation,  		.scale = tm.scale, @@ -120,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); @@ -130,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/example/AITest.cpp b/src/example/AITest.cpp index f4efc9f..93ba500 100644 --- a/src/example/AITest.cpp +++ b/src/example/AITest.cpp @@ -8,7 +8,6 @@  #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> @@ -47,14 +46,19 @@ public:  		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}, -												}); +		Asset img{"asset/texture/test_ap43.png"}; + +		Sprite & test_sprite = game_object1.add_component<Sprite>( +			img, Sprite::Data{ +					 .color = Color::MAGENTA, +					 .flip = Sprite::FlipSettings{false, false}, +					 .sorting_in_layer = 2, +					 .order_in_layer = 2, +					 .size = {0, 100}, +					 .angle_offset = 0, +					 .position_offset = {0, 0}, +				 }); +  		AI & ai = game_object1.add_component<AI>(3000);  		// ai.arrive_on();  		// ai.flee_on(); @@ -63,7 +67,7 @@ public:  		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}, +			.max_linear_velocity = 40,  		});  		game_object1.add_component<BehaviorScript>().set_script<Script1>(); diff --git a/src/example/game.cpp b/src/example/game.cpp index 4239c15..8ea50ea 100644 --- a/src/example/game.cpp +++ b/src/example/game.cpp @@ -2,6 +2,7 @@  #include "api/Scene.h"  #include "manager/ComponentManager.h"  #include "manager/Mediator.h" +#include "types.h"  #include <crepe/api/BoxCollider.h>  #include <crepe/api/Camera.h>  #include <crepe/api/Color.h> @@ -11,7 +12,6 @@  #include <crepe/api/Rigidbody.h>  #include <crepe/api/Script.h>  #include <crepe/api/Sprite.h> -#include <crepe/api/Texture.h>  #include <crepe/api/Transform.h>  #include <crepe/api/Vector2.h> @@ -29,23 +29,23 @@ class MyScript1 : public Script {  		Log::logf("Box script keypressed()");  		switch (test.key) {  			case Keycode::A: { -				Transform & tf = this->get_component<Transform>(); -				tf.position.x -= 1; +				Rigidbody & tf = this->get_component<Rigidbody>(); +				tf.data.linear_velocity.x -= 1;  				break;  			}  			case Keycode::W: { -				Transform & tf = this->get_component<Transform>(); -				tf.position.y -= 1; +				Rigidbody & tf = this->get_component<Rigidbody>(); +				tf.data.linear_velocity.y -= 1;  				break;  			}  			case Keycode::S: { -				Transform & tf = this->get_component<Transform>(); -				tf.position.y += 1; +				Rigidbody & tf = this->get_component<Rigidbody>(); +				tf.data.linear_velocity.y += 1;  				break;  			}  			case Keycode::D: { -				Transform & tf = this->get_component<Transform>(); -				tf.position.x += 1; +				Rigidbody & tf = this->get_component<Rigidbody>(); +				tf.data.linear_velocity.x += 1;  				break;  			}  			case Keycode::E: { @@ -66,6 +66,11 @@ class MyScript1 : public Script {  				//add collider switch  				break;  			} +			case Keycode::Q: { +				Rigidbody & rg = this->get_component<Rigidbody>(); +				rg.data.angular_velocity = 1; +				break; +			}  			default:  				break;  		} @@ -80,7 +85,10 @@ class MyScript1 : public Script {  			[this](const KeyPressEvent & ev) -> bool { return this->keypressed(ev); });  	}  	void update() { -		// Retrieve component from the same GameObject this script is on +		Rigidbody & tf = this->get_component<Rigidbody>(); +		Log::logf("linear_velocity.x {}", tf.data.linear_velocity.x); +		Log::logf("linear_velocity.y {}", tf.data.linear_velocity.y); +		// tf.data.linear_velocity = {0,0};  	}  }; @@ -155,15 +163,13 @@ public:  	void load_scene() { -		Mediator & m = this->mediator; -		ComponentManager & mgr = m.component_manager;  		Color color(0, 0, 0, 255);  		float screen_size_width = 320;  		float screen_size_height = 240;  		float world_collider = 1000;  		//define playable world -		GameObject world = mgr.new_object( +		GameObject world = new_object(  			"Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1);  		world.add_component<Rigidbody>(Rigidbody::Data{  			.mass = 0, @@ -184,17 +190,20 @@ public:  		world.add_component<BoxCollider>(vec2{screen_size_width / 2 + world_collider / 2, 0},  										 vec2{world_collider, world_collider}); // right  		world.add_component<Camera>( -			Color::WHITE,  			ivec2{static_cast<int>(screen_size_width), static_cast<int>(screen_size_height)}, -			vec2{screen_size_width, screen_size_height}, 1.0f); +			vec2{screen_size_width, screen_size_height}, +			Camera::Data{ +				.bg_color = Color::WHITE, +				.zoom = 1, +			}); -		GameObject game_object1 = mgr.new_object( +		GameObject game_object1 = new_object(  			"Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1);  		game_object1.add_component<Rigidbody>(Rigidbody::Data{  			.mass = 1, -			.gravity_scale = 0, +			.gravity_scale = 1,  			.body_type = Rigidbody::BodyType::DYNAMIC, -			.linear_velocity = {0, 0}, +			.linear_velocity = {0, 1},  			.constraints = {0, 0, 0},  			.elastisity_coefficient = 1,  			.offset = {0, 0}, @@ -203,19 +212,24 @@ public:  		// add box with boxcollider  		game_object1.add_component<BoxCollider>(vec2{0, 0}, vec2{20, 20});  		game_object1.add_component<BehaviorScript>().set_script<MyScript1>(); -		auto img1 = Texture("asset/texture/square.png"); -		game_object1.add_component<Sprite>(img1, color, Sprite::FlipSettings{false, false}, 1, -										   1, 20); + +		Asset img1{"asset/texture/square.png"}; +		game_object1.add_component<Sprite>(img1, Sprite::Data{ +													 .size = {20, 20}, +												 });  		//add circle with cirlcecollider deactiveated  		game_object1.add_component<CircleCollider>(vec2{0, 0}, 10).active = false; -		auto img2 = Texture("asset/texture/circle.png"); +		Asset img2{"asset/texture/circle.png"};  		game_object1 -			.add_component<Sprite>(img2, color, Sprite::FlipSettings{false, false}, 1, 1, 20) +			.add_component<Sprite>(img2, +								   Sprite::Data{ +									   .size = {20, 20}, +								   })  			.active  			= false; -		GameObject game_object2 = mgr.new_object( +		GameObject game_object2 = new_object(  			"Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1);  		game_object2.add_component<Rigidbody>(Rigidbody::Data{  			.mass = 1, @@ -230,15 +244,19 @@ public:  		// add box with boxcollider  		game_object2.add_component<BoxCollider>(vec2{0, 0}, vec2{20, 20});  		game_object2.add_component<BehaviorScript>().set_script<MyScript2>(); -		auto img3 = Texture("asset/texture/square.png"); -		game_object2.add_component<Sprite>(img3, color, Sprite::FlipSettings{false, false}, 1, -										   1, 20); + +		game_object2.add_component<Sprite>(img1, Sprite::Data{ +													 .size = {20, 20}, +												 });  		//add circle with cirlcecollider deactiveated  		game_object2.add_component<CircleCollider>(vec2{0, 0}, 10).active = false; -		auto img4 = Texture("asset/texture/circle.png"); +  		game_object2 -			.add_component<Sprite>(img4, color, Sprite::FlipSettings{false, false}, 1, 1, 20) +			.add_component<Sprite>(img2, +								   Sprite::Data{ +									   .size = {20, 20}, +								   })  			.active  			= false;  	} diff --git a/src/example/rendering_particle.cpp b/src/example/rendering_particle.cpp index bd4ef95..13e625f 100644 --- a/src/example/rendering_particle.cpp +++ b/src/example/rendering_particle.cpp @@ -1,6 +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> @@ -66,10 +67,21 @@ public:  		//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/test/CMakeLists.txt b/src/test/CMakeLists.txt index 7196404..11b4ca9 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -15,6 +15,8 @@ target_sources(test_main PUBLIC  	ValueBrokerTest.cpp  	DBTest.cpp  	Vector2Test.cpp +	LoopManagerTest.cpp +	LoopTimerTest.cpp  	InputTest.cpp  	ScriptEventTest.cpp  	ScriptSceneTest.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/EventTest.cpp b/src/test/EventTest.cpp index 4a4872d..82272b5 100644 --- a/src/test/EventTest.cpp +++ b/src/test/EventTest.cpp @@ -1,56 +1,41 @@ -#include <gmock/gmock.h> -#include <gtest/gtest.h> -  #include <crepe/api/Event.h> -#include <crepe/api/IKeyListener.h> -#include <crepe/api/IMouseListener.h>  #include <crepe/manager/EventManager.h> - +#include <crepe/manager/Mediator.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h>  using namespace std;  using namespace std::chrono_literals;  using namespace crepe;  class EventManagerTest : public ::testing::Test {  protected: +	Mediator mediator; +	EventManager event_mgr{mediator};  	void SetUp() override {  		// Clear any existing subscriptions or events before each test -		EventManager::get_instance().clear(); +		event_mgr.clear();  	}  	void TearDown() override {  		// Ensure cleanup after each test -		EventManager::get_instance().clear(); +		event_mgr.clear();  	}  }; -class MockKeyListener : public IKeyListener { -public: -	MOCK_METHOD(bool, on_key_pressed, (const KeyPressEvent & event), (override)); -	MOCK_METHOD(bool, on_key_released, (const KeyReleaseEvent & event), (override)); -}; - -class MockMouseListener : public IMouseListener { -public: -	MOCK_METHOD(bool, on_mouse_clicked, (const MouseClickEvent & event), (override)); -	MOCK_METHOD(bool, on_mouse_pressed, (const MousePressEvent & event), (override)); -	MOCK_METHOD(bool, on_mouse_released, (const MouseReleaseEvent & event), (override)); -	MOCK_METHOD(bool, on_mouse_moved, (const MouseMoveEvent & event), (override)); -}; -  TEST_F(EventManagerTest, EventSubscription) {  	EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) { return true; };  	// Subscribe to KeyPressEvent -	EventManager::get_instance().subscribe<KeyPressEvent>(key_handler, 1); +	event_mgr.subscribe<KeyPressEvent>(key_handler, 1);  	// Verify subscription (not directly verifiable; test by triggering event) -	EventManager::get_instance().trigger_event<KeyPressEvent>( +	event_mgr.trigger_event<KeyPressEvent>(  		KeyPressEvent{  			.repeat = true,  			.key = Keycode::A,  		},  		1); -	EventManager::get_instance().trigger_event<KeyPressEvent>( +	event_mgr.trigger_event<KeyPressEvent>(  		KeyPressEvent{  			.repeat = true,  			.key = Keycode::A, @@ -68,13 +53,11 @@ TEST_F(EventManagerTest, EventManagerTest_trigger_all_channels) {  		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);  		return false;  	}; -	EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, -															EventManager::CHANNEL_ALL); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler, EventManager::CHANNEL_ALL);  	MouseClickEvent click_event{  		.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; -	EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, -																EventManager::CHANNEL_ALL); +	event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);  	EXPECT_TRUE(triggered);  } @@ -88,19 +71,17 @@ TEST_F(EventManagerTest, EventManagerTest_trigger_one_channel) {  		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);  		return false;  	}; -	EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, test_channel); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler, test_channel);  	MouseClickEvent click_event{  		.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; -	EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, -																EventManager::CHANNEL_ALL); +	event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);  	EXPECT_FALSE(triggered); -	EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, test_channel); +	event_mgr.trigger_event<MouseClickEvent>(click_event, test_channel);  }  TEST_F(EventManagerTest, EventManagerTest_callback_propagation) { -	EventManager & event_manager = EventManager::get_instance();  	// Flags to track handler calls  	bool triggered_true = false; @@ -126,11 +107,11 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) {  	// Test event  	MouseClickEvent click_event{  		.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; -	event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); -	event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL);  	// Trigger event -	event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); +	event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);  	// Check that only the true handler was triggered  	EXPECT_TRUE(triggered_true); @@ -139,12 +120,12 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) {  	// Reset and clear  	triggered_true = false;  	triggered_false = false; -	event_manager.clear(); -	event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); -	event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); +	event_mgr.clear(); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL);  	// Trigger event again -	event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); +	event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);  	// Check that both handlers were triggered  	EXPECT_TRUE(triggered_true); @@ -152,47 +133,37 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) {  }  TEST_F(EventManagerTest, EventManagerTest_queue_dispatch) { -	EventManager & event_manager = EventManager::get_instance();  	bool triggered1 = false;  	bool triggered2 = false;  	int test_channel = 1; - -	// Adjusted to use KeyPressEvent with repeat as the first variable -	EventHandler<KeyPressEvent> key_handler1 = [&](const KeyPressEvent & e) { +	EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) {  		triggered1 = true; -		EXPECT_EQ(e.repeat, false); // Expecting repeat to be false -		EXPECT_EQ(e.key, Keycode::A); // Adjust expected key code +		EXPECT_EQ(e.mouse_x, 100); +		EXPECT_EQ(e.mouse_y, 200); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);  		return false; // Allows propagation  	}; - -	EventHandler<KeyPressEvent> key_handler2 = [&](const KeyPressEvent & e) { +	EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) {  		triggered2 = true; -		EXPECT_EQ(e.repeat, false); // Expecting repeat to be false -		EXPECT_EQ(e.key, Keycode::A); // Adjust expected key code +		EXPECT_EQ(e.mouse_x, 100); +		EXPECT_EQ(e.mouse_y, 200); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);  		return false; // Allows propagation  	}; +	event_mgr.subscribe<MouseClickEvent>(mouse_handler1); +	event_mgr.subscribe<MouseClickEvent>(mouse_handler2, test_channel); -	// Subscribe handlers to KeyPressEvent -	event_manager.subscribe<KeyPressEvent>(key_handler1); -	event_manager.subscribe<KeyPressEvent>(key_handler2, test_channel); - -	// Queue a KeyPressEvent instead of KeyDownEvent -	event_manager.queue_event<KeyPressEvent>(KeyPressEvent{ -		.repeat = false, .key = Keycode::A}); // Adjust event with repeat flag first - -	event_manager.queue_event<KeyPressEvent>( -		KeyPressEvent{.repeat = false, -					  .key = Keycode::A}, // Adjust event for second subscription +	event_mgr.queue_event<MouseClickEvent>( +		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); +	event_mgr.queue_event<MouseClickEvent>( +		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE},  		test_channel); - -	event_manager.dispatch_events(); - +	event_mgr.dispatch_events();  	EXPECT_TRUE(triggered1);  	EXPECT_TRUE(triggered2);  }  TEST_F(EventManagerTest, EventManagerTest_unsubscribe) { -	EventManager & event_manager = EventManager::get_instance();  	// Flags to track if handlers are triggered  	bool triggered1 = false; @@ -215,15 +186,15 @@ TEST_F(EventManagerTest, EventManagerTest_unsubscribe) {  		return false; // Allows propagation  	};  	// Subscribe handlers -	subscription_t handler1_id = event_manager.subscribe<MouseClickEvent>(mouse_handler1); -	subscription_t handler2_id = event_manager.subscribe<MouseClickEvent>(mouse_handler2); +	subscription_t handler1_id = event_mgr.subscribe<MouseClickEvent>(mouse_handler1); +	subscription_t handler2_id = event_mgr.subscribe<MouseClickEvent>(mouse_handler2);  	// Queue events -	event_manager.queue_event<MouseClickEvent>( +	event_mgr.queue_event<MouseClickEvent>(  		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE});  	// Dispatch events - both handlers should be triggered -	event_manager.dispatch_events(); +	event_mgr.dispatch_events();  	EXPECT_TRUE(triggered1); // Handler 1 should be triggered  	EXPECT_TRUE(triggered2); // Handler 2 should be triggered @@ -232,14 +203,14 @@ TEST_F(EventManagerTest, EventManagerTest_unsubscribe) {  	triggered2 = false;  	// Unsubscribe handler1 -	event_manager.unsubscribe(handler1_id); +	event_mgr.unsubscribe(handler1_id);  	// Queue the same event again -	event_manager.queue_event<MouseClickEvent>( +	event_mgr.queue_event<MouseClickEvent>(  		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE});  	// Dispatch events - only handler 2 should be triggered, handler 1 should NOT -	event_manager.dispatch_events(); +	event_mgr.dispatch_events();  	EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered  	EXPECT_TRUE(triggered2); // Handler 2 should be triggered @@ -247,14 +218,14 @@ TEST_F(EventManagerTest, EventManagerTest_unsubscribe) {  	triggered2 = false;  	// Unsubscribe handler2 -	event_manager.unsubscribe(handler2_id); +	event_mgr.unsubscribe(handler2_id);  	// Queue the event again -	event_manager.queue_event<MouseClickEvent>( +	event_mgr.queue_event<MouseClickEvent>(  		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE});  	// Dispatch events - no handler should be triggered -	event_manager.dispatch_events(); +	event_mgr.dispatch_events();  	EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered  	EXPECT_FALSE(triggered2); // Handler 2 should NOT be triggered  } diff --git a/src/test/InputTest.cpp b/src/test/InputTest.cpp index 4c05e56..8b40cea 100644 --- a/src/test/InputTest.cpp +++ b/src/test/InputTest.cpp @@ -1,3 +1,4 @@ +#include "system/RenderSystem.h"  #include <gtest/gtest.h>  #define protected public  #define private public @@ -27,15 +28,20 @@ public:  	SDLContext sdl_context{mediator};  	InputSystem input_system{mediator}; - -	EventManager & event_manager = EventManager::get_instance(); +	RenderSystem render{mediator}; +	EventManager event_manager{mediator};  	//GameObject camera;  protected:  	void SetUp() override { -		mediator.event_manager = event_manager; -		mediator.component_manager = mgr; -		event_manager.clear(); +		GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); +		auto & camera +			= obj.add_component<Camera>(ivec2{500, 500}, vec2{500, 500}, +										Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); +		render.update(); +		//mediator.event_manager = event_manager; +		//mediator.component_manager = mgr; +		//event_manager.clear();  	}  	void simulate_mouse_click(int mouse_x, int mouse_y, Uint8 mouse_button) { @@ -60,10 +66,6 @@ protected:  };  TEST_F(InputTest, MouseDown) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>( -		ivec2{0, 0}, vec2{500, 500}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); -	camera.active = true;  	bool mouse_triggered = false;  	EventHandler<MousePressEvent> on_mouse_down = [&](const MousePressEvent & event) {  		mouse_triggered = true; @@ -90,10 +92,6 @@ TEST_F(InputTest, MouseDown) {  }  TEST_F(InputTest, MouseUp) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>( -		ivec2{0, 0}, vec2{500, 500}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); -	camera.active = true;  	bool function_triggered = false;  	EventHandler<MouseReleaseEvent> on_mouse_release = [&](const MouseReleaseEvent & e) {  		function_triggered = true; @@ -118,10 +116,6 @@ TEST_F(InputTest, MouseUp) {  }  TEST_F(InputTest, MouseMove) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>( -		ivec2{0, 0}, vec2{500, 500}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); -	camera.active = true;  	bool function_triggered = false;  	EventHandler<MouseMoveEvent> on_mouse_move = [&](const MouseMoveEvent & e) {  		function_triggered = true; @@ -148,10 +142,6 @@ TEST_F(InputTest, MouseMove) {  }  TEST_F(InputTest, KeyDown) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>( -		ivec2{0, 0}, vec2{500, 500}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); -	camera.active = true;  	bool function_triggered = false;  	// Define event handler for KeyPressEvent @@ -179,10 +169,6 @@ TEST_F(InputTest, KeyDown) {  }  TEST_F(InputTest, KeyUp) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>( -		ivec2{0, 0}, vec2{500, 500}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); -	camera.active = true;  	bool function_triggered = false;  	EventHandler<KeyReleaseEvent> on_key_release = [&](const KeyReleaseEvent & event) {  		function_triggered = true; @@ -203,10 +189,6 @@ TEST_F(InputTest, KeyUp) {  }  TEST_F(InputTest, MouseClick) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>( -		ivec2{0, 0}, vec2{500, 500}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); -	camera.active = true;  	bool on_click_triggered = false;  	EventHandler<MouseClickEvent> on_mouse_click = [&](const MouseClickEvent & event) {  		on_click_triggered = true; @@ -224,10 +206,6 @@ TEST_F(InputTest, MouseClick) {  }  TEST_F(InputTest, testButtonClick) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>( -		ivec2{0, 0}, vec2{500, 500}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); -	camera.active = true;  	GameObject button_obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1);  	bool button_clicked = false;  	std::function<void()> on_click = [&]() { button_clicked = true; }; @@ -251,10 +229,6 @@ TEST_F(InputTest, testButtonClick) {  }  TEST_F(InputTest, testButtonHover) { -	GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); -	auto & camera = obj.add_component<Camera>( -		ivec2{0, 0}, vec2{500, 500}, Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); -	camera.active = true;  	GameObject button_obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1);  	bool button_clicked = false;  	std::function<void()> on_click = [&]() { button_clicked = true; }; diff --git a/src/test/LoopManagerTest.cpp b/src/test/LoopManagerTest.cpp new file mode 100644 index 0000000..df132ae --- /dev/null +++ b/src/test/LoopManagerTest.cpp @@ -0,0 +1,78 @@ +#include <chrono> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <thread> +#define private public +#define protected public +#include <crepe/api/LoopManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/manager/LoopTimerManager.h> +using namespace std::chrono; +using namespace crepe; + +class DISABLED_LoopManagerTest : public ::testing::Test { +protected: +	class TestGameLoop : public crepe::LoopManager { +	public: +		MOCK_METHOD(void, fixed_update, (), (override)); +		MOCK_METHOD(void, frame_update, (), (override)); +	}; + +	TestGameLoop test_loop; +	void SetUp() override {} +}; + +TEST_F(DISABLED_LoopManagerTest, FixedUpdate) { +	// Arrange +	test_loop.loop_timer.set_target_framerate(60); + +	// Set expectations for the mock calls +	EXPECT_CALL(test_loop, frame_update).Times(::testing::Between(55, 65)); +	EXPECT_CALL(test_loop, fixed_update).Times(::testing::Between(48, 52)); + +	// Start the loop in a separate thread +	std::thread loop_thread([&]() { test_loop.start(); }); + +	// Let the loop run for exactly 1 second +	std::this_thread::sleep_for(std::chrono::seconds(1)); + +	// Stop the game loop +	test_loop.game_running = false; +	// Wait for the loop thread to finish +	loop_thread.join(); + +	// Test finished +} + +TEST_F(DISABLED_LoopManagerTest, ScaledFixedUpdate) { +	// Arrange +	test_loop.loop_timer.set_target_framerate(60); + +	// Set expectations for the mock calls +	EXPECT_CALL(test_loop, frame_update).Times(::testing::Between(55, 65)); +	EXPECT_CALL(test_loop, fixed_update).Times(::testing::Between(48, 52)); + +	// Start the loop in a separate thread +	std::thread loop_thread([&]() { test_loop.start(); }); + +	// Let the loop run for exactly 1 second +	std::this_thread::sleep_for(std::chrono::seconds(1)); + +	// Stop the game loop +	test_loop.game_running = false; +	// Wait for the loop thread to finish +	loop_thread.join(); + +	// Test finished +} + +TEST_F(DISABLED_LoopManagerTest, ShutDown) { +	// Arrange +	test_loop.loop_timer.set_target_framerate(60); +	// Start the loop in a separate thread +	std::thread loop_thread([&]() { test_loop.start(); }); +	std::this_thread::sleep_for(std::chrono::milliseconds(1)); +	test_loop.event_manager.trigger_event<ShutDownEvent>(ShutDownEvent{}); +	// Wait for the loop thread to finish +	loop_thread.join(); +} diff --git a/src/test/LoopTimerTest.cpp b/src/test/LoopTimerTest.cpp new file mode 100644 index 0000000..7bd6305 --- /dev/null +++ b/src/test/LoopTimerTest.cpp @@ -0,0 +1,97 @@ +#include <chrono> +#include <gtest/gtest.h> +#include <thread> + +#define private public +#define protected public + +#include <crepe/manager/LoopTimerManager.h> +#include <crepe/manager/Mediator.h> + +using namespace std::chrono; +using namespace crepe; + +class LoopTimerTest : public ::testing::Test { +protected: +	Mediator mediator; +	LoopTimerManager loop_timer{mediator}; + +	void SetUp() override { loop_timer.start(); } +}; + +TEST_F(LoopTimerTest, EnforcesTargetFrameRate) { +	// Set the target FPS to 60 (which gives a target time per frame of ~16.67 ms) +	loop_timer.set_target_framerate(60); + +	auto start_time = steady_clock::now(); +	loop_timer.enforce_frame_rate(); + +	auto elapsed_time = steady_clock::now() - start_time; +	auto elapsed_ms = duration_cast<milliseconds>(elapsed_time).count(); + +	// For 60 FPS, the target frame time is around 16.67ms +	ASSERT_NEAR(elapsed_ms, 16.7, 5); +} + +TEST_F(LoopTimerTest, SetTargetFps) { +	// Set the target FPS to 120 +	loop_timer.set_target_framerate(120); + +	// Calculate the expected frame time (~8.33ms per frame) +	duration_t expected_frame_time = std::chrono::duration<float>(1.0 / 120.0); + +	ASSERT_NEAR(loop_timer.frame_target_time.count(), expected_frame_time.count(), 0.001); +} + +TEST_F(LoopTimerTest, DeltaTimeCalculation) { +	// Set the target FPS to 60 (16.67 ms per frame) +	loop_timer.set_target_framerate(60); + +	auto start_time = steady_clock::now(); +	loop_timer.update(); +	auto end_time = steady_clock::now(); + +	// Check the delta time +	duration_t delta_time = loop_timer.get_delta_time(); + +	auto elapsed_time = duration_cast<seconds>(end_time - start_time).count(); + +	// Assert that delta_time is close to the elapsed time +	ASSERT_NEAR(delta_time.count(), elapsed_time, 1); +} + +TEST_F(LoopTimerTest, DISABLED_getCurrentTime) { +	// Set the target FPS to 60 (16.67 ms per frame) +	loop_timer.set_target_framerate(60); + +	auto start_time = steady_clock::now(); + +	// Sleep +	std::this_thread::sleep_for(std::chrono::milliseconds(100)); + +	loop_timer.update(); + +	auto end_time = steady_clock::now(); + +	// Get the elapsed time in seconds as a double +	auto elapsed_time +		= std::chrono::duration_cast<elapsed_time_t>(end_time - start_time).count(); + +	ASSERT_NEAR(loop_timer.get_elapsed_time().count(), elapsed_time, 5); +} +TEST_F(LoopTimerTest, getFPS) { +	// Set the target FPS to 60 (which gives a target time per frame of ~16.67 ms) +	loop_timer.set_target_framerate(60); + +	auto start_time = steady_clock::now(); +	loop_timer.enforce_frame_rate(); + +	auto elapsed_time = steady_clock::now() - start_time; +	loop_timer.update(); +	unsigned int fps = loop_timer.get_fps(); +	auto elapsed_ms = duration_cast<milliseconds>(elapsed_time).count(); + +	// For 60 FPS, the target frame time is around 16.67ms +	ASSERT_NEAR(elapsed_ms, 16.7, 1); +	ASSERT_NEAR(fps, 60, 2); +} diff --git a/src/test/PhysicsTest.cpp b/src/test/PhysicsTest.cpp index 43d2931..3afb3c7 100644 --- a/src/test/PhysicsTest.cpp +++ b/src/test/PhysicsTest.cpp @@ -3,6 +3,8 @@  #include <crepe/api/Rigidbody.h>  #include <crepe/api/Transform.h>  #include <crepe/manager/ComponentManager.h> +#include <crepe/manager/LoopTimerManager.h> +#include <crepe/manager/Mediator.h>  #include <crepe/system/PhysicsSystem.h>  #include <gtest/gtest.h> @@ -16,6 +18,7 @@ class PhysicsTest : public ::testing::Test {  public:  	ComponentManager component_manager{m};  	PhysicsSystem system{m}; +	LoopTimerManager loop_timer{m};  	void SetUp() override {  		ComponentManager & mgr = this->component_manager; @@ -27,7 +30,7 @@ public:  				.mass = 1,  				.gravity_scale = 1,  				.body_type = Rigidbody::BodyType::DYNAMIC, -				.max_linear_velocity = vec2{10, 10}, +				.max_linear_velocity = 10,  				.max_angular_velocity = 10,  				.constraints = {0, 0},  			}); @@ -55,39 +58,40 @@ TEST_F(PhysicsTest, gravity) {  	EXPECT_EQ(transform.position.y, 0);  	system.update(); -	EXPECT_EQ(transform.position.y, 1); +	EXPECT_NEAR(transform.position.y, 0.0004, 0.0001);  	system.update(); -	EXPECT_EQ(transform.position.y, 3); +	EXPECT_NEAR(transform.position.y, 0.002, 0.001);  }  TEST_F(PhysicsTest, max_velocity) {  	ComponentManager & mgr = this->component_manager;  	vector<reference_wrapper<Rigidbody>> rigidbodies = mgr.get_components_by_id<Rigidbody>(0);  	Rigidbody & rigidbody = rigidbodies.front().get(); +	rigidbody.data.gravity_scale = 0;  	ASSERT_FALSE(rigidbodies.empty());  	EXPECT_EQ(rigidbody.data.linear_velocity.y, 0);  	rigidbody.add_force_linear({100, 100});  	rigidbody.add_force_angular(100);  	system.update(); -	EXPECT_EQ(rigidbody.data.linear_velocity.y, 10); -	EXPECT_EQ(rigidbody.data.linear_velocity.x, 10); +	EXPECT_NEAR(rigidbody.data.linear_velocity.y, 7.07, 0.01); +	EXPECT_NEAR(rigidbody.data.linear_velocity.x, 7.07, 0.01);  	EXPECT_EQ(rigidbody.data.angular_velocity, 10);  	rigidbody.add_force_linear({-100, -100});  	rigidbody.add_force_angular(-100);  	system.update(); -	EXPECT_EQ(rigidbody.data.linear_velocity.y, -10); -	EXPECT_EQ(rigidbody.data.linear_velocity.x, -10); +	EXPECT_NEAR(rigidbody.data.linear_velocity.y, -7.07, 0.01); +	EXPECT_NEAR(rigidbody.data.linear_velocity.x, -7.07, 0.01);  	EXPECT_EQ(rigidbody.data.angular_velocity, -10);  }  TEST_F(PhysicsTest, movement) { -	Config::get_instance().physics.gravity = 0;  	ComponentManager & mgr = this->component_manager;  	vector<reference_wrapper<Rigidbody>> rigidbodies = mgr.get_components_by_id<Rigidbody>(0);  	Rigidbody & rigidbody = rigidbodies.front().get(); +	rigidbody.data.gravity_scale = 0;  	vector<reference_wrapper<Transform>> transforms = mgr.get_components_by_id<Transform>(0);  	const Transform & transform = transforms.front().get();  	ASSERT_FALSE(rigidbodies.empty()); @@ -96,31 +100,33 @@ TEST_F(PhysicsTest, movement) {  	rigidbody.add_force_linear({1, 1});  	rigidbody.add_force_angular(1);  	system.update(); -	EXPECT_EQ(transform.position.x, 1); -	EXPECT_EQ(transform.position.y, 1); -	EXPECT_EQ(transform.rotation, 1); +	EXPECT_NEAR(transform.position.x, 0.02, 0.001); +	EXPECT_NEAR(transform.position.y, 0.02, 0.001); +	EXPECT_NEAR(transform.rotation, 0.02, 0.001);  	rigidbody.data.constraints = {1, 1, 1}; -	EXPECT_EQ(transform.position.x, 1); -	EXPECT_EQ(transform.position.y, 1); -	EXPECT_EQ(transform.rotation, 1); - +	EXPECT_NEAR(transform.position.x, 0.02, 0.001); +	EXPECT_NEAR(transform.position.y, 0.02, 0.001); +	EXPECT_NEAR(transform.rotation, 0.02, 0.001); +	rigidbody.data.constraints = {0, 0, 0};  	rigidbody.data.linear_velocity_coefficient.x = 0.5;  	rigidbody.data.linear_velocity_coefficient.y = 0.5;  	rigidbody.data.angular_velocity_coefficient = 0.5;  	system.update(); -	EXPECT_EQ(rigidbody.data.linear_velocity.x, 0.5); -	EXPECT_EQ(rigidbody.data.linear_velocity.y, 0.5); -	EXPECT_EQ(rigidbody.data.angular_velocity, 0.5); +	EXPECT_NEAR(rigidbody.data.linear_velocity.x, 0.98, 0.01); +	EXPECT_NEAR(rigidbody.data.linear_velocity.y, 0.98, 0.01); +	EXPECT_NEAR(rigidbody.data.angular_velocity, 0.98, 0.01);  	rigidbody.data.constraints = {1, 1, 0};  	rigidbody.data.angular_velocity_coefficient = 0;  	rigidbody.data.max_angular_velocity = 1000;  	rigidbody.data.angular_velocity = 360;  	system.update(); -	EXPECT_EQ(transform.rotation, 1); +	EXPECT_NEAR(transform.rotation, 7.24, 0.01);  	rigidbody.data.angular_velocity = -360;  	system.update(); -	EXPECT_EQ(transform.rotation, 1); +	EXPECT_NEAR(transform.rotation, 0.04, 0.001); +	system.update(); +	EXPECT_NEAR(transform.rotation, 352.84, 0.01);  } diff --git a/src/test/Profiling.cpp b/src/test/Profiling.cpp index cc4c637..35f52dc 100644 --- a/src/test/Profiling.cpp +++ b/src/test/Profiling.cpp @@ -17,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> diff --git a/src/test/SceneManagerTest.cpp b/src/test/SceneManagerTest.cpp index 9bb260c..480e07a 100644 --- a/src/test/SceneManagerTest.cpp +++ b/src/test/SceneManagerTest.cpp @@ -15,11 +15,9 @@ using namespace crepe;  class ConcreteScene1 : public Scene {  public:  	void load_scene() { -		Mediator & mediator = this->mediator; -		ComponentManager & mgr = mediator.component_manager; -		GameObject object1 = mgr.new_object("scene_1", "tag_scene_1", vec2{0, 0}, 0, 1); -		GameObject object2 = mgr.new_object("scene_1", "tag_scene_1", vec2{1, 0}, 0, 1); -		GameObject object3 = mgr.new_object("scene_1", "tag_scene_1", vec2{2, 0}, 0, 1); +		GameObject object1 = new_object("scene_1", "tag_scene_1", vec2{0, 0}, 0, 1); +		GameObject object2 = new_object("scene_1", "tag_scene_1", vec2{1, 0}, 0, 1); +		GameObject object3 = new_object("scene_1", "tag_scene_1", vec2{2, 0}, 0, 1);  	}  	string get_name() const { return "scene1"; } @@ -28,12 +26,10 @@ public:  class ConcreteScene2 : public Scene {  public:  	void load_scene() { -		Mediator & mediator = this->mediator; -		ComponentManager & mgr = mediator.component_manager; -		GameObject object1 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 0}, 0, 1); -		GameObject object2 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 1}, 0, 1); -		GameObject object3 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 2}, 0, 1); -		GameObject object4 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 3}, 0, 1); +		GameObject object1 = new_object("scene_2", "tag_scene_2", vec2{0, 0}, 0, 1); +		GameObject object2 = new_object("scene_2", "tag_scene_2", vec2{0, 1}, 0, 1); +		GameObject object3 = new_object("scene_2", "tag_scene_2", vec2{0, 2}, 0, 1); +		GameObject object4 = new_object("scene_2", "tag_scene_2", vec2{0, 3}, 0, 1);  	}  	string get_name() const { return "scene2"; } @@ -44,9 +40,7 @@ public:  	ConcreteScene3(const string & name) : name(name) {}  	void load_scene() { -		Mediator & mediator = this->mediator; -		ComponentManager & mgr = mediator.component_manager; -		GameObject object1 = mgr.new_object("scene_3", "tag_scene_3", vec2{0, 0}, 0, 1); +		GameObject object1 = new_object("scene_3", "tag_scene_3", vec2{0, 0}, 0, 1);  	}  	string get_name() const { return name; } diff --git a/src/test/ScriptTest.h b/src/test/ScriptTest.h index 309e016..31fa7c9 100644 --- a/src/test/ScriptTest.h +++ b/src/test/ScriptTest.h @@ -6,8 +6,8 @@  #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; @@ -16,6 +16,7 @@ protected:  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 { |