diff options
author | Loek Le Blansch <loek@pipeframe.xyz> | 2024-11-14 11:10:31 +0100 |
---|---|---|
committer | Loek Le Blansch <loek@pipeframe.xyz> | 2024-11-14 11:10:31 +0100 |
commit | 6e13510f3c6d4155707f748d237bb1fa05243450 (patch) | |
tree | b21ca972d14aac56b7c58c85c54d8a2fd1278a7e /src | |
parent | 319d511135da1b58acb78ca939d7ee01fee4bbf3 (diff) | |
parent | be1e97bc7a494963ab1567492fafcda99e36f683 (diff) |
merge `master` into `loek/audio`
Diffstat (limited to 'src')
65 files changed, 1867 insertions, 488 deletions
diff --git a/src/crepe/Exception.cpp b/src/crepe/Exception.cpp index f27d5a8..dab8f2e 100644 --- a/src/crepe/Exception.cpp +++ b/src/crepe/Exception.cpp @@ -6,9 +6,7 @@ using namespace std; using namespace crepe; -const char * Exception::what() { - return error.c_str(); -} +const char * Exception::what() { return error.c_str(); } Exception::Exception(const char * fmt, ...) { va_list args; @@ -16,4 +14,3 @@ Exception::Exception(const char * fmt, ...) { this->error = va_stringf(args, fmt); va_end(args); } - diff --git a/src/crepe/Exception.h b/src/crepe/Exception.h index e4a7bb8..6473043 100644 --- a/src/crepe/Exception.h +++ b/src/crepe/Exception.h @@ -17,7 +17,6 @@ protected: Exception() = default; //! Formatted error message std::string error; - }; -} +} // namespace crepe 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.h b/src/crepe/ValueBroker.h index 88988b4..d844d6a 100644 --- a/src/crepe/ValueBroker.h +++ b/src/crepe/ValueBroker.h @@ -23,10 +23,12 @@ public: virtual const T & get(); typedef std::function<void(const T & target)> setter_t; - typedef std::function<const T & ()> getter_t; + typedef std::function<const T &()> getter_t; + private: setter_t setter; getter_t getter; + public: /** * \param setter Function that sets the variable @@ -35,7 +37,6 @@ public: ValueBroker(const setter_t & setter, const getter_t & getter); }; -} +} // namespace crepe #include "ValueBroker.hpp" - diff --git a/src/crepe/ValueBroker.hpp b/src/crepe/ValueBroker.hpp index 0d08333..5c3bed9 100644 --- a/src/crepe/ValueBroker.hpp +++ b/src/crepe/ValueBroker.hpp @@ -5,11 +5,9 @@ namespace crepe { template <typename T> -ValueBroker<T>::ValueBroker(const setter_t & setter, const getter_t & getter) : - setter(setter), - getter(getter) - { -} +ValueBroker<T>::ValueBroker(const setter_t & setter, const getter_t & getter) + : setter(setter), + getter(getter) {} template <typename T> const T & ValueBroker<T>::get() { @@ -21,5 +19,4 @@ void ValueBroker<T>::set(const T & value) { this->setter(value); } -} - +} // namespace crepe diff --git a/src/crepe/api/Animator.cpp b/src/crepe/api/Animator.cpp new file mode 100644 index 0000000..58fee2a --- /dev/null +++ b/src/crepe/api/Animator.cpp @@ -0,0 +1,26 @@ + +#include <cstdint> + +#include "util/log.h" + +#include "Animator.h" +#include "Component.h" +#include "Sprite.h" + +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) { + dbg_trace(); + + animator_rect = spritesheet.sprite_rect; + animator_rect.h /= col; + animator_rect.w /= row; + animator_rect.x = 0; + animator_rect.y = col_animator * animator_rect.h; + this->active = false; +} +Animator::~Animator() { dbg_trace(); } diff --git a/src/crepe/api/Animator.h b/src/crepe/api/Animator.h new file mode 100644 index 0000000..def0240 --- /dev/null +++ b/src/crepe/api/Animator.h @@ -0,0 +1,76 @@ +#pragma once + +#include <cstdint> + +#include "Component.h" +#include "Sprite.h" + +namespace crepe { +class AnimatorSystem; +class SDLContext; + +/** + * \brief The Animator component is used to animate sprites by managing the movement + * and frame changes within a sprite sheet. + * + * This component allows for controlling sprite animation through rows and columns of a sprite sheet. + * It can be used to play animations, loop them, or stop them. + */ +class Animator : public Component { + +public: + //TODO: need to implement this + void loop(); + void stop(); + +public: + /** + * \brief Constructs an Animator object that will control animations for a sprite sheet. + * + * \param id The unique identifier for the component, typically assigned automatically. + * \param spritesheet A reference to the Sprite object which holds the sprite sheet for animation. + * \param row The maximum number of rows in the sprite sheet. + * \param col The maximum number of columns in the sprite sheet. + * \param col__animate The specific col index of the sprite sheet to animate. This allows selecting which col to animate from multiple col in the sheet. + * + * This constructor sets up the Animator with the given parameters, and initializes the animation system. + */ + Animator(uint32_t id, Sprite & spritesheet, int row, int col, + int col_animate); + + ~Animator(); // dbg_trace + Animator(const Animator &) = delete; + Animator(Animator &&) = delete; + Animator & operator=(const Animator &) = delete; + Animator & operator=(Animator &&) = delete; + +private: + //! A reference to the Sprite sheet containing the animation frames. + Sprite & spritesheet; + + //! The maximum number of columns in the sprite sheet. + const int col; + + //! The maximum number of rows in the sprite sheet. + const int row; + + //! The current col being animated. + int curr_col = 0; + + //! The current row being animated. + int curr_row = 0; + + Rect animator_rect; + + //TODO: Is this necessary? + //int fps; + +private: + //! AnimatorSystem adjust the private member parameters of Animator; + friend class AnimatorSystem; + + //! SDLContext reads the Animator member var's + friend class SDLContext; +}; +} // namespace crepe +// 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/AudioSource.h b/src/crepe/api/AudioSource.h index fd2b6f1..5bc70f9 100644 --- a/src/crepe/api/AudioSource.h +++ b/src/crepe/api/AudioSource.h @@ -11,7 +11,7 @@ namespace crepe { //! Audio source component class AudioSource : public Component { public: - AudioSource(game_object_id_t id, std::unique_ptr<Asset> audio_clip); + AudioSource(game_object_id_t id, const Asset & source); virtual ~AudioSource() = default; public: diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 3b20142..85696c4 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -16,6 +16,10 @@ target_sources(crepe PUBLIC Scene.cpp SceneManager.cpp Vector2.cpp + Camera.cpp + Animator.cpp + LoopManager.cpp + LoopTimer.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -38,4 +42,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Metadata.h SceneManager.h SceneManager.hpp + Camera.h + Animator.h + LoopManager.h + LoopTimer.h ) diff --git a/src/crepe/api/Camera.cpp b/src/crepe/api/Camera.cpp new file mode 100644 index 0000000..6355a03 --- /dev/null +++ b/src/crepe/api/Camera.cpp @@ -0,0 +1,18 @@ + +#include <cstdint> + +#include "util/log.h" + +#include "Camera.h" +#include "Color.h" +#include "Component.h" + +using namespace crepe; + +Camera::Camera(uint32_t id, const Color & bg_color) + : Component(id), + bg_color(bg_color) { + dbg_trace(); +} + +Camera::~Camera() { dbg_trace(); } diff --git a/src/crepe/api/Camera.h b/src/crepe/api/Camera.h new file mode 100644 index 0000000..ba3a9ef --- /dev/null +++ b/src/crepe/api/Camera.h @@ -0,0 +1,55 @@ +#pragma once + +#include <cstdint> + +#include "Color.h" +#include "Component.h" + +namespace crepe { + +/** + * \class Camera + * \brief Represents a camera component for rendering in the game. + * + * The Camera class defines the view parameters, including background color, + * aspect ratio, position, and zoom level. It controls what part of the game + * world is visible on the screen. + */ +class Camera : public Component { + +public: + /** + * \brief Constructs a Camera with the specified ID and background color. + * \param id Unique identifier for the camera component. + * \param bg_color Background color for the camera view. + */ + Camera(uint32_t id, const Color & bg_color); + ~Camera(); // dbg_trace only + +public: + //! Background color of the camera view. + Color bg_color; + + //! Aspect ratio height for the camera. + double aspect_height = 480; + + //! Aspect ratio width for the camera. + double aspect_width = 640; + + //! X-coordinate of the camera position. + double x = 0.0; + + //! Y-coordinate of the camera position. + double y = 0.0; + + //! Zoom level of the camera view. + double zoom = 1.0; + +public: + /** + * \brief Gets the maximum number of camera instances allowed. + * \return Maximum instance count as an integer. + */ + virtual int get_instances_max() const { return 10; } +}; +} // namespace crepe diff --git a/src/crepe/api/CircleCollider.h b/src/crepe/api/CircleCollider.h index caa7e43..e77a592 100644 --- a/src/crepe/api/CircleCollider.h +++ b/src/crepe/api/CircleCollider.h @@ -6,7 +6,8 @@ namespace crepe { class CircleCollider : public Collider { public: CircleCollider(game_object_id_t game_object_id, int radius) - : Collider(game_object_id), radius(radius) {} + : Collider(game_object_id), + radius(radius) {} int radius; }; diff --git a/src/crepe/api/Color.cpp b/src/crepe/api/Color.cpp index fc6313d..9e5f187 100644 --- a/src/crepe/api/Color.cpp +++ b/src/crepe/api/Color.cpp @@ -11,8 +11,7 @@ Color Color::cyan = Color(0, 255, 255, 0); Color Color::yellow = Color(255, 255, 0, 0); Color Color::magenta = Color(255, 0, 255, 0); -// FIXME: do we really need double precision for color values? -Color::Color(double red, double green, double blue, double alpha) { +Color::Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) { this->a = alpha; this->r = red; this->g = green; diff --git a/src/crepe/api/Color.h b/src/crepe/api/Color.h index 6b54888..aa47bf4 100644 --- a/src/crepe/api/Color.h +++ b/src/crepe/api/Color.h @@ -1,14 +1,17 @@ #pragma once +#include <cstdint> + namespace crepe { +// TODO: make Color a struct w/o constructors/destructors class Color { // FIXME: can't these colors be defined as a `static constexpr const Color` // instead? public: - Color(double red, double green, double blue, double alpha); + Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha); static const Color & get_white(); static const Color & get_red(); static const Color & get_green(); @@ -19,10 +22,11 @@ public: static const Color & get_black(); private: - double r; - double g; - double b; - double a; + // TODO: why are these private!? + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; static Color white; static Color red; @@ -32,6 +36,9 @@ private: static Color magenta; static Color yellow; static Color black; + +private: + friend class SDLContext; }; } // namespace crepe diff --git a/src/crepe/api/Config.cpp b/src/crepe/api/Config.cpp index d6206da..0100bcc 100644 --- a/src/crepe/api/Config.cpp +++ b/src/crepe/api/Config.cpp @@ -6,4 +6,3 @@ Config & Config::get_instance() { static Config instance; return instance; } - diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h index 56e3af5..8c9e643 100644 --- a/src/crepe/api/Config.h +++ b/src/crepe/api/Config.h @@ -7,6 +7,7 @@ namespace crepe { class Config { private: Config() = default; + public: ~Config() = default; @@ -16,8 +17,8 @@ public: // singleton Config(const Config &) = delete; Config(Config &&) = delete; - Config & operator = (const Config &) = delete; - Config & operator = (Config &&) = delete; + Config & operator=(const Config &) = delete; + Config & operator=(Config &&) = delete; public: //! Logging-related settings @@ -60,4 +61,3 @@ public: }; } // 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/SaveManager.cpp b/src/crepe/api/SaveManager.cpp index 23587e4..43276c5 100644 --- a/src/crepe/api/SaveManager.cpp +++ b/src/crepe/api/SaveManager.cpp @@ -2,8 +2,8 @@ #include "../util/log.h" #include "Config.h" -#include "ValueBroker.h" #include "SaveManager.h" +#include "ValueBroker.h" using namespace std; using namespace crepe; @@ -65,17 +65,33 @@ string SaveManager::deserialize(const string & value) const noexcept { return value; } -template <> uint8_t SaveManager::deserialize(const string & value) const noexcept { return deserialize<uint64_t>(value); } -template <> int8_t SaveManager::deserialize(const string & value) const noexcept { return deserialize<int64_t>(value); } -template <> uint16_t SaveManager::deserialize(const string & value) const noexcept { return deserialize<uint64_t>(value); } -template <> int16_t SaveManager::deserialize(const string & value) const noexcept { return deserialize<int64_t>(value); } -template <> uint32_t SaveManager::deserialize(const string & value) const noexcept { return deserialize<uint64_t>(value); } -template <> int32_t SaveManager::deserialize(const string & value) const noexcept { return deserialize<int64_t>(value); } - -SaveManager::SaveManager() { - dbg_trace(); +template <> +uint8_t SaveManager::deserialize(const string & value) const noexcept { + return deserialize<uint64_t>(value); +} +template <> +int8_t SaveManager::deserialize(const string & value) const noexcept { + return deserialize<int64_t>(value); +} +template <> +uint16_t SaveManager::deserialize(const string & value) const noexcept { + return deserialize<uint64_t>(value); +} +template <> +int16_t SaveManager::deserialize(const string & value) const noexcept { + return deserialize<int64_t>(value); +} +template <> +uint32_t SaveManager::deserialize(const string & value) const noexcept { + return deserialize<uint64_t>(value); +} +template <> +int32_t SaveManager::deserialize(const string & value) const noexcept { + return deserialize<int64_t>(value); } +SaveManager::SaveManager() { dbg_trace(); } + SaveManager & SaveManager::get_instance() { dbg_trace(); static SaveManager instance; @@ -118,17 +134,19 @@ template void SaveManager::set(const string &, const double &); template <typename T> ValueBroker<T> SaveManager::get(const string & key, const T & default_value) { - if (!this->has(key)) - this->set<T>(key, default_value); + if (!this->has(key)) this->set<T>(key, default_value); return this->get<T>(key); } template ValueBroker<uint8_t> SaveManager::get(const string &, const uint8_t &); template ValueBroker<int8_t> SaveManager::get(const string &, const int8_t &); -template ValueBroker<uint16_t> SaveManager::get(const string &, const uint16_t &); +template ValueBroker<uint16_t> SaveManager::get(const string &, + const uint16_t &); template ValueBroker<int16_t> SaveManager::get(const string &, const int16_t &); -template ValueBroker<uint32_t> SaveManager::get(const string &, const uint32_t &); +template ValueBroker<uint32_t> SaveManager::get(const string &, + const uint32_t &); template ValueBroker<int32_t> SaveManager::get(const string &, const int32_t &); -template ValueBroker<uint64_t> SaveManager::get(const string &, const uint64_t &); +template ValueBroker<uint64_t> SaveManager::get(const string &, + const uint64_t &); template ValueBroker<int64_t> SaveManager::get(const string &, const int64_t &); template ValueBroker<float> SaveManager::get(const string &, const float &); template ValueBroker<double> SaveManager::get(const string &, const double &); @@ -138,8 +156,8 @@ template <typename T> ValueBroker<T> SaveManager::get(const string & key) { T value; return { - [this, key] (const T & target) { this->set<T>(key, target); }, - [this, key, value] () mutable -> const T & { + [this, key](const T & target) { this->set<T>(key, target); }, + [this, key, value]() mutable -> const T & { value = this->deserialize<T>(this->get_db().get(key)); return value; }, @@ -156,4 +174,3 @@ template ValueBroker<int64_t> SaveManager::get(const string &); template ValueBroker<float> SaveManager::get(const string &); template ValueBroker<double> SaveManager::get(const string &); template ValueBroker<string> SaveManager::get(const string &); - diff --git a/src/crepe/api/SaveManager.h b/src/crepe/api/SaveManager.h index 3073656..4be85fb 100644 --- a/src/crepe/api/SaveManager.h +++ b/src/crepe/api/SaveManager.h @@ -93,8 +93,8 @@ public: static SaveManager & get_instance(); SaveManager(const SaveManager &) = delete; SaveManager(SaveManager &&) = delete; - SaveManager & operator = (const SaveManager &) = delete; - SaveManager & operator = (SaveManager &&) = delete; + SaveManager & operator=(const SaveManager &) = delete; + SaveManager & operator=(SaveManager &&) = delete; private: /** @@ -110,5 +110,4 @@ private: static DB & get_db(); }; -} - +} // namespace crepe diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp index d3465c7..6f0433f 100644 --- a/src/crepe/api/Sprite.cpp +++ b/src/crepe/api/Sprite.cpp @@ -1,7 +1,7 @@ -#include <cstdint> #include <memory> #include "../util/log.h" +#include "facade/SDLContext.h" #include "Component.h" #include "Sprite.h" @@ -10,10 +10,16 @@ using namespace std; using namespace crepe; -Sprite::Sprite(game_object_id_t id, shared_ptr<Texture> image, +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(); + this->sprite_rect.h = sprite_image->get_height(); } Sprite::~Sprite() { dbg_trace(); } diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h index 00dcb27..deb3f93 100644 --- a/src/crepe/api/Sprite.h +++ b/src/crepe/api/Sprite.h @@ -1,32 +1,88 @@ #pragma once -#include <SDL2/SDL_rect.h> #include <cstdint> #include <memory> -#include "api/Color.h" -#include "api/Texture.h" - +#include "Color.h" #include "Component.h" +#include "Texture.h" namespace crepe { +struct Rect { + int w = 0; + int h = 0; + int x = 0; + int y = 0; +}; + struct FlipSettings { - bool flip_x = true; - bool flip_y = true; + bool flip_x = false; + bool flip_y = false; }; +class SDLContext; +class Animator; +class AnimatorSystem; + +/** + * \brief Represents a renderable sprite component. + * + * A renderable sprite that can be displayed in the game. It includes a texture, + * color, and flip settings, and is managed in layers with defined sorting orders. + */ class Sprite : public Component { public: - Sprite(game_object_id_t id, std::shared_ptr<Texture> image, + // TODO: Loek comment in github #27 will be looked another time + // about shared_ptr Texture + /** + * \brief Constructs a Sprite with specified parameters. + * \param game_id Unique identifier for the game object this sprite belongs to. + * \param image Shared pointer to the texture for this sprite. + * \param color Color tint applied to the sprite. + * \param flip Flip settings for horizontal and vertical orientation. + */ + Sprite(game_object_id_t id, const std::shared_ptr<Texture> image, const Color & color, const FlipSettings & flip); + + /** + * \brief Destroys the Sprite instance. + */ ~Sprite(); - std::shared_ptr<Texture> sprite_image; + + //! Texture used for the sprite + const std::shared_ptr<Texture> sprite_image; + //! Color tint of the sprite Color color; + //! Flip settings for the sprite FlipSettings flip; - uint8_t sorting_in_layer; - uint8_t order_in_layer; + //! Layer sorting level of the sprite + uint8_t sorting_in_layer = 0; + //! Order within the sorting layer + uint8_t order_in_layer = 0; + +public: + /** + * \brief Gets the maximum number of instances allowed for this sprite. + * \return Maximum instance count as an integer. + * + * For now is this number randomly picked. I think it will eventually be 1. + */ + virtual int get_instances_max() const { return 10; } + +private: + //! Reads the sprite_rect of sprite + friend class SDLContext; + + //! Reads the all the variables plus the sprite_rect + friend class Animator; + + //! Reads the all the variables plus the sprite_rect + friend class AnimatorSystem; + + //! Render area of the sprite this will also be adjusted by the AnimatorSystem if an Animator object is present in GameObject + Rect sprite_rect; }; } // namespace crepe diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp index 8fc5c13..5ebd23d 100644 --- a/src/crepe/api/Texture.cpp +++ b/src/crepe/api/Texture.cpp @@ -21,12 +21,19 @@ Texture::Texture(const char * src) { Texture::~Texture() { dbg_trace(); - if (this->texture != nullptr) { - SDL_DestroyTexture(this->texture); - } + this->texture.reset(); } void Texture::load(unique_ptr<Asset> res) { SDLContext & ctx = SDLContext::get_instance(); - this->texture = ctx.texture_from_path(res->canonical()); + this->texture = std::move(ctx.texture_from_path(res->canonical())); +} + +int Texture::get_width() const { + if (this->texture == nullptr) return 0; + return SDLContext::get_instance().get_width(*this); +} +int Texture::get_height() const { + if (this->texture == nullptr) return 0; + return SDLContext::get_instance().get_width(*this); } diff --git a/src/crepe/api/Texture.h b/src/crepe/api/Texture.h index 9a86f6f..b89bc17 100644 --- a/src/crepe/api/Texture.h +++ b/src/crepe/api/Texture.h @@ -3,31 +3,75 @@ // FIXME: this header can't be included because this is an API header, and SDL2 // development headers won't be bundled with crepe. Why is this facade in the // API namespace? + #include <SDL2/SDL_render.h> +#include <functional> #include <memory> #include "Asset.h" namespace crepe { -class SDLContext; -} -namespace crepe { +class SDLContext; +class Animator; +/** + * \class Texture + * \brief Manages texture loading and properties. + * + * The Texture class is responsible for loading an image from a source + * and providing access to its dimensions. Textures can be used for rendering. + */ class Texture { public: + /** + * \brief Constructs a Texture from a file path. + * \param src Path to the image file to be loaded as a texture. + */ Texture(const char * src); + + /** + * \brief Constructs a Texture from an Asset resource. + * \param res Unique pointer to an Asset resource containing texture data. + */ Texture(std::unique_ptr<Asset> res); + + /** + * \brief Destroys the Texture instance, freeing associated resources. + */ ~Texture(); + // FIXME: this constructor shouldn't be necessary because this class doesn't + // manage memory + + /** + * \brief Gets the width of the texture. + * \return Width of the texture in pixels. + */ + int get_width() const; + + /** + * \brief Gets the height of the texture. + * \return Height of the texture in pixels. + */ + int get_height() const; private: + /** + * \brief Loads the texture from an Asset resource. + * \param res Unique pointer to an Asset resource to load the texture from. + */ void load(std::unique_ptr<Asset> res); private: - SDL_Texture * texture = nullptr; + //! The texture of the class from the library + std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> texture; + + //! Grants SDLContext access to private members. + friend class SDLContext; - friend class crepe::SDLContext; + //! Grants Animator access to private members. + friend class Animator; }; } // namespace crepe 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/api/Transform.h b/src/crepe/api/Transform.h index d7a5b8a..756e45b 100644 --- a/src/crepe/api/Transform.h +++ b/src/crepe/api/Transform.h @@ -32,7 +32,7 @@ public: public: //! Translation (shift) Vector2 position; - //! Rotation, in radians + //! Rotation, in degrees double rotation; //! Multiplication factor double scale; diff --git a/src/crepe/api/Vector2.cpp b/src/crepe/api/Vector2.cpp index 09bb59b..09b3fa3 100644 --- a/src/crepe/api/Vector2.cpp +++ b/src/crepe/api/Vector2.cpp @@ -3,7 +3,7 @@ namespace crepe { // Constructor with initial values -Vector2::Vector2(float x, float y) : x(x), y(y) {} +Vector2::Vector2(double x, double y) : x(x), y(y) {} // Subtracts another vector from this vector and returns the result. Vector2 Vector2::operator-(const Vector2 & other) const { @@ -16,7 +16,7 @@ Vector2 Vector2::operator+(const Vector2 & other) const { } // Multiplies this vector by a scalar and returns the result. -Vector2 Vector2::operator*(float scalar) const { +Vector2 Vector2::operator*(double scalar) const { return {x * scalar, y * scalar}; } @@ -35,7 +35,7 @@ Vector2 & Vector2::operator+=(const Vector2 & other) { } // Adds a scalar value to both components of this vector and updates this vector. -Vector2 & Vector2::operator+=(float other) { +Vector2 & Vector2::operator+=(double other) { x += other; y += other; return *this; diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h index 741951b..5a57484 100644 --- a/src/crepe/api/Vector2.h +++ b/src/crepe/api/Vector2.h @@ -6,15 +6,15 @@ namespace crepe { class Vector2 { public: //! X component of the vector - float x; + double x; //! Y component of the vector - float y; + double y; //! Default constructor Vector2() = default; //! Constructor with initial values - Vector2(float x, float y); + Vector2(double x, double y); //! Subtracts another vector from this vector and returns the result. Vector2 operator-(const Vector2 & other) const; @@ -23,7 +23,7 @@ public: Vector2 operator+(const Vector2 & other) const; //! Multiplies this vector by a scalar and returns the result. - Vector2 operator*(float scalar) const; + Vector2 operator*(double scalar) const; //! Multiplies this vector by another vector element-wise and updates this vector. Vector2 & operator*=(const Vector2 & other); @@ -32,7 +32,7 @@ public: Vector2 & operator+=(const Vector2 & other); //! Adds a scalar value to both components of this vector and updates this vector. - Vector2 & operator+=(float other); + Vector2 & operator+=(double other); //! Returns the negation of this vector. Vector2 operator-() const; diff --git a/src/crepe/facade/DB.cpp b/src/crepe/facade/DB.cpp index c885560..0a2f455 100644 --- a/src/crepe/facade/DB.cpp +++ b/src/crepe/facade/DB.cpp @@ -1,7 +1,7 @@ #include <cstring> -#include "util/log.h" #include "Exception.h" +#include "util/log.h" #include "DB.h" @@ -16,20 +16,21 @@ DB::DB(const string & path) { libdb::DB * db; if ((ret = libdb::db_create(&db, NULL, 0)) != 0) throw Exception("db_create: %s", libdb::db_strerror(ret)); - this->db = { db, [] (libdb::DB * db) { db->close(db, 0); } }; + this->db = {db, [](libdb::DB * db) { db->close(db, 0); }}; // load or create database file - if ((ret = this->db->open(this->db.get(), NULL, path.c_str(), NULL, libdb::DB_BTREE, DB_CREATE, 0)) != 0) + if ((ret = this->db->open(this->db.get(), NULL, path.c_str(), NULL, + libdb::DB_BTREE, DB_CREATE, 0)) + != 0) throw Exception("db->open: %s", libdb::db_strerror(ret)); // create cursor libdb::DBC * cursor; if ((ret = this->db->cursor(this->db.get(), NULL, &cursor, 0)) != 0) throw Exception("db->cursor: %s", libdb::db_strerror(ret)); - this->cursor = { cursor, [] (libdb::DBC * cursor) { cursor->close(cursor); } }; + this->cursor = {cursor, [](libdb::DBC * cursor) { cursor->close(cursor); }}; } - libdb::DBT DB::to_thing(const string & thing) const noexcept { libdb::DBT thang; memset(&thang, 0, sizeof(libdb::DBT)); @@ -44,17 +45,15 @@ string DB::get(const string & key) { memset(&db_val, 0, sizeof(libdb::DBT)); int ret = this->cursor->get(this->cursor.get(), &db_key, &db_val, DB_FIRST); - if (ret != 0) - throw Exception("cursor->get: %s", libdb::db_strerror(ret)); - return { static_cast<char *>(db_val.data), db_val.size }; + if (ret != 0) throw Exception("cursor->get: %s", libdb::db_strerror(ret)); + return {static_cast<char *>(db_val.data), db_val.size}; } void DB::set(const string & key, const string & value) { libdb::DBT db_key = this->to_thing(key); libdb::DBT db_val = this->to_thing(value); int ret = this->db->put(this->db.get(), NULL, &db_key, &db_val, 0); - if (ret != 0) - throw Exception("cursor->get: %s", libdb::db_strerror(ret)); + if (ret != 0) throw Exception("cursor->get: %s", libdb::db_strerror(ret)); } bool DB::has(const std::string & key) noexcept { @@ -65,4 +64,3 @@ bool DB::has(const std::string & key) noexcept { } return true; } - diff --git a/src/crepe/facade/DB.h b/src/crepe/facade/DB.h index b62a974..7c757a2 100644 --- a/src/crepe/facade/DB.h +++ b/src/crepe/facade/DB.h @@ -1,14 +1,14 @@ #pragma once -#include <string> #include <functional> #include <memory> +#include <string> namespace libdb { extern "C" { #include <db.h> } -} +} // namespace libdb namespace crepe { @@ -71,5 +71,4 @@ private: libdb::DBT to_thing(const std::string & thing) const noexcept; }; -} - +} // namespace crepe diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index 8da93e9..236bf8c 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -1,16 +1,22 @@ #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> +#include <SDL2/SDL_rect.h> #include <SDL2/SDL_render.h> #include <SDL2/SDL_surface.h> #include <SDL2/SDL_video.h> #include <cmath> #include <cstddef> +#include <functional> #include <iostream> +#include <memory> +#include <string> +#include <utility> #include "../api/Sprite.h" #include "../api/Texture.h" #include "../api/Transform.h" #include "../util/log.h" +#include "Exception.h" #include "SDLContext.h" @@ -21,34 +27,6 @@ SDLContext & SDLContext::get_instance() { return instance; } -void SDLContext::handle_events(bool & running) { - SDL_Event event; - while (SDL_PollEvent(&event)) { - if (event.type == SDL_QUIT) { - running = false; - } - } -} - -SDLContext::~SDLContext() { - dbg_trace(); - - if (this->game_renderer != nullptr) - SDL_DestroyRenderer(this->game_renderer); - - if (this->game_window != nullptr) { - SDL_DestroyWindow(this->game_window); - } - - // TODO: how are we going to ensure that these are called from the same - // thread that SDL_Init() was called on? This has caused problems for me - // before. - IMG_Quit(); - SDL_Quit(); -} - -void SDLContext::clear_screen() { SDL_RenderClear(this->game_renderer); } - SDLContext::SDLContext() { dbg_trace(); // FIXME: read window defaults from config manager @@ -59,26 +37,32 @@ SDLContext::SDLContext() { << std::endl; return; } - - this->game_window = SDL_CreateWindow( + SDL_Window * tmp_window = SDL_CreateWindow( "Crepe Game Engine", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - 1920, 1080, SDL_WINDOW_SHOWN); - if (!this->game_window) { + this->viewport.w, this->viewport.h, 0); + if (!tmp_window) { // FIXME: throw exception std::cerr << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl; + return; } + this->game_window + = {tmp_window, [](SDL_Window * window) { SDL_DestroyWindow(window); }}; - this->game_renderer - = SDL_CreateRenderer(this->game_window, -1, SDL_RENDERER_ACCELERATED); - if (!this->game_renderer) { + SDL_Renderer * tmp_renderer = SDL_CreateRenderer( + this->game_window.get(), -1, SDL_RENDERER_ACCELERATED); + if (!tmp_renderer) { // FIXME: throw exception std::cerr << "Renderer could not be created! SDL_Error: " << SDL_GetError() << std::endl; - SDL_DestroyWindow(this->game_window); + SDL_DestroyWindow(this->game_window.get()); return; } + this->game_renderer = {tmp_renderer, [](SDL_Renderer * renderer) { + SDL_DestroyRenderer(renderer); + }}; + int img_flags = IMG_INIT_PNG; if (!(IMG_Init(img_flags) & img_flags)) { // FIXME: throw exception @@ -87,71 +71,124 @@ SDLContext::SDLContext() { } } -void SDLContext::present_screen() { SDL_RenderPresent(this->game_renderer); } +SDLContext::~SDLContext() { + dbg_trace(); -void SDLContext::draw(const Sprite & sprite, const Transform & transform) { + this->game_renderer.reset(); + this->game_window.reset(); - static SDL_RendererFlip render_flip + // TODO: how are we going to ensure that these are called from the same + // thread that SDL_Init() was called on? This has caused problems for me + // before. + IMG_Quit(); + SDL_Quit(); +} + +void SDLContext::handle_events(bool & running) { + //TODO: wouter i need events + /* + SDL_Event event; + SDL_PollEvent(&event); + switch (event.type) { + case SDL_QUIT: + running = false; + break; + case SDL_KEYDOWN: + triggerEvent(KeyPressedEvent(getCustomKey(event.key.keysym.sym))); + break; + case SDL_MOUSEBUTTONDOWN: + int x, y; + SDL_GetMouseState(&x, &y); + triggerEvent(MousePressedEvent(x, y)); + break; + } + */ +} + +void SDLContext::clear_screen() { SDL_RenderClear(this->game_renderer.get()); } +void SDLContext::present_screen() { + SDL_RenderPresent(this->game_renderer.get()); +} + +void SDLContext::draw(const Sprite & sprite, const Transform & transform, + const Camera & cam) { + + SDL_RendererFlip render_flip = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); - int w, h; - SDL_QueryTexture(sprite.sprite_image->texture, NULL, NULL, &w, &h); - // needs maybe camera for position + double adjusted_x = (transform.position.x - cam.x) * cam.zoom; + double adjusted_y = (transform.position.y - cam.y) * cam.zoom; + double adjusted_w = sprite.sprite_rect.w * transform.scale * cam.zoom; + double adjusted_h = sprite.sprite_rect.h * transform.scale * cam.zoom; + + SDL_Rect srcrect = { + .x = sprite.sprite_rect.x, + .y = sprite.sprite_rect.y, + .w = sprite.sprite_rect.w, + .h = sprite.sprite_rect.h, + }; + SDL_Rect dstrect = { - .x = static_cast<int>(transform.position.x), - .y = static_cast<int>(transform.position.y), - .w = static_cast<int>(w * transform.scale), - .h = static_cast<int>(h * transform.scale), + .x = static_cast<int>(adjusted_x), + .y = static_cast<int>(adjusted_y), + .w = static_cast<int>(adjusted_w), + .h = static_cast<int>(adjusted_h), }; - double degrees = transform.rotation * 180 / M_PI; - SDL_RenderCopyEx(this->game_renderer, sprite.sprite_image->texture, NULL, - &dstrect, degrees, NULL, render_flip); + SDL_RenderCopyEx(this->game_renderer.get(), + sprite.sprite_image->texture.get(), &srcrect, + + &dstrect, transform.rotation, NULL, render_flip); } -/* -SDL_Texture * SDLContext::setTextureFromPath(const char * path, SDL_Rect & clip, - const int row, const int col) { - dbg_trace(); +void SDLContext::camera(const Camera & cam) { + this->viewport.w = static_cast<int>(cam.aspect_width); + this->viewport.h = static_cast<int>(cam.aspect_height); + this->viewport.x = static_cast<int>(cam.x) - (SCREEN_WIDTH / 2); + this->viewport.y = static_cast<int>(cam.y) - (SCREEN_HEIGHT / 2); - SDL_Surface * tmp = IMG_Load(path); - if (!tmp) { - std::cerr << "Error surface " << IMG_GetError << std::endl; - } + SDL_SetRenderDrawColor(this->game_renderer.get(), cam.bg_color.r, + cam.bg_color.g, cam.bg_color.b, cam.bg_color.a); +} - clip. - w = tmp->w / col; - clip.h = tmp->h / row; +uint64_t SDLContext::get_ticks() const { return SDL_GetTicks64(); } - SDL_Texture * CreatedTexture - = SDL_CreateTextureFromSurface(this->game_renderer, tmp); +std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> +SDLContext::texture_from_path(const std::string & path) { - if (!CreatedTexture) { - std::cerr << "Error could not create texture " << IMG_GetError - << std::endl; + SDL_Surface * tmp = IMG_Load(path.c_str()); + if (tmp == nullptr) { + tmp = IMG_Load("../asset/texture/ERROR.png"); } - SDL_FreeSurface(tmp); - return CreatedTexture; -} -*/ + std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>> + img_surface; + img_surface + = {tmp, [](SDL_Surface * surface) { SDL_FreeSurface(surface); }}; -SDL_Texture * SDLContext::texture_from_path(const char * path) { - dbg_trace(); + SDL_Texture * tmp_texture = SDL_CreateTextureFromSurface( + this->game_renderer.get(), img_surface.get()); - SDL_Surface * tmp = IMG_Load(path); - if (!tmp) { - std::cerr << "Error surface " << IMG_GetError << std::endl; + if (tmp_texture == nullptr) { + throw Exception("Texture cannot be load from %s", path.c_str()); } - SDL_Texture * created_texture - = SDL_CreateTextureFromSurface(this->game_renderer, tmp); - if (!created_texture) { - std::cerr << "Error could not create texture " << IMG_GetError - << std::endl; - } - SDL_FreeSurface(tmp); + std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> + img_texture; + img_texture = {tmp_texture, + [](SDL_Texture * texture) { SDL_DestroyTexture(texture); }}; - return created_texture; + return img_texture; +} +int SDLContext::get_width(const Texture & ctx) const { + int w; + SDL_QueryTexture(ctx.texture.get(), NULL, NULL, &w, NULL); + return w; +} +int SDLContext::get_height(const Texture & ctx) const { + int h; + 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 f1ba8a6..536dec5 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -1,48 +1,157 @@ #pragma once +#include <SDL2/SDL_keycode.h> #include <SDL2/SDL_render.h> #include <SDL2/SDL_video.h> +#include <functional> +#include <memory> +#include <string> #include "../api/Sprite.h" #include "../api/Transform.h" -#include "../system/RenderSystem.h" +#include "api/Camera.h" + +// FIXME: this needs to be removed +const int SCREEN_WIDTH = 640; +const int SCREEN_HEIGHT = 480; namespace crepe { +// TODO: SDL_Keycode is defined in a header not distributed with crepe, which +// means this typedef is unusable when crepe is packaged. Wouter will fix this +// later. +typedef SDL_Keycode CREPE_KEYCODES; + class Texture; +class LoopManager; + +/** + * \class SDLContext + * \brief Facade for the SDL library + * + * SDLContext is a singleton that handles the SDL window and renderer, provides methods + * for event handling, and rendering to the screen. It is never used directly by the user + */ class SDLContext { public: - // singleton + /** + * \brief Gets the singleton instance of SDLContext. + * \return Reference to the SDLContext instance. + */ static SDLContext & get_instance(); + SDLContext(const SDLContext &) = delete; SDLContext(SDLContext &&) = delete; SDLContext & operator=(const SDLContext &) = delete; SDLContext & operator=(SDLContext &&) = delete; - //TODO decide events wouter? - private: + //! will only use handle_events + friend class LoopManager; + /** + * \brief Handles SDL events such as window close and input. + * \param running Reference to a boolean flag that controls the main loop. + */ void handle_events(bool & running); 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: + /** + * \brief Constructs an SDLContext instance. + * Initializes SDL, creates a window and renderer. + */ SDLContext(); - virtual ~SDLContext(); + + /** + * \brief Destroys the SDLContext instance. + * Cleans up SDL resources, including the window and renderer. + */ + ~SDLContext(); private: + //! Will use the funtions: texture_from_path, get_width,get_height. friend class Texture; - SDL_Texture * texture_from_path(const char *); - //SDL_Texture* setTextureFromPath(const char*, SDL_Rect& clip, const int row, const int col); + + //! Will use the funtions: texture_from_path, get_width,get_height. + friend class Animator; + + /** + * \brief Loads a texture from a file path. + * \param path Path to the image file. + * \return Pointer to the created SDL_Texture. + */ + std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> + texture_from_path(const std::string & path); + /** + * \brief Gets the width of a texture. + * \param texture Reference to the Texture object. + * \return Width of the texture as an integer. + */ + int get_width(const Texture &) const; + + /** + * \brief Gets the height of a texture. + * \param texture Reference to the Texture object. + * \return Height of the texture as an integer. + */ + int get_height(const Texture &) const; private: + //! Will use draw,clear_screen, present_screen, camera. friend class RenderSystem; - void draw(const Sprite &, const Transform &); + + /** + * \brief Draws a sprite to the screen using the specified transform and camera. + * \param sprite Reference to the Sprite to draw. + * \param transform Reference to the Transform for positioning. + * \param camera Reference to the Camera for view adjustments. + */ + void draw(const Sprite & sprite, const Transform & transform, + const Camera & camera); + + //! Clears the screen, preparing for a new frame. void clear_screen(); + + //! Presents the rendered frame to the screen. void present_screen(); + /** + * \brief Sets the current camera for rendering. + * \param camera Reference to the Camera object. + */ + void camera(const Camera & camera); + private: - SDL_Window * game_window = nullptr; - SDL_Renderer * game_renderer = nullptr; + //! sdl Window + std::unique_ptr<SDL_Window, std::function<void(SDL_Window *)>> game_window; + + //! renderer for the crepe engine + std::unique_ptr<SDL_Renderer, std::function<void(SDL_Renderer *)>> + game_renderer; + + //! viewport for the camera window + SDL_Rect viewport = {0, 0, 640, 480}; }; } // namespace crepe diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp new file mode 100644 index 0000000..bf45362 --- /dev/null +++ b/src/crepe/system/AnimatorSystem.cpp @@ -0,0 +1,37 @@ + +#include <cstdint> +#include <functional> +#include <vector> + +#include "api/Animator.h" +#include "facade/SDLContext.h" +#include "util/log.h" + +#include "AnimatorSystem.h" +#include "ComponentManager.h" + +using namespace crepe; + +AnimatorSystem::AnimatorSystem() { dbg_trace(); } +AnimatorSystem::~AnimatorSystem() { dbg_trace(); } + +AnimatorSystem & AnimatorSystem::get_instance() { + static AnimatorSystem instance; + return instance; +} + +void AnimatorSystem::update() { + ComponentManager & mgr = ComponentManager::get_instance(); + + std::vector<std::reference_wrapper<Animator>> animations + = mgr.get_components_by_type<Animator>(); + + uint64_t tick = SDLContext::get_instance().get_ticks(); + for (Animator & a : animations) { + if (a.active) { + a.curr_row = (tick / 100) % a.row; + a.animator_rect.x = (a.curr_row * a.animator_rect.w) + a.curr_col; + a.spritesheet.sprite_rect = a.animator_rect; + } + } +} diff --git a/src/crepe/system/AnimatorSystem.h b/src/crepe/system/AnimatorSystem.h new file mode 100644 index 0000000..969e9d1 --- /dev/null +++ b/src/crepe/system/AnimatorSystem.h @@ -0,0 +1,44 @@ +#pragma once + +#include "System.h" + +//TODO: +// control if flip works with animation system + +namespace crepe { + +/** + * \brief The AnimatorSystem is responsible for managing and updating all Animator components. + * + * This system is responsible for controlling the behavior of the animations for all entities + * that have the Animator component attached. It updates the animations by controlling their + * frame changes, looping behavior, and overall animation state. + */ +class AnimatorSystem : public System { + +public: + /** + * \brief Retrieves the singleton instance of the AnimatorSystem. + * + * \return A reference to the single instance of the AnimatorSystem. + * + * This method ensures that there is only one instance of the AnimatorSystem, following the + * singleton design pattern. It can be used to access the system globally. + */ + static AnimatorSystem & get_instance(); + + /** + * \brief Updates the Animator components. + * + * This method is called periodically (likely every frame) to update the state of all + * Animator components, moving the animations forward and managing their behavior (e.g., looping). + */ + void update() override; + +private: + // private because singleton + AnimatorSystem(); // dbg_trace + ~AnimatorSystem(); // dbg_trace +}; + +} // namespace crepe diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp index 93c0955..9fdd7eb 100644 --- a/src/crepe/system/AudioSystem.cpp +++ b/src/crepe/system/AudioSystem.cpp @@ -1,5 +1,3 @@ -#pragma once - #include "AudioSystem.h" #include "ComponentManager.h" diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h index 8fb681f..e037f51 100644 --- a/src/crepe/system/AudioSystem.h +++ b/src/crepe/system/AudioSystem.h @@ -8,13 +8,11 @@ namespace crepe { class AudioSystem : public System { public: - AudioSystem(SoundContext & ctx); - -public: - void update(); + using System::System; + void update() override; private: - SoundContext & ctx; + SoundContext context {}; }; } // namespace crepe diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index ca62add..f507b90 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(crepe PUBLIC CollisionSystem.cpp RenderSystem.cpp AudioSystem.cpp + AnimatorSystem.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -15,4 +16,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES CollisionSystem.h RenderSystem.h AudioSystem.h + AnimatorSystem.h ) 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/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index 5a07cc2..10211a3 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -20,7 +20,25 @@ RenderSystem & RenderSystem::get_instance() { return instance; } -void RenderSystem::update() { +void RenderSystem::clear_screen() const { + SDLContext::get_instance().clear_screen(); +} + +void RenderSystem::present_screen() const { + SDLContext::get_instance().present_screen(); +} +void RenderSystem::update_camera() { + ComponentManager & mgr = ComponentManager::get_instance(); + + std::vector<std::reference_wrapper<Camera>> cameras + = mgr.get_components_by_type<Camera>(); + + for (Camera & cam : cameras) { + SDLContext::get_instance().camera(cam); + this->curr_cam = &cam; + } +} +void RenderSystem::render_sprites() const { ComponentManager & mgr = ComponentManager::get_instance(); @@ -28,14 +46,16 @@ void RenderSystem::update() { = mgr.get_components_by_type<Sprite>(); SDLContext & render = SDLContext::get_instance(); - render.clear_screen(); - for (const Sprite & sprite : sprites) { - std::vector<std::reference_wrapper<Transform>> transforms + auto transforms = mgr.get_components_by_id<Transform>(sprite.game_object_id); - for (const Transform & transform : transforms) { - render.draw(sprite, transform); - } + render.draw(sprite, transforms[0], *curr_cam); } - render.present_screen(); +} + +void RenderSystem::update() { + this->clear_screen(); + this->update_camera(); + this->render_sprites(); + this->present_screen(); } diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h index 4b910a4..70db21a 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -1,17 +1,64 @@ #pragma once +#include "api/Camera.h" + #include "System.h" namespace crepe { +/** + * \class RenderSystem + * \brief Manages rendering operations for all game objects. + * + * RenderSystem is responsible for rendering sprites, clearing and presenting the screen, + * and managing the active camera. It functions as a singleton, providing centralized + * rendering services for the application. + */ class RenderSystem : public System { public: + /** + * \brief Gets the singleton instance of RenderSystem. + * \return Reference to the RenderSystem instance. + */ static RenderSystem & get_instance(); - void update(); + + /** + * \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; private: + // Private constructor to enforce singleton pattern. RenderSystem(); ~RenderSystem(); + + //! Clears the screen in preparation for rendering. + void clear_screen() const; + + //! Presents the rendered frame to the display. + void present_screen() const; + + //! Updates the active camera used for rendering. + void update_camera(); + + //! Renders all active sprites to the screen. + void render_sprites() const; + + /** + * \todo Include color handling for sprites. + * \todo Implement particle emitter rendering with sprites. + * \todo Add text rendering using SDL_ttf for text components. + * \todo Implement a text component and a button component. + * \todo Ensure each sprite is checked for active status before rendering. + * \todo Sort all layers by order before rendering. + * \todo Consider adding text input functionality. + */ + +private: + //! Pointer to the current active camera for rendering + Camera * curr_cam = nullptr; + // TODO: needs a better solution }; } // namespace crepe diff --git a/src/crepe/util/Proxy.h b/src/crepe/util/Proxy.h index fbfed0c..f84e462 100644 --- a/src/crepe/util/Proxy.h +++ b/src/crepe/util/Proxy.h @@ -16,9 +16,9 @@ template <typename T> class Proxy { public: //! Set operator - Proxy & operator = (const T &); + Proxy & operator=(const T &); //! Get operator - operator const T & (); + operator const T &(); public: Proxy(ValueBroker<T>); @@ -27,7 +27,6 @@ private: ValueBroker<T> broker; }; -} +} // namespace crepe #include "Proxy.hpp" - diff --git a/src/crepe/util/Proxy.hpp b/src/crepe/util/Proxy.hpp index 4aec9e9..b9923db 100644 --- a/src/crepe/util/Proxy.hpp +++ b/src/crepe/util/Proxy.hpp @@ -5,18 +5,17 @@ namespace crepe { template <typename T> -Proxy<T>::Proxy(ValueBroker<T> broker) : broker(broker) { } +Proxy<T>::Proxy(ValueBroker<T> broker) : broker(broker) {} template <typename T> -Proxy<T> & Proxy<T>::operator = (const T & val) { +Proxy<T> & Proxy<T>::operator=(const T & val) { this->broker.set(val); return *this; } template <typename T> -Proxy<T>::operator const T & () { +Proxy<T>::operator const T &() { return this->broker.get(); } -} - +} // namespace crepe diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 36f9d4d..3a5b543 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -28,4 +28,6 @@ add_example(proxy) add_example(db) add_example(ecs) add_example(scene_manager) +add_example(particles) +add_example(gameloop) diff --git a/src/example/db.cpp b/src/example/db.cpp index c046421..8c06a84 100644 --- a/src/example/db.cpp +++ b/src/example/db.cpp @@ -1,12 +1,12 @@ -#include <crepe/facade/DB.h> #include <crepe/api/Config.h> +#include <crepe/facade/DB.h> #include <crepe/util/log.h> using namespace crepe; using namespace std; // run before main -static auto _ = [] () { +static auto _ = []() { auto & cfg = Config::get_instance(); cfg.log.level = LogLevel::TRACE; return 0; 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/proxy.cpp b/src/example/proxy.cpp index 9f54f96..0afff41 100644 --- a/src/example/proxy.cpp +++ b/src/example/proxy.cpp @@ -5,15 +5,15 @@ #include <crepe/ValueBroker.h> #include <crepe/api/Config.h> -#include <crepe/util/log.h> #include <crepe/util/Proxy.h> +#include <crepe/util/log.h> using namespace std; using namespace crepe; -void test_ro_ref(const int & val) { } -void test_rw_ref(int & val) { } -void test_ro_val(int val) { } +void test_ro_ref(const int & val) {} +void test_rw_ref(int & val) {} +void test_ro_val(int val) {} int main() { auto & cfg = Config::get_instance(); @@ -21,18 +21,19 @@ int main() { int real_value = 0; - ValueBroker<int> broker { - [&real_value] (const int & target) { - dbg_logf("set %s to %s", to_string(real_value).c_str(), to_string(target).c_str()); + ValueBroker<int> broker{ + [&real_value](const int & target) { + dbg_logf("set %s to %s", to_string(real_value).c_str(), + to_string(target).c_str()); real_value = target; }, - [&real_value] () -> const int & { + [&real_value]() -> const int & { dbg_logf("get %s", to_string(real_value).c_str()); return real_value; }, }; - Proxy<int> proxy { broker }; + Proxy<int> proxy{broker}; broker.set(54); proxy = 84; @@ -43,4 +44,3 @@ int main() { 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/example/savemgr.cpp b/src/example/savemgr.cpp index c8dd2bc..436fb5a 100644 --- a/src/example/savemgr.cpp +++ b/src/example/savemgr.cpp @@ -4,21 +4,21 @@ */ #include <cassert> -#include <crepe/util/log.h> -#include <crepe/util/Proxy.h> -#include <crepe/api/SaveManager.h> #include <crepe/api/Config.h> +#include <crepe/api/SaveManager.h> +#include <crepe/util/Proxy.h> +#include <crepe/util/log.h> using namespace crepe; // unrelated setup code -int _ = [] () { +int _ = []() { // make sure all log messages get printed auto & cfg = Config::get_instance(); cfg.log.level = LogLevel::TRACE; return 0; // satisfy compiler -} (); +}(); int main() { const char * key = "mygame.test"; @@ -27,7 +27,7 @@ int main() { dbg_logf("has key = %s", mgr.has(key) ? "true" : "false"); ValueBroker<int> prop = mgr.get<int>(key, 0); - Proxy<int> val = mgr.get<int>(key, 0); + Proxy<int> val = mgr.get<int>(key, 0); dbg_logf("val = %d", mgr.get<int>(key).get()); prop.set(1); @@ -42,4 +42,3 @@ int main() { return 0; } - 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/AudioTest.cpp b/src/test/AudioTest.cpp new file mode 100644 index 0000000..a9d5e6c --- /dev/null +++ b/src/test/AudioTest.cpp @@ -0,0 +1,24 @@ +#include "system/AudioSystem.h" +#include <gtest/gtest.h> + +#include <crepe/ComponentManager.h> +#include <crepe/api/AudioSource.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class AudioTest : public Test { +public: + ComponentManager component_manager{}; + AudioSystem system {component_manager}; + + void SetUp() override { + auto & mgr = this->component_manager; + GameObject entity = mgr.new_object("name"); + } +}; + +TEST_F(AudioTest, Default) { +} + diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 0e4eaed..f63a733 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,6 +1,6 @@ target_sources(test_main PUBLIC - dummy.cpp - # audio.cpp - PhysicsTest.cpp + # PhysicsTest.cpp + # ParticleTest.cpp + AudioTest.cpp ) 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); +} diff --git a/src/test/audio.cpp b/src/test/audio.cpp deleted file mode 100644 index d6ff689..0000000 --- a/src/test/audio.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include <gtest/gtest.h> - -using namespace std; -using namespace std::chrono_literals; - -// using namespace crepe; - -// TODO: mock internal audio class - -TEST(audio, play) { ASSERT_TRUE(true); } diff --git a/src/test/dummy.cpp b/src/test/dummy.cpp deleted file mode 100644 index a00a9c6..0000000 --- a/src/test/dummy.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include <gtest/gtest.h> - -TEST(dummy, foo) { ASSERT_TRUE(1); } |