diff options
| author | JAROWMR <jarorutjes07@gmail.com> | 2024-11-15 12:56:45 +0100 | 
|---|---|---|
| committer | JAROWMR <jarorutjes07@gmail.com> | 2024-11-15 12:56:45 +0100 | 
| commit | de1c6053033483c7e824f8018d75be6af424d14d (patch) | |
| tree | 8904c58c52501da6e647661c20384f68951df58d | |
| parent | 355b0178eaaf3602b00975adb8f56e2141dcd982 (diff) | |
| parent | be1e97bc7a494963ab1567492fafcda99e36f683 (diff) | |
merge with master
44 files changed, 1203 insertions, 269 deletions
| diff --git a/.clang-format b/.clang-format index 3ae6c28..8ce4033 100644 --- a/.clang-format +++ b/.clang-format @@ -23,6 +23,7 @@ ReflowComments: false  AlignEscapedNewlines: DontAlign  BreakBeforeBinaryOperators: All  AlwaysBreakTemplateDeclarations: Yes +PackConstructorInitializers: CurrentLine  ...  # vim: ft=yaml @@ -15,4 +15,4 @@ compile_commands.json  CTestTestfile.cmake  _deps  CMakeUserPresets.json - +compile.sh diff --git a/contributing.md b/contributing.md index a80f2b4..3090727 100644 --- a/contributing.md +++ b/contributing.md @@ -54,8 +54,6 @@ that you can click on to open them.    - Implementation details (if applicable)  - Header files (`.h`) contain the following types of comments:    - [Usage documentation](#documentation) (required) -    > [!NOTE] -    > Constructors/destructors aren't required to have a `\brief` description    - Implementation details (if they affect the header)    - Design/data structure decisions (if applicable)  - <details><summary> @@ -436,7 +434,8 @@ that you can click on to open them.    ```    </td></tr></table></details>  - <details><summary> -  Variables that are being moved always use the fully qualified <code>std::move</code> +  Variables that are being moved always use the fully qualified +  <code>std::move</code>    </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td>    ```cpp @@ -666,6 +665,19 @@ that you can click on to open them.    friend class ComponentManager;    ```    </td></tr></table></details> +- <details><summary> +  Do not <i>pick</i> fixed-width integer types (unless required) +  </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> + +  ```cpp +  unsigned long long foo(); +  ``` +  </td><td> + +  ```cpp +  uint64_t foo(); +  ``` +  </td></tr></table></details>  ## CMakeLists-specific @@ -716,6 +728,67 @@ that you can click on to open them.    void foo();    ```    </td></tr></table></details> +- <details><summary> +  The default constructor and destructor aren't required to have a +  <code>\brief</code> description +  </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> + +  ```cpp +  Foo(); +  virtual ~Foo(); +  ``` +  </td><td> + +  ```cpp +  //! Create instance of Foo +  Foo(); +  //! Destroy instance of Foo +  virtual ~Foo(); +  ``` +  </td></tr></table></details> +- <details><summary> +  Parameter direction shouldn't be specified using Doxygen +  </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> + +  ```cpp +  /** +   * \param bar  Reference to Bar +   */ +  void foo(const Bar & bar); +  ``` +  </td><td> + +  ```cpp +  /** +   * \param[in] bar  Reference to Bar +   */ +  void foo(const Bar & bar); +  ``` +  </td></tr></table></details> +- <details><summary> +  Deleted functions shouldn't have Doxygen comments +  </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> + +  ```cpp +  // singleton +  Foo(const Foo &) = delete; +  Foo(Foo &&) = delete; +  Foo & operator=(const Foo &) = delete; +  Foo & operator=(Foo &&) = delete; +  ``` +  </td><td> + +  ```cpp +  //! Deleted copy constructor +  Foo(const Foo &) = delete; +  //! Deleted move constructor +  Foo(Foo &&) = delete; +  //! Deleted copy assignment operator +  Foo & operator=(const Foo &) = delete; +  //! Deleted move assignment operator +  Foo & operator=(Foo &&) = delete; +  ``` +  </td></tr></table></details>  # Libraries diff --git a/mwe/ecs-homemade/src/Components.cpp b/mwe/ecs-homemade/src/Components.cpp index de8753e..0d62bd5 100644 --- a/mwe/ecs-homemade/src/Components.cpp +++ b/mwe/ecs-homemade/src/Components.cpp @@ -6,7 +6,9 @@ Component::Component() : mActive(true) {}  Sprite::Sprite(std::string path) : mPath(path) {}  Rigidbody::Rigidbody(int mass, int gravityScale, int bodyType) -	: mMass(mass), mGravityScale(gravityScale), mBodyType(bodyType) {} +	: mMass(mass), +	  mGravityScale(gravityScale), +	  mBodyType(bodyType) {}  Colider::Colider(int size) : mSize(size) {} diff --git a/mwe/ecs-homemade/src/GameObjectMax.cpp b/mwe/ecs-homemade/src/GameObjectMax.cpp index b0c5af7..753c8e2 100644 --- a/mwe/ecs-homemade/src/GameObjectMax.cpp +++ b/mwe/ecs-homemade/src/GameObjectMax.cpp @@ -4,4 +4,8 @@  GameObject::GameObject(std::uint32_t id, std::string name, std::string tag,  					   int layer) -	: mId(id), mName(name), mTag(tag), mActive(true), mLayer(layer) {} +	: mId(id), +	  mName(name), +	  mTag(tag), +	  mActive(true), +	  mLayer(layer) {} diff --git a/mwe/ecs-memory-efficient/inc/ContiguousContainer.hpp b/mwe/ecs-memory-efficient/inc/ContiguousContainer.hpp index 408d5aa..ff8fde4 100644 --- a/mwe/ecs-memory-efficient/inc/ContiguousContainer.hpp +++ b/mwe/ecs-memory-efficient/inc/ContiguousContainer.hpp @@ -1,5 +1,6 @@  template <typename T> -ContiguousContainer<T>::ContiguousContainer() : mSize(0), mCapacity(10) { +ContiguousContainer<T>::ContiguousContainer() : mSize(0), +												mCapacity(10) {  	// Allocate memory for 10 objects initially  	mData = static_cast<T *>(malloc(mCapacity * sizeof(T)));  	if (!mData) { diff --git a/mwe/ecs-memory-efficient/src/Components.cpp b/mwe/ecs-memory-efficient/src/Components.cpp index c8347b3..2ec8609 100644 --- a/mwe/ecs-memory-efficient/src/Components.cpp +++ b/mwe/ecs-memory-efficient/src/Components.cpp @@ -6,6 +6,8 @@ Component::Component() : mActive(true) {}  Sprite::Sprite(std::string path) : mPath(path) {}  Rigidbody::Rigidbody(int mass, int gravityScale, int bodyType) -	: mMass(mass), mGravityScale(gravityScale), mBodyType(bodyType) {} +	: mMass(mass), +	  mGravityScale(gravityScale), +	  mBodyType(bodyType) {}  Colider::Colider(int size) : mSize(size) {} diff --git a/mwe/ecs-memory-efficient/src/GameObjectMax.cpp b/mwe/ecs-memory-efficient/src/GameObjectMax.cpp index b0c5af7..753c8e2 100644 --- a/mwe/ecs-memory-efficient/src/GameObjectMax.cpp +++ b/mwe/ecs-memory-efficient/src/GameObjectMax.cpp @@ -4,4 +4,8 @@  GameObject::GameObject(std::uint32_t id, std::string name, std::string tag,  					   int layer) -	: mId(id), mName(name), mTag(tag), mActive(true), mLayer(layer) {} +	: mId(id), +	  mName(name), +	  mTag(tag), +	  mActive(true), +	  mLayer(layer) {} diff --git a/mwe/events/include/customTypes.h b/mwe/events/include/customTypes.h index a5d8dc9..415b989 100644 --- a/mwe/events/include/customTypes.h +++ b/mwe/events/include/customTypes.h @@ -33,6 +33,8 @@ struct Collision {  	// Constructor to initialize a Collision  	Collision(int idA, int idB, const Vector2 & point, const Vector2 & normal,  			  float depth) -		: objectIdA(idA), objectIdB(idB), contactPoint(point), +		: objectIdA(idA), +		  objectIdB(idB), +		  contactPoint(point),  		  contactNormal(normal) {}  }; diff --git a/mwe/events/include/eventHandler.h b/mwe/events/include/eventHandler.h index aa8f63b..3a83b15 100644 --- a/mwe/events/include/eventHandler.h +++ b/mwe/events/include/eventHandler.h @@ -24,7 +24,8 @@ class EventHandlerWrapper : public IEventHandlerWrapper {  public:  	explicit EventHandlerWrapper(const EventHandler<EventType> & handler,  								 const bool destroyOnSuccess = false) -		: m_handler(handler), m_handlerType(m_handler.target_type().name()), +		: m_handler(handler), +		  m_handlerType(m_handler.target_type().name()),  		  m_destroyOnSuccess(destroyOnSuccess) {  		// std::cout << m_handlerType << std::endl;  	} diff --git a/mwe/events/src/event.cpp b/mwe/events/src/event.cpp index 0c9f3ed..8ffa0b1 100644 --- a/mwe/events/src/event.cpp +++ b/mwe/events/src/event.cpp @@ -23,7 +23,9 @@ void Event::markHandled() { isHandled = true; }  // KeyPressedEvent class methods  KeyPressedEvent::KeyPressedEvent(int keycode) -	: Event("KeyPressedEvent"), key(keycode), repeatCount(0) {} +	: Event("KeyPressedEvent"), +	  key(keycode), +	  repeatCount(0) {}  Keycode KeyPressedEvent::getKeyCode() const { return key; } @@ -31,13 +33,16 @@ int KeyPressedEvent::getRepeatCount() const { return repeatCount; }  // KeyReleasedEvent class methods  KeyReleasedEvent::KeyReleasedEvent(int keycode) -	: Event("KeyReleasedEvent"), key(keycode) {} +	: Event("KeyReleasedEvent"), +	  key(keycode) {}  Keycode KeyReleasedEvent::getKeyCode() const { return key; }  // MousePressedEvent class methods  MousePressedEvent::MousePressedEvent(int mouseX, int mouseY) -	: Event("MousePressedEvent"), mouseX(mouseX), mouseY(mouseY) {} +	: Event("MousePressedEvent"), +	  mouseX(mouseX), +	  mouseY(mouseY) {}  std::pair<int, int> MousePressedEvent::getMousePosition() const {  	return {mouseX, mouseY}; @@ -45,26 +50,36 @@ std::pair<int, int> MousePressedEvent::getMousePosition() const {  //Collision event  CollisionEvent::CollisionEvent(Collision collision) -	: collisionData(collision), Event("CollisionEvent") {} +	: collisionData(collision), +	  Event("CollisionEvent") {}  Collision CollisionEvent::getCollisionData() const {  	return this->collisionData;  }  TextSubmitEvent::TextSubmitEvent(std::string text) -	: text(text), Event("TextSubmitEvent") {} +	: text(text), +	  Event("TextSubmitEvent") {}  std::string TextSubmitEvent::getText() const { return this->text; }  MouseReleasedEvent::MouseReleasedEvent(int x, int y, MouseButton button) -	: mouseX(x), mouseY(y), button(button), Event("MouseReleased") {} +	: mouseX(x), +	  mouseY(y), +	  button(button), +	  Event("MouseReleased") {}  std::pair<int, int> MouseReleasedEvent::getMousePosition() const {  	return {mouseX, mouseY};  }  MouseClickEvent::MouseClickEvent(int x, int y, MouseButton button) -	: mouseX(x), mouseY(y), button(button), Event("MouseClickEvent") {} +	: mouseX(x), +	  mouseY(y), +	  button(button), +	  Event("MouseClickEvent") {}  MouseMovedEvent::MouseMovedEvent(int x, int y) -	: mouseX(x), mouseY(y), Event("MouseMovedEvent") {} +	: mouseX(x), +	  mouseY(y), +	  Event("MouseMovedEvent") {}  std::pair<int, int> MouseClickEvent::getMousePosition() const {  	return {mouseX, mouseY};  } diff --git a/mwe/events/src/main.cpp b/mwe/events/src/main.cpp index d49cf74..f4e7390 100644 --- a/mwe/events/src/main.cpp +++ b/mwe/events/src/main.cpp @@ -11,7 +11,9 @@  class PlayerDamagedEvent : public Event {  public:  	PlayerDamagedEvent(int damage, int playerID) -		: Event("PlayerDamaged"), damage(damage), playerID(playerID) {} +		: Event("PlayerDamaged"), +		  damage(damage), +		  playerID(playerID) {}  	REGISTER_EVENT_TYPE(PlayerDamagedEvent); diff --git a/mwe/events/src/uiObject.cpp b/mwe/events/src/uiObject.cpp index 8405469..947d1a2 100644 --- a/mwe/events/src/uiObject.cpp +++ b/mwe/events/src/uiObject.cpp @@ -7,7 +7,9 @@ UIObject::UIObject(int width, int height) : width(width), height(height) {}  Button::Button(int width, int height) : UIObject(width, height) {}  Text::Text(int width, int height) -	: UIObject(width, height), size(12), font(nullptr), +	: UIObject(width, height), +	  size(12), +	  font(nullptr),  	  color{255, 255, 255} { // Default size and color  	alignment.horizontal = Alignment::Horizontal::CENTER;  	alignment.vertical = Alignment::Vertical::MIDDLE; @@ -15,8 +17,13 @@ Text::Text(int width, int height)  }  TextInput::TextInput(int width, int height) -	: UIObject(width, height), textBuffer(""), placeholder(""), isActive(false), -	  textColor{255, 255, 255}, backgroundColor{0, 0, 0}, maxLength(100), +	: UIObject(width, height), +	  textBuffer(""), +	  placeholder(""), +	  isActive(false), +	  textColor{255, 255, 255}, +	  backgroundColor{0, 0, 0}, +	  maxLength(100),  	  font(nullptr) {  	alignment.horizontal = Alignment::Horizontal::LEFT;  	alignment.vertical = Alignment::Vertical::TOP; diff --git a/mwe/gameloop/src/gameObject.cpp b/mwe/gameloop/src/gameObject.cpp index 78217c4..b33dc78 100644 --- a/mwe/gameloop/src/gameObject.cpp +++ b/mwe/gameloop/src/gameObject.cpp @@ -26,5 +26,10 @@ void GameObject::setVelY(float value) { velY = value; }  GameObject::GameObject(std::string name, float x, float y, float width,  					   float height, float velX, float velY) -	: name(name), x(x), y(y), width(width), height(height), velX(velX), +	: name(name), +	  x(x), +	  y(y), +	  width(width), +	  height(height), +	  velX(velX),  	  velY(velY) {} diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp index 4810e80..582edf4 100644 --- a/src/crepe/Particle.cpp +++ b/src/crepe/Particle.cpp @@ -2,19 +2,32 @@  using namespace crepe; -Particle::Particle() { this->active = false; } - -void Particle::reset(float lifespan, Position position, Position velocity) { +void Particle::reset(uint32_t lifespan, const Vector2 & position, +					 const Vector2 & velocity, double angle) { +	// Initialize the particle state  	this->time_in_life = 0;  	this->lifespan = lifespan;  	this->position = position;  	this->velocity = velocity; +	this->angle = angle;  	this->active = true; +	// Reset force accumulation +	this->force_over_time = {0, 0}; +} + +void Particle::update() { +	// Deactivate particle if it has exceeded its lifespan +	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;  } -void Particle::update(float deltaTime) { -	time_in_life += deltaTime; -	position.x += velocity.x * deltaTime; -	position.y += velocity.y * deltaTime; -	if (time_in_life >= lifespan) this->active = false; +void Particle::stop_movement() { +	// Reset velocity to halt movement +	this->velocity = {0, 0};  } diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h index 21e691d..3eaebc3 100644 --- a/src/crepe/Particle.h +++ b/src/crepe/Particle.h @@ -1,22 +1,64 @@  #pragma once -#include "Position.h" +#include <cstdint> + +#include "api/Vector2.h"  namespace crepe { +/** + * \brief Represents a particle in the particle emitter. + * + * This class stores information about a single particle, including its position, + * velocity, lifespan, and other properties. Particles can be updated over time + * to simulate movement and can also be reset or stopped. + */  class Particle { +	// TODO: add friend particleSsytem and rendersystem. Unit test will fail. +  public: -	Position position; -	// FIXME: `Position` is an awkward name for a 2D vector. See FIXME comment in -	// api/Transform.h for fix proposal. -	Position velocity; -	float lifespan; -	bool active; +	//! Position of the particle in 2D space. +	Vector2 position; +	//! Velocity vector indicating the speed and direction of the particle. +	Vector2 velocity; +	//! Accumulated force affecting the particle over time. +	Vector2 force_over_time; +	//! Total lifespan of the particle in milliseconds. +	uint32_t 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; +	//! The angle at which the particle is oriented or moving. +	double angle = 0; -	Particle(); -	void reset(float lifespan, Position position, Position velocity); -	void update(float deltaTime); -	float time_in_life; +	/** +	 * \brief Resets the particle with new properties. +	 * +	 * This method initializes the particle with a specific lifespan, position, +	 * velocity, and angle, marking it as active and resetting its life counter. +	 * +	 * \param lifespan  The lifespan of the particle in amount of updates. +	 * \param position  The starting position of the particle. +	 * \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 Vector2 & position, +			   const Vector2 & velocity, double 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. +	 */ +	void update(); +	/** +	 * \brief Stops the particle's movement. +	 * +	 * Sets the particle's velocity to zero, effectively halting any further +	 * movement. +	 */ +	void stop_movement();  };  } // namespace crepe diff --git a/src/crepe/ValueBroker.hpp b/src/crepe/ValueBroker.hpp index 927142f..5c3bed9 100644 --- a/src/crepe/ValueBroker.hpp +++ b/src/crepe/ValueBroker.hpp @@ -6,7 +6,8 @@ namespace crepe {  template <typename T>  ValueBroker<T>::ValueBroker(const setter_t & setter, const getter_t & getter) -	: setter(setter), getter(getter) {} +	: setter(setter), +	  getter(getter) {}  template <typename T>  const T & ValueBroker<T>::get() { diff --git a/src/crepe/api/Animator.cpp b/src/crepe/api/Animator.cpp index 8b396af..58fee2a 100644 --- a/src/crepe/api/Animator.cpp +++ b/src/crepe/api/Animator.cpp @@ -10,7 +10,10 @@  using namespace crepe;  Animator::Animator(uint32_t id, Sprite & ss, int row, int col, int col_animator) -	: Component(id), spritesheet(ss), row(row), col(col) { +	: Component(id), +	  spritesheet(ss), +	  row(row), +	  col(col) {  	dbg_trace();  	animator_rect = spritesheet.sprite_rect; diff --git a/src/crepe/api/AssetManager.h b/src/crepe/api/AssetManager.h index fefbed9..86a9902 100644 --- a/src/crepe/api/AssetManager.h +++ b/src/crepe/api/AssetManager.h @@ -7,9 +7,19 @@  namespace crepe { +/** + * \brief The AssetManager is responsible for storing and managing assets over + * multiple scenes. + *  + * The AssetManager ensures that assets are loaded once and can be accessed + * across different scenes. It caches assets to avoid reloading them every time + * a scene is loaded. Assets are retained in memory until the AssetManager is + * destroyed, at which point the cached assets are cleared. + */  class AssetManager {  private: +	//! A cache that holds all the assets, accessible by their file path, over multiple scenes.  	std::unordered_map<std::string, std::any> asset_cache;  private: @@ -22,12 +32,32 @@ public:  	AssetManager & operator=(const AssetManager &) = delete;  	AssetManager & operator=(AssetManager &&) = delete; +	/** +	 * \brief Retrieves the singleton instance of the AssetManager. +	 * +	 * \return A reference to the single instance of the AssetManager. +	 */  	static AssetManager & get_instance();  public: -	template <typename asset> -	std::shared_ptr<asset> cache(const std::string & file_path, -								 bool reload = false); +	/** +	 * \brief Caches an asset by loading it from the given file path. +	 * +	 * \param file_path The path to the asset file to load. +	 * \param reload If true, the asset will be reloaded from the file, even if +	 * it is already cached. +	 * \tparam T The type of asset to cache (e.g., texture, sound, etc.). +	 *  +	 * \return A shared pointer to the cached asset. +	 *  +	 * This template function caches the asset at the given file path. If the +	 * asset is already cached and `reload` is false, the existing cached version +	 * will be returned. Otherwise, the asset will be reloaded and added to the +	 * cache. +	 */ +	template <typename T> +	std::shared_ptr<T> cache(const std::string & file_path, +							 bool reload = false);  };  } // namespace crepe diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 6915074..de1b4b3 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -18,8 +18,13 @@ target_sources(crepe PUBLIC  	Vector2.cpp  	Camera.cpp  	Animator.cpp +<<<<<<< HEAD  	BoxCollider.cpp  	CircleCollider.cpp +======= +	LoopManager.cpp +	LoopTimer.cpp +>>>>>>> be1e97bc7a494963ab1567492fafcda99e36f683  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -44,6 +49,11 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	SceneManager.hpp  	Camera.h  	Animator.h +<<<<<<< HEAD  	BoxCollider.h  	CircleCollider.h +======= +	LoopManager.h +	LoopTimer.h +>>>>>>> be1e97bc7a494963ab1567492fafcda99e36f683  ) diff --git a/src/crepe/api/Camera.cpp b/src/crepe/api/Camera.cpp index 820a6a8..6355a03 100644 --- a/src/crepe/api/Camera.cpp +++ b/src/crepe/api/Camera.cpp @@ -10,7 +10,8 @@  using namespace crepe;  Camera::Camera(uint32_t id, const Color & bg_color) -	: Component(id), bg_color(bg_color) { +	: Component(id), +	  bg_color(bg_color) {  	dbg_trace();  } diff --git a/src/crepe/api/CircleCollider.h b/src/crepe/api/CircleCollider.h index 29a9c1e..e4f0452 100644 --- a/src/crepe/api/CircleCollider.h +++ b/src/crepe/api/CircleCollider.h @@ -8,8 +8,15 @@ namespace crepe {  class CircleCollider : public Collider {  public: +<<<<<<< HEAD  	CircleCollider(game_object_id_t game_object_id,Vector2 offset, int radius);  	double radius; +======= +	CircleCollider(game_object_id_t game_object_id, int radius) +		: Collider(game_object_id), +		  radius(radius) {} +	int radius; +>>>>>>> be1e97bc7a494963ab1567492fafcda99e36f683  };  } // namespace crepe diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp new file mode 100644 index 0000000..2e9823f --- /dev/null +++ b/src/crepe/api/LoopManager.cpp @@ -0,0 +1,55 @@ + +#include "../facade/SDLContext.h" +#include "../system/RenderSystem.h" +#include "../system/ScriptSystem.h" + +#include "LoopManager.h" +#include "LoopTimer.h" + +using namespace crepe; + +LoopManager::LoopManager() {} +void LoopManager::process_input() { +	SDLContext::get_instance().handle_events(this->game_running); +} +void LoopManager::start() { +	this->setup(); +	this->loop(); +} +void LoopManager::set_running(bool running) { this->game_running = running; } + +void LoopManager::fixed_update() {} + +void LoopManager::loop() { +	LoopTimer & timer = LoopTimer::get_instance(); +	timer.start(); + +	while (game_running) { +		timer.update(); + +		while (timer.get_lag() >= timer.get_fixed_delta_time()) { +			this->process_input(); +			this->fixed_update(); +			timer.advance_fixed_update(); +		} + +		this->update(); +		this->render(); + +		timer.enforce_frame_rate(); +	} +} + +void LoopManager::setup() { +	this->game_running = true; +	LoopTimer::get_instance().start(); +	LoopTimer::get_instance().set_fps(60); +} + +void LoopManager::render() { +	if (this->game_running) { +		RenderSystem::get_instance().update(); +	} +} + +void LoopManager::update() { LoopTimer & timer = LoopTimer::get_instance(); } diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h new file mode 100644 index 0000000..2f03193 --- /dev/null +++ b/src/crepe/api/LoopManager.h @@ -0,0 +1,79 @@ +#pragma once + +#include <memory> + +class RenderSystem; +class SDLContext; +class LoopTimer; +class ScriptSystem; +class SoundSystem; +class ParticleSystem; +class PhysicsSystem; +class AnimatorSystem; +class CollisionSystem; +namespace crepe { + +class LoopManager { +public: +	void start(); +	LoopManager(); + +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 Function for handling input-related system calls. +	 * +	 * Processes user inputs from keyboard and mouse. +	 */ +	void process_input(); + +	/** +	 * \brief Per-frame update. +	 * +	 * Updates the game state based on the elapsed time since the last frame. +	 */ +	void update(); + +	/** +	 * \brief Late update which is called after update(). +	 * +	 * This function can be used for final adjustments before rendering. +	 */ +	void late_update(); + +	/** +	 * \brief Fixed update executed at a fixed rate. +	 * +	 * This function updates physics and game logic based on LoopTimer's fixed_delta_time. +	 */ +	void fixed_update(); +	/** +	 * \brief Set game running variable +	 * +	 * \param running running (false = game shutdown, true = game running) +	 */ +	void set_running(bool running); +	/** +	 * \brief Function for executing render-related systems. +	 * +	 * Renders the current state of the game to the screen. +	 */ +	void render(); + +	bool game_running = false; +	//#TODO add system instances +}; + +} // namespace crepe diff --git a/src/crepe/api/LoopTimer.cpp b/src/crepe/api/LoopTimer.cpp new file mode 100644 index 0000000..8f09e41 --- /dev/null +++ b/src/crepe/api/LoopTimer.cpp @@ -0,0 +1,86 @@ +#include <chrono> + +#include "../facade/SDLContext.h" +#include "../util/log.h" + +#include "LoopTimer.h" + +using namespace crepe; + +LoopTimer::LoopTimer() { dbg_trace(); } + +LoopTimer & LoopTimer::get_instance() { +	static LoopTimer instance; +	return instance; +} + +void LoopTimer::start() { +	this->last_frame_time = std::chrono::steady_clock::now(); +	this->elapsed_time = std::chrono::milliseconds(0); +	this->elapsed_fixed_time = std::chrono::milliseconds(0); +	this->delta_time = std::chrono::milliseconds(0); +} + +void LoopTimer::update() { +	auto current_frame_time = std::chrono::steady_clock::now(); +	// Convert to duration in seconds for delta time +	this->delta_time +		= std::chrono::duration_cast<std::chrono::duration<double>>( +			current_frame_time - last_frame_time); + +	if (this->delta_time > this->maximum_delta_time) { +		this->delta_time = this->maximum_delta_time; +	} + +	this->delta_time *= this->game_scale; +	this->elapsed_time += this->delta_time; +	this->last_frame_time = current_frame_time; +} + +double LoopTimer::get_delta_time() const { return this->delta_time.count(); } + +double LoopTimer::get_current_time() const { +	return this->elapsed_time.count(); +} + +void LoopTimer::advance_fixed_update() { +	this->elapsed_fixed_time += this->fixed_delta_time; +} + +double LoopTimer::get_fixed_delta_time() const { +	return this->fixed_delta_time.count(); +} + +void LoopTimer::set_fps(int fps) { +	this->fps = fps; +	// target time per frame in seconds +	this->frame_target_time = std::chrono::seconds(1) / fps; +} + +int LoopTimer::get_fps() const { return this->fps; } + +void LoopTimer::set_game_scale(double value) { this->game_scale = value; } + +double LoopTimer::get_game_scale() const { return this->game_scale; } +void LoopTimer::enforce_frame_rate() { +	std::chrono::steady_clock::time_point current_frame_time +		= std::chrono::steady_clock::now(); +	std::chrono::milliseconds frame_duration +		= std::chrono::duration_cast<std::chrono::milliseconds>( +			current_frame_time - this->last_frame_time); + +	if (frame_duration < this->frame_target_time) { +		std::chrono::milliseconds delay_time +			= std::chrono::duration_cast<std::chrono::milliseconds>( +				this->frame_target_time - frame_duration); +		if (delay_time.count() > 0) { +			SDLContext::get_instance().delay(delay_time.count()); +		} +	} + +	this->last_frame_time = current_frame_time; +} + +double LoopTimer::get_lag() const { +	return (this->elapsed_time - this->elapsed_fixed_time).count(); +} diff --git a/src/crepe/api/LoopTimer.h b/src/crepe/api/LoopTimer.h new file mode 100644 index 0000000..85687be --- /dev/null +++ b/src/crepe/api/LoopTimer.h @@ -0,0 +1,147 @@ +#pragma once + +#include <chrono> +#include <cstdint> + +namespace crepe { + +class LoopTimer { +public: +	/** +     * \brief Get the singleton instance of LoopTimer. +     * +     * \return A reference to the LoopTimer instance. +     */ +	static LoopTimer & get_instance(); + +	/** +     * \brief Get the current delta time for the current frame. +     * +     * \return Delta time in seconds since the last frame. +     */ +	double get_delta_time() const; + +	/** +     * \brief Get the current game time. +     * +     * \note The current game time may vary from real-world elapsed time. +     * It is the cumulative sum of each frame's delta time. +     * +     * \return Elapsed game time in seconds. +     */ +	double get_current_time() const; + +	/** +     * \brief Set the target frames per second (FPS). +     * +     * \param fps The desired frames rendered per second. +     */ +	void set_fps(int fps); + +	/** +     * \brief Get the current frames per second (FPS). +     * +     * \return Current FPS. +     */ +	int get_fps() const; + +	/** +     * \brief Get the current game scale. +     * +     * \return The current game scale, where 0 = paused, 1 = normal speed, and values > 1 speed up the game. +     */ +	double get_game_scale() const; + +	/** +     * \brief Set the game scale. +     * +     * \param game_scale The desired game scale (0 = pause, 1 = normal speed, > 1 = speed up). +     */ +	void set_game_scale(double game_scale); + +private: +	friend class LoopManager; + +	/** +     * \brief Start the loop timer. +     * +     * Initializes the timer to begin tracking frame times. +     */ +	void start(); + +	/** +     * \brief Enforce the frame rate limit. +     * +     * Ensures that the game loop does not exceed the target FPS by delaying +     * frame updates as necessary. +     */ +	void enforce_frame_rate(); + +	/** +     * \brief Get the fixed delta time for consistent updates. +     * +     * Fixed delta time is used for operations that require uniform time steps, +     * such as physics calculations. +     * +     * \return Fixed delta time in seconds. +     */ +	double get_fixed_delta_time() const; + +	/** +     * \brief Get the accumulated lag in the game loop. +     * +     * Lag represents the difference between the target frame time and the +     * actual frame time, useful for managing fixed update intervals. +     * +     * \return Accumulated lag in seconds. +     */ +	double get_lag() const; + +	/** +     * \brief Construct a new LoopTimer object. +     * +     * Private constructor for singleton pattern to restrict instantiation +     * outside the class. +     */ +	LoopTimer(); + +	/** +     * \brief Update the timer to the current frame. +     * +     * Calculates and updates the delta time for the current frame and adds it to +     * the cumulative game time. +     */ +	void update(); + +	/** +     * \brief Advance the game loop by a fixed update interval. +     * +     * This method progresses the game state by a consistent, fixed time step, +     * allowing for stable updates independent of frame rate fluctuations. +     */ +	void advance_fixed_update(); + +private: +	//! Current frames per second +	int fps = 50; +	//! Current game scale +	double game_scale = 1; +	//! Maximum delta time in seconds to avoid large jumps +	std::chrono::duration<double> maximum_delta_time{0.25}; +	//! Delta time for the current frame in seconds +	std::chrono::duration<double> delta_time{0.0}; +	//! Target time per frame in seconds +	std::chrono::duration<double> frame_target_time +		= std::chrono::seconds(1) / fps; +	//! Fixed delta time for fixed updates in seconds +	std::chrono::duration<double> fixed_delta_time +		= std::chrono::seconds(1) / 50; +	//! Total elapsed game time in seconds +	std::chrono::duration<double> elapsed_time{0.0}; +	//! Total elapsed time for fixed updates in seconds +	std::chrono::duration<double> elapsed_fixed_time{0.0}; +	//! Time of the last frame +	std::chrono::steady_clock::time_point last_frame_time; +}; + +} // namespace crepe diff --git a/src/crepe/api/Metadata.cpp b/src/crepe/api/Metadata.cpp index 76f11d7..d421de5 100644 --- a/src/crepe/api/Metadata.cpp +++ b/src/crepe/api/Metadata.cpp @@ -4,4 +4,6 @@ using namespace crepe;  using namespace std;  Metadata::Metadata(game_object_id_t id, const string & name, const string & tag) -	: Component(id), name(name), tag(tag) {} +	: Component(id), +	  name(name), +	  tag(tag) {} diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp index 3b2e2f2..35f960d 100644 --- a/src/crepe/api/ParticleEmitter.cpp +++ b/src/crepe/api/ParticleEmitter.cpp @@ -1,36 +1,12 @@ -#include <ctime> -#include <iostream> - -#include "Particle.h"  #include "ParticleEmitter.h"  using namespace crepe; -ParticleEmitter::ParticleEmitter(game_object_id_t id, uint32_t max_particles, -								 uint32_t emission_rate, uint32_t speed, -								 uint32_t speed_offset, uint32_t angle, -								 uint32_t angleOffset, float begin_lifespan, -								 float end_lifespan) -	: Component(id), max_particles(max_particles), emission_rate(emission_rate), -	  speed(speed), speed_offset(speed_offset), position{0, 0}, -	  begin_lifespan(begin_lifespan), end_lifespan(end_lifespan) { -	std::srand( -		static_cast<uint32_t>(std::time(nullptr))); // initialize random seed -	std::cout << "Create emitter" << std::endl; -	// FIXME: Why do these expressions start with `360 +`, only to be `% 360`'d -	// right after? This does not make any sense to me. -	min_angle = (360 + angle - (angleOffset % 360)) % 360; -	max_angle = (360 + angle + (angleOffset % 360)) % 360; -	position.x = 400; // FIXME: what are these magic values? -	position.y = 400; -	for (size_t i = 0; i < max_particles; i++) { -		this->particles.emplace_back(); -	} -} - -ParticleEmitter::~ParticleEmitter() { -	std::vector<Particle>::iterator it = this->particles.begin(); -	while (it != this->particles.end()) { -		it = this->particles.erase(it); +ParticleEmitter::ParticleEmitter(game_object_id_t game_object_id, +								 const Data & data) +	: Component(game_object_id), +	  data(data) { +	for (size_t i = 0; i < this->data.max_particles; i++) { +		this->data.particles.emplace_back();  	}  } diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h index 5939723..a9e872f 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -1,42 +1,85 @@  #pragma once -#include <cstdint>  #include <vector>  #include "Component.h"  #include "Particle.h" +#include "Vector2.h"  namespace crepe { +class Sprite; + +/** + * \brief Data holder for particle emission parameters. + * + * The ParticleEmitter class stores configuration data for particle properties, + * defining the characteristics and boundaries of particle emissions. + */  class ParticleEmitter : public Component {  public: -	ParticleEmitter(game_object_id_t id, uint32_t max_particles, -					uint32_t emission_rate, uint32_t speed, -					uint32_t speed_offset, uint32_t angle, uint32_t angleOffset, -					float begin_lifespan, float end_lifespan); -	~ParticleEmitter(); - -	//! position of the emitter -	Position position; -	//! maximum number of particles -	uint32_t max_particles; -	//! rate of particle emission -	uint32_t emission_rate; -	//! base speed of the particles -	uint32_t speed; -	//! offset for random speed variation -	uint32_t speed_offset; -	//! min angle of particle emission -	uint32_t min_angle; -	//! max angle of particle emission -	uint32_t max_angle; -	//! begin Lifespan of particle (only visual) -	float begin_lifespan; -	//! begin Lifespan of particle -	float end_lifespan; - -	//! collection of particles -	std::vector<Particle> particles; +	/** +	 * \brief Defines the boundary within which particles are constrained. +	 * +	 * This structure specifies the boundary's size and offset, as well as the +	 * behavior of particles upon reaching the boundary limits. +	 */ +	struct Boundary { +		//! boundary width (midpoint is emitter location) +		double width = 0.0; +		//! boundary height (midpoint is emitter location) +		double height = 0.0; +		//! boundary offset from particle emitter location +		Vector2 offset; +		//! reset on exit or stop velocity and set max postion +		bool reset_on_exit = false; +	}; + +	/** +	 * \brief Holds parameters that control particle emission. +	 * +	 * Contains settings for the emitter’s position, particle speed, angle, lifespan, +	 * boundary, and the sprite used for rendering particles. +	 */ +	struct Data { +		//! position of the emitter +		Vector2 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; +		//! min speed of the particles +		double min_speed = 0; +		//! min speed of the particles +		double max_speed = 0; +		//! min angle of particle emission +		double 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; +		//! force over time (physics) +		Vector2 force_over_time; +		//! particle boundary +		Boundary boundary; +		//! collection of particles +		std::vector<Particle> particles; +		//! sprite reference +		const Sprite & sprite; +	}; + +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); + +public: +	//! Configuration data for particle emission settings. +	Data data;  };  } // namespace crepe diff --git a/src/crepe/api/Rigidbody.cpp b/src/crepe/api/Rigidbody.cpp index cbf1325..3bf1c5b 100644 --- a/src/crepe/api/Rigidbody.cpp +++ b/src/crepe/api/Rigidbody.cpp @@ -3,7 +3,8 @@  using namespace crepe;  crepe::Rigidbody::Rigidbody(uint32_t game_object_id, const Data & data) -	: Component(game_object_id), data(data) {} +	: Component(game_object_id), +	  data(data) {}  void crepe::Rigidbody::add_force_linear(const Vector2 & force) {  	this->data.linear_velocity += force; diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp index f9cd761..6f0433f 100644 --- a/src/crepe/api/Sprite.cpp +++ b/src/crepe/api/Sprite.cpp @@ -12,7 +12,10 @@ using namespace crepe;  Sprite::Sprite(game_object_id_t id, const shared_ptr<Texture> image,  			   const Color & color, const FlipSettings & flip) -	: Component(id), color(color), flip(flip), sprite_image(image) { +	: Component(id), +	  color(color), +	  flip(flip), +	  sprite_image(image) {  	dbg_trace();  	this->sprite_rect.w = sprite_image->get_width(); diff --git a/src/crepe/api/Transform.cpp b/src/crepe/api/Transform.cpp index a244bc5..e401120 100644 --- a/src/crepe/api/Transform.cpp +++ b/src/crepe/api/Transform.cpp @@ -6,6 +6,9 @@ using namespace crepe;  Transform::Transform(game_object_id_t id, const Vector2 & point,  					 double rotation, double scale) -	: Component(id), position(point), rotation(rotation), scale(scale) { +	: Component(id), +	  position(point), +	  rotation(rotation), +	  scale(scale) {  	dbg_trace();  } diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index c4c96e2..236bf8c 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -159,7 +159,7 @@ SDLContext::texture_from_path(const std::string & path) {  	SDL_Surface * tmp = IMG_Load(path.c_str());  	if (tmp == nullptr) { -		throw Exception("surface cannot be load from %s", path.c_str()); +		tmp = IMG_Load("../asset/texture/ERROR.png");  	}  	std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>> @@ -191,3 +191,4 @@ int SDLContext::get_height(const Texture & ctx) const {  	SDL_QueryTexture(ctx.texture.get(), NULL, NULL, NULL, &h);  	return h;  } +void SDLContext::delay(int ms) const { SDL_Delay(ms); } diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index e358c21..536dec5 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -58,12 +58,23 @@ private:  private:  	//! Will only use get_ticks  	friend class AnimatorSystem; - +	//! Will only use delay +	friend class LoopTimer;  	/**  	 * \brief Gets the current SDL ticks since the program started.  	 * \return Current ticks in milliseconds as a constant uint64_t.  	 */  	uint64_t get_ticks() const; +	/** +	 * \brief Pauses the execution for a specified duration. +	 * +	 * This function uses SDL's delay function to halt the program execution +	 * for a given number of milliseconds, allowing for frame rate control +	 * or other timing-related functionality. +	 * +	 * \param ms Duration of the delay in milliseconds. +	 */ +	void delay(int ms) const;  private:  	/** diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index 397b586..e7a3bec 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -1,62 +1,141 @@  #include <cmath> +#include <cstdlib>  #include <ctime> -#include "../ComponentManager.h" -#include "../api/ParticleEmitter.h" +#include "api/ParticleEmitter.h" +#include "api/Transform.h" +#include "api/Vector2.h" +#include "ComponentManager.h"  #include "ParticleSystem.h"  using namespace crepe; -ParticleSystem::ParticleSystem() : elapsed_time(0.0f) {} -  void ParticleSystem::update() { +	// Get all emitters  	ComponentManager & mgr = ComponentManager::get_instance();  	std::vector<std::reference_wrapper<ParticleEmitter>> emitters  		= mgr.get_components_by_type<ParticleEmitter>(); -	float delta_time = 0.10; +  	for (ParticleEmitter & emitter : emitters) { -		float update_amount = 1 / static_cast<float>(emitter.emission_rate); -		for (float i = 0; i < delta_time; i += update_amount) { -			emit_particle(emitter); +		// Get transform linked to emitter +		const Transform & transform +			= 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);  		} -		for (size_t j = 0; j < emitter.particles.size(); j++) { -			if (emitter.particles[j].active) { -				emitter.particles[j].update(delta_time); + +		// Update all particles +		for (Particle & particle : emitter.data.particles) { +			if (particle.active) { +				particle.update();  			}  		} + +		// Check if within boundary +		check_bounds(emitter, transform);  	} + +	this->update_count = (this->update_count + 1) % this->MAX_UPDATE_COUNT;  } -void ParticleSystem::emit_particle(ParticleEmitter & emitter) { -	Position initial_position = {emitter.position.x, emitter.position.y}; -	float random_angle = 0.0f; -	if (emitter.max_angle < emitter.min_angle) { -		random_angle = ((emitter.min_angle -						 + (std::rand() -							% (static_cast<uint32_t>(emitter.max_angle + 360 -													 - emitter.min_angle + 1)))) -						% 360); -	} else { -		random_angle = emitter.min_angle -					   + (std::rand() -						  % (static_cast<uint32_t>(emitter.max_angle -												   - emitter.min_angle + 1))); -	} -	float angle_in_radians = random_angle * (M_PI / 180.0f); -	float random_speed_offset = (static_cast<float>(std::rand()) / RAND_MAX) -									* (2 * emitter.speed_offset) -								- emitter.speed_offset; -	float velocity_x -		= (emitter.speed + random_speed_offset) * std::cos(angle_in_radians); -	float velocity_y -		= (emitter.speed + random_speed_offset) * std::sin(angle_in_radians); -	Position initial_velocity = {velocity_x, velocity_y}; -	for (size_t i = 0; i < emitter.particles.size(); i++) { -		if (!emitter.particles[i].active) { -			emitter.particles[i].reset(emitter.end_lifespan, initial_position, -									   initial_velocity); +void ParticleSystem::emit_particle(ParticleEmitter & emitter, +								   const Transform & transform) { +	constexpr double DEG_TO_RAD = M_PI / 180.0; + +	Vector2 initial_position = emitter.data.position + transform.position; +	double random_angle +		= generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); + +	double random_speed +		= generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); +	double angle_radians = random_angle * DEG_TO_RAD; + +	Vector2 velocity = {random_speed * std::cos(angle_radians), +						random_speed * std::sin(angle_radians)}; + +	for (Particle & particle : emitter.data.particles) { +		if (!particle.active) { +			particle.reset(emitter.data.end_lifespan, initial_position, +						   velocity, random_angle);  			break;  		}  	}  } + +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) { +	Vector2 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; + +	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; + +	for (Particle & particle : emitter.data.particles) { +		const Vector2 & position = particle.position; +		bool within_bounds = (position.x >= LEFT && position.x <= RIGHT +							  && position.y >= TOP && position.y <= BOTTOM); + +		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; +			} +		} +	} +} + +double ParticleSystem::generate_random_angle(double min_angle, +											 double 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)); +	} else { +		double angle_offset = (360 - min_angle) + max_angle; +		double random_angle = min_angle +							  + static_cast<double>( +								  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 { +	if (min_speed == max_speed) { +		return min_speed; +	} else { +		return min_speed +			   + static_cast<double>(std::rand() +									 % static_cast<int>(max_speed - min_speed)); +	} +} diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index 3ac1d3f..d7ca148 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -1,18 +1,71 @@  #pragma once -#include "../api/ParticleEmitter.h" +#include <cstdint> -namespace crepe { +#include "System.h" -class ParticleSystem { +namespace crepe { +class ParticleEmitter; +class Transform; +/** + 	* \brief ParticleSystem class responsible for managing particle emission, updates, and bounds checking. + 	*/ +class ParticleSystem : public System {  public: -	ParticleSystem(); -	void update(); +	/** +		* \brief Updates all particle emitters by emitting particles, updating particle states, and checking bounds. +		*/ +	void update() override;  private: -	void emit_particle(ParticleEmitter & emitter); //emits a new particle +	/** +		* \brief Emits a particle from the specified emitter based on its emission properties. +		*  +		* \param emitter Reference to the ParticleEmitter. +		* \param transform Const reference to the Transform component associated with the emitter. +		*/ +	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. +		*  +		* \param emitter Reference to the ParticleEmitter. +		* \param transform Const reference to the Transform component associated with the emitter. +		*/ +	void check_bounds(ParticleEmitter & emitter, const Transform & transform); -	float elapsed_time; //elapsed time since the last emission +	/** +		* \brief Generates a random angle for particle emission within the specified range. +		*  +		* \param min_angle Minimum emission angle in degrees. +		* \param max_angle Maximum emission angle in degrees. +		* \return Random angle in degrees. +		*/ +	double generate_random_angle(double min_angle, double max_angle) const; + +	/** +		* \brief Generates a random speed for particle emission within the specified range. +		*  +		* \param min_speed Minimum emission speed. +		* \param max_speed Maximum emission speed. +		* \return Random speed. +		*/ +	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;  };  } // namespace crepe diff --git a/src/crepe/system/PhysicsSystem.h b/src/crepe/system/PhysicsSystem.h index cc13b70..038c120 100644 --- a/src/crepe/system/PhysicsSystem.h +++ b/src/crepe/system/PhysicsSystem.h @@ -1,5 +1,7 @@  #pragma once +#include "System.h" +  namespace crepe {  /**   * \brief System that controls all physics @@ -7,18 +9,14 @@ namespace crepe {   * This class is a physics system that uses a rigidbody and transform   * to add physics to a game object.   */ -class PhysicsSystem { +class PhysicsSystem : public System {  public:  	/** -	 * Constructor is default -	 */ -	PhysicsSystem() = default; -	/**  	 * \brief updates the physics system.  	 *   	 * It calculates new velocties and changes the postion in the transform.  	 */ -	void update(); +	void update() override;  };  } // namespace crepe diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index d43d56c..5b7d7c2 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -28,5 +28,10 @@ add_example(proxy)  add_example(db)  add_example(ecs)  add_example(scene_manager) +<<<<<<< HEAD  add_example(collision) +======= +add_example(particles) +add_example(gameloop) +>>>>>>> be1e97bc7a494963ab1567492fafcda99e36f683 diff --git a/src/example/gameloop.cpp b/src/example/gameloop.cpp new file mode 100644 index 0000000..a676f20 --- /dev/null +++ b/src/example/gameloop.cpp @@ -0,0 +1,7 @@ +#include "crepe/api/LoopManager.h" +using namespace crepe; +int main() { +	LoopManager gameloop; +	gameloop.start(); +	return 1; +} diff --git a/src/example/particles.cpp b/src/example/particles.cpp new file mode 100644 index 0000000..6eab046 --- /dev/null +++ b/src/example/particles.cpp @@ -0,0 +1,43 @@ +#include <crepe/ComponentManager.h> +#include <crepe/api/AssetManager.h> + +#include <crepe/Component.h> +#include <crepe/api/Color.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> +#include <crepe/api/Transform.h> + +using namespace crepe; +using namespace std; + +int main(int argc, char * argv[]) { +	GameObject game_object(0, "", "", Vector2{0, 0}, 0, 0); +	Color color(0, 0, 0, 0); +	Sprite test_sprite = game_object.add_component<Sprite>( +		make_shared<Texture>("../asset/texture/img.png"), color, +		FlipSettings{true, true}); +	game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{ +		.position = {0, 0}, +		.max_particles = 100, +		.emission_rate = 0, +		.min_speed = 0, +		.max_speed = 0, +		.min_angle = 0, +		.max_angle = 0, +		.begin_lifespan = 0, +		.end_lifespan = 0, +		.force_over_time = Vector2{0, 0}, +		.boundary{ +			.width = 0, +			.height = 0, +			.offset = Vector2{0, 0}, +			.reset_on_exit = false, +		}, +		.sprite = test_sprite, +	}); + +	return 0; +} diff --git a/src/example/rendering.cpp b/src/example/rendering.cpp index d554a8a..827ad07 100644 --- a/src/example/rendering.cpp +++ b/src/example/rendering.cpp @@ -1,3 +1,4 @@ +#include "api/Camera.h"  #include <crepe/ComponentManager.h>  #include <crepe/api/GameObject.h>  #include <crepe/system/RenderSystem.h> @@ -23,26 +24,28 @@ int main() {  	auto obj1 = GameObject(1, "name", "tag", Vector2{500, 0}, 1, 0.1);  	auto obj2 = GameObject(2, "name", "tag", Vector2{800, 0}, 1, 0.1); -	auto & mgr = AssetManager::get_instance();  	// Normal adding components  	{  		Color color(0, 0, 0, 0);  		obj.add_component<Sprite>(  			make_shared<Texture>("../asset/texture/img.png"), color, -			FlipSettings{true, true}); +			FlipSettings{false, false}); +		obj.add_component<Camera>(Color::get_red());  	} -  	{  		Color color(0, 0, 0, 0); -		auto img = mgr.cache<Texture>("../asset/texture/second.png"); -		obj1.add_component<Sprite>(img, color, FlipSettings{true, true}); +		obj1.add_component<Sprite>( +			make_shared<Texture>("../asset/texture/second.png"), color, +			FlipSettings{true, true});  	} +	/*  	{  		Color color(0, 0, 0, 0);  		auto img = mgr.cache<Texture>("../asset/texture/second.png");  		obj2.add_component<Sprite>(img, color, FlipSettings{true, true});  	} +	*/  	auto & sys = crepe::RenderSystem::get_instance();  	auto start = std::chrono::steady_clock::now(); diff --git a/src/makefile b/src/makefile index b9c44c8..5f80204 100644 --- a/src/makefile +++ b/src/makefile @@ -1,104 +1,6 @@  .PHONY: FORCE -# STEPS FOR BIG CLEANUP -# -# 1. Change TODO to your name (in capitals) for each file in the list below -#    that is yours (or you are going to fix) -# 2. Update the name between parentheses below this list (see comment) to your -#    name -# 3. Create a git commit at this point (ensure `git status` reports "working -#    tree clean") -# 4. Run `make format` in the REPOSITORY ROOT DIRECTORY (NOT HERE), and start -#    fixing reported errors or miscorrections manually until everything works -#    again. -# 5. Once everything is working again, create another git commit, and create a -#    pull request. Make sure to ask someone to review the code standards for -#    each ENTIRE FILE in this pull request. - -LOEK += crepe/Asset.cpp -LOEK += crepe/Asset.h -TODO += crepe/Collider.cpp -TODO += crepe/Collider.h -MAX += crepe/Component.cpp -MAX += crepe/Component.h -MAX += crepe/ComponentManager.cpp -MAX += crepe/ComponentManager.h -MAX += crepe/ComponentManager.hpp -MAX += crepe/api/Metadata.cpp -MAX += crepe/api/Metadata.h -TODO += crepe/Particle.cpp -TODO += crepe/Particle.h -TODO += crepe/Position.h -TODO += crepe/api/AssetManager.cpp -TODO += crepe/api/AssetManager.h -TODO += crepe/api/AssetManager.hpp -LOEK += crepe/api/AudioSource.cpp -LOEK += crepe/api/AudioSource.h -LOEK += crepe/api/BehaviorScript.cpp -LOEK += crepe/api/BehaviorScript.h -LOEK += crepe/api/BehaviorScript.hpp -TODO += crepe/api/CircleCollider.h -TODO += crepe/api/Color.cpp -TODO += crepe/api/Color.h -LOEK += crepe/api/Config.h -MAX += crepe/api/GameObject.cpp -MAX += crepe/api/GameObject.h -MAX += crepe/api/GameObject.hpp -TODO += crepe/api/ParticleEmitter.cpp -TODO += crepe/api/ParticleEmitter.h -TODO += crepe/api/Vector2.h -TODO += crepe/api/Vector2.cpp -JARO += crepe/api/Rigidbody.cpp -JARO += crepe/api/Rigidbody.h -LOEK += crepe/api/Script.cpp -LOEK += crepe/api/Script.h -LOEK += crepe/api/Script.hpp -TODO += crepe/api/Sprite.cpp -TODO += crepe/api/Sprite.h -TODO += crepe/api/Texture.cpp -TODO += crepe/api/Texture.h -MAX += crepe/api/Transform.cpp -MAX += crepe/api/Transform.h -TODO += crepe/facade/SDLContext.cpp -TODO += crepe/facade/SDLContext.h -LOEK += crepe/facade/Sound.cpp -LOEK += crepe/facade/Sound.h -LOEK += crepe/facade/SoundContext.cpp -LOEK += crepe/facade/SoundContext.h -TODO += crepe/system/CollisionSystem.cpp -TODO += crepe/system/CollisionSystem.h -TODO += crepe/system/ParticleSystem.cpp -TODO += crepe/system/ParticleSystem.h -JARO += crepe/system/PhysicsSystem.cpp -JARO += crepe/system/PhysicsSystem.h -TODO += crepe/system/RenderSystem.cpp -TODO += crepe/system/RenderSystem.h -LOEK += crepe/system/ScriptSystem.cpp -LOEK += crepe/system/ScriptSystem.h -LOEK += crepe/system/System.h -LOEK += crepe/util/LogColor.cpp -LOEK += crepe/util/LogColor.h -LOEK += crepe/util/fmt.cpp -LOEK += crepe/util/fmt.h -LOEK += crepe/util/log.cpp -LOEK += crepe/util/log.h -TODO += example/asset_manager.cpp -LOEK += example/audio_internal.cpp -TODO += example/components_internal.cpp -MAX += example/ecs.cpp -LOEK += example/log.cpp -TODO += example/particle.cpp -JARO += example/physics.cpp -TODO += example/rendering.cpp -LOEK += example/script.cpp -LOEK += test/audio.cpp -LOEK += test/dummy.cpp -JARO += test/PhysicsTest.cpp - -FMT := $(JARO) #<<< CHANGE THIS TO YOUR NAME FOR STEP 2 +FMT := $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp')  format: FORCE  	clang-tidy -p build/compile_commands.json --fix-errors $(FMT) -# FMT += $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp') -# TODO: re-enable linter after all corrections - diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 8618ae6..773f685 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,7 +1,12 @@  target_sources(test_main PUBLIC  	dummy.cpp  	# audio.cpp +<<<<<<< HEAD  	PhysicsTest.cpp  	CollisionTest.cpp +======= +	# PhysicsTest.cpp +	ParticleTest.cpp +>>>>>>> be1e97bc7a494963ab1567492fafcda99e36f683  ) diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp new file mode 100644 index 0000000..6fe3133 --- /dev/null +++ b/src/test/ParticleTest.cpp @@ -0,0 +1,206 @@ +#include "api/Vector2.h" +#include <crepe/ComponentManager.h> +#include <crepe/Particle.h> +#include <crepe/api/Config.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> +#include <crepe/system/ParticleSystem.h> +#include <gtest/gtest.h> +#include <math.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; + +class ParticlesTest : public ::testing::Test { +protected: +	ParticleSystem particle_system; +	void SetUp() override { +		ComponentManager & mgr = ComponentManager::get_instance(); +		std::vector<std::reference_wrapper<Transform>> transforms +			= mgr.get_components_by_id<Transform>(0); +		if (transforms.empty()) { + +			GameObject game_object(0, "", "", Vector2{0, 0}, 0, 0); + +			Color color(0, 0, 0, 0); +			Sprite test_sprite = game_object.add_component<Sprite>( +				make_shared<Texture>("../asset/texture/img.png"), color, +				FlipSettings{true, true}); + +			game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{ +				.position = {0, 0}, +				.max_particles = 100, +				.emission_rate = 0, +				.min_speed = 0, +				.max_speed = 0, +				.min_angle = 0, +				.max_angle = 0, +				.begin_lifespan = 0, +				.end_lifespan = 0, +				.force_over_time = Vector2{0, 0}, +				.boundary{ +					.width = 0, +					.height = 0, +					.offset = Vector2{0, 0}, +					.reset_on_exit = false, +				}, +				.sprite = test_sprite, +			}); +		} +		transforms = mgr.get_components_by_id<Transform>(0); +		Transform & transform = transforms.front().get(); +		transform.position.x = 0.0; +		transform.position.y = 0.0; +		transform.rotation = 0.0; +		std::vector<std::reference_wrapper<ParticleEmitter>> rigidbodies +			= mgr.get_components_by_id<ParticleEmitter>(0); +		ParticleEmitter & emitter = rigidbodies.front().get(); +		emitter.data.position = {0, 0}; +		emitter.data.emission_rate = 0; +		emitter.data.min_speed = 0; +		emitter.data.max_speed = 0; +		emitter.data.min_angle = 0; +		emitter.data.max_angle = 0; +		emitter.data.begin_lifespan = 0; +		emitter.data.end_lifespan = 0; +		emitter.data.force_over_time = Vector2{0, 0}; +		emitter.data.boundary = {0, 0, Vector2{0, 0}, false}; +		for (auto & particle : emitter.data.particles) { +			particle.active = false; +		} +	} +}; + +TEST_F(ParticlesTest, spawnParticle) { +	Config::get_instance().physics.gravity = 1; +	ComponentManager & mgr = ComponentManager::get_instance(); +	ParticleEmitter & emitter +		= mgr.get_components_by_id<ParticleEmitter>(0).front().get(); +	emitter.data.end_lifespan = 5; +	emitter.data.boundary.height = 100; +	emitter.data.boundary.width = 100; +	emitter.data.max_speed = 0.1; +	emitter.data.max_angle = 0.1; +	emitter.data.max_speed = 10; +	emitter.data.max_angle = 10; +	particle_system.update(); +	//check if nothing happend +	EXPECT_EQ(emitter.data.particles[0].active, false); +	emitter.data.emission_rate = 1; +	//check particle spawnes +	particle_system.update(); +	EXPECT_EQ(emitter.data.particles[0].active, true); +	particle_system.update(); +	EXPECT_EQ(emitter.data.particles[1].active, true); +	particle_system.update(); +	EXPECT_EQ(emitter.data.particles[2].active, true); +	particle_system.update(); +	EXPECT_EQ(emitter.data.particles[3].active, true); + +	for (auto & particle : emitter.data.particles) { +		// Check velocity range +		EXPECT_GE(particle.velocity.x, emitter.data.min_speed); +		// Speed should be greater than or equal to min_speed +		EXPECT_LE(particle.velocity.x, emitter.data.max_speed); +		// Speed should be less than or equal to max_speed +		EXPECT_GE(particle.velocity.y, emitter.data.min_speed); +		// Speed should be greater than or equal to min_speed +		EXPECT_LE(particle.velocity.y, emitter.data.max_speed); +		// Speed should be less than or equal to max_speed + +		// Check angle range +		EXPECT_GE(particle.angle, emitter.data.min_angle); +		// Angle should be greater than or equal to min_angle +		EXPECT_LE(particle.angle, emitter.data.max_angle); +		// Angle should be less than or equal to max_angle +	} +} + +TEST_F(ParticlesTest, moveParticleHorizontal) { +	Config::get_instance().physics.gravity = 1; +	ComponentManager & mgr = ComponentManager::get_instance(); +	ParticleEmitter & emitter +		= mgr.get_components_by_id<ParticleEmitter>(0).front().get(); +	emitter.data.end_lifespan = 100; +	emitter.data.boundary.height = 100; +	emitter.data.boundary.width = 100; +	emitter.data.min_speed = 1; +	emitter.data.max_speed = 1; +	emitter.data.max_angle = 0; +	emitter.data.emission_rate = 1; +	for (int a = 1; a < emitter.data.boundary.width / 2; a++) { +		particle_system.update(); +		EXPECT_EQ(emitter.data.particles[0].position.x, a); +	} +} + +TEST_F(ParticlesTest, moveParticleVertical) { +	Config::get_instance().physics.gravity = 1; +	ComponentManager & mgr = ComponentManager::get_instance(); +	ParticleEmitter & emitter +		= mgr.get_components_by_id<ParticleEmitter>(0).front().get(); +	emitter.data.end_lifespan = 100; +	emitter.data.boundary.height = 100; +	emitter.data.boundary.width = 100; +	emitter.data.min_speed = 1; +	emitter.data.max_speed = 1; +	emitter.data.min_angle = 90; +	emitter.data.max_angle = 90; +	emitter.data.emission_rate = 1; +	for (int a = 1; a < emitter.data.boundary.width / 2; a++) { +		particle_system.update(); +		EXPECT_EQ(emitter.data.particles[0].position.y, a); +	} +} + +TEST_F(ParticlesTest, boundaryParticleReset) { +	Config::get_instance().physics.gravity = 1; +	ComponentManager & mgr = ComponentManager::get_instance(); +	ParticleEmitter & emitter +		= mgr.get_components_by_id<ParticleEmitter>(0).front().get(); +	emitter.data.end_lifespan = 100; +	emitter.data.boundary.height = 10; +	emitter.data.boundary.width = 10; +	emitter.data.boundary.reset_on_exit = true; +	emitter.data.min_speed = 1; +	emitter.data.max_speed = 1; +	emitter.data.min_angle = 90; +	emitter.data.max_angle = 90; +	emitter.data.emission_rate = 1; +	for (int a = 0; a < emitter.data.boundary.width / 2 + 1; a++) { +		particle_system.update(); +	} +	EXPECT_EQ(emitter.data.particles[0].active, false); +} + +TEST_F(ParticlesTest, boundaryParticleStop) { +	Config::get_instance().physics.gravity = 1; +	ComponentManager & mgr = ComponentManager::get_instance(); +	ParticleEmitter & emitter +		= mgr.get_components_by_id<ParticleEmitter>(0).front().get(); +	emitter.data.end_lifespan = 100; +	emitter.data.boundary.height = 10; +	emitter.data.boundary.width = 10; +	emitter.data.boundary.reset_on_exit = false; +	emitter.data.min_speed = 1; +	emitter.data.max_speed = 1; +	emitter.data.min_angle = 90; +	emitter.data.max_angle = 90; +	emitter.data.emission_rate = 1; +	for (int a = 0; a < emitter.data.boundary.width / 2 + 1; a++) { +		particle_system.update(); +	} +	const double TOLERANCE = 0.01; +	EXPECT_NEAR(emitter.data.particles[0].velocity.x, 0, TOLERANCE); +	EXPECT_NEAR(emitter.data.particles[0].velocity.y, 0, TOLERANCE); +	if (emitter.data.particles[0].velocity.x != 0) +		EXPECT_NEAR(std::abs(emitter.data.particles[0].position.x), +					emitter.data.boundary.height / 2, TOLERANCE); +	if (emitter.data.particles[0].velocity.y != 0) +		EXPECT_NEAR(std::abs(emitter.data.particles[0].position.y), +					emitter.data.boundary.width / 2, TOLERANCE); +} |