diff options
Diffstat (limited to 'src/crepe')
83 files changed, 1674 insertions, 752 deletions
| diff --git a/src/crepe/Component.cpp b/src/crepe/Component.cpp index acfd35c..ae76e65 100644 --- a/src/crepe/Component.cpp +++ b/src/crepe/Component.cpp @@ -1,5 +1,17 @@  #include "Component.h"  using namespace crepe; +using namespace std;  Component::Component(game_object_id_t id) : game_object_id(id) {} + +Component & Component::operator=(const Component & other) { +	this->active = other.active; +	return *this; +} + +unique_ptr<Component> Component::save() const { +	return unique_ptr<Component>(new Component(*this)); +} + +void Component::restore(const Component & snapshot) { *this = snapshot; } diff --git a/src/crepe/Component.h b/src/crepe/Component.h index eff5a58..52e06d5 100644 --- a/src/crepe/Component.h +++ b/src/crepe/Component.h @@ -1,5 +1,7 @@  #pragma once +#include <memory> +  #include "types.h"  namespace crepe { @@ -32,11 +34,33 @@ protected:  	//! Only ComponentManager can create components  	friend class ComponentManager; -	Component(const Component &) = delete; +	// components are never moved  	Component(Component &&) = delete; -	virtual Component & operator=(const Component &) = delete;  	virtual Component & operator=(Component &&) = delete; +protected: +	/** +	 * \name ReplayManager (Memento) functions +	 * \{ +	 */ +	/** +	 * \brief Save a snapshot of this component's state +	 * \note This function should only be implemented on components that should be saved/restored +	 * by ReplayManager. +	 * \returns Unique pointer to a deep copy of this component +	 */ +	virtual std::unique_ptr<Component> save() const; +	//! Copy constructor (used by \c save()) +	Component(const Component &) = default; +	/** +	 * \brief Restore this component from a snapshot +	 * \param snapshot Data to fill this component with (as returned by \c save()) +	 */ +	virtual void restore(const Component & snapshot); +	//! Copy assignment operator (used by \c restore()) +	virtual Component & operator=(const Component &); +	//! \} +  public:  	virtual ~Component() = default; diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp index 485a0d4..b340826 100644 --- a/src/crepe/Particle.cpp +++ b/src/crepe/Particle.cpp @@ -2,8 +2,8 @@  using namespace crepe; -void Particle::reset(uint32_t lifespan, const vec2 & position, const vec2 & velocity, -					 double angle) { +void Particle::reset(unsigned int lifespan, const vec2 & position, const vec2 & velocity, +					 float angle) {  	// Initialize the particle state  	this->time_in_life = 0;  	this->lifespan = lifespan; @@ -15,16 +15,17 @@ void Particle::reset(uint32_t lifespan, const vec2 & position, const vec2 & velo  	this->force_over_time = {0, 0};  } -void Particle::update() { +void Particle::update(double dt) {  	// Deactivate particle if it has exceeded its lifespan -	if (++time_in_life >= lifespan) { +	time_in_life += dt; +	if (time_in_life >= lifespan) {  		this->active = false;  		return;  	}  	// Update velocity based on accumulated force and update position -	this->velocity += force_over_time; -	this->position += velocity; +	this->velocity += force_over_time * dt; +	this->position += velocity * dt;  }  void Particle::stop_movement() { diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h index d0397c9..ee0cd66 100644 --- a/src/crepe/Particle.h +++ b/src/crepe/Particle.h @@ -14,8 +14,6 @@ namespace crepe {   * can also be reset or stopped.   */  class Particle { -	// TODO: add friend particleSsytem and rendersystem. Unit test will fail. -  public:  	//! Position of the particle in 2D space.  	vec2 position; @@ -24,13 +22,13 @@ public:  	//! Accumulated force affecting the particle over time.  	vec2 force_over_time;  	//! Total lifespan of the particle in milliseconds. -	uint32_t lifespan; +	float lifespan;  	//! Active state of the particle; true if it is in use, false otherwise.  	bool active = false;  	//! The time the particle has been alive, in milliseconds. -	uint32_t time_in_life = 0; +	float time_in_life = 0;  	//! The angle at which the particle is oriented or moving. -	double angle = 0; +	float angle = 0;  	/**  	 * \brief Resets the particle with new properties. @@ -43,14 +41,16 @@ public:  	 * \param velocity  The initial velocity of the particle.  	 * \param angle     The angle of the particle's trajectory or orientation.  	 */ -	void reset(uint32_t lifespan, const vec2 & position, const vec2 & velocity, double angle); +	void reset(unsigned int lifespan, const vec2 & position, const vec2 & velocity, +			   float angle);  	/**  	 * \brief Updates the particle's state.  	 *  	 * Advances the particle's position based on its velocity and applies accumulated forces.  	 * Deactivates the particle if its lifespan has expired. +	 * \param dt The amount of fixed delta time that has passed.  	 */ -	void update(); +	void update(double dt);  	/**  	 * \brief Stops the particle's movement.  	 * diff --git a/src/crepe/api/Animator.cpp b/src/crepe/api/Animator.cpp index 4ce4bf0..203cef3 100644 --- a/src/crepe/api/Animator.cpp +++ b/src/crepe/api/Animator.cpp @@ -1,5 +1,5 @@ -#include "util/Log.h" +#include "util/dbg.h"  #include "Animator.h"  #include "Component.h" diff --git a/src/crepe/api/Asset.h b/src/crepe/api/Asset.h index bfd0ac7..d802e83 100644 --- a/src/crepe/api/Asset.h +++ b/src/crepe/api/Asset.h @@ -43,13 +43,13 @@ private:  	/**  	 * \brief Locate asset path, or throw exception if it cannot be found  	 * -	 * This function resolves asset locations relative to crepe::Config::root_pattern if it is +	 * This function resolves asset locations relative to Config::asset::root_pattern if it is  	 * set and \p src is a relative path. If \p src is an absolute path, it is canonicalized.  	 * This function only returns if the file can be found.  	 *  	 * \param src Arbitrary path to resource file  	 * -	 * \returns \p src if crepe::Config::root_pattern is empty +	 * \returns \p src if Config::asset::root_pattern is empty  	 * \returns Canonical path to \p src  	 *  	 * \throws std::runtime_error if root_pattern cannot be found diff --git a/src/crepe/api/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp index d22afdf..af7572c 100644 --- a/src/crepe/api/BehaviorScript.cpp +++ b/src/crepe/api/BehaviorScript.cpp @@ -10,6 +10,6 @@ BehaviorScript::BehaviorScript(game_object_id_t id, Mediator & mediator)  template <>  BehaviorScript & GameObject::add_component<BehaviorScript>() { -	ComponentManager & mgr = this->component_manager; -	return mgr.add_component<BehaviorScript>(this->id, mgr.mediator); +	ComponentManager & mgr = this->mediator.component_manager; +	return mgr.add_component<BehaviorScript>(this->id, this->mediator);  } diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp index b9bb1e2..353d5e2 100644 --- a/src/crepe/api/BehaviorScript.hpp +++ b/src/crepe/api/BehaviorScript.hpp @@ -2,8 +2,6 @@  #include <type_traits> -#include "../util/Log.h" -  #include "BehaviorScript.h"  #include "Script.h" @@ -11,7 +9,6 @@ namespace crepe {  template <class T, typename... Args>  BehaviorScript & BehaviorScript::set_script(Args &&... args) { -	dbg_trace();  	static_assert(std::is_base_of<Script, T>::value);  	this->script = std::unique_ptr<Script>(new T(std::forward<Args>(args)...)); diff --git a/src/crepe/api/BoxCollider.cpp b/src/crepe/api/BoxCollider.cpp index c097a24..a893d41 100644 --- a/src/crepe/api/BoxCollider.cpp +++ b/src/crepe/api/BoxCollider.cpp @@ -4,7 +4,7 @@  using namespace crepe; -BoxCollider::BoxCollider(game_object_id_t game_object_id, const vec2 & offset, -						 const vec2 & dimensions) +BoxCollider::BoxCollider(game_object_id_t game_object_id, const vec2 & dimensions, +						 const vec2 & offset)  	: Collider(game_object_id, offset),  	  dimensions(dimensions) {} diff --git a/src/crepe/api/BoxCollider.h b/src/crepe/api/BoxCollider.h index 1ac4d46..3835e2c 100644 --- a/src/crepe/api/BoxCollider.h +++ b/src/crepe/api/BoxCollider.h @@ -13,7 +13,7 @@ namespace crepe {   */  class BoxCollider : public Collider {  public: -	BoxCollider(game_object_id_t game_object_id, const vec2 & offset, const vec2 & dimensions); +	BoxCollider(game_object_id_t game_object_id, const vec2 & dimensions, const vec2 & offset = { 0, 0 });  	//! Width and height of the box collider  	vec2 dimensions; diff --git a/src/crepe/api/Button.cpp b/src/crepe/api/Button.cpp index 76f74f0..305922c 100644 --- a/src/crepe/api/Button.cpp +++ b/src/crepe/api/Button.cpp @@ -3,9 +3,8 @@  namespace crepe {  Button::Button(game_object_id_t id, const vec2 & dimensions, const vec2 & offset, -			   const std::function<void()> & on_click, bool is_toggle) +			   const std::function<void()> & on_click)  	: UIObject(id, dimensions, offset), -	  is_toggle(is_toggle),  	  on_click(on_click) {}  } // namespace crepe diff --git a/src/crepe/api/Button.h b/src/crepe/api/Button.h index 61b18d7..08f5dec 100644 --- a/src/crepe/api/Button.h +++ b/src/crepe/api/Button.h @@ -15,19 +15,11 @@ public:  	 * \param id The unique ID of the game object associated with this button.  	 * \param dimensions The width and height of the UIObject  	 * \param offset The offset relative this GameObjects Transform -	 * \param is_toggle Optional flag to indicate if the button is a toggle button. Defaults to false.  	 * \param on_click callback function that will be invoked when the button is clicked.  	 */  	Button(game_object_id_t id, const vec2 & dimensions, const vec2 & offset, -		   const std::function<void()> & on_click, bool is_toggle = false); +		   const std::function<void()> & on_click); -	/** -	 * \brief Indicates if the button is a toggle button (can be pressed and released). -	 * -	 * A toggle button allows for a pressed/released state, whereas a regular button -	 * typically only has an on-click state. -	 */ -	bool is_toggle = false;  	// TODO: create separate toggle button class  	/**  	 * \brief The callback function to be executed when the button is clicked. @@ -56,8 +48,6 @@ public:  private:  	//! friend relation for is_pressed and hover variables  	friend class InputSystem; -	//! Indicates whether the toggle button is pressed -	bool is_pressed = false;  	//! Indicates whether the mouse is currently hovering over the button  	bool hover = false; diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 8f84f06..18d6942 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -13,7 +13,7 @@ target_sources(crepe PUBLIC  	Animator.cpp  	BoxCollider.cpp  	CircleCollider.cpp -	LoopManager.cpp +	Engine.cpp  	Asset.cpp  	EventHandler.cpp  	Script.cpp @@ -46,7 +46,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	EventHandler.h  	EventHandler.hpp  	Event.h -	LoopManager.h +	Engine.h +	Engine.hpp  	Asset.h  	Button.h  	UIObject.h diff --git a/src/crepe/api/Camera.cpp b/src/crepe/api/Camera.cpp index 179dc18..9befc3f 100644 --- a/src/crepe/api/Camera.cpp +++ b/src/crepe/api/Camera.cpp @@ -1,4 +1,4 @@ -#include "util/Log.h" +#include "util/dbg.h"  #include "Camera.h"  #include "Component.h" @@ -6,10 +6,8 @@  using namespace crepe; -Camera::Camera(game_object_id_t id, const ivec2 & screen, const vec2 & viewport_size, -			   const Data & data) +Camera::Camera(game_object_id_t id, const vec2 & viewport_size, const Data & data)  	: Component(id), -	  screen(screen),  	  viewport_size(viewport_size),  	  data(data) {  	dbg_trace(); diff --git a/src/crepe/api/Camera.h b/src/crepe/api/Camera.h index 54d9a73..2fea9f3 100644 --- a/src/crepe/api/Camera.h +++ b/src/crepe/api/Camera.h @@ -30,7 +30,7 @@ public:  		 * zoom < 1 --> zoom out  		 * zoom > 1 --> zoom in  		 */ -		double zoom = 1; +		float zoom = 1.0;  		//! offset postion from the game object transform component  		vec2 postion_offset; @@ -40,19 +40,14 @@ public:  	/**  	 * \brief Constructs a Camera with the specified ID and background color.  	 * \param id Unique identifier for the camera component. -	 * \param screen is the actual screen size in pixels  	 * \param viewport_size is the view of the world in game units  	 * \param data the camera component data  	 */ -	Camera(game_object_id_t id, const ivec2 & screen, const vec2 & viewport_size, -		   const Camera::Data & data); +	Camera(game_object_id_t id, const vec2 & viewport_size, const Data & data);  	~Camera(); // dbg_trace only  public: -	Camera::Data data; - -	//! screen the display size in pixels ( output resolution ) -	const ivec2 screen; +	Data data;  	//! viewport is the area of the world visible through the camera (in world units)  	const vec2 viewport_size; diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h index ca2d3f1..3cc67c2 100644 --- a/src/crepe/api/Config.h +++ b/src/crepe/api/Config.h @@ -3,24 +3,21 @@  #include <string>  #include "../util/Log.h" - -#include "types.h" +#include "../types.h"  namespace crepe {  /**   * \brief Global configuration interface   * - * This class stores engine default settings. Properties on this class are only supposed to be - * modified *before* execution is handed over from the game programmer to the engine (i.e. the - * main loop is started). + * This struct stores both engine default settings and global configuration parameters.   */  struct Config final {  	//! Retrieve handle to global Config instance  	static Config & get_instance();  	//! Logging-related settings -	struct { +	struct log { // NOLINT  		/**  		 * \brief Log level  		 * @@ -28,7 +25,7 @@ struct Config final {  		 */  		Log::Level level = Log::Level::INFO;  		/** -		 * \brief Colored log output +		 * \brief Enable colored log output  		 *  		 * Enables log coloring using ANSI escape codes.  		 */ @@ -36,7 +33,7 @@ struct Config final {  	} log;  	//! Save manager -	struct { +	struct savemgr { // NOLINT  		/**  		 * \brief Save file location  		 * @@ -46,8 +43,8 @@ struct Config final {  		std::string location = "save.crepe.db";  	} savemgr; -	//! physics-related settings -	struct { +	//! Physics-related settings +	struct physics { // NOLINT  		/**  		 * \brief gravity value of physics system  		 * @@ -56,15 +53,16 @@ struct Config final {  		float gravity = 10;  	} physics; -	//! default window settings -	struct { -		//! default screen size in pixels -		ivec2 default_size = {1280, 720}; -		std::string window_title = "Jetpack joyride clone"; -	} window_settings; +	//! Default window settings +	struct window { // NOLINT +		//! Default window size (in pixels) +		ivec2 size = {1280, 720}; +		//! Default window title +		std::string title = "Jetpack joyride clone"; +	} window;  	//! Asset loading options -	struct { +	struct asset { // NOLINT  		/**  		 * \brief Pattern to match for Asset base directory  		 * @@ -76,6 +74,11 @@ struct Config final {  		 */  		std::string root_pattern = ".crepe-root";  	} asset; +	//! Configuration for click tolerance. +	struct { +		//! The maximum number of pixels the mouse can move between MouseDown and MouseUp events to be considered a click. +		int click_tolerance = 5; +	} input;  	//! Audio system settings  	struct { diff --git a/src/crepe/api/Engine.cpp b/src/crepe/api/Engine.cpp new file mode 100644 index 0000000..bbb4494 --- /dev/null +++ b/src/crepe/api/Engine.cpp @@ -0,0 +1,63 @@ +#include "../util/Log.h" + +#include "Engine.h" + +using namespace crepe; +using namespace std; + +int Engine::main() noexcept { +	try { +		this->setup(); +	} catch (const exception & e) { +		Log::logf(Log::Level::ERROR, "Uncaught exception in setup: {}\n", e.what()); +		return EXIT_FAILURE; +	} + +	try { +		this->loop(); +	} catch (const exception & e) { +		Log::logf(Log::Level::ERROR, "Uncaught exception in main loop: {}\n", e.what()); +		this->event_manager.trigger_event<ShutDownEvent>(); +	} + +	return EXIT_SUCCESS; +} + +void Engine::setup() { +	this->loop_timer.start(); +	this->scene_manager.load_next_scene(); + +	this->event_manager.subscribe<ShutDownEvent>([this](const ShutDownEvent & event) { +		this->game_running = false; + +		// propagate to possible user ShutDownEvent listeners +		return false; +	}); +} + +void Engine::loop() { +	LoopTimerManager & timer = this->loop_timer; +	SystemManager & systems = this->system_manager; + +	while (game_running) { +		timer.update(); + +		while (timer.get_lag() >= timer.get_fixed_delta_time()) { +			try { +				systems.fixed_update(); +			} catch (const exception & e) { +				Log::logf(Log::Level::WARNING, +						  "Uncaught exception in fixed update function: {}\n", e.what()); +			} +			timer.advance_fixed_elapsed_time(); +		} + +		try { +			systems.frame_update(); +		} catch (const exception & e) { +			Log::logf(Log::Level::WARNING, "Uncaught exception in frame update function: {}\n", +					  e.what()); +		} +		timer.enforce_frame_rate(); +	} +} diff --git a/src/crepe/api/Engine.h b/src/crepe/api/Engine.h new file mode 100644 index 0000000..3145723 --- /dev/null +++ b/src/crepe/api/Engine.h @@ -0,0 +1,78 @@ +#pragma once + +#include "../facade/SDLContext.h" +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/ReplayManager.h" +#include "../manager/ResourceManager.h" +#include "../manager/SaveManager.h" +#include "../manager/SceneManager.h" +#include "../manager/SystemManager.h" + +namespace crepe { + +/** + * \brief Main game entrypoint + * + * This class is responsible for managing the game loop, including initialization and updating. + */ +class Engine { +public: +	/** +	 * \brief Engine entrypoint +	 * +	 * This function is called by the game programmer after registering all scenes +	 * +	 * \returns process exit code +	 */ +	int main() noexcept; + +	//! \copydoc SceneManager::add_scene +	template <typename T> +	void add_scene(); + +private: +	/** +	 * \brief Setup function for one-time initialization. +	 * +	 * This function initializes necessary components for the game. +	 */ +	void setup(); +	/** +	 * \brief Main game loop function. +	 * +	 * This function runs the main loop, handling game updates and rendering. +	 */ +	void loop(); + +	//! Game loop condition +	bool game_running = true; + +private: +	//! Global context +	Mediator mediator; + +	//! Component manager instance +	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}; +	//! ReplayManager instance +	ReplayManager replay_manager{mediator}; +	//! SystemManager +	SystemManager system_manager{mediator}; +}; + +} // namespace crepe + +#include "Engine.hpp" diff --git a/src/crepe/api/Engine.hpp b/src/crepe/api/Engine.hpp new file mode 100644 index 0000000..f2fdc0a --- /dev/null +++ b/src/crepe/api/Engine.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "Engine.h" + +namespace crepe { + +template <class T> +void Engine::add_scene() { +	this->scene_manager.add_scene<T>(); +} + +} // namespace crepe diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h index f2f3daf..17ae809 100644 --- a/src/crepe/api/Event.h +++ b/src/crepe/api/Event.h @@ -4,6 +4,7 @@  #include <string>  #include "KeyCodes.h" +#include "types.h"  namespace crepe { @@ -38,11 +39,8 @@ public:   */  class MousePressEvent : public Event {  public: -	//! X-coordinate of the mouse position at the time of the event. -	int mouse_x = 0; - -	//! Y-coordinate of the mouse position at the time of the event. -	int mouse_y = 0; +	//! mouse position +	vec2 mouse_pos = {0, 0};  	//! The mouse button that was pressed.  	MouseButton button = MouseButton::NONE; @@ -53,11 +51,8 @@ public:   */  class MouseClickEvent : public Event {  public: -	//! X-coordinate of the mouse position at the time of the event. -	int mouse_x = 0; - -	//! Y-coordinate of the mouse position at the time of the event. -	int mouse_y = 0; +	//! mouse position +	vec2 mouse_pos = {0, 0};  	//! The mouse button that was clicked.  	MouseButton button = MouseButton::NONE; @@ -68,11 +63,8 @@ public:   */  class MouseReleaseEvent : public Event {  public: -	//! X-coordinate of the mouse position at the time of the event. -	int mouse_x = 0; - -	//! Y-coordinate of the mouse position at the time of the event. -	int mouse_y = 0; +	//! mouse position +	vec2 mouse_pos = {0, 0};  	//! The mouse button that was released.  	MouseButton button = MouseButton::NONE; @@ -83,17 +75,10 @@ public:   */  class MouseMoveEvent : public Event {  public: -	//! X-coordinate of the mouse position at the time of the event. -	int mouse_x = 0; - -	//! Y-coordinate of the mouse position at the time of the event. -	int mouse_y = 0; - -	// Movement since last event in x -	int delta_x = 0; - -	// Movement since last event in y -	int delta_y = 0; +	//! new mouse position +	vec2 mouse_pos = {0, 0}; +	//! The change in mouse position relative to the last position (in pixels). +	ivec2 mouse_delta = {0, 0};  };  /** @@ -101,12 +86,8 @@ public:   */  class MouseScrollEvent : public Event {  public: -	//! X-coordinate of the mouse position at the time of the event. -	int mouse_x = 0; - -	//! Y-coordinate of the mouse position at the time of the event. -	int mouse_y = 0; - +	//! mouse position when the scroll happened. +	vec2 mouse_pos = {0, 0};  	//! scroll direction (-1 = down, 1 = up)  	int scroll_direction = 0;  	//! scroll amount in y axis (from and away from the person). @@ -127,4 +108,55 @@ public:   */  class ShutDownEvent : public Event {}; +/** + * \brief Event triggered to indicate the window is overlapped by another window. + *  + * When two windows overlap the bottom window gets distorted and that window has to be redrawn. + */ +class WindowExposeEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window is resized. + */ +class WindowResizeEvent : public Event { +public: +	//! new window dimensions +	ivec2 dimensions = {0, 0}; +}; + +/** + * \brief Event triggered to indicate the window is moved. + */ +class WindowMoveEvent : public Event { +public: +	//! The change in position relative to the last position (in pixels). +	ivec2 delta_move = {0, 0}; +}; + +/** + * \brief Event triggered to indicate the window is minimized. + */ +class WindowMinimizeEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window is maximized + */ +class WindowMaximizeEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window gained focus + *  + * This event is triggered when the window receives focus, meaning it becomes the active window + * for user interaction. + */ +class WindowFocusGainEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window lost focus + *  + * This event is triggered when the window loses focus, meaning it is no longer the active window + * for user interaction. + */ +class WindowFocusLostEvent : public Event {}; +  } // namespace crepe diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp index 9ef4682..9b94cad 100644 --- a/src/crepe/api/GameObject.cpp +++ b/src/crepe/api/GameObject.cpp @@ -7,20 +7,17 @@  using namespace crepe;  using namespace std; -GameObject::GameObject(ComponentManager & component_manager, game_object_id_t id, -					   const std::string & name, const std::string & tag, -					   const vec2 & position, double rotation, double scale) +GameObject::GameObject(Mediator & mediator, game_object_id_t id, const std::string & name, +					   const std::string & tag, const vec2 & position, double rotation, +					   double scale)  	: id(id), -	  component_manager(component_manager) { - -	// Add Transform and Metadata components -	ComponentManager & mgr = this->component_manager; -	mgr.add_component<Transform>(this->id, position, rotation, scale); -	mgr.add_component<Metadata>(this->id, name, tag); -} +	  mediator(mediator), +	  transform(mediator.component_manager->add_component<Transform>(this->id, position, +																	 rotation, scale)), +	  metadata(mediator.component_manager->add_component<Metadata>(this->id, name, tag)) {}  void GameObject::set_parent(const GameObject & parent) { -	ComponentManager & mgr = this->component_manager; +	ComponentManager & mgr = this->mediator.component_manager;  	// Set parent on own Metadata component  	RefVector<Metadata> this_metadata = mgr.get_components_by_id<Metadata>(this->id); @@ -32,7 +29,7 @@ void GameObject::set_parent(const GameObject & parent) {  }  void GameObject::set_persistent(bool persistent) { -	ComponentManager & mgr = this->component_manager; +	ComponentManager & mgr = this->mediator.component_manager;  	mgr.set_persistent(this->id, persistent);  } diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h index ff80f49..572ce3a 100644 --- a/src/crepe/api/GameObject.h +++ b/src/crepe/api/GameObject.h @@ -6,7 +6,9 @@  namespace crepe { -class ComponentManager; +class Mediator; +class Transform; +class Metadata;  /**   * \brief Represents a GameObject @@ -20,7 +22,7 @@ private:  	 * This constructor creates a new GameObject. It creates a new Transform and Metadata  	 * component and adds them to the ComponentManager.  	 * -	 * \param component_manager Reference to component_manager +	 * \param mediator Reference to mediator  	 * \param id The id of the GameObject  	 * \param name The name of the GameObject  	 * \param tag The tag of the GameObject @@ -28,13 +30,20 @@ private:  	 * \param rotation The rotation of the GameObject  	 * \param scale The scale of the GameObject  	 */ -	GameObject(ComponentManager & component_manager, game_object_id_t id, -			   const std::string & name, const std::string & tag, const vec2 & position, -			   double rotation, double scale); +	GameObject(Mediator & mediator, game_object_id_t id, const std::string & name, +			   const std::string & tag, const vec2 & position, double rotation, double scale);  	//! ComponentManager instances GameObject  	friend class ComponentManager;  public: +	//! The id of the GameObject +	const game_object_id_t id; +	//! This entity's transform +	Transform & transform; +	//! This entity's metadata +	Metadata & metadata; + +public:  	/**  	 * \brief Set the parent of this GameObject  	 * @@ -68,12 +77,8 @@ public:  	 */  	void set_persistent(bool persistent = true); -public: -	//! The id of the GameObject -	const game_object_id_t id; -  protected: -	ComponentManager & component_manager; +	Mediator & mediator;  };  } // namespace crepe diff --git a/src/crepe/api/GameObject.hpp b/src/crepe/api/GameObject.hpp index a6b45b0..69f7d73 100644 --- a/src/crepe/api/GameObject.hpp +++ b/src/crepe/api/GameObject.hpp @@ -8,7 +8,7 @@ namespace crepe {  template <typename T, typename... Args>  T & GameObject::add_component(Args &&... args) { -	ComponentManager & mgr = this->component_manager; +	ComponentManager & mgr = this->mediator.component_manager;  	return mgr.add_component<T>(this->id, std::forward<Args>(args)...);  } diff --git a/src/crepe/api/KeyCodes.h b/src/crepe/api/KeyCodes.h index fcfc080..748e8f6 100644 --- a/src/crepe/api/KeyCodes.h +++ b/src/crepe/api/KeyCodes.h @@ -1,5 +1,9 @@  #pragma once + +#include <unordered_map> +  namespace crepe { +  //! Enumeration for mouse button inputs, including standard and extended buttons.  enum class MouseButton {  	NONE = 0, //!< No mouse button input. @@ -151,4 +155,7 @@ enum class Keycode {  	/// \}  	MENU = 348, //!< Menu key.  }; + +typedef std::unordered_map<Keycode, bool> keyboard_state_t; +  } // namespace crepe diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index b5e5ff7..7a78019 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -65,6 +65,7 @@ void LoopManager::fixed_update() {  	this->get_system<InputSystem>().update();  	this->event_manager.dispatch_events();  	this->get_system<ScriptSystem>().update(); +	this->get_system<ParticleSystem>().update();  	this->get_system<AISystem>().update();  	this->get_system<PhysicsSystem>().update();  	this->get_system<CollisionSystem>().update(); diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h deleted file mode 100644 index 40e6b38..0000000 --- a/src/crepe/api/LoopManager.h +++ /dev/null @@ -1,122 +0,0 @@ -#pragma once - -#include <memory> - -#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" - -namespace crepe { -/** - * \brief Main game loop manager - * - * This class is responsible for managing the game loop, including initialization and updating. - */ -class LoopManager { -public: -	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 -	 * -	 * \tparam T  Type of concrete scene -	 */ -	template <typename T> -	void add_scene(); - -private: -	/** -	 * \brief Setup function for one-time initialization. -	 * -	 * This function initializes necessary components for the game. -	 */ -	void setup(); -	/** -	 * \brief Main game loop function. -	 * -	 * This function runs the main loop, handling game updates and rendering. -	 */ -	void loop(); - -	/** -	 * \brief Per-frame update. -	 * -	 * Updates the game state based on the elapsed time since the last frame. -	 */ -	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. -	 */ -	virtual void fixed_update(); - -	//! Indicates whether the game is running. -	bool game_running = false; - -private: -	//! Global context -	Mediator mediator; - -	//! Component manager instance -	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}; - -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 LoopManager using LoopManager::load_system. -	 */ -	std::unordered_map<std::type_index, std::unique_ptr<System>> systems; -	/** -	 * \brief Initialize a system -	 * \tparam T System type (must be derivative of \c System) -	 */ -	template <class T> -	void load_system(); -	/** -	 * \brief Retrieve a reference to ECS system -	 * \tparam T System type -	 * \returns Reference to system instance -	 * \throws std::runtime_error if the System is not initialized -	 */ -	template <class T> -	T & get_system(); -}; - -} // namespace crepe - -#include "LoopManager.hpp" diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp index 90b77a0..9a70334 100644 --- a/src/crepe/api/ParticleEmitter.cpp +++ b/src/crepe/api/ParticleEmitter.cpp @@ -1,11 +1,28 @@  #include "ParticleEmitter.h" +#include "api/Sprite.h"  using namespace crepe; +using namespace std; -ParticleEmitter::ParticleEmitter(game_object_id_t game_object_id, const Data & data) +ParticleEmitter::ParticleEmitter(game_object_id_t game_object_id, const Sprite & sprite, +								 const Data & data)  	: Component(game_object_id), +	  sprite(sprite),  	  data(data) {  	for (size_t i = 0; i < this->data.max_particles; i++) { -		this->data.particles.emplace_back(); +		this->particles.emplace_back();  	}  } + +unique_ptr<Component> ParticleEmitter::save() const { +	return unique_ptr<Component>{new ParticleEmitter(*this)}; +} + +void ParticleEmitter::restore(const Component & snapshot) { +	*this = static_cast<const ParticleEmitter &>(snapshot); +} + +ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter & other) { +	this->particles = other.particles; +	return *this; +} diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h index b83fd61..626b356 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -1,7 +1,11 @@  #pragma once +#include <cmath>  #include <vector> +#include "system/ParticleSystem.h" +#include "system/RenderSystem.h" +  #include "Component.h"  #include "Particle.h"  #include "types.h" @@ -26,15 +30,18 @@ public:  	 */  	struct Boundary {  		//! boundary width (midpoint is emitter location) -		double width = 0.0; +		float width = INFINITY;  		//! boundary height (midpoint is emitter location) -		double height = 0.0; +		float height = INFINITY;  		//! boundary offset from particle emitter location  		vec2 offset;  		//! reset on exit or stop velocity and set max postion  		bool reset_on_exit = false;  	}; +	//! sprite reference of displayed sprite +	const Sprite & sprite; +  	/**  	 * \brief Holds parameters that control particle emission.  	 * @@ -45,29 +52,25 @@ public:  		//! position of the emitter  		vec2 position;  		//! maximum number of particles -		const unsigned int max_particles = 0; -		//! rate of particle emission per update (Lowest value = 0.001 any lower is ignored) -		double emission_rate = 0; +		const unsigned int max_particles = 256; +		//! rate of particle emission per second +		float emission_rate = 50;  		//! min speed of the particles -		double min_speed = 0; +		float min_speed = 100;  		//! min speed of the particles -		double max_speed = 0; +		float max_speed = 100;  		//! min angle of particle emission -		double min_angle = 0; +		float min_angle = 0;  		//! max angle of particle emission -		double max_angle = 0; -		//! begin Lifespan of particle (only visual) -		double begin_lifespan = 0.0; -		//! end Lifespan of particle -		double end_lifespan = 0.0; +		float max_angle = 0; +		//! begin Lifespan of particle in seconds (only visual) +		float begin_lifespan = 0.0; +		//! end Lifespan of particle in seconds +		float end_lifespan = 10.0;  		//! force over time (physics)  		vec2 force_over_time;  		//! particle boundary  		Boundary boundary; -		//! collection of particles -		std::vector<Particle> particles; -		//! sprite reference -		const Sprite & sprite;  	};  public: @@ -75,11 +78,27 @@ public:  	 * \param game_object_id  Identifier for the game object using this emitter.  	 * \param data            Configuration data defining particle properties.  	 */ -	ParticleEmitter(game_object_id_t game_object_id, const Data & data); +	ParticleEmitter(game_object_id_t game_object_id, const Sprite & sprite, const Data & data);  public:  	//! Configuration data for particle emission settings.  	Data data; + +protected: +	virtual std::unique_ptr<Component> save() const; +	ParticleEmitter(const ParticleEmitter &) = default; +	virtual void restore(const Component & snapshot); +	virtual ParticleEmitter & operator=(const ParticleEmitter &); + +private: +	//! Only ParticleSystem can move and read particles +	friend ParticleSystem; +	//! Only RenderSystem can read particles +	friend RenderSystem; +	//! Saves time left over from last update event. +	float spawn_accumulator = 0; +	//! collection of particles +	std::vector<Particle> particles;  };  } // namespace crepe diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h index b08c8db..b2bfc0d 100644 --- a/src/crepe/api/Rigidbody.h +++ b/src/crepe/api/Rigidbody.h @@ -61,7 +61,7 @@ public:  		* gravity force, allowing for fine-grained control over how the object responds to gravity.  		*  		*/ -		float gravity_scale = 0; +		float gravity_scale = 1.0;  		//! Defines the type of the physics body, which determines how the physics system interacts with the object.  		BodyType body_type = BodyType::DYNAMIC; @@ -139,7 +139,7 @@ public:  		 * Each element represents a layer ID, and the GameObject will only detect  		 * collisions with other GameObjects that belong to these layers.  		 */ -		std::set<int> collision_layers; +		std::set<int> collision_layers = {0};  	};  public: diff --git a/src/crepe/api/Scene.h b/src/crepe/api/Scene.h index dcca9d4..5c34b36 100644 --- a/src/crepe/api/Scene.h +++ b/src/crepe/api/Scene.h @@ -39,7 +39,7 @@ public:  	 * \brief Get the scene's name  	 * \return The scene's name  	 */ -	virtual std::string get_name() const = 0; +	virtual std::string get_name() const { return ""; };  	// 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!!! diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp index 753a9e3..d5f3f06 100644 --- a/src/crepe/api/Script.cpp +++ b/src/crepe/api/Script.cpp @@ -1,6 +1,7 @@  #include <string>  #include "../manager/SceneManager.h" +#include "../facade/SDLContext.h"  #include "Script.h" @@ -25,3 +26,39 @@ void Script::set_next_scene(const string & name) {  }  SaveManager & Script::get_save_manager() const { return this->mediator->save_manager; } + +void Script::replay::record_start() { +	ReplayManager & mgr = this->mediator->replay_manager; +	return mgr.record_start(); +} + +recording_t Script::replay::record_end() { +	ReplayManager & mgr = this->mediator->replay_manager; +	return mgr.record_end(); +} + +void Script::replay::play(recording_t recording) { +	ReplayManager & mgr = this->mediator->replay_manager; +	return mgr.play(recording); +} + +void Script::replay::release(recording_t recording) { +	ReplayManager & mgr = this->mediator->replay_manager; +	return mgr.release(recording); +} + +LoopTimerManager & Script::get_loop_timer() const { return this->mediator->loop_timer; } + +const keyboard_state_t & Script::get_keyboard_state() const { +	SDLContext & sdl_context = this->mediator->sdl_context; +	return sdl_context.get_keyboard_state(); +} + +bool Script::get_key_state(Keycode key) const noexcept { +	try { +		return this->get_keyboard_state().at(key); +	} catch (...) { +		return false; +	} +} + diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index 668e5d1..634a459 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -3,9 +3,12 @@  #include <vector>  #include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h"  #include "../manager/Mediator.h" +#include "../manager/ReplayManager.h"  #include "../system/CollisionSystem.h"  #include "../types.h" +#include "../util/Log.h"  #include "../util/OptionalRef.h"  namespace crepe { @@ -46,9 +49,17 @@ protected:  	/**  	 * \brief Script update function (empty by default)  	 * +	 * \param delta_time Time since last fixed update +	 *  	 * This function is called during the ScriptSystem::update() routine if the \c BehaviorScript  	 * component holding this script instance is active.  	 */ +	virtual void update(duration_t delta_time) { return this->update(); } +	/** +	 * \brief Fallback script update function (empty by default) +	 * +	 * Allows the game programmer to ignore parameters passed to \c update() +	 */  	virtual void update() {}  	//! \} @@ -57,84 +68,116 @@ protected:  protected:  	/** -	 * \name Utility functions +	 * \name Component query functions +	 * \see ComponentManager  	 * \{  	 */ -  	/**  	 * \brief Get single component of type \c T on this game object -	 *  	 * \tparam T Type of component -	 *  	 * \returns Reference to component -	 *  	 * \throws std::runtime_error if this game object does not have a component with type \c T  	 */  	template <typename T>  	T & get_component() const; -	// TODO: make get_component calls for component types that can have more than 1 instance -	// cause compile-time errors -  	/**  	 * \brief Get all components of type \c T on this game object -	 *  	 * \tparam T Type of component -	 *  	 * \returns List of component references  	 */  	template <typename T>  	RefVector<T> get_components() const; - -	/** -	 * \copydoc ComponentManager::get_components_by_id -	 * \see ComponentManager::get_components_by_id -	 */ +	//! \copydoc ComponentManager::get_components_by_id  	template <typename T>  	RefVector<T> get_components_by_id(game_object_id_t id) const; -	/** -	 * \copydoc ComponentManager::get_components_by_name -	 * \see ComponentManager::get_components_by_name -	 */ +	//! \copydoc ComponentManager::get_components_by_name  	template <typename T>  	RefVector<T> get_components_by_name(const std::string & name) const; -	/** -	 * \copydoc ComponentManager::get_components_by_tag -	 * \see ComponentManager::get_components_by_tag -	 */ +	//! \copydoc ComponentManager::get_components_by_tag  	template <typename T>  	RefVector<T> get_components_by_tag(const std::string & tag) const; +	//! \}  	/** -	 * \brief Log a message using Log::logf -	 * -	 * \tparam Args Log::logf parameters -	 * \param args  Log::logf parameters +	 * \name Logging functions +	 * \see Log +	 * \{  	 */ -	template <typename... Args> -	void logf(Args &&... args); +	//! \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); +	// \}  	/** -	 * \brief Subscribe to an event with an explicit channel -	 * \see EventManager::subscribe +	 * \name Event manager functions +	 * \see EventManager +	 * \{  	 */ +	//! \copydoc EventManager::subscribe  	template <typename EventType>  	void subscribe(const EventHandler<EventType> & callback, event_channel_t channel); -	/** -	 * \brief Subscribe to an event on EventManager::CHANNEL_ALL -	 * \see EventManager::subscribe -	 */ +	//! \copydoc EventManager::subscribe  	template <typename EventType>  	void subscribe(const EventHandler<EventType> & callback); +	//! \copydoc EventManager::trigger_event +	template <typename EventType> +	void trigger_event(const EventType & event = {}, +					   event_channel_t channel = EventManager::CHANNEL_ALL); +	//! \copydoc EventManager::queue_event +	template <typename EventType> +	void queue_event(const EventType & event = {}, +					 event_channel_t channel = EventManager::CHANNEL_ALL); +	//! \}  	/** -	 * \brief Set the next scene using SceneManager -	 * \see SceneManager::set_next_scene +	 * \name Scene-related functions +	 * \see SceneManager +	 * \{  	 */ +	//! \copydoc SceneManager::set_next_scene  	void set_next_scene(const std::string & name); +	//! \} +	/** +	 * \name Save data management functions +	 * \see SaveManager +	 * \{ +	 */  	//! Retrieve SaveManager reference  	SaveManager & get_save_manager() const; +	//! \} + +	//! Replay management functions +	struct replay { // NOLINT +		//! \copydoc ReplayManager::record_start +		void record_start(); +		//! \copydoc ReplayManager::record_end +		recording_t record_end(); +		//! \copydoc ReplayManager::play +		void play(recording_t); +		//! \copydoc ReplayManager::release +		void release(recording_t); + +	private: +		OptionalRef<Mediator> & mediator; +		replay(OptionalRef<Mediator> & mediator) : mediator(mediator) {} +		friend class Script; +	} replay{mediator}; +	//! Retrieve LoopTimerManager reference +	LoopTimerManager & get_loop_timer() const; + +	/** +	 * \name Input +	 * \{ +	 */ +	//! Get keyboard state +	const keyboard_state_t & get_keyboard_state() const; +	//! Get state of specific key +	bool get_key_state(Keycode key) const noexcept;  	//! \}  private: diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index 225a51c..4462a41 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -1,6 +1,7 @@  #pragma once  #include "../manager/ComponentManager.h" +#include "../manager/ReplayManager.h"  #include "BehaviorScript.h"  #include "Script.h" @@ -23,9 +24,14 @@ RefVector<T> Script::get_components() const {  	return this->get_components_by_id<T>(this->game_object_id);  } -template <typename... Args> -void Script::logf(Args &&... args) { -	Log::logf(std::forward<Args>(args)...); +template <class... Args> +void Script::logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args) { +	Log::logf(level, fmt, std::forward<Args>(args)...); +} + +template <class... Args> +void Script::logf(std::format_string<Args...> fmt, Args &&... args) { +	Log::logf(fmt, std::forward<Args>(args)...);  }  template <typename EventType> @@ -34,8 +40,18 @@ void Script::subscribe_internal(const EventHandler<EventType> & callback,  	EventManager & mgr = this->mediator->event_manager;  	subscription_t listener = mgr.subscribe<EventType>(  		[this, callback](const EventType & data) -> bool { +			// check if (parent) BehaviorScript component is active  			bool & active = this->active;  			if (!active) return false; + +			// check if replay manager is playing (if initialized) +			try { +				ReplayManager & replay = this->mediator->replay_manager; +				if (replay.get_state() == ReplayManager::PLAYING) return false; +			} catch (const std::runtime_error &) { +			} + +			// call user-provided callback  			return callback(data);  		},  		channel); @@ -52,6 +68,18 @@ void Script::subscribe(const EventHandler<EventType> & callback) {  	this->subscribe_internal(callback, EventManager::CHANNEL_ALL);  } +template <typename EventType> +void Script::trigger_event(const EventType & event, event_channel_t channel) { +	EventManager & mgr = this->mediator->event_manager; +	mgr.trigger_event(event, channel); +} + +template <typename EventType> +void Script::queue_event(const EventType & event, event_channel_t channel) { +	EventManager & mgr = this->mediator->event_manager; +	mgr.queue_event(event, channel); +} +  template <typename T>  RefVector<T> Script::get_components_by_id(game_object_id_t id) const {  	Mediator & mediator = this->mediator; diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp index ba684ba..0107c7b 100644 --- a/src/crepe/api/Sprite.cpp +++ b/src/crepe/api/Sprite.cpp @@ -1,6 +1,6 @@  #include <cmath> -#include "../util/Log.h" +#include "../util/dbg.h"  #include "api/Asset.h"  #include "Component.h" diff --git a/src/crepe/api/Transform.cpp b/src/crepe/api/Transform.cpp index a85b792..fcfce14 100644 --- a/src/crepe/api/Transform.cpp +++ b/src/crepe/api/Transform.cpp @@ -1,8 +1,9 @@ -#include "../util/Log.h" +#include "../util/dbg.h"  #include "Transform.h"  using namespace crepe; +using namespace std;  Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, double scale)  	: Component(id), @@ -11,3 +12,11 @@ Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, d  	  scale(scale) {  	dbg_trace();  } + +unique_ptr<Component> Transform::save() const { +	return unique_ptr<Component>{new Transform(*this)}; +} + +void Transform::restore(const Component & snapshot) { +	*this = static_cast<const Transform &>(snapshot); +} diff --git a/src/crepe/api/Transform.h b/src/crepe/api/Transform.h index 7ee6d65..a6f3486 100644 --- a/src/crepe/api/Transform.h +++ b/src/crepe/api/Transform.h @@ -35,6 +35,12 @@ protected:  	virtual int get_instances_max() const { return 1; }  	//! ComponentManager instantiates all components  	friend class ComponentManager; + +protected: +	virtual std::unique_ptr<Component> save() const; +	Transform(const Transform &) = default; +	virtual void restore(const Component & snapshot); +	virtual Transform & operator=(const Transform &) = default;  };  } // namespace crepe diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h index bf9d124..f4ba2b2 100644 --- a/src/crepe/api/Vector2.h +++ b/src/crepe/api/Vector2.h @@ -1,5 +1,7 @@  #pragma once +#include <format> +  namespace crepe {  //! 2D vector @@ -94,4 +96,9 @@ struct Vector2 {  } // namespace crepe +template <typename T> +struct std::formatter<crepe::Vector2<T>> : std::formatter<std::string> { +	format_context::iterator format(crepe::Vector2<T> vec, format_context & ctx) const; +}; +  #include "Vector2.hpp" diff --git a/src/crepe/api/Vector2.hpp b/src/crepe/api/Vector2.hpp index ff53cb0..75d875a 100644 --- a/src/crepe/api/Vector2.hpp +++ b/src/crepe/api/Vector2.hpp @@ -164,3 +164,9 @@ Vector2<T> Vector2<T>::perpendicular() const {  }  } // namespace crepe + +template <typename T> +std::format_context::iterator std::formatter<crepe::Vector2<T>>::format(crepe::Vector2<T> vec, format_context & ctx) const { +	return formatter<string>::format(std::format("{{{}, {}}}", vec.x, vec.y), ctx); +} + diff --git a/src/crepe/facade/DB.cpp b/src/crepe/facade/DB.cpp index ae2d4bc..7a3e473 100644 --- a/src/crepe/facade/DB.cpp +++ b/src/crepe/facade/DB.cpp @@ -1,6 +1,6 @@  #include <cstring> -#include "util/Log.h" +#include "util/dbg.h"  #include "DB.h" diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index 20bb030..d6e3159 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -19,7 +19,7 @@  #include "../api/Color.h"  #include "../api/Config.h"  #include "../api/Sprite.h" -#include "../util/Log.h" +#include "../util/dbg.h"  #include "manager/Mediator.h"  #include "SDLContext.h" @@ -35,10 +35,10 @@ SDLContext::SDLContext(Mediator & mediator) {  		throw runtime_error(format("SDLContext: SDL_Init error: {}", SDL_GetError()));  	} -	auto & cfg = Config::get_instance().window_settings; +	auto & cfg = Config::get_instance().window;  	SDL_Window * tmp_window -		= SDL_CreateWindow(cfg.window_title.c_str(), SDL_WINDOWPOS_CENTERED, -						   SDL_WINDOWPOS_CENTERED, cfg.default_size.x, cfg.default_size.y, 0); +		= SDL_CreateWindow(cfg.title.c_str(), SDL_WINDOWPOS_CENTERED, +						   SDL_WINDOWPOS_CENTERED, cfg.size.x, cfg.size.y, 0);  	if (!tmp_window) {  		throw runtime_error(format("SDLContext: SDL_Window error: {}", SDL_GetError()));  	} @@ -75,118 +75,22 @@ SDLContext::~SDLContext() {  	SDL_Quit();  } -Keycode SDLContext::sdl_to_keycode(SDL_Keycode sdl_key) { -	static const std::array<Keycode, SDL_NUM_SCANCODES> LOOKUP_TABLE = [] { -		std::array<Keycode, SDL_NUM_SCANCODES> table{}; -		table.fill(Keycode::NONE); - -		table[SDL_SCANCODE_SPACE] = Keycode::SPACE; -		table[SDL_SCANCODE_APOSTROPHE] = Keycode::APOSTROPHE; -		table[SDL_SCANCODE_COMMA] = Keycode::COMMA; -		table[SDL_SCANCODE_MINUS] = Keycode::MINUS; -		table[SDL_SCANCODE_PERIOD] = Keycode::PERIOD; -		table[SDL_SCANCODE_SLASH] = Keycode::SLASH; -		table[SDL_SCANCODE_0] = Keycode::D0; -		table[SDL_SCANCODE_1] = Keycode::D1; -		table[SDL_SCANCODE_2] = Keycode::D2; -		table[SDL_SCANCODE_3] = Keycode::D3; -		table[SDL_SCANCODE_4] = Keycode::D4; -		table[SDL_SCANCODE_5] = Keycode::D5; -		table[SDL_SCANCODE_6] = Keycode::D6; -		table[SDL_SCANCODE_7] = Keycode::D7; -		table[SDL_SCANCODE_8] = Keycode::D8; -		table[SDL_SCANCODE_9] = Keycode::D9; -		table[SDL_SCANCODE_SEMICOLON] = Keycode::SEMICOLON; -		table[SDL_SCANCODE_EQUALS] = Keycode::EQUAL; -		table[SDL_SCANCODE_A] = Keycode::A; -		table[SDL_SCANCODE_B] = Keycode::B; -		table[SDL_SCANCODE_C] = Keycode::C; -		table[SDL_SCANCODE_D] = Keycode::D; -		table[SDL_SCANCODE_E] = Keycode::E; -		table[SDL_SCANCODE_F] = Keycode::F; -		table[SDL_SCANCODE_G] = Keycode::G; -		table[SDL_SCANCODE_H] = Keycode::H; -		table[SDL_SCANCODE_I] = Keycode::I; -		table[SDL_SCANCODE_J] = Keycode::J; -		table[SDL_SCANCODE_K] = Keycode::K; -		table[SDL_SCANCODE_L] = Keycode::L; -		table[SDL_SCANCODE_M] = Keycode::M; -		table[SDL_SCANCODE_N] = Keycode::N; -		table[SDL_SCANCODE_O] = Keycode::O; -		table[SDL_SCANCODE_P] = Keycode::P; -		table[SDL_SCANCODE_Q] = Keycode::Q; -		table[SDL_SCANCODE_R] = Keycode::R; -		table[SDL_SCANCODE_S] = Keycode::S; -		table[SDL_SCANCODE_T] = Keycode::T; -		table[SDL_SCANCODE_U] = Keycode::U; -		table[SDL_SCANCODE_V] = Keycode::V; -		table[SDL_SCANCODE_W] = Keycode::W; -		table[SDL_SCANCODE_X] = Keycode::X; -		table[SDL_SCANCODE_Y] = Keycode::Y; -		table[SDL_SCANCODE_Z] = Keycode::Z; -		table[SDL_SCANCODE_LEFTBRACKET] = Keycode::LEFT_BRACKET; -		table[SDL_SCANCODE_BACKSLASH] = Keycode::BACKSLASH; -		table[SDL_SCANCODE_RIGHTBRACKET] = Keycode::RIGHT_BRACKET; -		table[SDL_SCANCODE_GRAVE] = Keycode::GRAVE_ACCENT; -		table[SDL_SCANCODE_ESCAPE] = Keycode::ESCAPE; -		table[SDL_SCANCODE_RETURN] = Keycode::ENTER; -		table[SDL_SCANCODE_TAB] = Keycode::TAB; -		table[SDL_SCANCODE_BACKSPACE] = Keycode::BACKSPACE; -		table[SDL_SCANCODE_INSERT] = Keycode::INSERT; -		table[SDL_SCANCODE_DELETE] = Keycode::DELETE; -		table[SDL_SCANCODE_RIGHT] = Keycode::RIGHT; -		table[SDL_SCANCODE_LEFT] = Keycode::LEFT; -		table[SDL_SCANCODE_DOWN] = Keycode::DOWN; -		table[SDL_SCANCODE_UP] = Keycode::UP; -		table[SDL_SCANCODE_PAGEUP] = Keycode::PAGE_UP; -		table[SDL_SCANCODE_PAGEDOWN] = Keycode::PAGE_DOWN; -		table[SDL_SCANCODE_HOME] = Keycode::HOME; -		table[SDL_SCANCODE_END] = Keycode::END; -		table[SDL_SCANCODE_CAPSLOCK] = Keycode::CAPS_LOCK; -		table[SDL_SCANCODE_SCROLLLOCK] = Keycode::SCROLL_LOCK; -		table[SDL_SCANCODE_NUMLOCKCLEAR] = Keycode::NUM_LOCK; -		table[SDL_SCANCODE_PRINTSCREEN] = Keycode::PRINT_SCREEN; -		table[SDL_SCANCODE_PAUSE] = Keycode::PAUSE; -		table[SDL_SCANCODE_F1] = Keycode::F1; -		table[SDL_SCANCODE_F2] = Keycode::F2; -		table[SDL_SCANCODE_F3] = Keycode::F3; -		table[SDL_SCANCODE_F4] = Keycode::F4; -		table[SDL_SCANCODE_F5] = Keycode::F5; -		table[SDL_SCANCODE_F6] = Keycode::F6; -		table[SDL_SCANCODE_F7] = Keycode::F7; -		table[SDL_SCANCODE_F8] = Keycode::F8; -		table[SDL_SCANCODE_F9] = Keycode::F9; -		table[SDL_SCANCODE_F10] = Keycode::F10; -		table[SDL_SCANCODE_F11] = Keycode::F11; -		table[SDL_SCANCODE_F12] = Keycode::F12; -		table[SDL_SCANCODE_KP_0] = Keycode::KP0; -		table[SDL_SCANCODE_KP_1] = Keycode::KP1; -		table[SDL_SCANCODE_KP_2] = Keycode::KP2; -		table[SDL_SCANCODE_KP_3] = Keycode::KP3; -		table[SDL_SCANCODE_KP_4] = Keycode::KP4; -		table[SDL_SCANCODE_KP_5] = Keycode::KP5; -		table[SDL_SCANCODE_KP_6] = Keycode::KP6; -		table[SDL_SCANCODE_KP_7] = Keycode::KP7; -		table[SDL_SCANCODE_KP_8] = Keycode::KP8; -		table[SDL_SCANCODE_KP_9] = Keycode::KP9; -		table[SDL_SCANCODE_LSHIFT] = Keycode::LEFT_SHIFT; -		table[SDL_SCANCODE_LCTRL] = Keycode::LEFT_CONTROL; -		table[SDL_SCANCODE_LALT] = Keycode::LEFT_ALT; -		table[SDL_SCANCODE_LGUI] = Keycode::LEFT_SUPER; -		table[SDL_SCANCODE_RSHIFT] = Keycode::RIGHT_SHIFT; -		table[SDL_SCANCODE_RCTRL] = Keycode::RIGHT_CONTROL; -		table[SDL_SCANCODE_RALT] = Keycode::RIGHT_ALT; -		table[SDL_SCANCODE_RGUI] = Keycode::RIGHT_SUPER; -		table[SDL_SCANCODE_MENU] = Keycode::MENU; - -		return table; -	}(); - -	if (sdl_key < 0 || sdl_key >= SDL_NUM_SCANCODES) { +Keycode SDLContext::sdl_to_keycode(SDL_Scancode sdl_key) { +	if (!LOOKUP_TABLE.contains(sdl_key))  		return Keycode::NONE; -	} -	return LOOKUP_TABLE[sdl_key]; +	return LOOKUP_TABLE.at(sdl_key); +} +void SDLContext::update_keyboard_state() { +	// Array to hold the key states (true if pressed, false if not) +	SDL_PumpEvents(); +	const Uint8 * current_state = SDL_GetKeyboardState(nullptr); + +	for (int i = 0; i < SDL_NUM_SCANCODES; ++i) { +		Keycode key = sdl_to_keycode(static_cast<SDL_Scancode>(i)); +		if (key == Keycode::NONE) continue; +		this->keyboard_state[key] = current_state[i] != 0; +	}  }  MouseButton SDLContext::sdl_to_mousebutton(Uint8 sdl_button) { @@ -279,13 +183,13 @@ void SDLContext::draw(const RenderContext & ctx) {  }  void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) { -  	const Camera::Data & cam_data = cam.data; +	const Config & config = Config::get_instance();  	// resize window  	int w, h;  	SDL_GetWindowSize(this->game_window.get(), &w, &h); -	if (w != cam.screen.x || h != cam.screen.y) { -		SDL_SetWindowSize(this->game_window.get(), cam.screen.x, cam.screen.y); +	if (w != config.window.size.x || h != config.window.size.y) { +		SDL_SetWindowSize(this->game_window.get(), config.window.size.x, config.window.size.y);  	}  	vec2 & zoomed_viewport = this->cam_aux_data.zoomed_viewport; @@ -294,28 +198,28 @@ void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) {  	this->cam_aux_data.cam_pos = new_pos;  	zoomed_viewport = cam.viewport_size * cam_data.zoom; -	float screen_aspect = static_cast<float>(cam.screen.x) / cam.screen.y; +	float screen_aspect = static_cast<float>(config.window.size.x) / config.window.size.y;  	float viewport_aspect = zoomed_viewport.x / zoomed_viewport.y;  	// calculate black bars  	if (screen_aspect > viewport_aspect) {  		// pillarboxing -		float scale = cam.screen.y / zoomed_viewport.y; +		float scale = config.window.size.y / zoomed_viewport.y;  		float adj_width = zoomed_viewport.x * scale; -		float bar_width = (cam.screen.x - adj_width) / 2; -		this->black_bars[0] = {0, 0, bar_width, (float) cam.screen.y}; -		this->black_bars[1] = {(cam.screen.x - bar_width), 0, bar_width, (float) cam.screen.y}; +		float bar_width = (config.window.size.x - adj_width) / 2; +		this->black_bars[0] = {0, 0, bar_width, (float) config.window.size.y}; +		this->black_bars[1] = {(config.window.size.x - bar_width), 0, bar_width, (float) config.window.size.y};  		bar_size = {bar_width, 0};  		render_scale.x = render_scale.y = scale;  	} else {  		// letterboxing -		float scale = cam.screen.x / (cam.viewport_size.x * cam_data.zoom); +		float scale = config.window.size.x / (cam.viewport_size.x * cam_data.zoom);  		float adj_height = cam.viewport_size.y * scale; -		float bar_height = (cam.screen.y - adj_height) / 2; -		this->black_bars[0] = {0, 0, (float) cam.screen.x, bar_height}; +		float bar_height = (config.window.size.y - adj_height) / 2; +		this->black_bars[0] = {0, 0, (float) config.window.size.x, bar_height};  		this->black_bars[1] -			= {0, (cam.screen.y - bar_height), (float) cam.screen.x, bar_height}; +			= {0, (config.window.size.y - bar_height), (float) config.window.size.x, bar_height};  		bar_size = {0, bar_height};  		render_scale.x = render_scale.y = scale; @@ -327,8 +231,8 @@ void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) {  	SDL_Rect bg = {  		.x = 0,  		.y = 0, -		.w = cam.screen.x, -		.h = cam.screen.y, +		.w = config.window.size.x, +		.h = config.window.size.y,  	};  	// fill bg color @@ -375,61 +279,127 @@ std::vector<SDLContext::EventData> SDLContext::get_events() {  		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{ -					.event_type = SDLContext::EventType::SHUTDOWN, -				}); +				event_list.push_back({.event_type = SDLContext::EventType::SHUTDOWN});  				break; -			case SDL_KEYDOWN: -				event_list.push_back(EventData{ -					.event_type = SDLContext::EventType::KEYDOWN, +			case SDL_KEYDOWN: { +				this->update_keyboard_state(); +				EventData transfer_event; +				transfer_event.event_type = SDLContext::EventType::KEYDOWN; +				transfer_event.data.key_data = KeyData{  					.key = sdl_to_keycode(event.key.keysym.scancode), -					.key_repeat = (event.key.repeat != 0), -				}); +					.key_repeat = event.key.repeat != 0, +				}; +				event_list.push_back(transfer_event);  				break; -			case SDL_KEYUP: -				event_list.push_back(EventData{ -					.event_type = SDLContext::EventType::KEYUP, +			} + +			case SDL_KEYUP: { +				this->update_keyboard_state(); +				EventData transfer_event; +				transfer_event.event_type = SDLContext::EventType::KEYUP; +				transfer_event.data.key_data = KeyData{  					.key = sdl_to_keycode(event.key.keysym.scancode), -				}); +					.key_repeat = false, +				}; +				event_list.push_back(transfer_event);  				break; -			case SDL_MOUSEBUTTONDOWN: -				event_list.push_back(EventData{ -					.event_type = SDLContext::EventType::MOUSEDOWN, +			} + +			case SDL_MOUSEBUTTONDOWN: { +				EventData transfer_event; +				transfer_event.event_type = SDLContext::EventType::MOUSEDOWN; +				transfer_event.data.mouse_data = MouseData{  					.mouse_button = sdl_to_mousebutton(event.button.button),  					.mouse_position = mouse_pos, -				}); +				}; +				event_list.push_back(transfer_event);  				break; +			}  			case SDL_MOUSEBUTTONUP: { -				int x, y; -				SDL_GetMouseState(&x, &y); -				event_list.push_back(EventData{ -					.event_type = SDLContext::EventType::MOUSEUP, +				EventData transfer_event; +				transfer_event.event_type = SDLContext::EventType::MOUSEUP; +				transfer_event.data.mouse_data = MouseData{  					.mouse_button = sdl_to_mousebutton(event.button.button),  					.mouse_position = mouse_pos, -				}); -			} break; +				}; +				event_list.push_back(transfer_event); +				break; +			}  			case SDL_MOUSEMOTION: { -				event_list.push_back( -					EventData{.event_type = SDLContext::EventType::MOUSEMOVE, -							  .mouse_position = mouse_pos, -							  .rel_mouse_move = {event.motion.xrel, event.motion.yrel}}); -			} break; +				EventData transfer_event; +				transfer_event.event_type = SDLContext::EventType::MOUSEMOVE; +				transfer_event.data.mouse_data = MouseData{ +					.mouse_position = mouse_pos, +					.rel_mouse_move = {event.motion.xrel, event.motion.yrel}, +				}; +				event_list.push_back(transfer_event); +				break; +			}  			case SDL_MOUSEWHEEL: { -				event_list.push_back(EventData{ -					.event_type = SDLContext::EventType::MOUSEWHEEL, +				EventData transfer_event; +				transfer_event.event_type = SDLContext::EventType::MOUSEWHEEL; +				transfer_event.data.mouse_data = MouseData{  					.mouse_position = mouse_pos, -					// TODO: why is this needed?  					.scroll_direction = event.wheel.y < 0 ? -1 : 1,  					.scroll_delta = event.wheel.preciseY, -				}); -			} break; +				}; +				event_list.push_back(transfer_event); +				break; +			} +			case SDL_WINDOWEVENT: +				handle_window_event(event.window, event_list); +				break;  		}  	} +  	return event_list;  } + +void SDLContext::handle_window_event(const SDL_WindowEvent & window_event, +									 std::vector<SDLContext::EventData> & event_list) { +	switch (window_event.event) { +		case SDL_WINDOWEVENT_EXPOSED: +			event_list.push_back(EventData{SDLContext::EventType::WINDOW_EXPOSE}); +			break; +		case SDL_WINDOWEVENT_RESIZED: { +			EventData transfer_event; +			transfer_event.event_type = SDLContext::EventType::WINDOW_RESIZE; +			transfer_event.data.window_data +				= WindowData{.resize_dimension = {window_event.data1, window_event.data2}}; +			event_list.push_back(transfer_event); +			break; +		} +		case SDL_WINDOWEVENT_MOVED: { +			EventData transfer_event; +			transfer_event.event_type = SDLContext::EventType::WINDOW_MOVE; +			transfer_event.data.window_data +				= WindowData{.move_delta = {window_event.data1, window_event.data2}}; +			event_list.push_back(transfer_event); +			break; +		} +		case SDL_WINDOWEVENT_MINIMIZED: +			event_list.push_back(EventData{SDLContext::EventType::WINDOW_MINIMIZE}); +			break; +		case SDL_WINDOWEVENT_MAXIMIZED: +			event_list.push_back(EventData{SDLContext::EventType::WINDOW_MAXIMIZE}); +			break; +		case SDL_WINDOWEVENT_FOCUS_GAINED: +			event_list.push_back(EventData{SDLContext::EventType::WINDOW_FOCUS_GAIN}); +			break; +		case SDL_WINDOWEVENT_FOCUS_LOST: +			event_list.push_back(EventData{SDLContext::EventType::WINDOW_FOCUS_LOST}); +			break; +	} +} +  void SDLContext::set_color_texture(const Texture & texture, const Color & color) {  	SDL_SetTextureColorMod(texture.get_img(), color.r, color.g, color.b);  	SDL_SetTextureAlphaMod(texture.get_img(), color.a);  } + +const keyboard_state_t & SDLContext::get_keyboard_state() const { +	return this->keyboard_state; +} + diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index bcadf87..176982f 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -5,10 +5,12 @@  #include <SDL2/SDL_rect.h>  #include <SDL2/SDL_render.h>  #include <SDL2/SDL_video.h> +#include <array>  #include <cmath>  #include <functional>  #include <memory>  #include <string> +#include <unordered_map>  #include "api/Camera.h"  #include "api/Color.h" @@ -79,19 +81,56 @@ public:  		KEYUP,  		KEYDOWN,  		SHUTDOWN, - +		WINDOW_MINIMIZE, +		WINDOW_MAXIMIZE, +		WINDOW_FOCUS_GAIN, +		WINDOW_FOCUS_LOST, +		WINDOW_MOVE, +		WINDOW_RESIZE, +		WINDOW_EXPOSE,  	}; -	//! EventData struct for passing event data from facade -	struct EventData { -		SDLContext::EventType event_type = SDLContext::EventType::NONE; +	struct KeyData {  		Keycode key = Keycode::NONE;  		bool key_repeat = false; +	}; +	struct MouseData {  		MouseButton mouse_button = MouseButton::NONE;  		ivec2 mouse_position = {-1, -1};  		int scroll_direction = -1;  		float scroll_delta = INFINITY;  		ivec2 rel_mouse_move = {-1, -1};  	}; +	struct WindowData { +		ivec2 move_delta; +		ivec2 resize_dimension; +	}; +	//! EventData struct for passing event data from facade +	struct EventData { +		SDLContext::EventType event_type = SDLContext::EventType::NONE; + +		union EventDataUnion { +			KeyData key_data; +			MouseData mouse_data; +			WindowData window_data; + +			EventDataUnion() {} +			~EventDataUnion() {} +		} data; +	}; +	/** +	 * \brief Retrieves the current state of the keyboard. +	 * +	 * This method updates the state of all keys on the keyboard. Each element of the unordered map corresponds to a +	 * specific key defined in the `Keycode` enum, and the value indicates whether +	 * the key is currently pressed (true) or not pressed (false). +	 *  +	 */ +	void update_keyboard_state(); +	/** +	 * \brief Gets the singleton instance of SDLContext. +	 * \return Reference to the SDLContext instance. +	 */ +	static SDLContext & get_instance();  public:  	SDLContext(const SDLContext &) = delete; @@ -123,17 +162,24 @@ public:  	 * \return Events that occurred since last call to `get_events()`  	 */  	std::vector<SDLContext::EventData> get_events(); -  	/** -	 * \brief Converts an SDL key code to the custom Keycode type. +	 * \brief Fills event_list with triggered window events +	 * +	 * This method checks if any window events are triggered and adds them to the event_list. +	 * +	 */ +	void handle_window_event(const SDL_WindowEvent & window_event, +							 std::vector<SDLContext::EventData> & event_list); +	/** +	 * \brief Converts an SDL scan code to the custom Keycode type.  	 * -	 * This method maps an SDL key code to the corresponding `Keycode` enum value, +	 * This method maps an SDL scan code to the corresponding `Keycode` enum value,  	 * which is used internally by the system to identify the keys.  	 * -	 * \param sdl_key The SDL key code to convert. +	 * \param sdl_key The SDL scan code to convert.  	 * \return The corresponding `Keycode` value or `Keycode::NONE` if the key is unrecognized.  	 */ -	Keycode sdl_to_keycode(SDL_Keycode sdl_key); +	Keycode sdl_to_keycode(SDL_Scancode sdl_key);  	/**  	 * \brief Converts an SDL mouse button code to the custom MouseButton type. @@ -224,6 +270,8 @@ public:  	 */  	void set_color_texture(const Texture & texture, const Color & color); +	const keyboard_state_t & get_keyboard_state() const; +  private:  	//! sdl Window  	std::unique_ptr<SDL_Window, std::function<void(SDL_Window *)>> game_window; @@ -240,6 +288,108 @@ private:  	 * - this is defined in this class because get_events() needs this information aswell  	 */  	CameraAuxiliaryData cam_aux_data; + +private: +	keyboard_state_t keyboard_state; +	const std::unordered_map<SDL_Scancode, Keycode> LOOKUP_TABLE +		= {{SDL_SCANCODE_SPACE, Keycode::SPACE}, +		   {SDL_SCANCODE_APOSTROPHE, Keycode::APOSTROPHE}, +		   {SDL_SCANCODE_COMMA, Keycode::COMMA}, +		   {SDL_SCANCODE_MINUS, Keycode::MINUS}, +		   {SDL_SCANCODE_PERIOD, Keycode::PERIOD}, +		   {SDL_SCANCODE_SLASH, Keycode::SLASH}, +		   {SDL_SCANCODE_0, Keycode::D0}, +		   {SDL_SCANCODE_1, Keycode::D1}, +		   {SDL_SCANCODE_2, Keycode::D2}, +		   {SDL_SCANCODE_3, Keycode::D3}, +		   {SDL_SCANCODE_4, Keycode::D4}, +		   {SDL_SCANCODE_5, Keycode::D5}, +		   {SDL_SCANCODE_6, Keycode::D6}, +		   {SDL_SCANCODE_7, Keycode::D7}, +		   {SDL_SCANCODE_8, Keycode::D8}, +		   {SDL_SCANCODE_9, Keycode::D9}, +		   {SDL_SCANCODE_SEMICOLON, Keycode::SEMICOLON}, +		   {SDL_SCANCODE_EQUALS, Keycode::EQUAL}, +		   {SDL_SCANCODE_A, Keycode::A}, +		   {SDL_SCANCODE_B, Keycode::B}, +		   {SDL_SCANCODE_C, Keycode::C}, +		   {SDL_SCANCODE_D, Keycode::D}, +		   {SDL_SCANCODE_E, Keycode::E}, +		   {SDL_SCANCODE_F, Keycode::F}, +		   {SDL_SCANCODE_G, Keycode::G}, +		   {SDL_SCANCODE_H, Keycode::H}, +		   {SDL_SCANCODE_I, Keycode::I}, +		   {SDL_SCANCODE_J, Keycode::J}, +		   {SDL_SCANCODE_K, Keycode::K}, +		   {SDL_SCANCODE_L, Keycode::L}, +		   {SDL_SCANCODE_M, Keycode::M}, +		   {SDL_SCANCODE_N, Keycode::N}, +		   {SDL_SCANCODE_O, Keycode::O}, +		   {SDL_SCANCODE_P, Keycode::P}, +		   {SDL_SCANCODE_Q, Keycode::Q}, +		   {SDL_SCANCODE_R, Keycode::R}, +		   {SDL_SCANCODE_S, Keycode::S}, +		   {SDL_SCANCODE_T, Keycode::T}, +		   {SDL_SCANCODE_U, Keycode::U}, +		   {SDL_SCANCODE_V, Keycode::V}, +		   {SDL_SCANCODE_W, Keycode::W}, +		   {SDL_SCANCODE_X, Keycode::X}, +		   {SDL_SCANCODE_Y, Keycode::Y}, +		   {SDL_SCANCODE_Z, Keycode::Z}, +		   {SDL_SCANCODE_LEFTBRACKET, Keycode::LEFT_BRACKET}, +		   {SDL_SCANCODE_BACKSLASH, Keycode::BACKSLASH}, +		   {SDL_SCANCODE_RIGHTBRACKET, Keycode::RIGHT_BRACKET}, +		   {SDL_SCANCODE_GRAVE, Keycode::GRAVE_ACCENT}, +		   {SDL_SCANCODE_ESCAPE, Keycode::ESCAPE}, +		   {SDL_SCANCODE_RETURN, Keycode::ENTER}, +		   {SDL_SCANCODE_TAB, Keycode::TAB}, +		   {SDL_SCANCODE_BACKSPACE, Keycode::BACKSPACE}, +		   {SDL_SCANCODE_INSERT, Keycode::INSERT}, +		   {SDL_SCANCODE_DELETE, Keycode::DELETE}, +		   {SDL_SCANCODE_RIGHT, Keycode::RIGHT}, +		   {SDL_SCANCODE_LEFT, Keycode::LEFT}, +		   {SDL_SCANCODE_DOWN, Keycode::DOWN}, +		   {SDL_SCANCODE_UP, Keycode::UP}, +		   {SDL_SCANCODE_PAGEUP, Keycode::PAGE_UP}, +		   {SDL_SCANCODE_PAGEDOWN, Keycode::PAGE_DOWN}, +		   {SDL_SCANCODE_HOME, Keycode::HOME}, +		   {SDL_SCANCODE_END, Keycode::END}, +		   {SDL_SCANCODE_CAPSLOCK, Keycode::CAPS_LOCK}, +		   {SDL_SCANCODE_SCROLLLOCK, Keycode::SCROLL_LOCK}, +		   {SDL_SCANCODE_NUMLOCKCLEAR, Keycode::NUM_LOCK}, +		   {SDL_SCANCODE_PRINTSCREEN, Keycode::PRINT_SCREEN}, +		   {SDL_SCANCODE_PAUSE, Keycode::PAUSE}, +		   {SDL_SCANCODE_F1, Keycode::F1}, +		   {SDL_SCANCODE_F2, Keycode::F2}, +		   {SDL_SCANCODE_F3, Keycode::F3}, +		   {SDL_SCANCODE_F4, Keycode::F4}, +		   {SDL_SCANCODE_F5, Keycode::F5}, +		   {SDL_SCANCODE_F6, Keycode::F6}, +		   {SDL_SCANCODE_F7, Keycode::F7}, +		   {SDL_SCANCODE_F8, Keycode::F8}, +		   {SDL_SCANCODE_F9, Keycode::F9}, +		   {SDL_SCANCODE_F10, Keycode::F10}, +		   {SDL_SCANCODE_F11, Keycode::F11}, +		   {SDL_SCANCODE_F12, Keycode::F12}, +		   {SDL_SCANCODE_KP_0, Keycode::KP0}, +		   {SDL_SCANCODE_KP_1, Keycode::KP1}, +		   {SDL_SCANCODE_KP_2, Keycode::KP2}, +		   {SDL_SCANCODE_KP_3, Keycode::KP3}, +		   {SDL_SCANCODE_KP_4, Keycode::KP4}, +		   {SDL_SCANCODE_KP_5, Keycode::KP5}, +		   {SDL_SCANCODE_KP_6, Keycode::KP6}, +		   {SDL_SCANCODE_KP_7, Keycode::KP7}, +		   {SDL_SCANCODE_KP_8, Keycode::KP8}, +		   {SDL_SCANCODE_KP_9, Keycode::KP9}, +		   {SDL_SCANCODE_LSHIFT, Keycode::LEFT_SHIFT}, +		   {SDL_SCANCODE_LCTRL, Keycode::LEFT_CONTROL}, +		   {SDL_SCANCODE_LALT, Keycode::LEFT_ALT}, +		   {SDL_SCANCODE_LGUI, Keycode::LEFT_SUPER}, +		   {SDL_SCANCODE_RSHIFT, Keycode::RIGHT_SHIFT}, +		   {SDL_SCANCODE_RCTRL, Keycode::RIGHT_CONTROL}, +		   {SDL_SCANCODE_RALT, Keycode::RIGHT_ALT}, +		   {SDL_SCANCODE_RGUI, Keycode::RIGHT_SUPER}, +		   {SDL_SCANCODE_MENU, Keycode::MENU}};  };  } // namespace crepe diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index 97e455e..b1e6463 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -1,5 +1,5 @@  #include "../api/Asset.h" -#include "../util/Log.h" +#include "../util/dbg.h"  #include "Sound.h" diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp index b1f8cb3..5091e07 100644 --- a/src/crepe/facade/SoundContext.cpp +++ b/src/crepe/facade/SoundContext.cpp @@ -1,4 +1,4 @@ -#include "../util/Log.h" +#include "../util/dbg.h"  #include "SoundContext.h" diff --git a/src/crepe/facade/Texture.cpp b/src/crepe/facade/Texture.cpp index b63403d..23a9c8e 100644 --- a/src/crepe/facade/Texture.cpp +++ b/src/crepe/facade/Texture.cpp @@ -1,4 +1,4 @@ -#include "../util/Log.h" +#include "../util/dbg.h"  #include "facade/SDLContext.h"  #include "manager/Mediator.h" diff --git a/src/crepe/manager/CMakeLists.txt b/src/crepe/manager/CMakeLists.txt index f73e165..48e444f 100644 --- a/src/crepe/manager/CMakeLists.txt +++ b/src/crepe/manager/CMakeLists.txt @@ -6,6 +6,8 @@ target_sources(crepe PUBLIC  	SceneManager.cpp  	LoopTimerManager.cpp  	ResourceManager.cpp +	ReplayManager.cpp +	SystemManager.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -21,5 +23,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	LoopTimerManager.h  	ResourceManager.h  	ResourceManager.hpp +	ReplayManager.h +	SystemManager.h +	SystemManager.hpp  ) diff --git a/src/crepe/manager/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp index df30d27..745ddae 100644 --- a/src/crepe/manager/ComponentManager.cpp +++ b/src/crepe/manager/ComponentManager.cpp @@ -1,7 +1,7 @@  #include "../api/GameObject.h"  #include "../api/Metadata.h"  #include "../types.h" -#include "../util/Log.h" +#include "../util/dbg.h"  #include "ComponentManager.h" @@ -53,7 +53,7 @@ GameObject ComponentManager::new_object(const string & name, const string & tag,  		this->next_id++;  	} -	GameObject object{*this, this->next_id, name, tag, position, rotation, scale}; +	GameObject object{this->mediator, this->next_id, name, tag, position, rotation, scale};  	this->next_id++;  	return object; @@ -72,3 +72,28 @@ set<game_object_id_t> ComponentManager::get_objects_by_tag(const string & tag) c  	return this->get_objects_by_predicate<Metadata>(  		[tag](const Metadata & data) { return data.tag == tag; });  } + +ComponentManager::Snapshot ComponentManager::save() { +	Snapshot snapshot{}; +	for (const auto & [type, by_id_index] : this->components) { +		for (game_object_id_t id = 0; id < by_id_index.size(); id++) { +			const auto & components = by_id_index[id]; +			for (size_t index = 0; index < components.size(); index++) { +				const Component & component = *components[index]; +				snapshot.components.push_back(SnapshotComponent{ +					.type = type, +					.id = id, +					.index = index, +					.component = component.save(), +				}); +			} +		} +	} +	return snapshot; +} + +void ComponentManager::restore(const Snapshot & snapshot) { +	for (const SnapshotComponent & info : snapshot.components) { +		this->components[info.type][info.id][info.index]->restore(*info.component); +	} +} diff --git a/src/crepe/manager/ComponentManager.h b/src/crepe/manager/ComponentManager.h index 19a8e81..c3a5b4a 100644 --- a/src/crepe/manager/ComponentManager.h +++ b/src/crepe/manager/ComponentManager.h @@ -21,13 +21,6 @@ class GameObject;   * This class manages all components. It provides methods to add, delete and get components.   */  class ComponentManager : public Manager { -	// TODO: This relation should be removed! I (loek) believe that the scene manager should -	// create/destroy components because the GameObject's are stored in concrete Scene classes, -	// which will in turn call GameObject's destructor, which will in turn call -	// ComponentManager::delete_components_by_id or something. This is a pretty major change, so -	// here is a comment and temporary fix instead :tada: -	friend class SceneManager; -  public:  	ComponentManager(Mediator & mediator);  	~ComponentManager(); // dbg_trace @@ -49,12 +42,7 @@ public:  						  const vec2 & position = {0, 0}, double rotation = 0,  						  double scale = 1); -protected: -	/** -	 * GameObject is used as an interface to add/remove components, and the game programmer is -	 * supposed to use it instead of interfacing with the component manager directly. -	 */ -	friend class GameObject; +public:  	/**  	 * \brief Add a component to the ComponentManager  	 * @@ -154,6 +142,40 @@ public:  	template <typename T>  	RefVector<T> get_components_by_tag(const std::string & tag) const; +	//! Snapshot of single component (including path in \c components) +	struct SnapshotComponent { +		//! \c components path +		std::type_index type; +		//! \c components path +		game_object_id_t id; +		//! \c components path +		size_t index; +		//! Actual component snapshot +		std::unique_ptr<Component> component; +	}; +	//! Snapshot of the entire component manager state +	struct Snapshot { +		//! All components +		std::vector<SnapshotComponent> components; +		// TODO: some kind of hash code that ensures components exist in all the same places as +		// this snapshot +	}; +	/** +	 * \name ReplayManager (Memento) functions +	 * \{ +	 */ +	/** +	 * \brief Save a snapshot of the component manager state +	 * \returns Deep copy of the component manager's internal state +	 */ +	Snapshot save(); +	/** +	 * \brief Restore component manager from a snapshot +	 * \param snapshot Snapshot to restore from (as returned by \c save()) +	 */ +	void restore(const Snapshot & snapshot); +	//! \} +  private:  	/**  	 * \brief Get object IDs by predicate function diff --git a/src/crepe/manager/LoopTimerManager.cpp b/src/crepe/manager/LoopTimerManager.cpp index a6e4788..e78f92f 100644 --- a/src/crepe/manager/LoopTimerManager.cpp +++ b/src/crepe/manager/LoopTimerManager.cpp @@ -1,7 +1,7 @@  #include <chrono>  #include <thread> -#include "../util/Log.h" +#include "../util/dbg.h"  #include "LoopTimerManager.h" diff --git a/src/crepe/manager/LoopTimerManager.h b/src/crepe/manager/LoopTimerManager.h index 76b02d3..2f1e6b6 100644 --- a/src/crepe/manager/LoopTimerManager.h +++ b/src/crepe/manager/LoopTimerManager.h @@ -6,6 +6,8 @@  namespace crepe { +class Engine; +  typedef std::chrono::duration<float> duration_t;  typedef std::chrono::duration<unsigned long long, std::micro> elapsed_time_t; @@ -107,7 +109,7 @@ public:  private:  	//! Friend relation to use start,enforce_frame_rate,get_lag,update,advance_fixed_update. -	friend class LoopManager; +	friend class Engine;  	/**  	 * \brief Start the loop timer.  	 * diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h index a336410..842f1de 100644 --- a/src/crepe/manager/Mediator.h +++ b/src/crepe/manager/Mediator.h @@ -11,6 +11,8 @@ class LoopTimerManager;  class SaveManager;  class ResourceManager;  class SDLContext; +class ReplayManager; +class SystemManager;  /**   * Struct to pass references to classes that would otherwise need to be singletons down to @@ -32,6 +34,8 @@ struct Mediator {  	OptionalRef<LoopTimerManager> loop_timer;  	OptionalRef<SaveManager> save_manager;  	OptionalRef<ResourceManager> resource_manager; +	OptionalRef<ReplayManager> replay_manager; +	OptionalRef<SystemManager> system_manager;  };  } // namespace crepe diff --git a/src/crepe/manager/ReplayManager.cpp b/src/crepe/manager/ReplayManager.cpp new file mode 100644 index 0000000..090a94e --- /dev/null +++ b/src/crepe/manager/ReplayManager.cpp @@ -0,0 +1,70 @@ +#include <format> + +#include "Manager.h" +#include "ReplayManager.h" + +using namespace crepe; +using namespace std; + +ReplayManager::ReplayManager(Mediator & mediator) : Manager(mediator) { +	mediator.replay_manager = *this; +} + +void ReplayManager::record_start() { +	if (this->state == RECORDING) this->release(this->id); +	this->id++; +	this->memory[this->id] = make_unique<Recording>(); +	this->recording = *this->memory.at(this->id); +	this->state = RECORDING; +} + +recording_t ReplayManager::record_end() { +	this->state = IDLE; +	return this->id; +} + +void ReplayManager::play(recording_t handle) { +	if (!this->memory.contains(handle)) +		throw out_of_range(format("ReplayManager: no recording for handle {}", handle)); +	this->recording = *this->memory.at(handle); +	this->recording->frame = 0; +	this->state = PLAYING; +} + +void ReplayManager::release(recording_t handle) { +	if (!this->memory.contains(handle)) return; +	this->memory.erase(handle); +} + +void ReplayManager::frame_record() { +	if (this->state != RECORDING) +		throw runtime_error("ReplayManager: frame_step called while not playing"); + +	ComponentManager & components = this->mediator.component_manager; +	Recording & recording = this->recording; + +	recording.frames.push_back(components.save()); +	recording.frame++; +} + +bool ReplayManager::frame_step() { +	if (this->state != PLAYING) +		throw runtime_error("ReplayManager: frame_step called while not playing"); + +	ComponentManager & components = this->mediator.component_manager; +	Recording & recording = this->recording; + +	ComponentManager::Snapshot & frame = recording.frames.at(recording.frame); + +	components.restore(frame); +	recording.frame++; + +	if (recording.frame < recording.frames.size()) return false; +	// end of recording +	recording.frame = 0; +	this->state = IDLE; +	this->recording.clear(); +	return true; +} + +ReplayManager::State ReplayManager::get_state() const { return this->state; } diff --git a/src/crepe/manager/ReplayManager.h b/src/crepe/manager/ReplayManager.h new file mode 100644 index 0000000..ab15b27 --- /dev/null +++ b/src/crepe/manager/ReplayManager.h @@ -0,0 +1,94 @@ +#pragma once + +#include <unordered_map> + +#include "ComponentManager.h" +#include "Manager.h" +#include "util/OptionalRef.h" + +namespace crepe { + +typedef size_t recording_t; + +/** + * \brief Replay manager + * + * The replay manager is responsible for creating, storing and restoring ComponentManager + * snapshots. Sequential snapshots can be recorded and replayed in combination with + * ReplaySystem. + */ +class ReplayManager : public Manager { +	// TODO: Delete recordings at end of scene + +public: +	ReplayManager(Mediator & mediator); + +public: +	//! Start a new recording +	void record_start(); +	/** +	 * \brief End the latest recording started by \c record_start() +	 * \returns Handle to recording +	 */ +	recording_t record_end(); +	/** +	 * \brief Play a recording +	 * \param handle Handle to recording (as returned by \c record_end()) +	 */ +	void play(recording_t handle); +	/** +	 * \brief Delete a recording from memory +	 * \param handle Handle to recording (as returned by \c record_end()) +	 */ +	void release(recording_t handle); + +public: +	//! Internal state +	enum State { +		IDLE, //!< Not doing anything +		RECORDING, //!< Currently recording +		PLAYING, //!< Currently playing back a recording +	}; +	//! Get current internal state +	State get_state() const; + +public: +	/** +	 * \brief Record a single frame to the current recording +	 * +	 * This function is called by ReplaySystem after the game programmer has called \c +	 * record_start() +	 */ +	void frame_record(); +	/** +	 * \brief Play the next frame of the current recording +	 * +	 * \returns `true` if the recording is finished playing +	 * \returns `false` if there are more frames +	 * +	 * This function also automatically resets the internal state from PLAYING to IDLE at the end +	 * of a recording. +	 */ +	bool frame_step(); + +private: +	/** +	 * \brief Recording data +	 */ +	struct Recording { +		//! Current frame being shown +		size_t frame = 0; +		//! All frames in recording +		std::vector<ComponentManager::Snapshot> frames; +	}; +	//! Internal state +	State state = IDLE; +	//! Current recording handle +	recording_t id = -1; +	//! Current recording data +	OptionalRef<Recording> recording; +	//! Recording storage +	std::unordered_map<recording_t, std::unique_ptr<Recording>> memory; +}; + +} // namespace crepe diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp index a141a46..5713183 100644 --- a/src/crepe/manager/ResourceManager.cpp +++ b/src/crepe/manager/ResourceManager.cpp @@ -1,4 +1,4 @@ -#include "util/Log.h" +#include "util/dbg.h"  #include "ResourceManager.h" diff --git a/src/crepe/manager/SystemManager.cpp b/src/crepe/manager/SystemManager.cpp new file mode 100644 index 0000000..f029aa5 --- /dev/null +++ b/src/crepe/manager/SystemManager.cpp @@ -0,0 +1,67 @@ +#include "../system/AISystem.h" +#include "../system/AnimatorSystem.h" +#include "../system/AudioSystem.h" +#include "../system/CollisionSystem.h" +#include "../system/EventSystem.h" +#include "../system/InputSystem.h" +#include "../system/ParticleSystem.h" +#include "../system/PhysicsSystem.h" +#include "../system/RenderSystem.h" +#include "../system/ReplaySystem.h" +#include "../system/ScriptSystem.h" + +#include "SystemManager.h" + +using namespace crepe; +using namespace std; + +SystemManager::SystemManager(Mediator & mediator) : Manager(mediator) { +	this->load_system<InputSystem>(); +	this->load_system<EventSystem>(); +	this->load_system<ScriptSystem>(); +	this->load_system<ParticleSystem>(); +	this->load_system<AISystem>(); +	this->load_system<PhysicsSystem>(); +	this->load_system<CollisionSystem>(); +	this->load_system<AudioSystem>(); +	this->load_system<AnimatorSystem>(); +	this->load_system<RenderSystem>(); +	this->load_system<ReplaySystem>(); + +	this->mediator.system_manager = *this; +} + +void SystemManager::fixed_update() { +	for (System & system : this->system_order) { +		if (!system.active) continue; +		system.fixed_update(); +	} +} + +void SystemManager::frame_update() { +	for (System & system : this->system_order) { +		if (!system.active) continue; +		system.frame_update(); +	} +} + +SystemManager::Snapshot SystemManager::save() { +	Snapshot snapshot; +	for (auto & [type, system] : this->systems) { +		snapshot[type] = system->active; +	} +	return snapshot; +} + +void SystemManager::restore(const Snapshot & snapshot) { +	for (auto & [type, active] : snapshot) { +		this->systems[type]->active = active; +	} +} + +void SystemManager::disable_all() { +	for (auto & [type, system] : this->systems) { +		system->active = false; +	} +} + diff --git a/src/crepe/manager/SystemManager.h b/src/crepe/manager/SystemManager.h new file mode 100644 index 0000000..b4a001c --- /dev/null +++ b/src/crepe/manager/SystemManager.h @@ -0,0 +1,93 @@ +#pragma once + +#include <memory> +#include <vector> +#include <typeindex> +#include <unordered_map> + +#include "../system/System.h" + +#include "Manager.h" + +namespace crepe { + +/** + * \brief Collection of all systems + * + * This manager aggregates all systems and provides utility functions to retrieve references to + * and update systems. + */ +class SystemManager : public Manager { +public: +	SystemManager(Mediator &); + +	/** +	 * \brief Per-frame update. +	 * +	 * Updates the game state based on the elapsed time since the last frame. +	 */ +	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(); + +private: +	/** +	 * \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 SystemManager using SystemManager::load_system. +	 */ +	std::unordered_map<std::type_index, std::unique_ptr<System>> systems; +	/** +	 * \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 SystemManager using SystemManager::load_system. +	 */ +	std::vector<std::reference_wrapper<System>> system_order; +	/** +	 * \brief Initialize a system +	 * \tparam T System type (must be derivative of \c System) +	 */ +	template <class T> +	void load_system(); + +public: +	/** +	 * \brief Retrieve a reference to ECS system +	 * \tparam T System type +	 * \returns Reference to system instance +	 * \throws std::runtime_error if the System is not initialized +	 */ +	template <class T> +	T & get_system(); + +public: +	/** +	 * \brief SystemManager snapshot +	 * +	 * The SystemManager snapshot only stores which systems are active +	 */ +	typedef std::unordered_map<std::type_index, bool> Snapshot; +	/** +	 * \brief Save a snapshot of the systems' state +	 * \returns Copy of each system's active property +	 */ +	Snapshot save(); +	/** +	 * \brief Restore system active state from a snapshot +	 * \param snapshot Snapshot to restore from (as returned by \c save()) +	 */ +	void restore(const Snapshot & snapshot); +	//! Disable all systems +	void disable_all(); +}; + +} // namespace crepe + +#include "SystemManager.hpp" diff --git a/src/crepe/api/LoopManager.hpp b/src/crepe/manager/SystemManager.hpp index 266758a..8d06eb1 100644 --- a/src/crepe/api/LoopManager.hpp +++ b/src/crepe/manager/SystemManager.hpp @@ -4,26 +4,19 @@  #include <format>  #include <memory> -#include "../system/System.h" - -#include "LoopManager.h" +#include "SystemManager.h"  namespace crepe {  template <class T> -void LoopManager::add_scene() { -	this->scene_manager.add_scene<T>(); -} - -template <class T> -T & LoopManager::get_system() { +T & SystemManager::get_system() {  	using namespace std;  	static_assert(is_base_of<System, T>::value,  				  "get_system must recieve a derivative class of System");  	const type_info & type = typeid(T);  	if (!this->systems.contains(type)) -		throw runtime_error(format("LoopManager: {} is not initialized", type.name())); +		throw runtime_error(format("SystemManager: {} is not initialized", type.name()));  	System * system = this->systems.at(type).get();  	T * concrete_system = dynamic_cast<T *>(system); @@ -33,16 +26,17 @@ T & LoopManager::get_system() {  }  template <class T> -void LoopManager::load_system() { +void SystemManager::load_system() {  	using namespace std;  	static_assert(is_base_of<System, T>::value,  				  "load_system must recieve a derivative class of System");  	const type_info & type = typeid(T);  	if (this->systems.contains(type)) -		throw runtime_error(format("LoopManager: {} is already initialized", type.name())); +		throw runtime_error(format("SystemManager: {} is already initialized", type.name()));  	System * system = new T(this->mediator);  	this->systems[type] = unique_ptr<System>(system); +	this->system_order.push_back(*this->systems[type]);  }  } // namespace crepe diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp index 680dbb8..0f35010 100644 --- a/src/crepe/system/AISystem.cpp +++ b/src/crepe/system/AISystem.cpp @@ -10,7 +10,7 @@  using namespace crepe;  using namespace std::chrono; -void AISystem::update() { +void AISystem::fixed_update() {  	const Mediator & mediator = this->mediator;  	ComponentManager & mgr = mediator.component_manager;  	LoopTimerManager & loop_timer = mediator.loop_timer; diff --git a/src/crepe/system/AISystem.h b/src/crepe/system/AISystem.h index d5f8a8e..04807cf 100644 --- a/src/crepe/system/AISystem.h +++ b/src/crepe/system/AISystem.h @@ -20,7 +20,7 @@ public:  	using System::System;  	//! Update the AI system -	void update() override; +	void fixed_update() override;  private:  	/** diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index 107b25d..e5ab2fa 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -1,5 +1,3 @@ - -  #include "../api/Animator.h"  #include "../manager/ComponentManager.h"  #include "../manager/LoopTimerManager.h" @@ -10,7 +8,7 @@  using namespace crepe;  using namespace std::chrono; -void AnimatorSystem::update() { +void AnimatorSystem::frame_update() {  	ComponentManager & mgr = this->mediator.component_manager;  	LoopTimerManager & timer = this->mediator.loop_timer;  	RefVector<Animator> animations = mgr.get_components_by_type<Animator>(); diff --git a/src/crepe/system/AnimatorSystem.h b/src/crepe/system/AnimatorSystem.h index 7d3f565..092e131 100644 --- a/src/crepe/system/AnimatorSystem.h +++ b/src/crepe/system/AnimatorSystem.h @@ -22,7 +22,7 @@ public:  	 * Animator components, moving the animations forward and managing their behavior (e.g.,  	 * looping).  	 */ -	void update() override; +	void frame_update() override;  };  } // namespace crepe diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp index b1aa0f8..d4e8b9f 100644 --- a/src/crepe/system/AudioSystem.cpp +++ b/src/crepe/system/AudioSystem.cpp @@ -7,7 +7,7 @@  using namespace crepe;  using namespace std; -void AudioSystem::update() { +void AudioSystem::fixed_update() {  	ComponentManager & component_manager = this->mediator.component_manager;  	ResourceManager & resource_manager = this->mediator.resource_manager;  	RefVector<AudioSource> components diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h index 2ddc443..56fc98c 100644 --- a/src/crepe/system/AudioSystem.h +++ b/src/crepe/system/AudioSystem.h @@ -11,7 +11,7 @@ namespace crepe {  class AudioSystem : public System {  public:  	using System::System; -	void update() override; +	void fixed_update() override;  private:  	/** diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index 0e2db76..52369d0 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -8,6 +8,8 @@ target_sources(crepe PUBLIC  	AudioSystem.cpp  	AnimatorSystem.cpp  	InputSystem.cpp +	EventSystem.cpp +	ReplaySystem.cpp  	AISystem.cpp  ) @@ -20,5 +22,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	AudioSystem.h  	AnimatorSystem.h  	InputSystem.h +	EventSystem.h +	ReplaySystem.h  	AISystem.h  ) diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp index af8adce..9d88d9f 100644 --- a/src/crepe/system/CollisionSystem.cpp +++ b/src/crepe/system/CollisionSystem.cpp @@ -23,7 +23,7 @@  using namespace crepe; -void CollisionSystem::update() { +void CollisionSystem::fixed_update() {  	std::vector<CollisionInternal> all_colliders;  	game_object_id_t id = 0;  	ComponentManager & mgr = this->mediator.component_manager; diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h index 5b136c6..48a8f86 100644 --- a/src/crepe/system/CollisionSystem.h +++ b/src/crepe/system/CollisionSystem.h @@ -85,7 +85,7 @@ public:  public:  	//! Updates the collision system by checking for collisions between colliders and handling them. -	void update() override; +	void fixed_update() override;  private:  	/** diff --git a/src/crepe/system/EventSystem.cpp b/src/crepe/system/EventSystem.cpp new file mode 100644 index 0000000..7e168ab --- /dev/null +++ b/src/crepe/system/EventSystem.cpp @@ -0,0 +1,9 @@ +#include "EventSystem.h" +#include "../manager/EventManager.h" + +using namespace crepe; + +void EventSystem::fixed_update() { +	EventManager & ev = this->mediator.event_manager; +	ev.dispatch_events(); +} diff --git a/src/crepe/system/EventSystem.h b/src/crepe/system/EventSystem.h new file mode 100644 index 0000000..0ae48d2 --- /dev/null +++ b/src/crepe/system/EventSystem.h @@ -0,0 +1,21 @@ +#pragma once + +#include "System.h" + +namespace crepe { + +/** + * \brief EventManager dispatch helper system + */ +class EventSystem : public System { +public: +	using System::System; + +	/** +	 * \brief Dispatch queued events +	 * \see EventManager::dispatch_events +	 */ +	void fixed_update() override; +}; + +} // namespace crepe diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp index a710ae2..38d12c5 100644 --- a/src/crepe/system/InputSystem.cpp +++ b/src/crepe/system/InputSystem.cpp @@ -8,14 +8,16 @@  using namespace crepe; -void InputSystem::update() { +void InputSystem::fixed_update() {  	ComponentManager & mgr = this->mediator.component_manager; -	EventManager & event_mgr = this->mediator.event_manager; +  	SDLContext & context = this->mediator.sdl_context; +	context.update_keyboard_state();  	std::vector<SDLContext::EventData> event_list = context.get_events();  	RefVector<Button> buttons = mgr.get_components_by_type<Button>();  	RefVector<Camera> cameras = mgr.get_components_by_type<Camera>();  	OptionalRef<Camera> curr_cam_ref; +  	// Find the active camera  	for (Camera & cam : cameras) {  		if (!cam.active) continue; @@ -23,150 +25,190 @@ void InputSystem::update() {  		break;  	}  	if (!curr_cam_ref) return; +  	Camera & current_cam = curr_cam_ref;  	RefVector<Transform> transform_vec  		= mgr.get_components_by_id<Transform>(current_cam.game_object_id);  	Transform & cam_transform = transform_vec.front().get(); -	int camera_origin_x = cam_transform.position.x + current_cam.data.postion_offset.x -						  - (current_cam.viewport_size.x / 2); -	int camera_origin_y = cam_transform.position.y + current_cam.data.postion_offset.y -						  - (current_cam.viewport_size.y / 2); + +	vec2 camera_origin = cam_transform.position + current_cam.data.postion_offset +						 - (current_cam.viewport_size / 2);  	for (const SDLContext::EventData & event : event_list) { -		int world_mouse_x = event.mouse_position.x + camera_origin_x; -		int world_mouse_y = event.mouse_position.y + camera_origin_y; -		// check if the mouse is within the viewport -		bool mouse_in_viewport -			= !(world_mouse_x < camera_origin_x -				|| world_mouse_x > camera_origin_x + current_cam.viewport_size.x -				|| world_mouse_y < camera_origin_y -				|| world_mouse_y > camera_origin_y + current_cam.viewport_size.y); - -		switch (event.event_type) { -			case SDLContext::EventType::KEYDOWN: -				event_mgr.queue_event<KeyPressEvent>(KeyPressEvent{ -					.repeat = event.key_repeat, -					.key = event.key, -				}); -				break; -			case SDLContext::EventType::KEYUP: -				event_mgr.queue_event<KeyReleaseEvent>(KeyReleaseEvent{ -					.key = event.key, -				}); -				break; -			case SDLContext::EventType::MOUSEDOWN: -				if (!mouse_in_viewport) { -					break; -				} -				event_mgr.queue_event<MousePressEvent>(MousePressEvent{ -					.mouse_x = world_mouse_x, -					.mouse_y = world_mouse_y, -					.button = event.mouse_button, -				}); -				this->last_mouse_down_position = {world_mouse_x, world_mouse_y}; -				this->last_mouse_button = event.mouse_button; -				break; -			case SDLContext::EventType::MOUSEUP: { -				if (!mouse_in_viewport) { -					break; -				} -				event_mgr.queue_event<MouseReleaseEvent>(MouseReleaseEvent{ -					.mouse_x = world_mouse_x, -					.mouse_y = world_mouse_y, -					.button = event.mouse_button, -				}); -				//check if its a click by checking the last button down -				int delta_x = world_mouse_x - this->last_mouse_down_position.x; -				int delta_y = world_mouse_y - this->last_mouse_down_position.y; - -				if (this->last_mouse_button == event.mouse_button -					&& std::abs(delta_x) <= click_tolerance -					&& std::abs(delta_y) <= click_tolerance) { -					event_mgr.queue_event<MouseClickEvent>(MouseClickEvent{ -						.mouse_x = world_mouse_x, -						.mouse_y = world_mouse_y, -						.button = event.mouse_button, -					}); - -					this->handle_click(event.mouse_button, world_mouse_x, world_mouse_y); -				} -			} break; -			case SDLContext::EventType::MOUSEMOVE: -				if (!mouse_in_viewport) { -					break; -				} -				event_mgr.queue_event<MouseMoveEvent>(MouseMoveEvent{ -					.mouse_x = world_mouse_x, -					.mouse_y = world_mouse_y, -					.delta_x = event.rel_mouse_move.x, -					.delta_y = event.rel_mouse_move.y, -				}); -				this->handle_move(event, world_mouse_x, world_mouse_y); -				break; -			case SDLContext::EventType::MOUSEWHEEL: -				event_mgr.queue_event<MouseScrollEvent>(MouseScrollEvent{ -					.mouse_x = world_mouse_x, -					.mouse_y = world_mouse_y, -					.scroll_direction = event.scroll_direction, -					.scroll_delta = event.scroll_delta, +		// Only calculate mouse coordinates for relevant events +		if (this->is_mouse_event(event.event_type)) { +			this->handle_mouse_event(event, camera_origin, current_cam); + +		} else { +			this->handle_non_mouse_event(event); +		} +	} +} + +void InputSystem::handle_mouse_event(const SDLContext::EventData & event, +									 const vec2 & camera_origin, const Camera & current_cam) { +	EventManager & event_mgr = this->mediator.event_manager; +	vec2 adjusted_mouse; +	adjusted_mouse.x = event.data.mouse_data.mouse_position.x + camera_origin.x; +	adjusted_mouse.x = event.data.mouse_data.mouse_position.y + camera_origin.y; +	// Check if the mouse is within the viewport +	if ((adjusted_mouse.x < camera_origin.x +		 || adjusted_mouse.x > camera_origin.x + current_cam.viewport_size.x +		 || adjusted_mouse.y < camera_origin.y +		 || adjusted_mouse.y > camera_origin.y + current_cam.viewport_size.y)) +		return; + +	// Handle mouse-specific events +	switch (event.event_type) { +		case SDLContext::EventType::MOUSEDOWN: +			event_mgr.queue_event<MousePressEvent>({ +				.mouse_pos = adjusted_mouse, +				.button = event.data.mouse_data.mouse_button, +			}); +			this->last_mouse_down_position = adjusted_mouse; +			this->last_mouse_button = event.data.mouse_data.mouse_button; +			break; + +		case SDLContext::EventType::MOUSEUP: { +			event_mgr.queue_event<MouseReleaseEvent>({ +				.mouse_pos = adjusted_mouse, +				.button = event.data.mouse_data.mouse_button, +			}); +			vec2 delta_move = adjusted_mouse - this->last_mouse_down_position; +			int click_tolerance = Config::get_instance().input.click_tolerance; +			if (this->last_mouse_button == event.data.mouse_data.mouse_button +				&& std::abs(delta_move.x) <= click_tolerance +				&& std::abs(delta_move.y) <= click_tolerance) { +				event_mgr.queue_event<MouseClickEvent>({ +					.mouse_pos = adjusted_mouse, +					.button = event.data.mouse_data.mouse_button,  				}); -				break; -			case SDLContext::EventType::SHUTDOWN: -				event_mgr.queue_event<ShutDownEvent>(ShutDownEvent{}); -				break; -			default: -				break; +				this->handle_click(event.data.mouse_data.mouse_button, adjusted_mouse); +			} +			break;  		} + +		case SDLContext::EventType::MOUSEMOVE: +			event_mgr.queue_event<MouseMoveEvent>({ +				.mouse_pos = adjusted_mouse, +				.mouse_delta = event.data.mouse_data.rel_mouse_move, +			}); +			this->handle_move(event, adjusted_mouse); +			break; + +		case SDLContext::EventType::MOUSEWHEEL: +			event_mgr.queue_event<MouseScrollEvent>({ +				.mouse_pos = adjusted_mouse, +				.scroll_direction = event.data.mouse_data.scroll_direction, +				.scroll_delta = event.data.mouse_data.scroll_delta, +			}); +			break; + +		default: +			break; +	} +} + +void InputSystem::handle_non_mouse_event(const SDLContext::EventData & event) { +	EventManager & event_mgr = this->mediator.event_manager; +	switch (event.event_type) { +		case SDLContext::EventType::KEYDOWN: + +			event_mgr.queue_event<KeyPressEvent>( +				{.repeat = event.data.key_data.key_repeat, .key = event.data.key_data.key}); +			break; +		case SDLContext::EventType::KEYUP: +			event_mgr.queue_event<KeyReleaseEvent>({.key = event.data.key_data.key}); +			break; +		case SDLContext::EventType::SHUTDOWN: +			event_mgr.queue_event<ShutDownEvent>({}); +			break; +		case SDLContext::EventType::WINDOW_EXPOSE: +			event_mgr.queue_event<WindowExposeEvent>({}); +			break; +		case SDLContext::EventType::WINDOW_RESIZE: +			event_mgr.queue_event<WindowResizeEvent>( +				WindowResizeEvent{.dimensions = event.data.window_data.resize_dimension}); +			break; +		case SDLContext::EventType::WINDOW_MOVE: +			event_mgr.queue_event<WindowMoveEvent>( +				{.delta_move = event.data.window_data.move_delta}); +			break; +		case SDLContext::EventType::WINDOW_MINIMIZE: +			event_mgr.queue_event<WindowMinimizeEvent>({}); +			break; +		case SDLContext::EventType::WINDOW_MAXIMIZE: +			event_mgr.queue_event<WindowMaximizeEvent>({}); +			break; +		case SDLContext::EventType::WINDOW_FOCUS_GAIN: +			event_mgr.queue_event<WindowFocusGainEvent>({}); +			break; +		case SDLContext::EventType::WINDOW_FOCUS_LOST: +			event_mgr.queue_event<WindowFocusLostEvent>({}); +			break; +		default: +			break;  	}  } + +bool InputSystem::is_mouse_event(SDLContext::EventType event_type) { +	return (event_type == SDLContext::EventType::MOUSEDOWN +			|| event_type == SDLContext::EventType::MOUSEUP +			|| event_type == SDLContext::EventType::MOUSEMOVE +			|| event_type == SDLContext::EventType::MOUSEWHEEL); +} +  void InputSystem::handle_move(const SDLContext::EventData & event_data, -							  const int world_mouse_x, const int world_mouse_y) { +							  const vec2 & mouse_pos) {  	ComponentManager & mgr = this->mediator.component_manager;  	RefVector<Button> buttons = mgr.get_components_by_type<Button>();  	for (Button & button : buttons) { +		if (!button.active) continue;  		RefVector<Transform> transform_vec  			= mgr.get_components_by_id<Transform>(button.game_object_id);  		Transform & transform(transform_vec.front().get());  		bool was_hovering = button.hover; -		if (button.active -			&& this->is_mouse_inside_button(world_mouse_x, world_mouse_y, button, transform)) { +		if (this->is_mouse_inside_button(mouse_pos, button, transform)) {  			button.hover = true; -			if (!was_hovering && button.on_mouse_enter) { +			if (!button.on_mouse_enter) continue; +			if (!was_hovering) {  				button.on_mouse_enter();  			}  		} else {  			button.hover = false;  			// Trigger the on_exit callback if the hover state just changed to false -			if (was_hovering && button.on_mouse_exit) { +			if (!button.on_mouse_exit) continue; +			if (was_hovering) {  				button.on_mouse_exit();  			}  		}  	}  } -void InputSystem::handle_click(const MouseButton & mouse_button, const int world_mouse_x, -							   const int world_mouse_y) { +void InputSystem::handle_click(const MouseButton & mouse_button, const vec2 & mouse_pos) {  	ComponentManager & mgr = this->mediator.component_manager;  	RefVector<Button> buttons = mgr.get_components_by_type<Button>();  	for (Button & button : buttons) { +		if (!button.active) continue; +		if (!button.on_click) continue;  		RefVector<Transform> transform_vec  			= mgr.get_components_by_id<Transform>(button.game_object_id);  		Transform & transform = transform_vec.front().get(); -		if (button.active -			&& this->is_mouse_inside_button(world_mouse_x, world_mouse_y, button, transform)) { -			this->handle_button_press(button); +		if (this->is_mouse_inside_button(mouse_pos, button, transform)) { + +			button.on_click();  		}  	}  } -bool InputSystem::is_mouse_inside_button(const int mouse_x, const int mouse_y, -										 const Button & button, const Transform & transform) { +bool InputSystem::is_mouse_inside_button(const vec2 & mouse_pos, const Button & button, +										 const Transform & transform) {  	int actual_x = transform.position.x + button.offset.x;  	int actual_y = transform.position.y + button.offset.y; @@ -174,17 +216,6 @@ bool InputSystem::is_mouse_inside_button(const int mouse_x, const int mouse_y,  	int half_height = button.dimensions.y / 2;  	// Check if the mouse is within the button's boundaries -	return mouse_x >= actual_x - half_width && mouse_x <= actual_x + half_width -		   && mouse_y >= actual_y - half_height && mouse_y <= actual_y + half_height; -} - -void InputSystem::handle_button_press(Button & button) { -	if (button.is_toggle) { -		if (!button.is_pressed && button.on_click) { -			button.on_click(); -		} -		button.is_pressed = !button.is_pressed; -	} else if (button.on_click) { -		button.on_click(); -	} +	return mouse_pos.x >= actual_x - half_width && mouse_pos.x <= actual_x + half_width +		   && mouse_pos.y >= actual_y - half_height && mouse_pos.y <= actual_y + half_height;  } diff --git a/src/crepe/system/InputSystem.h b/src/crepe/system/InputSystem.h index 87e86f8..b03805a 100644 --- a/src/crepe/system/InputSystem.h +++ b/src/crepe/system/InputSystem.h @@ -1,5 +1,6 @@  #pragma once +#include "../api/Config.h"  #include "../facade/SDLContext.h"  #include "../types.h"  #include "../util/OptionalRef.h" @@ -27,19 +28,40 @@ public:  	 * \brief Updates the system, processing all input events.  	 * This method processes all events and triggers corresponding actions.  	 */ -	void update() override; +	void fixed_update() override;  private:  	//! Stores the last position of the mouse when the button was pressed. -	ivec2 last_mouse_down_position; +	vec2 last_mouse_down_position;  	// TODO: specify world/hud space and make regular `vec2`  	//! Stores the last mouse button pressed.  	MouseButton last_mouse_button = MouseButton::NONE; - -	//! The maximum allowable distance between mouse down and mouse up to register as a click. -	const int click_tolerance = 5; - +	/** +	 * \brief Determines whether the given event type is a mouse event. +	 * \param event_type The event type to check. +	 * \return True if the event type corresponds to a mouse event, false otherwise. +	 */ +	bool is_mouse_event(SDLContext::EventType event_type); +	/** +	 * \brief Handles mouse-related events. +	 * \param event The event data for the mouse event. +	 * \param camera_origin The origin position of the camera in world space. +	 * \param current_cam The currently active camera. +	 * +	 * This method processes mouse events, adjusts the mouse position to world coordinates, +	 * and triggers the appropriate mouse-specific event handling logic. +	 */ +	void handle_mouse_event(const SDLContext::EventData & event, const vec2 & camera_origin, +							const Camera & current_cam); +	/** +	 * \brief Handles non-mouse-related events. +	 * \param event The event data for the non-mouse event. +	 * +	 * This method processes events that do not involve the mouse, such as keyboard events, +	 * window events, and shutdown events, and triggers the corresponding event actions. +	 */ +	void handle_non_mouse_event(const SDLContext::EventData & event);  	/**  	* \brief Handles the mouse click event.  	* \param mouse_button The mouse button involved in the click. @@ -48,8 +70,7 @@ private:  	*  	* This method processes the mouse click event and triggers the corresponding button action.  	*/ -	void handle_click(const MouseButton & mouse_button, const int world_mouse_x, -					  const int world_mouse_y); +	void handle_click(const MouseButton & mouse_button, const vec2 & mouse_pos);  	/**  	* \brief Handles the mouse movement event. @@ -59,8 +80,7 @@ private:  	*  	* This method processes the mouse movement event and updates the button hover state.  	*/ -	void handle_move(const SDLContext::EventData & event_data, const int world_mouse_x, -					 const int world_mouse_y); +	void handle_move(const SDLContext::EventData & event_data, const vec2 & mouse_pos);  	/**  	* \brief Checks if the mouse position is inside the bounds of the button. @@ -70,8 +90,8 @@ private:  	* \param transform The transform component of the button.  	* \return True if the mouse is inside the button, false otherwise.  	*/ -	bool is_mouse_inside_button(const int world_mouse_x, const int world_mouse_y, -								const Button & button, const Transform & transform); +	bool is_mouse_inside_button(const vec2 & mouse_pos, const Button & button, +								const Transform & transform);  	/**  	* \brief Handles the button press event, calling the on_click callback if necessary. diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index b14c52f..4684ada 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -1,3 +1,4 @@ +#include <chrono>  #include <cmath>  #include <cstdlib>  #include <ctime> @@ -5,14 +6,19 @@  #include "../api/ParticleEmitter.h"  #include "../api/Transform.h"  #include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h"  #include "ParticleSystem.h"  using namespace crepe; -void ParticleSystem::update() { +void ParticleSystem::frame_update() {  	// Get all emitters -	ComponentManager & mgr = this->mediator.component_manager; +	const Mediator & mediator = this->mediator; +	LoopTimerManager & loop_timer = mediator.loop_timer; +	ComponentManager & mgr = mediator.component_manager; +	float dt = loop_timer.get_scaled_fixed_delta_time().count(); +  	RefVector<ParticleEmitter> emitters = mgr.get_components_by_type<ParticleEmitter>();  	for (ParticleEmitter & emitter : emitters) { @@ -21,38 +27,39 @@ void ParticleSystem::update() {  			= mgr.get_components_by_id<Transform>(emitter.game_object_id).front().get();  		// Emit particles based on emission_rate -		int updates = calculate_update(this->update_count, emitter.data.emission_rate); -		for (size_t i = 0; i < updates; i++) { -			emit_particle(emitter, transform); +		emitter.spawn_accumulator = emitter.data.emission_rate * dt; +		while (emitter.spawn_accumulator >= 1.0) { +			this->emit_particle(emitter, transform); +			emitter.spawn_accumulator -= 1.0;  		}  		// Update all particles -		for (Particle & particle : emitter.data.particles) { +		for (Particle & particle : emitter.particles) {  			if (particle.active) { -				particle.update(); +				particle.update(dt);  			}  		}  		// Check if within boundary -		check_bounds(emitter, transform); +		this->check_bounds(emitter, transform);  	} - -	this->update_count = (this->update_count + 1) % this->MAX_UPDATE_COUNT;  }  void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform & transform) {  	constexpr float DEG_TO_RAD = M_PI / 180.0;  	vec2 initial_position = emitter.data.position + transform.position; -	float random_angle = generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); +	float random_angle +		= this->generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); -	float random_speed = generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); +	float random_speed +		= this->generate_random_speed(emitter.data.min_speed, emitter.data.max_speed);  	float angle_radians = random_angle * DEG_TO_RAD;  	vec2 velocity  		= {random_speed * std::cos(angle_radians), random_speed * std::sin(angle_radians)}; -	for (Particle & particle : emitter.data.particles) { +	for (Particle & particle : emitter.particles) {  		if (!particle.active) {  			particle.reset(emitter.data.end_lifespan, initial_position, velocity,  						   random_angle); @@ -61,66 +68,54 @@ void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform &  	}  } -int ParticleSystem::calculate_update(int count, double emission) const { -	double integer_part = std::floor(emission); -	double fractional_part = emission - integer_part; - -	if (fractional_part > 0) { -		int denominator = static_cast<int>(1.0 / fractional_part); -		return (count % denominator == 0) ? 1 : 0; -	} - -	return static_cast<int>(emission); -} -  void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform & transform) {  	vec2 offset = emitter.data.boundary.offset + transform.position + emitter.data.position; -	double half_width = emitter.data.boundary.width / 2.0; -	double half_height = emitter.data.boundary.height / 2.0; +	float half_width = emitter.data.boundary.width / 2.0; +	float half_height = emitter.data.boundary.height / 2.0; -	const double LEFT = offset.x - half_width; -	const double RIGHT = offset.x + half_width; -	const double TOP = offset.y - half_height; -	const double BOTTOM = offset.y + half_height; +	float left = offset.x - half_width; +	float right = offset.x + half_width; +	float top = offset.y - half_height; +	float bottom = offset.y + half_height; -	for (Particle & particle : emitter.data.particles) { +	for (Particle & particle : emitter.particles) {  		const vec2 & position = particle.position; -		bool within_bounds = (position.x >= LEFT && position.x <= RIGHT && position.y >= TOP -							  && position.y <= BOTTOM); - +		bool within_bounds = (position.x >= left && position.x <= right && position.y >= top +							  && position.y <= bottom); +		//if not within bounds do a reset or stop velocity  		if (!within_bounds) {  			if (emitter.data.boundary.reset_on_exit) {  				particle.active = false;  			} else {  				particle.velocity = {0, 0}; -				if (position.x < LEFT) particle.position.x = LEFT; -				else if (position.x > RIGHT) particle.position.x = RIGHT; -				if (position.y < TOP) particle.position.y = TOP; -				else if (position.y > BOTTOM) particle.position.y = BOTTOM; +				if (position.x < left) particle.position.x = left; +				else if (position.x > right) particle.position.x = right; +				if (position.y < top) particle.position.y = top; +				else if (position.y > bottom) particle.position.y = bottom;  			}  		}  	}  } -double ParticleSystem::generate_random_angle(double min_angle, double max_angle) const { +float ParticleSystem::generate_random_angle(float min_angle, float max_angle) const {  	if (min_angle == max_angle) {  		return min_angle;  	} else if (min_angle < max_angle) {  		return min_angle -			   + static_cast<double>(std::rand() % static_cast<int>(max_angle - min_angle)); +			   + static_cast<float>(std::rand() % static_cast<int>(max_angle - min_angle));  	} else { -		double angle_offset = (360 - min_angle) + max_angle; -		double random_angle -			= min_angle + static_cast<double>(std::rand() % static_cast<int>(angle_offset)); +		float angle_offset = (360 - min_angle) + max_angle; +		float random_angle +			= min_angle + static_cast<float>(std::rand() % static_cast<int>(angle_offset));  		return (random_angle >= 360) ? random_angle - 360 : random_angle;  	}  } -double ParticleSystem::generate_random_speed(double min_speed, double max_speed) const { +float ParticleSystem::generate_random_speed(float min_speed, float max_speed) const {  	if (min_speed == max_speed) {  		return min_speed;  	} else {  		return min_speed -			   + static_cast<double>(std::rand() % static_cast<int>(max_speed - min_speed)); +			   + static_cast<float>(std::rand() % static_cast<int>(max_speed - min_speed));  	}  } diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index 068f01c..9eacc70 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -20,7 +20,7 @@ public:  	 * \brief Updates all particle emitters by emitting particles, updating particle states, and  	 * checking bounds.  	 */ -	void update() override; +	void frame_update() override;  private:  	/** @@ -32,16 +32,6 @@ private:  	void emit_particle(ParticleEmitter & emitter, const Transform & transform);  	/** -	 * \brief Calculates the number of times particles should be emitted based on emission rate -	 * and update count. -	 * -	 * \param count Current update count. -	 * \param emission Emission rate. -	 * \return The number of particles to emit. -	 */ -	int calculate_update(int count, double emission) const; - -	/**  	 * \brief Checks whether particles are within the emitter’s boundary, resets or stops  	 * particles if they exit.  	 * @@ -57,7 +47,7 @@ private:  	 * \param max_angle Maximum emission angle in degrees.  	 * \return Random angle in degrees.  	 */ -	double generate_random_angle(double min_angle, double max_angle) const; +	float generate_random_angle(float min_angle, float max_angle) const;  	/**  	 * \brief Generates a random speed for particle emission within the specified range. @@ -66,15 +56,7 @@ private:  	 * \param max_speed Maximum emission speed.  	 * \return Random speed.  	 */ -	double generate_random_speed(double min_speed, double max_speed) const; - -private: -	//! Counter to count updates to determine how many times emit_particle is -	// called. -	unsigned int update_count = 0; -	//! Determines the lowest amount of emission rate (1000 = 0.001 = 1 particle per 1000 -	// updates). -	static constexpr unsigned int MAX_UPDATE_COUNT = 100; +	float generate_random_speed(float min_speed, float max_speed) const;  };  } // namespace crepe diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index 3b3b8ab..62f8132 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -12,8 +12,7 @@  using namespace crepe; -void PhysicsSystem::update() { - +void PhysicsSystem::fixed_update() {  	const Mediator & mediator = this->mediator;  	ComponentManager & mgr = mediator.component_manager;  	LoopTimerManager & loop_timer = mediator.loop_timer; diff --git a/src/crepe/system/PhysicsSystem.h b/src/crepe/system/PhysicsSystem.h index 26152a5..5ed624f 100644 --- a/src/crepe/system/PhysicsSystem.h +++ b/src/crepe/system/PhysicsSystem.h @@ -18,7 +18,7 @@ public:  	 *  	 * It calculates new velocties and changes the postion in the transform.  	 */ -	void update() override; +	void fixed_update() override;  };  } // namespace crepe diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index afd9548..c403f6c 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -64,7 +64,7 @@ RefVector<Sprite> RenderSystem::sort(RefVector<Sprite> & objs) const {  	return sorted_objs;  } -void RenderSystem::update() { +void RenderSystem::frame_update() {  	this->clear_screen();  	this->render();  	this->present_screen(); @@ -83,11 +83,11 @@ bool RenderSystem::render_particle(const Sprite & sprite, const double & scale)  	bool rendering_particles = false;  	for (const ParticleEmitter & em : emitters) { -		if (&em.data.sprite != &sprite) continue; +		if (&em.sprite != &sprite) continue;  		rendering_particles = true;  		if (!em.active) continue; -		for (const Particle & p : em.data.particles) { +		for (const Particle & p : em.particles) {  			if (!p.active) continue;  			ctx.draw(SDLContext::RenderContext{ diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h index fc7b46e..1a61f99 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -24,7 +24,7 @@ public:  	 * \brief Updates the RenderSystem for the current frame.  	 * This method is called to perform all rendering operations for the current game frame.  	 */ -	void update() override; +	void frame_update() override;  private:  	//! Clears the screen in preparation for rendering. diff --git a/src/crepe/system/ReplaySystem.cpp b/src/crepe/system/ReplaySystem.cpp new file mode 100644 index 0000000..efc3be4 --- /dev/null +++ b/src/crepe/system/ReplaySystem.cpp @@ -0,0 +1,54 @@ +#include "../manager/ReplayManager.h" +#include "../manager/SystemManager.h" + +#include "EventSystem.h" +#include "RenderSystem.h" +#include "ReplaySystem.h" + +using namespace crepe; +using namespace std; + +void ReplaySystem::fixed_update() { +	ReplayManager & replay = this->mediator.replay_manager; +	ReplayManager::State state = replay.get_state(); +	ReplayManager::State last_state = this->last_state; +	this->last_state = state; + +	switch (state) { +		case ReplayManager::IDLE: +			break; +		case ReplayManager::RECORDING: { +			replay.frame_record(); +			break; +		} +		case ReplayManager::PLAYING: { +			if (last_state != ReplayManager::PLAYING) this->playback_begin(); +			bool last = replay.frame_step(); +			if (last) this->playback_end(); +			break; +		} +	} +} + +void ReplaySystem::playback_begin() { +	SystemManager & systems = this->mediator.system_manager; +	ComponentManager & components = this->mediator.component_manager; + +	this->playback = { +		.components = components.save(), +		.systems = systems.save(), +	}; + +	systems.disable_all(); +	systems.get_system<RenderSystem>().active = true; +	systems.get_system<ReplaySystem>().active = true; +	systems.get_system<EventSystem>().active = true; +} + +void ReplaySystem::playback_end() { +	SystemManager & systems = this->mediator.system_manager; +	ComponentManager & components = this->mediator.component_manager; + +	components.restore(this->playback.components); +	systems.restore(this->playback.systems); +} diff --git a/src/crepe/system/ReplaySystem.h b/src/crepe/system/ReplaySystem.h new file mode 100644 index 0000000..bbc8d76 --- /dev/null +++ b/src/crepe/system/ReplaySystem.h @@ -0,0 +1,44 @@ +#pragma once + +#include "../manager/ReplayManager.h" +#include "../manager/SystemManager.h" + +#include "System.h" + +namespace crepe { + +/** + * \brief ReplayManager helper system + * + * This system records and replays recordings using ReplayManager. + */ +class ReplaySystem : public System { +public: +	using System::System; + +	void fixed_update() override; + +private: +	//! Last ReplayManager state +	ReplayManager::State last_state = ReplayManager::IDLE; + +	/** +	 * \brief Playback snapshot +	 * +	 * When starting playback, the component state is saved and most systems are disabled. This +	 * struct stores the engine state before ReplayManager::play is called. +	 */ +	struct Snapshot { +		ComponentManager::Snapshot components; +		SystemManager::Snapshot systems; +	}; +	//! Before playback snapshot +	Snapshot playback; + +	//! Snapshot state and disable systems during playback +	void playback_begin(); +	//! Restore state from before \c playback_begin() +	void playback_end(); +}; + +} // namespace crepe diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp index d6b2ca1..58055d6 100644 --- a/src/crepe/system/ScriptSystem.cpp +++ b/src/crepe/system/ScriptSystem.cpp @@ -1,16 +1,18 @@  #include "../api/BehaviorScript.h"  #include "../api/Script.h"  #include "../manager/ComponentManager.h" +#include "../util/dbg.h"  #include "ScriptSystem.h"  using namespace std;  using namespace crepe; -void ScriptSystem::update() { +void ScriptSystem::fixed_update() {  	dbg_trace();  	ComponentManager & mgr = this->mediator.component_manager; +	LoopTimerManager & timer = this->mediator.loop_timer;  	RefVector<BehaviorScript> behavior_scripts = mgr.get_components_by_type<BehaviorScript>();  	for (BehaviorScript & behavior_script : behavior_scripts) { @@ -23,6 +25,8 @@ void ScriptSystem::update() {  			script->init();  			script->initialized = true;  		} -		script->update(); + +		duration_t delta_time = timer.get_delta_time(); +		script->update(delta_time);  	}  } diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h index 3db1b1e..612c2ae 100644 --- a/src/crepe/system/ScriptSystem.h +++ b/src/crepe/system/ScriptSystem.h @@ -22,7 +22,7 @@ public:  	 * method. It also calls Script::init() if this has not been done before on  	 * the \c BehaviorScript instance.  	 */ -	void update() override; +	void fixed_update() override;  };  } // namespace crepe diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h index 063dfbf..e2ce7eb 100644 --- a/src/crepe/system/System.h +++ b/src/crepe/system/System.h @@ -14,10 +14,12 @@ class ComponentManager;   */  class System {  public: -	/** -	 * \brief Process all components this system is responsible for. -	 */ -	virtual void update() = 0; +	//! Code that runs in the fixed loop +	virtual void fixed_update() {}; +	//! Code that runs in the frame loop +	virtual void frame_update() {}; +	//! Indicates that the update functions of this system should be run +	bool active = true;  public:  	System(const Mediator & m); diff --git a/src/crepe/util/Log.cpp b/src/crepe/util/Log.cpp index 84d80a8..ce25a1d 100644 --- a/src/crepe/util/Log.cpp +++ b/src/crepe/util/Log.cpp @@ -4,6 +4,7 @@  #include "../api/Config.h"  #include "Log.h" +#include "LogColor.h"  using namespace crepe;  using namespace std; diff --git a/src/crepe/util/Log.h b/src/crepe/util/Log.h index fc0bb3a..b43fe30 100644 --- a/src/crepe/util/Log.h +++ b/src/crepe/util/Log.h @@ -2,27 +2,6 @@  #include <format> -// allow user to disable debug macros -#ifndef CREPE_DISABLE_MACROS - -#include "LogColor.h" - -// utility macros -#define _crepe_logf_here(level, fmt, ...) \ -	crepe::Log::logf(level, "{}" fmt, \ -					 crepe::LogColor().fg_white(false).str(std::format( \ -						 "{} ({}:{})", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__)), \ -					 __VA_ARGS__) - -// very illegal global function-style macros -// NOLINTBEGIN -#define dbg_logf(fmt, ...) _crepe_logf_here(crepe::Log::Level::DEBUG, ": " fmt, __VA_ARGS__) -#define dbg_log(str) _crepe_logf_here(crepe::Log::Level::DEBUG, ": {}", str) -#define dbg_trace() _crepe_logf_here(crepe::Log::Level::TRACE, "", "") -// NOLINTEND - -#endif -  namespace crepe {  /** diff --git a/src/crepe/util/dbg.h b/src/crepe/util/dbg.h new file mode 100644 index 0000000..c7283ee --- /dev/null +++ b/src/crepe/util/dbg.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Log.h" +#include "LogColor.h" + +// utility macros +#define _crepe_logf_here(level, fmt, ...) \ +	crepe::Log::logf(level, "{}" fmt, \ +					 crepe::LogColor().fg_white(false).str(std::format( \ +						 "{} ({}:{})", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__)), \ +					 __VA_ARGS__) + +// very illegal global function-style macros +// NOLINTBEGIN +#define dbg_logf(fmt, ...) _crepe_logf_here(crepe::Log::Level::DEBUG, ": " fmt, __VA_ARGS__) +#define dbg_log(str) _crepe_logf_here(crepe::Log::Level::DEBUG, ": {}", str) +#define dbg_trace() _crepe_logf_here(crepe::Log::Level::TRACE, "", "") +// NOLINTEND |