diff options
Diffstat (limited to 'src/crepe')
69 files changed, 1937 insertions, 1138 deletions
diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt index da9d492..6cbb9fe 100644 --- a/src/crepe/CMakeLists.txt +++ b/src/crepe/CMakeLists.txt @@ -2,6 +2,7 @@ target_sources(crepe PUBLIC Particle.cpp Component.cpp Collider.cpp + Resource.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -9,6 +10,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Collider.h ValueBroker.h ValueBroker.hpp + Resource.h ) add_subdirectory(api) diff --git a/src/crepe/Component.h b/src/crepe/Component.h index c30419d..eff5a58 100644 --- a/src/crepe/Component.h +++ b/src/crepe/Component.h @@ -16,7 +16,12 @@ class Component { public: //! Whether the component is active bool active = true; - //! The id of the GameObject this component belongs to + /** + * \brief The id of the GameObject this component belongs to + * + * \note Only systems are supposed to use this member, but since friend + * relations aren't inherited this needs to be public. + */ const game_object_id_t game_object_id; protected: @@ -24,7 +29,7 @@ protected: * \param id The id of the GameObject this component belongs to */ Component(game_object_id_t id); - //! Only the ComponentManager can create components + //! Only ComponentManager can create components friend class ComponentManager; Component(const Component &) = delete; diff --git a/src/crepe/Resource.cpp b/src/crepe/Resource.cpp new file mode 100644 index 0000000..85913ed --- /dev/null +++ b/src/crepe/Resource.cpp @@ -0,0 +1,6 @@ +#include "Resource.h" +#include "manager/Mediator.h" + +using namespace crepe; + +Resource::Resource(const Asset & asset, Mediator & mediator) {} diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h new file mode 100644 index 0000000..d65206b --- /dev/null +++ b/src/crepe/Resource.h @@ -0,0 +1,30 @@ +#pragma once + +namespace crepe { + +class ResourceManager; +class Asset; +class Mediator; + +/** + * \brief Resource interface + * + * Resource is an interface class used to represent a (deserialized) game resource (e.g. + * textures, sounds). Resources are always created from \ref Asset "assets" by ResourceManager. + * + * The game programmer has the ability to use the ResourceManager to keep instances of concrete + * resources between scenes, preventing them from being reinstantiated during a scene + * transition. + */ +class Resource { +public: + Resource(const Asset & src, Mediator & mediator); + virtual ~Resource() = default; + + Resource(const Resource &) = delete; + Resource(Resource &&) = delete; + Resource & operator=(const Resource &) = delete; + Resource & operator=(Resource &&) = delete; +}; + +} // namespace crepe diff --git a/src/crepe/api/AI.cpp b/src/crepe/api/AI.cpp new file mode 100644 index 0000000..2195249 --- /dev/null +++ b/src/crepe/api/AI.cpp @@ -0,0 +1,89 @@ +#include <stdexcept> +#include <type_traits> + +#include "AI.h" +#include "types.h" + +namespace crepe { + +AI::AI(game_object_id_t id, float max_force) : Component(id), max_force(max_force) {} + +void AI::make_circle_path(float radius, const vec2 & center, float start_angle, + bool clockwise) { + if (radius <= 0) { + throw std::runtime_error("Radius must be greater than 0"); + } + + // The step size is determined by the radius (step size is in radians) + float step = RADIUS_TO_STEP / radius; + // Force at least MIN_STEP steps (in case of a small radius) + if (step > 2 * M_PI / MIN_STEP) { + step = 2 * M_PI / MIN_STEP; + } + // The path node distance is determined by the step size and the radius + this->path_node_distance = radius * step * PATH_NODE_DISTANCE_FACTOR; + + if (clockwise) { + for (float i = start_angle; i < 2 * M_PI + start_angle; i += step) { + path.push_back(vec2{static_cast<float>(center.x + radius * cos(i)), + static_cast<float>(center.y + radius * sin(i))}); + } + } else { + for (float i = start_angle; i > start_angle - 2 * M_PI; i -= step) { + path.push_back(vec2{static_cast<float>(center.x + radius * cos(i)), + static_cast<float>(center.y + radius * sin(i))}); + } + } +} + +void AI::make_oval_path(float radius_x, float radius_y, const vec2 & center, float start_angle, + bool clockwise, float rotation) { + if (radius_x <= 0 && radius_y <= 0) { + throw std::runtime_error("Radius must be greater than 0"); + } + + float max_radius = std::max(radius_x, radius_y); + // The step size is determined by the radius (step size is in radians) + float step = RADIUS_TO_STEP / max_radius; + // Force at least MIN_STEP steps (in case of a small radius) + if (step > 2 * M_PI / MIN_STEP) { + step = 2 * M_PI / MIN_STEP; + } + // The path node distance is determined by the step size and the radius + this->path_node_distance = max_radius * step * PATH_NODE_DISTANCE_FACTOR; + + std::function<vec2(vec2, vec2)> rotate_point = [rotation](vec2 point, vec2 center) { + float s = sin(rotation); + float c = cos(rotation); + + // Translate point back to origin + point.x -= center.x; + point.y -= center.y; + + // Rotate point + float xnew = point.x * c - point.y * s; + float ynew = point.x * s + point.y * c; + + // Translate point back + point.x = xnew + center.x; + point.y = ynew + center.y; + + return point; + }; + + if (clockwise) { + for (float i = start_angle; i < 2 * M_PI + start_angle; i += step) { + vec2 point = {static_cast<float>(center.x + radius_x * cos(i)), + static_cast<float>(center.y + radius_y * sin(i))}; + path.push_back(rotate_point(point, center)); + } + } else { + for (float i = start_angle; i > start_angle - 2 * M_PI; i -= step) { + vec2 point = {static_cast<float>(center.x + radius_x * cos(i)), + static_cast<float>(center.y + radius_y * sin(i))}; + path.push_back(rotate_point(point, center)); + } + } +} + +} // namespace crepe diff --git a/src/crepe/api/AI.h b/src/crepe/api/AI.h new file mode 100644 index 0000000..c780a91 --- /dev/null +++ b/src/crepe/api/AI.h @@ -0,0 +1,128 @@ +#pragma once + +#include "Component.h" +#include "types.h" + +namespace crepe { + +/** + * \brief The AI component is used to control the movement of an entity using AI. + * + * The AI component can be used to control the movement of an entity. The AI component can be used + * to implement different behaviors such as seeking, fleeing, arriving, and path following. + */ +class AI : public Component { +public: + //! The different types of behaviors that can be used + enum BehaviorTypeMask { + SEEK = 0x00002, + FLEE = 0x00004, + ARRIVE = 0x00008, + PATH_FOLLOW = 0x00010, + }; + +public: + /** + * \param id The id of the game object + * \param max_force The maximum force that can be applied to the entity + */ + AI(game_object_id_t id, float max_force); + + /** + * \brief Check if a behavior is on (aka activated) + * + * \param behavior The behavior to check + * \return true if the behavior is on, false otherwise + */ + bool on(BehaviorTypeMask behavior) const { return (flags & behavior); } + //! Turn on the seek behavior + void seek_on() { flags |= SEEK; } + //! Turn off the seek behavior + void seek_off() { flags &= ~SEEK; } + //! Turn on the flee behavior + void flee_on() { flags |= FLEE; } + //! Turn off the flee behavior + void flee_off() { flags &= ~FLEE; } + //! Turn on the arrive behavior + void arrive_on() { flags |= ARRIVE; } + //! Turn off the arrive behavior + void arrive_off() { flags &= ~ARRIVE; } + //! Turn on the path follow behavior + void path_follow_on() { flags |= PATH_FOLLOW; } + //! Turn off the path follow behavior + void path_follow_off() { flags &= ~PATH_FOLLOW; } + + /** + * \brief Add a path node (for the path following behavior) + * + * \note The path is not relative to the entity's position (it is an absolute path) + * + * \param node The path node to add + */ + void add_path_node(const vec2 & node) { path.push_back(node); } + /** + * \brief Make a circle path (for the path following behavior) + * + * \note The path is not relative to the entity's position (it is an absolute path) + * + * \param radius The radius of the circle (in game units) + * \param center The center of the circle (in game units) + * \param start_angle The start angle of the circle (in radians) + * \param clockwise The direction of the circle + */ + void make_circle_path(float radius, const vec2 & center = {0, 0}, float start_angle = 0, + bool clockwise = true); + /** + * \brief Make an oval path (for the path following behavior) + * + * \note The path is not relative to the entity's position (it is an absolute path) + * + * \param radius_x The x radius of the oval (in game units) + * \param radius_y The y radius of the oval (in game units) + * \param center The center of the oval (in game units) + * \param start_angle The start angle of the oval (in radians) + * \param clockwise The direction of the oval + * \param rotation The rotation of the oval (in radians) + */ + void make_oval_path(float radius_x, float radius_y, const vec2 & center = {0, 0}, + float start_angle = 0, bool clockwise = true, float rotation = 0); + +public: + //! The maximum force that can be applied to the entity (higher values will make the entity adjust faster) + float max_force; + + //! The target to seek at + vec2 seek_target; + //! The target to arrive at + vec2 arrive_target; + //! The target to flee from + vec2 flee_target; + //! The distance at which the entity will start to flee from the target + float square_flee_panic_distance = 200.0f * 200.0f; + //! The deceleration rate for the arrive behavior (higher values will make the entity decelerate faster (less overshoot)) + float arrive_deceleration = 40.0f; + //! The path to follow (for the path following behavior) + std::vector<vec2> path; + //! The distance from the path node at which the entity will move to the next node (automatically set by make_circle_path()) + float path_node_distance = 400.0f; + //! Looping behavior for the path + bool path_loop = true; + +private: + //! The flags for the behaviors + int flags = 0; + //! The current path index + size_t path_index = 0; + + //! The AISystem is the only class that should access the flags and path_index variables + friend class AISystem; + + //! The minimum amount of steps for the path following behavior + static constexpr int MIN_STEP = 16; + //! The radius to step size ratio for the path following behavior + static constexpr float RADIUS_TO_STEP = 400.0f; + //! The path node distance factor for the path following behavior + static constexpr float PATH_NODE_DISTANCE_FACTOR = 0.75f; +}; + +} // namespace crepe diff --git a/src/crepe/api/Animator.cpp b/src/crepe/api/Animator.cpp index b8a91dc..4ce4bf0 100644 --- a/src/crepe/api/Animator.cpp +++ b/src/crepe/api/Animator.cpp @@ -7,23 +7,21 @@ using namespace crepe; -Animator::Animator(game_object_id_t id, Sprite & spritesheet, unsigned int max_row, - unsigned int max_col, const Animator::Data & data) +Animator::Animator(game_object_id_t id, Sprite & spritesheet, const ivec2 & single_frame_size, + const uvec2 & grid_size, const Animator::Data & data) : Component(id), spritesheet(spritesheet), - max_rows(max_row), - max_columns(max_col), + grid_size(grid_size), data(data) { dbg_trace(); - this->spritesheet.mask.h /= this->max_columns; - this->spritesheet.mask.w /= this->max_rows; - this->spritesheet.mask.x = this->data.row * this->spritesheet.mask.w; - this->spritesheet.mask.y = this->data.col * this->spritesheet.mask.h; + this->spritesheet.mask.w = single_frame_size.x; + this->spritesheet.mask.h = single_frame_size.y; + this->spritesheet.mask.x = 0; + this->spritesheet.mask.y = 0; - // need to do this for to get the aspect ratio for a single clipping in the spritesheet this->spritesheet.aspect_ratio - = static_cast<double>(this->spritesheet.mask.w) / this->spritesheet.mask.h; + = static_cast<float>(single_frame_size.x) / single_frame_size.y; } Animator::~Animator() { dbg_trace(); } @@ -54,6 +52,6 @@ void Animator::set_anim(int col) { void Animator::next_anim() { Animator::Data & ctx = this->data; - ctx.row = ctx.row++ % this->max_rows; + ctx.row = ctx.row++ % this->grid_size.x; this->spritesheet.mask.x = ctx.row * this->spritesheet.mask.w; } diff --git a/src/crepe/api/Animator.h b/src/crepe/api/Animator.h index 7c850b8..5918800 100644 --- a/src/crepe/api/Animator.h +++ b/src/crepe/api/Animator.h @@ -75,27 +75,28 @@ public: * * \param id The unique identifier for the component, typically assigned automatically. * \param spritesheet the reference to the spritesheet - * \param max_row maximum of rows inside the given spritesheet - * \param max_col maximum of columns inside the given spritesheet + * \param single_frame_size the width and height in pixels of a single frame inside the + * spritesheet + * \param grid_size the max rows and columns inside the given spritesheet * \param data extra animation data for more control * * This constructor sets up the Animator with the given parameters, and initializes the * animation system. */ - Animator(game_object_id_t id, Sprite & spritesheet, unsigned int max_row, - unsigned int max_col, const Animator::Data & data); + Animator(game_object_id_t id, Sprite & spritesheet, const ivec2 & single_frame_size, + const uvec2 & grid_size, const Animator::Data & data); ~Animator(); // dbg_trace public: - //! The maximum number of columns in the sprite sheet. - const unsigned int max_columns; - //! The maximum number of rows in the sprite sheet. - const unsigned int max_rows; Animator::Data data; private: //! A reference to the Sprite sheet containing. Sprite & spritesheet; + + //! The maximum number of rows and columns inside the spritesheet + const uvec2 grid_size; + //! Uses the spritesheet friend AnimatorSystem; }; diff --git a/src/crepe/api/AssetManager.cpp b/src/crepe/api/AssetManager.cpp deleted file mode 100644 index 3925758..0000000 --- a/src/crepe/api/AssetManager.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "util/Log.h" - -#include "AssetManager.h" - -using namespace crepe; - -AssetManager & AssetManager::get_instance() { - static AssetManager instance; - return instance; -} - -AssetManager::~AssetManager() { - dbg_trace(); - this->asset_cache.clear(); -} - -AssetManager::AssetManager() { dbg_trace(); } diff --git a/src/crepe/api/AssetManager.h b/src/crepe/api/AssetManager.h deleted file mode 100644 index 3b1cc4b..0000000 --- a/src/crepe/api/AssetManager.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include <any> -#include <memory> -#include <string> -#include <unordered_map> - -namespace crepe { - -/** - * \brief The AssetManager is responsible for storing and managing assets over multiple scenes. - * - * The AssetManager ensures that assets are loaded once and can be accessed across different - * scenes. It caches assets to avoid reloading them every time a scene is loaded. Assets are - * retained in memory until the AssetManager is destroyed, at which point the cached assets are - * cleared. - */ -class AssetManager { - -private: - //! A cache that holds all the assets, accessible by their file path, over multiple scenes. - std::unordered_map<std::string, std::any> asset_cache; - -private: - AssetManager(); - virtual ~AssetManager(); - -public: - AssetManager(const AssetManager &) = delete; - AssetManager(AssetManager &&) = delete; - AssetManager & operator=(const AssetManager &) = delete; - AssetManager & operator=(AssetManager &&) = delete; - - /** - * \brief Retrieves the singleton instance of the AssetManager. - * - * \return A reference to the single instance of the AssetManager. - */ - static AssetManager & get_instance(); - -public: - /** - * \brief Caches an asset by loading it from the given file path. - * - * \param file_path The path to the asset file to load. - * \param reload If true, the asset will be reloaded from the file, even if it is already - * cached. - * \tparam T The type of asset to cache (e.g., texture, sound, etc.). - * - * \return A shared pointer to the cached asset. - * - * This template function caches the asset at the given file path. If the asset is already - * cached and `reload` is false, the existing cached version will be returned. Otherwise, the - * asset will be reloaded and added to the cache. - */ - template <typename T> - std::shared_ptr<T> cache(const std::string & file_path, bool reload = false); -}; - -} // namespace crepe - -#include "AssetManager.hpp" diff --git a/src/crepe/api/AssetManager.hpp b/src/crepe/api/AssetManager.hpp deleted file mode 100644 index 1c0e978..0000000 --- a/src/crepe/api/AssetManager.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "AssetManager.h" - -namespace crepe { - -template <typename asset> -std::shared_ptr<asset> AssetManager::cache(const std::string & file_path, bool reload) { - auto it = asset_cache.find(file_path); - - if (!reload && it != asset_cache.end()) { - return std::any_cast<std::shared_ptr<asset>>(it->second); - } - - std::shared_ptr<asset> new_asset = std::make_shared<asset>(file_path.c_str()); - - asset_cache[file_path] = new_asset; - - return new_asset; -} - -} // namespace crepe diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp new file mode 100644 index 0000000..7b05cb1 --- /dev/null +++ b/src/crepe/api/AudioSource.cpp @@ -0,0 +1,15 @@ +#include "AudioSource.h" + +using namespace crepe; +using namespace std; + +AudioSource::AudioSource(game_object_id_t id, const Asset & src) + : Component(id), + source(src) {} + +void AudioSource::play(bool looping) { + this->loop = looping; + this->oneshot_play = true; +} + +void AudioSource::stop() { this->oneshot_stop = true; } diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h new file mode 100644 index 0000000..b20e490 --- /dev/null +++ b/src/crepe/api/AudioSource.h @@ -0,0 +1,74 @@ +#pragma once + +#include "../Component.h" +#include "../facade/SoundHandle.h" +#include "../types.h" + +#include "Asset.h" +#include "GameObject.h" + +namespace crepe { + +class AudioSystem; + +//! Audio source component +class AudioSource : public Component { + //! AudioSource components are handled by AudioSystem + friend class AudioSystem; + +protected: + /** + * \param source Sound sample to load + */ + AudioSource(game_object_id_t id, const Asset & source); + //! Only ComponentManager creates components + friend class ComponentManager; + +public: + // std::unique_ptr needs to be able to destoy this component + virtual ~AudioSource() = default; + +public: + //! Start this audio source + void play(bool looping = false); + //! Stop this audio source + void stop(); + +public: + //! Play when this component becomes active + bool play_on_awake = false; + //! Repeat the current audio clip during playback + bool loop = false; + //! Normalized volume (0.0 - 1.0) + float volume = 1.0; + +private: + //! This audio source's clip + const Asset source; + + /** + * \name One-shot state variables + * + * These variables trigger function calls when set to true, and are unconditionally reset on + * every system update. + * + * \{ + */ + //! Play this sample + bool oneshot_play = false; + //! Stop this sample + bool oneshot_stop = false; + //! \} + /** + * \name State diffing variables + * \{ + */ + typeof(active) last_active = false; + typeof(volume) last_volume = volume; + typeof(loop) last_loop = loop; + //! \} + //! This source's voice handle + SoundHandle voice{}; +}; + +} // namespace crepe diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 593c4e6..fb11c8d 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -1,13 +1,11 @@ target_sources(crepe PUBLIC - # AudioSource.cpp + AudioSource.cpp BehaviorScript.cpp GameObject.cpp Rigidbody.cpp ParticleEmitter.cpp Transform.cpp Color.cpp - Texture.cpp - AssetManager.cpp Sprite.cpp Config.cpp Metadata.cpp @@ -15,19 +13,17 @@ target_sources(crepe PUBLIC Animator.cpp BoxCollider.cpp CircleCollider.cpp - IKeyListener.cpp - IMouseListener.cpp LoopManager.cpp - LoopTimer.cpp Asset.cpp EventHandler.cpp Script.cpp Button.cpp UIObject.cpp + AI.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES - # AudioSource.h + AudioSource.h BehaviorScript.h Config.h Script.h @@ -39,9 +35,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Vector2.h Vector2.hpp Color.h - Texture.h - AssetManager.h - AssetManager.hpp Scene.h Metadata.h Camera.h @@ -51,11 +44,9 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES EventHandler.h EventHandler.hpp Event.h - IKeyListener.h - IMouseListener.h LoopManager.h - LoopTimer.h Asset.h Button.h UIObject.h + AI.h ) diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h index 324e639..925ded8 100644 --- a/src/crepe/api/Config.h +++ b/src/crepe/api/Config.h @@ -15,20 +15,10 @@ namespace crepe { * modified *before* execution is handed over from the game programmer to the engine (i.e. the * main loop is started). */ -class Config final { -public: +struct Config final { //! Retrieve handle to global Config instance static Config & get_instance(); -private: - Config() = default; - ~Config() = default; - Config(const Config &) = default; - Config(Config &&) = default; - Config & operator=(const Config &) = default; - Config & operator=(Config &&) = default; - -public: //! Logging-related settings struct { /** @@ -91,6 +81,12 @@ public: //! The maximum number of pixels the mouse can move between MouseDown and MouseUp events to be considered a click. int click_tolerance = 5; } input; + + //! Audio system settings + struct { + //! Max amount of simultanious voices + unsigned int voices = 32; + } audio; }; } // namespace crepe diff --git a/src/crepe/api/IKeyListener.cpp b/src/crepe/api/IKeyListener.cpp deleted file mode 100644 index 8642655..0000000 --- a/src/crepe/api/IKeyListener.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "IKeyListener.h" - -using namespace crepe; - -// Constructor with specified channel -IKeyListener::IKeyListener(event_channel_t channel) - : event_manager(EventManager::get_instance()) { - this->press_id = event_manager.subscribe<KeyPressEvent>( - [this](const KeyPressEvent & event) { return this->on_key_pressed(event); }, channel); - this->release_id = event_manager.subscribe<KeyReleaseEvent>( - [this](const KeyReleaseEvent & event) { return this->on_key_released(event); }, - channel); -} - -// Destructor, unsubscribe events -IKeyListener::~IKeyListener() { - event_manager.unsubscribe(this->press_id); - event_manager.unsubscribe(this->release_id); -} diff --git a/src/crepe/api/IKeyListener.h b/src/crepe/api/IKeyListener.h deleted file mode 100644 index 180a0a6..0000000 --- a/src/crepe/api/IKeyListener.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include "../manager/EventManager.h" - -#include "Event.h" -#include "EventHandler.h" - -namespace crepe { - -/** - * \class IKeyListener - * \brief Interface for keyboard event handling in the application. - */ -class IKeyListener { -public: - /** - * \brief Constructs an IKeyListener with a specified channel. - * \param channel The channel ID for event handling. - */ - IKeyListener(event_channel_t channel = EventManager::CHANNEL_ALL); - virtual ~IKeyListener(); - IKeyListener(const IKeyListener &) = delete; - IKeyListener & operator=(const IKeyListener &) = delete; - IKeyListener & operator=(IKeyListener &&) = delete; - IKeyListener(IKeyListener &&) = delete; - - /** - * \brief Pure virtual function to handle key press events. - * \param event The key press event to handle. - * \return True if the event was handled, false otherwise. - */ - virtual bool on_key_pressed(const KeyPressEvent & event) = 0; - - /** - * \brief Pure virtual function to handle key release events. - * \param event The key release event to handle. - * \return True if the event was handled, false otherwise. - */ - virtual bool on_key_released(const KeyReleaseEvent & event) = 0; - -private: - //! Key press event id - subscription_t press_id = -1; - //! Key release event id - subscription_t release_id = -1; - //! EventManager reference - EventManager & event_manager; -}; - -} // namespace crepe diff --git a/src/crepe/api/IMouseListener.cpp b/src/crepe/api/IMouseListener.cpp deleted file mode 100644 index 989aeb3..0000000 --- a/src/crepe/api/IMouseListener.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "IMouseListener.h" - -using namespace crepe; - -IMouseListener::IMouseListener(event_channel_t channel) - : event_manager(EventManager::get_instance()) { - this->click_id = event_manager.subscribe<MouseClickEvent>( - [this](const MouseClickEvent & event) { return this->on_mouse_clicked(event); }, - channel); - - this->press_id = event_manager.subscribe<MousePressEvent>( - [this](const MousePressEvent & event) { return this->on_mouse_pressed(event); }, - channel); - - this->release_id = event_manager.subscribe<MouseReleaseEvent>( - [this](const MouseReleaseEvent & event) { return this->on_mouse_released(event); }, - channel); - - this->move_id = event_manager.subscribe<MouseMoveEvent>( - [this](const MouseMoveEvent & event) { return this->on_mouse_moved(event); }, channel); -} - -IMouseListener::~IMouseListener() { - // Unsubscribe event handlers - event_manager.unsubscribe(this->click_id); - event_manager.unsubscribe(this->press_id); - event_manager.unsubscribe(this->release_id); - event_manager.unsubscribe(this->move_id); -} diff --git a/src/crepe/api/IMouseListener.h b/src/crepe/api/IMouseListener.h deleted file mode 100644 index e19897d..0000000 --- a/src/crepe/api/IMouseListener.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include "../manager/EventManager.h" - -#include "Event.h" -#include "EventHandler.h" - -namespace crepe { - -/** - * \class IMouseListener - * \brief Interface for mouse event handling in the application. - */ -class IMouseListener { -public: - /** - * \brief Constructs an IMouseListener with a specified channel. - * \param channel The channel ID for event handling. - */ - IMouseListener(event_channel_t channel = EventManager::CHANNEL_ALL); - virtual ~IMouseListener(); - IMouseListener & operator=(const IMouseListener &) = delete; - IMouseListener(const IMouseListener &) = delete; - IMouseListener & operator=(const IMouseListener &&) = delete; - IMouseListener(IMouseListener &&) = delete; - - /** - * \brief Move assignment operator (deleted). - */ - IMouseListener & operator=(IMouseListener &&) = delete; - - /** - * \brief Handles a mouse click event. - * \param event The mouse click event to handle. - * \return True if the event was handled, false otherwise. - */ - virtual bool on_mouse_clicked(const MouseClickEvent & event) = 0; - - /** - * \brief Handles a mouse press event. - * \param event The mouse press event to handle. - * \return True if the event was handled, false otherwise. - */ - virtual bool on_mouse_pressed(const MousePressEvent & event) = 0; - - /** - * \brief Handles a mouse release event. - * \param event The mouse release event to handle. - * \return True if the event was handled, false otherwise. - */ - virtual bool on_mouse_released(const MouseReleaseEvent & event) = 0; - - /** - * \brief Handles a mouse move event. - * \param event The mouse move event to handle. - * \return True if the event was handled, false otherwise. - */ - virtual bool on_mouse_moved(const MouseMoveEvent & event) = 0; - -private: - //! Mouse click event id - subscription_t click_id = -1; - //! Mouse press event id - subscription_t press_id = -1; - //! Mouse release event id - subscription_t release_id = -1; - //! Mouse move event id - subscription_t move_id = -1; - //! EventManager reference - EventManager & event_manager; -}; - -} //namespace crepe diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index 88243c4..b5e5ff7 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -1,11 +1,16 @@ +#include "../facade/SDLContext.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h" +#include "../system/AISystem.h" #include "../system/AnimatorSystem.h" +#include "../system/AudioSystem.h" #include "../system/CollisionSystem.h" #include "../system/InputSystem.h" #include "../system/ParticleSystem.h" #include "../system/PhysicsSystem.h" #include "../system/RenderSystem.h" #include "../system/ScriptSystem.h" -#include "manager/EventManager.h" +#include "../util/Log.h" #include "LoopManager.h" @@ -20,58 +25,62 @@ LoopManager::LoopManager() { this->load_system<RenderSystem>(); this->load_system<ScriptSystem>(); this->load_system<InputSystem>(); + this->event_manager.subscribe<ShutDownEvent>( + [this](const ShutDownEvent & event) { return this->on_shutdown(event); }); + this->load_system<AudioSystem>(); + this->load_system<AISystem>(); } - -void LoopManager::process_input() { this->get_system<InputSystem>().update(); } - void LoopManager::start() { this->setup(); this->loop(); } -void LoopManager::set_running(bool running) { this->game_running = running; } -void LoopManager::fixed_update() { - // TODO: retrieve EventManager from direct member after singleton refactor - EventManager & ev = this->mediator.event_manager; - ev.dispatch_events(); - this->get_system<ScriptSystem>().update(); - this->get_system<PhysicsSystem>().update(); - this->get_system<CollisionSystem>().update(); +void LoopManager::setup() { + this->game_running = true; + this->loop_timer.start(); + this->scene_manager.load_next_scene(); } void LoopManager::loop() { - LoopTimer & timer = this->loop_timer; - timer.start(); + try { + while (game_running) { + this->loop_timer.update(); - while (game_running) { - timer.update(); + while (this->loop_timer.get_lag() >= this->loop_timer.get_fixed_delta_time()) { + this->fixed_update(); + this->loop_timer.advance_fixed_elapsed_time(); + } - while (timer.get_lag() >= timer.get_fixed_delta_time()) { - this->process_input(); - this->fixed_update(); - timer.advance_fixed_update(); + this->frame_update(); + this->loop_timer.enforce_frame_rate(); } - - this->update(); - this->render(); - - timer.enforce_frame_rate(); + } catch (const exception & e) { + Log::logf(Log::Level::ERROR, "Exception caught in main loop: {}", e.what()); + this->event_manager.trigger_event<ShutDownEvent>(ShutDownEvent{}); } } -void LoopManager::setup() { - LoopTimer & timer = this->loop_timer; - this->game_running = true; - this->scene_manager.load_next_scene(); - timer.start(); - timer.set_fps(200); +// will be called at a fixed interval +void LoopManager::fixed_update() { + this->get_system<InputSystem>().update(); + this->event_manager.dispatch_events(); + this->get_system<ScriptSystem>().update(); + this->get_system<AISystem>().update(); + this->get_system<PhysicsSystem>().update(); + this->get_system<CollisionSystem>().update(); + this->get_system<AudioSystem>().update(); } -void LoopManager::render() { - if (!this->game_running) return; - +// will be called every frame +void LoopManager::frame_update() { + this->scene_manager.load_next_scene(); this->get_system<AnimatorSystem>().update(); + //render this->get_system<RenderSystem>().update(); } -void LoopManager::update() {} +bool LoopManager::on_shutdown(const ShutDownEvent & e) { + this->game_running = false; + // propagate to possible user ShutDownEvent listeners + return false; +} diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h index d8910a0..2915315 100644 --- a/src/crepe/api/LoopManager.h +++ b/src/crepe/api/LoopManager.h @@ -4,13 +4,16 @@ #include "../facade/SDLContext.h" #include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/Mediator.h" +#include "../manager/ResourceManager.h" +#include "../manager/SaveManager.h" #include "../manager/SceneManager.h" #include "../system/System.h" -#include "LoopTimer.h" namespace crepe { - /** * \brief Main game loop manager * @@ -18,8 +21,14 @@ namespace crepe { */ class LoopManager { public: - void start(); LoopManager(); + /** + * \brief Start the gameloop + * + * This is the start of the engine where the setup is called and then the loop keeps running until the game stops running. + * The Game programmer needs to call this function to run the game. This should be done after creating and adding all scenes. + */ + void start(); /** * \brief Add a new concrete scene to the scene manager @@ -44,47 +53,20 @@ private: void loop(); /** - * \brief Function for handling input-related system calls. - * - * Processes user inputs from keyboard and mouse. - */ - void process_input(); - - /** * \brief Per-frame update. * * Updates the game state based on the elapsed time since the last frame. */ - void update(); - - /** - * \brief Late update which is called after update(). - * - * This function can be used for final adjustments before rendering. - */ - void late_update(); + virtual void frame_update(); /** * \brief Fixed update executed at a fixed rate. * * This function updates physics and game logic based on LoopTimer's fixed_delta_time. */ - void fixed_update(); - - /** - * \brief Set game running variable - * - * \param running running (false = game shutdown, true = game running) - */ - void set_running(bool running); - - /** - * \brief Function for executing render-related systems. - * - * Renders the current state of the game to the screen. - */ - void render(); + virtual void fixed_update(); + //! Indicates whether the game is running. bool game_running = false; private: @@ -95,18 +77,29 @@ private: ComponentManager component_manager{mediator}; //! Scene manager instance SceneManager scene_manager{mediator}; - - //! SDL context \todo no more singletons! - SDLContext & sdl_context = SDLContext::get_instance(); - //! Loop timer \todo no more singletons! - LoopTimer & loop_timer = LoopTimer::get_instance(); + //! LoopTimerManager instance + LoopTimerManager loop_timer{mediator}; + //! EventManager instance + EventManager event_manager{mediator}; + //! Resource manager instance + ResourceManager resource_manager{mediator}; + //! Save manager instance + SaveManager save_manager{mediator}; + //! SDLContext instance + SDLContext sdl_context{mediator}; private: /** + * \brief Callback function for ShutDownEvent + * + * This function sets the game_running variable to false, stopping the gameloop and therefor quitting the game. + */ + bool on_shutdown(const ShutDownEvent & e); + /** * \brief Collection of System instances * * This map holds System instances indexed by the system's class typeid. It is filled in the - * constructor of \c LoopManager using LoopManager::load_system. + * constructor of LoopManager using LoopManager::load_system. */ std::unordered_map<std::type_index, std::unique_ptr<System>> systems; /** diff --git a/src/crepe/api/LoopTimer.cpp b/src/crepe/api/LoopTimer.cpp deleted file mode 100644 index 15a0e3a..0000000 --- a/src/crepe/api/LoopTimer.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include <chrono> - -#include "../facade/SDLContext.h" -#include "../util/Log.h" - -#include "LoopTimer.h" - -using namespace crepe; - -LoopTimer::LoopTimer() { dbg_trace(); } - -LoopTimer & LoopTimer::get_instance() { - static LoopTimer instance; - return instance; -} - -void LoopTimer::start() { - this->last_frame_time = std::chrono::steady_clock::now(); - this->elapsed_time = std::chrono::milliseconds(0); - this->elapsed_fixed_time = std::chrono::milliseconds(0); - this->delta_time = std::chrono::milliseconds(0); -} - -void LoopTimer::update() { - auto current_frame_time = std::chrono::steady_clock::now(); - // Convert to duration in seconds for delta time - this->delta_time = std::chrono::duration_cast<std::chrono::duration<double>>( - current_frame_time - last_frame_time); - - if (this->delta_time > this->maximum_delta_time) { - this->delta_time = this->maximum_delta_time; - } - - this->delta_time *= this->game_scale; - this->elapsed_time += this->delta_time; - this->last_frame_time = current_frame_time; -} - -double LoopTimer::get_delta_time() const { return this->delta_time.count(); } - -double LoopTimer::get_current_time() const { return this->elapsed_time.count(); } - -void LoopTimer::advance_fixed_update() { this->elapsed_fixed_time += this->fixed_delta_time; } - -double LoopTimer::get_fixed_delta_time() const { return this->fixed_delta_time.count(); } - -void LoopTimer::set_fps(int fps) { - this->fps = fps; - // target time per frame in seconds - this->frame_target_time = std::chrono::duration<double>(1.0) / fps; -} - -int LoopTimer::get_fps() const { return this->fps; } - -void LoopTimer::set_game_scale(double value) { this->game_scale = value; } - -double LoopTimer::get_game_scale() const { return this->game_scale; } -void LoopTimer::enforce_frame_rate() { - std::chrono::steady_clock::time_point current_frame_time - = std::chrono::steady_clock::now(); - std::chrono::milliseconds frame_duration - = std::chrono::duration_cast<std::chrono::milliseconds>(current_frame_time - - this->last_frame_time); - - if (frame_duration < this->frame_target_time) { - std::chrono::milliseconds delay_time - = std::chrono::duration_cast<std::chrono::milliseconds>(this->frame_target_time - - frame_duration); - if (delay_time.count() > 0) { - SDLContext::get_instance().delay(delay_time.count()); - } - } - - this->last_frame_time = current_frame_time; -} - -double LoopTimer::get_lag() const { - return (this->elapsed_time - this->elapsed_fixed_time).count(); -} diff --git a/src/crepe/api/LoopTimer.h b/src/crepe/api/LoopTimer.h deleted file mode 100644 index 9393439..0000000 --- a/src/crepe/api/LoopTimer.h +++ /dev/null @@ -1,144 +0,0 @@ -#pragma once - -#include <chrono> - -namespace crepe { - -class LoopTimer { -public: - /** - * \brief Get the singleton instance of LoopTimer. - * - * \return A reference to the LoopTimer instance. - */ - static LoopTimer & get_instance(); - - /** - * \brief Get the current delta time for the current frame. - * - * \return Delta time in seconds since the last frame. - */ - double get_delta_time() const; - - /** - * \brief Get the current game time. - * - * \note The current game time may vary from real-world elapsed time. It is the cumulative - * sum of each frame's delta time. - * - * \return Elapsed game time in seconds. - */ - double get_current_time() const; - - /** - * \brief Set the target frames per second (FPS). - * - * \param fps The desired frames rendered per second. - */ - void set_fps(int fps); - - /** - * \brief Get the current frames per second (FPS). - * - * \return Current FPS. - */ - int get_fps() const; - - /** - * \brief Get the current game scale. - * - * \return The current game scale, where 0 = paused, 1 = normal speed, and values > 1 speed - * up the game. - */ - double get_game_scale() const; - - /** - * \brief Set the game scale. - * - * \param game_scale The desired game scale (0 = pause, 1 = normal speed, > 1 = speed up). - */ - void set_game_scale(double game_scale); - -private: - friend class LoopManager; - - /** - * \brief Start the loop timer. - * - * Initializes the timer to begin tracking frame times. - */ - void start(); - - /** - * \brief Enforce the frame rate limit. - * - * Ensures that the game loop does not exceed the target FPS by delaying frame updates as - * necessary. - */ - void enforce_frame_rate(); - - /** - * \brief Get the fixed delta time for consistent updates. - * - * Fixed delta time is used for operations that require uniform time steps, such as physics - * calculations. - * - * \return Fixed delta time in seconds. - */ - double get_fixed_delta_time() const; - - /** - * \brief Get the accumulated lag in the game loop. - * - * Lag represents the difference between the target frame time and the actual frame time, - * useful for managing fixed update intervals. - * - * \return Accumulated lag in seconds. - */ - double get_lag() const; - - /** - * \brief Construct a new LoopTimer object. - * - * Private constructor for singleton pattern to restrict instantiation outside the class. - */ - LoopTimer(); - - /** - * \brief Update the timer to the current frame. - * - * Calculates and updates the delta time for the current frame and adds it to the cumulative - * game time. - */ - void update(); - - /** - * \brief Advance the game loop by a fixed update interval. - * - * This method progresses the game state by a consistent, fixed time step, allowing for - * stable updates independent of frame rate fluctuations. - */ - void advance_fixed_update(); - -private: - //! Current frames per second - int fps = 50; - //! Current game scale - double game_scale = 1; - //! Maximum delta time in seconds to avoid large jumps - std::chrono::duration<double> maximum_delta_time{0.25}; - //! Delta time for the current frame in seconds - std::chrono::duration<double> delta_time{0.0}; - //! Target time per frame in seconds - std::chrono::duration<double> frame_target_time = std::chrono::duration<double>(1.0) / fps; - //! Fixed delta time for fixed updates in seconds - std::chrono::duration<double> fixed_delta_time = std::chrono::duration<double>(1.0) / 50.0; - //! Total elapsed game time in seconds - std::chrono::duration<double> elapsed_time{0.0}; - //! Total elapsed time for fixed updates in seconds - std::chrono::duration<double> elapsed_fixed_time{0.0}; - //! Time of the last frame - std::chrono::steady_clock::time_point last_frame_time; -}; - -} // namespace crepe diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp index 4091fd4..753a9e3 100644 --- a/src/crepe/api/Script.cpp +++ b/src/crepe/api/Script.cpp @@ -8,8 +8,7 @@ using namespace crepe; using namespace std; Script::~Script() { - Mediator & mediator = this->mediator; - EventManager & mgr = mediator.event_manager; + EventManager & mgr = this->mediator->event_manager; for (auto id : this->listeners) { mgr.unsubscribe(id); } @@ -21,7 +20,8 @@ void Script::subscribe(const EventHandler<CollisionEvent> & callback) { } void Script::set_next_scene(const string & name) { - Mediator & mediator = this->mediator; - SceneManager & mgr = mediator.scene_manager; + SceneManager & mgr = this->mediator->scene_manager; mgr.set_next_scene(name); } + +SaveManager & Script::get_save_manager() const { return this->mediator->save_manager; } diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index d99ab0e..668e5d1 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -86,6 +86,25 @@ protected: RefVector<T> get_components() const; /** + * \copydoc ComponentManager::get_components_by_id + * \see ComponentManager::get_components_by_id + */ + template <typename T> + RefVector<T> get_components_by_id(game_object_id_t id) const; + /** + * \copydoc ComponentManager::get_components_by_name + * \see ComponentManager::get_components_by_name + */ + template <typename T> + RefVector<T> get_components_by_name(const std::string & name) const; + /** + * \copydoc ComponentManager::get_components_by_tag + * \see ComponentManager::get_components_by_tag + */ + template <typename T> + RefVector<T> get_components_by_tag(const std::string & tag) const; + + /** * \brief Log a message using Log::logf * * \tparam Args Log::logf parameters @@ -113,6 +132,9 @@ protected: */ void set_next_scene(const std::string & name); + //! Retrieve SaveManager reference + SaveManager & get_save_manager() const; + //! \} private: diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index 45f1ff1..225a51c 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -20,10 +20,7 @@ T & Script::get_component() const { template <typename T> RefVector<T> Script::get_components() const { - Mediator & mediator = this->mediator; - ComponentManager & mgr = mediator.component_manager; - - return mgr.get_components_by_id<T>(this->game_object_id); + return this->get_components_by_id<T>(this->game_object_id); } template <typename... Args> @@ -34,8 +31,7 @@ void Script::logf(Args &&... args) { template <typename EventType> void Script::subscribe_internal(const EventHandler<EventType> & callback, event_channel_t channel) { - Mediator & mediator = this->mediator; - EventManager & mgr = mediator.event_manager; + EventManager & mgr = this->mediator->event_manager; subscription_t listener = mgr.subscribe<EventType>( [this, callback](const EventType & data) -> bool { bool & active = this->active; @@ -56,4 +52,26 @@ void Script::subscribe(const EventHandler<EventType> & callback) { this->subscribe_internal(callback, EventManager::CHANNEL_ALL); } +template <typename T> +RefVector<T> Script::get_components_by_id(game_object_id_t id) const { + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + + return mgr.get_components_by_id<T>(id); +} +template <typename T> +RefVector<T> Script::get_components_by_name(const std::string & name) const { + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + + return mgr.get_components_by_name<T>(name); +} +template <typename T> +RefVector<T> Script::get_components_by_tag(const std::string & tag) const { + Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + + return mgr.get_components_by_tag<T>(tag); +} + } // namespace crepe diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp index cc0e20a..ba684ba 100644 --- a/src/crepe/api/Sprite.cpp +++ b/src/crepe/api/Sprite.cpp @@ -1,26 +1,21 @@ #include <cmath> -#include <utility> #include "../util/Log.h" +#include "api/Asset.h" #include "Component.h" #include "Sprite.h" -#include "Texture.h" #include "types.h" using namespace std; using namespace crepe; -Sprite::Sprite(game_object_id_t id, Texture & texture, const Sprite::Data & data) +Sprite::Sprite(game_object_id_t id, const Asset & texture, const Sprite::Data & data) : Component(id), - texture(std::move(texture)), + source(texture), data(data) { dbg_trace(); - - this->mask.w = this->texture.get_size().x; - this->mask.h = this->texture.get_size().y; - this->aspect_ratio = static_cast<double>(this->mask.w) / this->mask.h; } Sprite::~Sprite() { dbg_trace(); } diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h index dbf41e4..a2409c2 100644 --- a/src/crepe/api/Sprite.h +++ b/src/crepe/api/Sprite.h @@ -1,9 +1,9 @@ #pragma once #include "../Component.h" +#include "api/Asset.h" #include "Color.h" -#include "Texture.h" #include "types.h" namespace crepe { @@ -74,24 +74,15 @@ public: * \param texture asset of the image * \param ctx all the sprite data */ - Sprite(game_object_id_t id, Texture & texture, const Data & data); + Sprite(game_object_id_t id, const Asset & texture, const Data & data); ~Sprite(); //! Texture used for the sprite - const Texture texture; + const Asset source; Data data; private: - /** - * \brief ratio of the img - * - * - This will multiply one of \c size variable if it is 0. - * - Will be adjusted if \c Animator component is added to an GameObject that is why this - * value cannot be const. - */ - float aspect_ratio; - //! Reads the mask of sprite friend class SDLContext; @@ -101,6 +92,14 @@ private: //! Reads the all the variables plus the mask friend class AnimatorSystem; + /** + * \aspect_ratio the ratio of the sprite image + * + * - this value will only be set by the \c Animator component for the ratio of the Animation + * - if \c Animator component is not added it will not use this ratio (because 0) and will use aspect_ratio of the Asset. + */ + float aspect_ratio = 0; + struct Rect { int w = 0; int h = 0; diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp deleted file mode 100644 index 2b56271..0000000 --- a/src/crepe/api/Texture.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../facade/SDLContext.h" -#include "../util/Log.h" - -#include "Asset.h" -#include "Texture.h" -#include "types.h" - -using namespace crepe; -using namespace std; - -Texture::Texture(const Asset & src) { - dbg_trace(); - this->load(src); -} - -Texture::~Texture() { - dbg_trace(); - this->texture.reset(); -} - -Texture::Texture(Texture && other) noexcept : texture(std::move(other.texture)) {} - -Texture & Texture::operator=(Texture && other) noexcept { - if (this != &other) { - texture = std::move(other.texture); - } - return *this; -} - -void Texture::load(const Asset & res) { - SDLContext & ctx = SDLContext::get_instance(); - this->texture = ctx.texture_from_path(res.get_path()); -} - -ivec2 Texture::get_size() const { - if (this->texture == nullptr) return {}; - return SDLContext::get_instance().get_size(*this); -} diff --git a/src/crepe/api/Texture.h b/src/crepe/api/Texture.h deleted file mode 100644 index 1817910..0000000 --- a/src/crepe/api/Texture.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -// FIXME: this header can't be included because this is an API header, and SDL2 development -// headers won't be bundled with crepe. Why is this facade in the API namespace? - -#include <SDL2/SDL_render.h> -#include <functional> -#include <memory> - -#include "Asset.h" -#include "types.h" - -namespace crepe { - -class SDLContext; -class Animator; - -/** - * \class Texture - * \brief Manages texture loading and properties. - * - * The Texture class is responsible for loading an image from a source and providing access to - * its dimensions. Textures can be used for rendering. - */ -class Texture { - -public: - /** - * \brief Constructs a Texture from an Asset resource. - * \param src Asset with texture data to load. - */ - Texture(const Asset & src); - - /** - * \brief Destroys the Texture instance, freeing associated resources. - */ - ~Texture(); - // FIXME: this constructor shouldn't be necessary because this class doesn't manage memory - - Texture(Texture && other) noexcept; - Texture & operator=(Texture && other) noexcept; - Texture(const Texture &) = delete; - Texture & operator=(const Texture &) = delete; - - /** - * \brief Gets the width and height of the texture. - * \return Width and height of the texture in pixels. - */ - ivec2 get_size() const; - -private: - /** - * \brief Loads the texture from an Asset resource. - * \param res Unique pointer to an Asset resource to load the texture from. - */ - void load(const Asset & res); - -private: - //! The texture of the class from the library - std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> texture; - - //! Grants SDLContext access to private members. - friend class SDLContext; - - //! Grants Animator access to private members. - friend class Animator; -}; - -} // namespace crepe diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h index c278c87..bf9d124 100644 --- a/src/crepe/api/Vector2.h +++ b/src/crepe/api/Vector2.h @@ -66,6 +66,30 @@ struct Vector2 { //! Checks if this vector is not equal to another vector. bool operator!=(const Vector2<T> & other) const; + + //! Truncates the vector to a maximum length. + void truncate(T max); + + //! Normalizes the vector (resulting in vector with a length of 1). + void normalize(); + + //! Returns the length of the vector. + T length() const; + + //! Returns the squared length of the vector. + T length_squared() const; + + //! Returns the dot product (inwendig product) of this vector and another vector. + T dot(const Vector2<T> & other) const; + + //! Returns the distance between this vector and another vector. + T distance(const Vector2<T> & other) const; + + //! Returns the squared distance between this vector and another vector. + T distance_squared(const Vector2<T> & other) const; + + //! Returns the perpendicular vector to this vector. + Vector2 perpendicular() const; }; } // namespace crepe diff --git a/src/crepe/api/Vector2.hpp b/src/crepe/api/Vector2.hpp index cad15f8..ff53cb0 100644 --- a/src/crepe/api/Vector2.hpp +++ b/src/crepe/api/Vector2.hpp @@ -1,5 +1,7 @@ #pragma once +#include <cmath> + #include "Vector2.h" namespace crepe { @@ -115,4 +117,50 @@ bool Vector2<T>::operator!=(const Vector2<T> & other) const { return !(*this == other); } +template <class T> +void Vector2<T>::truncate(T max) { + if (length() > max) { + normalize(); + *this *= max; + } +} + +template <class T> +void Vector2<T>::normalize() { + T len = length(); + if (len > 0) { + *this /= len; + } +} + +template <class T> +T Vector2<T>::length() const { + return std::sqrt(x * x + y * y); +} + +template <class T> +T Vector2<T>::length_squared() const { + return x * x + y * y; +} + +template <class T> +T Vector2<T>::dot(const Vector2<T> & other) const { + return x * other.x + y * other.y; +} + +template <class T> +T Vector2<T>::distance(const Vector2<T> & other) const { + return (*this - other).length(); +} + +template <class T> +T Vector2<T>::distance_squared(const Vector2<T> & other) const { + return (*this - other).length_squared(); +} + +template <class T> +Vector2<T> Vector2<T>::perpendicular() const { + return {-y, x}; +} + } // namespace crepe diff --git a/src/crepe/facade/CMakeLists.txt b/src/crepe/facade/CMakeLists.txt index 4cc53bc..0598e16 100644 --- a/src/crepe/facade/CMakeLists.txt +++ b/src/crepe/facade/CMakeLists.txt @@ -1,5 +1,6 @@ target_sources(crepe PUBLIC Sound.cpp + Texture.cpp SoundContext.cpp SDLContext.cpp DB.cpp @@ -7,6 +8,7 @@ target_sources(crepe PUBLIC target_sources(crepe PUBLIC FILE_SET HEADERS FILES Sound.h + Texture.h SoundContext.h SDLContext.h DB.h diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index 8f6c02c..4552605 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -2,6 +2,7 @@ #include <SDL2/SDL_blendmode.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_keycode.h> +#include <SDL2/SDL_pixels.h> #include <SDL2/SDL_rect.h> #include <SDL2/SDL_render.h> #include <SDL2/SDL_surface.h> @@ -18,23 +19,18 @@ #include "../api/Color.h" #include "../api/Config.h" #include "../api/Sprite.h" -#include "../api/Texture.h" #include "../util/Log.h" +#include "manager/Mediator.h" #include "SDLContext.h" +#include "Texture.h" #include "types.h" using namespace crepe; using namespace std; -SDLContext & SDLContext::get_instance() { - static SDLContext instance; - return instance; -} - -SDLContext::SDLContext() { +SDLContext::SDLContext(Mediator & mediator) { dbg_trace(); - if (SDL_Init(SDL_INIT_VIDEO) != 0) { throw runtime_error(format("SDLContext: SDL_Init error: {}", SDL_GetError())); } @@ -62,6 +58,8 @@ SDLContext::SDLContext() { if (!(IMG_Init(img_flags) & img_flags)) { throw runtime_error("SDLContext: SDL_image could not initialize!"); } + + mediator.sdl_context = *this; } SDLContext::~SDLContext() { @@ -236,35 +234,26 @@ void SDLContext::present_screen() { SDL_RenderPresent(this->game_renderer.get()); } -SDL_Rect SDLContext::get_src_rect(const Sprite & sprite) const { - return SDL_Rect{ - .x = sprite.mask.x, - .y = sprite.mask.y, - .w = sprite.mask.w, - .h = sprite.mask.h, - }; -} - SDL_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const { const Sprite::Data & data = ctx.sprite.data; + float aspect_ratio + = (ctx.sprite.aspect_ratio == 0) ? ctx.texture.get_ratio() : ctx.sprite.aspect_ratio; + vec2 size = data.size; if (data.size.x == 0 && data.size.y != 0) { - size.x = data.size.y * ctx.sprite.aspect_ratio; + size.x = data.size.y * aspect_ratio; } if (data.size.y == 0 && data.size.x != 0) { - size.y = data.size.x / ctx.sprite.aspect_ratio; + size.y = data.size.x / aspect_ratio; } + size *= cam_aux_data.render_scale * ctx.img_scale * data.scale_offset; - const CameraValues & cam = ctx.cam; - - size *= cam.render_scale * ctx.img_scale * data.scale_offset; - - vec2 screen_pos - = (ctx.pos + data.position_offset - cam.cam_pos + (cam.zoomed_viewport) / 2) - * cam.render_scale - - size / 2 + cam.bar_size; + vec2 screen_pos = (ctx.pos + data.position_offset - cam_aux_data.cam_pos + + (cam_aux_data.zoomed_viewport) / 2) + * cam_aux_data.render_scale + - size / 2 + cam_aux_data.bar_size; return SDL_FRect{ .x = screen_pos.x, @@ -275,31 +264,38 @@ SDL_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const { } void SDLContext::draw(const RenderContext & ctx) { - const Sprite::Data & data = ctx.sprite.data; SDL_RendererFlip render_flip = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * data.flip.flip_x) | (SDL_FLIP_VERTICAL * data.flip.flip_y)); - SDL_Rect srcrect = this->get_src_rect(ctx.sprite); + SDL_Rect srcrect; + SDL_Rect * srcrect_ptr = NULL; + if (ctx.sprite.mask.w != 0 || ctx.sprite.mask.h != 0) { + srcrect.w = ctx.sprite.mask.w; + srcrect.h = ctx.sprite.mask.h; + srcrect.x = ctx.sprite.mask.x; + srcrect.y = ctx.sprite.mask.y; + srcrect_ptr = &srcrect; + } + SDL_FRect dstrect = this->get_dst_rect(SDLContext::DestinationRectangleData{ .sprite = ctx.sprite, - .cam = ctx.cam, + .texture = ctx.texture, .pos = ctx.pos, .img_scale = ctx.scale, }); double angle = ctx.angle + data.angle_offset; - this->set_color_texture(ctx.sprite.texture, ctx.sprite.data.color); - SDL_RenderCopyExF(this->game_renderer.get(), ctx.sprite.texture.texture.get(), &srcrect, - &dstrect, angle, NULL, render_flip); + this->set_color_texture(ctx.texture, ctx.sprite.data.color); + SDL_RenderCopyExF(this->game_renderer.get(), ctx.texture.get_img(), srcrect_ptr, &dstrect, + angle, NULL, render_flip); } -SDLContext::CameraValues SDLContext::set_camera(const Camera & cam) { +void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) { const Camera::Data & cam_data = cam.data; - CameraValues ret_cam; // resize window int w, h; SDL_GetWindowSize(this->game_window.get(), &w, &h); @@ -307,9 +303,10 @@ SDLContext::CameraValues SDLContext::set_camera(const Camera & cam) { SDL_SetWindowSize(this->game_window.get(), cam.screen.x, cam.screen.y); } - vec2 & zoomed_viewport = ret_cam.zoomed_viewport; - vec2 & bar_size = ret_cam.bar_size; - vec2 & render_scale = ret_cam.render_scale; + vec2 & zoomed_viewport = this->cam_aux_data.zoomed_viewport; + vec2 & bar_size = this->cam_aux_data.bar_size; + vec2 & render_scale = this->cam_aux_data.render_scale; + this->cam_aux_data.cam_pos = new_pos; zoomed_viewport = cam.viewport_size * cam_data.zoom; float screen_aspect = static_cast<float>(cam.screen.x) / cam.screen.y; @@ -351,12 +348,8 @@ SDLContext::CameraValues SDLContext::set_camera(const Camera & cam) { // fill bg color SDL_RenderFillRect(this->game_renderer.get(), &bg); - - return ret_cam; } -uint64_t SDLContext::get_ticks() const { return SDL_GetTicks64(); } - std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> SDLContext::texture_from_path(const std::string & path) { @@ -383,18 +376,17 @@ SDLContext::texture_from_path(const std::string & path) { ivec2 SDLContext::get_size(const Texture & ctx) { ivec2 size; - SDL_QueryTexture(ctx.texture.get(), NULL, NULL, &size.x, &size.y); + SDL_QueryTexture(ctx.get_img(), NULL, NULL, &size.x, &size.y); return size; } -void SDLContext::delay(int ms) const { SDL_Delay(ms); } - std::vector<SDLContext::EventData> SDLContext::get_events() { std::vector<SDLContext::EventData> event_list; SDL_Event event; - - // Handle general SDL events while (SDL_PollEvent(&event)) { + ivec2 mouse_pos; + mouse_pos.x = (event.button.x - cam.bar_size.x) / cam.render_scale.x; + mouse_pos.y = (event.button.y - cam.bar_size.y) / cam.render_scale.y; switch (event.type) { case SDL_QUIT: event_list.push_back({SDLContext::EventType::SHUTDOWN, {}, {}, {}}); @@ -413,41 +405,38 @@ std::vector<SDLContext::EventData> SDLContext::get_events() { {}}); break; case SDL_MOUSEBUTTONDOWN: - event_list.push_back({SDLContext::EventType::MOUSEDOWN, - {}, - {sdl_to_mousebutton(event.button.button), - {event.button.x, event.button.y}}, - {}}); + event_list.push_back(EventData{ + .event_type = SDLContext::EventType::MOUSEDOWN, + .mouse_button = sdl_to_mousebutton(event.button.button), + .mouse_position = {event.button.x, event.button.y}, + }); break; - case SDL_MOUSEBUTTONUP: - event_list.push_back({SDLContext::EventType::MOUSEUP, - {}, - {sdl_to_mousebutton(event.button.button), - {event.button.x, event.button.y}}, - {}}); - break; - case SDL_MOUSEMOTION: - event_list.push_back({SDLContext::EventType::MOUSEMOVE, - {}, - {{}, - {event.motion.x, event.motion.y}, - -1, - INFINITY, - {event.motion.xrel, event.motion.yrel}}, - {}}); - break; - case SDL_MOUSEWHEEL: + case SDL_MOUSEBUTTONUP: { + int x, y; + SDL_GetMouseState(&x, &y); + event_list.push_back(EventData{ + .event_type = SDLContext::EventType::MOUSEUP, + .mouse_button = sdl_to_mousebutton(event.button.button), + .mouse_position = {event.button.x, event.button.y}, + }); + } break; + + case SDL_MOUSEMOTION: { event_list.push_back( - {SDLContext::EventType::MOUSEWHEEL, - {}, - {{}, {}, event.wheel.y < 0 ? -1 : 1, event.wheel.preciseY, {}}, - {}}); - break; - - // Forward window events for further processing - case SDL_WINDOWEVENT: - this->handle_window_event(event.window, event_list); - break; + EventData{.event_type = SDLContext::EventType::MOUSEMOVE, + .mouse_position = {event.motion.x, event.motion.y}, + .rel_mouse_move = {event.motion.xrel, event.motion.yrel}}); + } break; + + case SDL_MOUSEWHEEL: { + event_list.push_back(EventData{ + .event_type = SDLContext::EventType::MOUSEWHEEL, + .mouse_position = {event.motion.x, event.motion.y}, + // TODO: why is this needed? + .scroll_direction = event.wheel.y < 0 ? -1 : 1, + .scroll_delta = event.wheel.preciseY, + }); + } break; } } @@ -489,6 +478,6 @@ void SDLContext::handle_window_event(const SDL_WindowEvent & window_event, } void SDLContext::set_color_texture(const Texture & texture, const Color & color) { - SDL_SetTextureColorMod(texture.texture.get(), color.r, color.g, color.b); - SDL_SetTextureAlphaMod(texture.texture.get(), color.a); + SDL_SetTextureColorMod(texture.get_img(), color.r, color.g, color.b); + SDL_SetTextureAlphaMod(texture.get_img(), color.a); } diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index 053aa59..0a2456d 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -15,16 +15,16 @@ #include "api/Color.h" #include "api/KeyCodes.h" #include "api/Sprite.h" -#include "api/Texture.h" #include "api/Transform.h" + #include "types.h" namespace crepe { -class LoopManager; -class InputSystem; +class Texture; +class Mediator; + /** - * \class SDLContext * \brief Facade for the SDL library * * SDLContext is a singleton that handles the SDL window and renderer, provides methods for @@ -33,7 +33,7 @@ class InputSystem; class SDLContext { public: //! data that the camera component cannot hold - struct CameraValues { + struct CameraAuxiliaryData { //! zoomed in viewport in game_units vec2 zoomed_viewport; @@ -63,7 +63,7 @@ public: //! rendering data needed to render on screen struct RenderContext { const Sprite & sprite; - const CameraValues & cam; + const Texture & texture; const vec2 & pos; const double & angle; const double & scale; @@ -129,14 +129,26 @@ public: */ static SDLContext & get_instance(); +public: SDLContext(const SDLContext &) = delete; SDLContext(SDLContext &&) = delete; SDLContext & operator=(const SDLContext &) = delete; SDLContext & operator=(SDLContext &&) = delete; -private: - //! will only use get_events - friend class InputSystem; +public: + /** + * \brief Constructs an SDLContext instance. + * Initializes SDL, creates a window and renderer. + */ + SDLContext(Mediator & mediator); + + /** + * \brief Destroys the SDLContext instance. + * Cleans up SDL resources, including the window and renderer. + */ + ~SDLContext(); + +public: /** * \brief Retrieves a list of all events from the SDL context. * @@ -177,9 +189,7 @@ private: */ MouseButton sdl_to_mousebutton(Uint8 sdl_button); -private: - //! Will only use delay - friend class LoopTimer; +public: /** * \brief Gets the current SDL ticks since the program started. * \return Current ticks in milliseconds as a constant uint64_t. @@ -195,23 +205,7 @@ private: */ void delay(int ms) const; -private: - /** - * \brief Constructs an SDLContext instance. - * Initializes SDL, creates a window and renderer. - */ - SDLContext(); - - /** - * \brief Destroys the SDLContext instance. - * Cleans up SDL resources, including the window and renderer. - */ - ~SDLContext(); - -private: - //! Will use the funtions: texture_from_path, get_width,get_height. - friend class Texture; - +public: /** * \brief Loads a texture from a file path. * \param path Path to the image file. @@ -222,14 +216,11 @@ private: /** * \brief Gets the size of a texture. * \param texture Reference to the Texture object. - * \return Width and height of the texture as an integer. + * \return Width and height of the texture as an integer in pixels. */ ivec2 get_size(const Texture & ctx); -private: - //! Will use draw,clear_screen, present_screen, camera. - friend class RenderSystem; - +public: /** * \brief Draws a sprite to the screen using the specified transform and camera. * \param RenderContext Reference to rendering data to draw @@ -243,35 +234,28 @@ private: void present_screen(); /** - * \brief sets the background of the camera (will be adjusted in future PR) - * \param camera Reference to the Camera object. + * \brief calculates camera view settings. such as black_bars, zoomed_viewport, scaling and + * adjusting window size. + * + * \note only supports windowed mode. + * \param camera Reference to the current Camera object in the scene. + * \param new_pos new camera position from transform and offset */ - CameraValues set_camera(const Camera & camera); + void update_camera_view(const Camera & camera, const vec2 & new_pos); -private: +public: //! the data needed to construct a sdl dst rectangle struct DestinationRectangleData { const Sprite & sprite; - const CameraValues & cam; + const Texture & texture; const vec2 & pos; const double & img_scale; }; - /** - * \brief calculates the sqaure size of the image - * - * \param sprite Reference to the sprite to calculate the rectangle - * \return sdl rectangle to draw a src image - */ - SDL_Rect get_src_rect(const Sprite & sprite) const; /** * \brief calculates the sqaure size of the image for destination * - * \param sprite Reference to the sprite to calculate rectangle - * \param pos the pos in world units - * \param cam the camera of the current scene - * \param cam_pos the current postion of the camera - * \param img_scale the image multiplier for increasing img size + * \param data needed to calculate a destination rectangle * \return sdl rectangle to draw a dst image to draw on the screen */ SDL_FRect get_dst_rect(const DestinationRectangleData & data) const; @@ -292,6 +276,13 @@ private: //! black bars rectangle to draw SDL_FRect black_bars[2] = {}; + + /** + * \cam_aux_data extra data that the component cannot hold. + * + * - this is defined in this class because get_events() needs this information aswell + */ + CameraAuxiliaryData cam_aux_data; }; } // namespace crepe diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index 4d3abf5..97e455e 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -1,59 +1,13 @@ +#include "../api/Asset.h" #include "../util/Log.h" #include "Sound.h" -#include "SoundContext.h" using namespace crepe; using namespace std; -Sound::Sound(unique_ptr<Asset> res) { +Sound::Sound(const Asset & src, Mediator & mediator) : Resource(src, mediator) { + this->sample.load(src.get_path().c_str()); dbg_trace(); - this->load(std::move(res)); -} - -Sound::Sound(const char * src) { - dbg_trace(); - this->load(make_unique<Asset>(src)); -} - -void Sound::load(unique_ptr<Asset> res) { this->sample.load(res->get_path().c_str()); } - -void Sound::play() { - SoundContext & ctx = SoundContext::get_instance(); - if (ctx.engine.getPause(this->handle)) { - // resume if paused - ctx.engine.setPause(this->handle, false); - } else { - // or start new sound - this->handle = ctx.engine.play(this->sample, this->volume); - ctx.engine.setLooping(this->handle, this->looping); - } -} - -void Sound::pause() { - SoundContext & ctx = SoundContext::get_instance(); - if (ctx.engine.getPause(this->handle)) return; - ctx.engine.setPause(this->handle, true); -} - -void Sound::rewind() { - SoundContext & ctx = SoundContext::get_instance(); - if (!ctx.engine.isValidVoiceHandle(this->handle)) return; - ctx.engine.seek(this->handle, 0); -} - -void Sound::set_volume(float volume) { - this->volume = volume; - - SoundContext & ctx = SoundContext::get_instance(); - if (!ctx.engine.isValidVoiceHandle(this->handle)) return; - ctx.engine.setVolume(this->handle, this->volume); -} - -void Sound::set_looping(bool looping) { - this->looping = looping; - - SoundContext & ctx = SoundContext::get_instance(); - if (!ctx.engine.isValidVoiceHandle(this->handle)) return; - ctx.engine.setLooping(this->handle, this->looping); } +Sound::~Sound() { dbg_trace(); } diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index ee43d94..4a5d692 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -1,84 +1,31 @@ #pragma once -#include <memory> #include <soloud/soloud.h> #include <soloud/soloud_wav.h> -#include "../api/Asset.h" +#include "../Resource.h" namespace crepe { +class SoundContext; +class Mediator; + /** * \brief Sound resource facade * - * This class is a wrapper around a \c SoLoud::Wav instance, which holds a - * single sample. It is part of the sound facade. + * This class is a wrapper around a \c SoLoud::Wav instance, which holds a single sample. It is + * part of the sound facade. */ -class Sound { -public: - /** - * \brief Pause this sample - * - * Pauses this sound if it is playing, or does nothing if it is already paused. The playhead - * position is saved, such that calling \c play() after this function makes the sound resume. - */ - void pause(); - /** - * \brief Play this sample - * - * Resume playback if this sound is paused, or start from the beginning of the sample. - * - * \note This class only saves a reference to the most recent 'voice' of this sound. Calling - * \c play() while the sound is already playing causes multiple instances of the sample to - * play simultaniously. The sample started last is the one that is controlled afterwards. - */ - void play(); - /** - * \brief Reset playhead position - * - * Resets the playhead position so that calling \c play() after this function makes it play - * from the start of the sample. If the sound is not paused before calling this function, - * this function will stop playback. - */ - void rewind(); - /** - * \brief Set playback volume / gain - * - * \param volume Volume (0 = muted, 1 = full volume) - */ - void set_volume(float volume); - /** - * \brief Get playback volume / gain - * - * \return Volume - */ - float get_volume() const { return this->volume; } - /** - * \brief Set looping behavior for this sample - * - * \param looping Looping behavior (false = one-shot, true = loop) - */ - void set_looping(bool looping); - /** - * \brief Get looping behavior - * - * \return true if looping, false if one-shot - */ - bool get_looping() const { return this->looping; } - +class Sound : public Resource { public: - Sound(const char * src); - Sound(std::unique_ptr<Asset> res); - -private: - void load(std::unique_ptr<Asset> res); + Sound(const Asset & src, Mediator & mediator); + ~Sound(); // dbg_trace private: + //! Deserialized resource (soloud) SoLoud::Wav sample; - SoLoud::handle handle; - - float volume = 1.0f; - bool looping = false; + //! SoundContext uses \c sample + friend class SoundContext; }; } // namespace crepe diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp index deb2b62..b1f8cb3 100644 --- a/src/crepe/facade/SoundContext.cpp +++ b/src/crepe/facade/SoundContext.cpp @@ -4,17 +4,33 @@ using namespace crepe; -SoundContext & SoundContext::get_instance() { - static SoundContext instance; - return instance; -} - SoundContext::SoundContext() { dbg_trace(); - engine.init(); + this->engine.init(); + this->engine.setMaxActiveVoiceCount(this->config.audio.voices); } SoundContext::~SoundContext() { dbg_trace(); - engine.deinit(); + this->engine.deinit(); +} + +SoundHandle SoundContext::play(Sound & resource) { + SoLoud::handle real_handle = this->engine.play(resource.sample, 1.0f); + SoundHandle handle = this->next_handle; + this->registry[handle] = real_handle; + this->next_handle++; + return handle; +} + +void SoundContext::stop(const SoundHandle & handle) { + this->engine.stop(this->registry[handle]); +} + +void SoundContext::set_volume(const SoundHandle & handle, float volume) { + this->engine.setVolume(this->registry[handle], volume); +} + +void SoundContext::set_loop(const SoundHandle & handle, bool loop) { + this->engine.setLooping(this->registry[handle], loop); } diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h index d703c16..d986c59 100644 --- a/src/crepe/facade/SoundContext.h +++ b/src/crepe/facade/SoundContext.h @@ -2,30 +2,80 @@ #include <soloud/soloud.h> +#include "../api/Config.h" + #include "Sound.h" +#include "SoundHandle.h" namespace crepe { /** * \brief Sound engine facade * - * This class is a wrapper around a \c SoLoud::Soloud instance, which provides - * the methods for playing \c Sound instances. It is part of the sound facade. + * This class is a wrapper around a \c SoLoud::Soloud instance, which provides the methods for + * playing \c Sound instances. It is part of the sound facade. */ class SoundContext { -private: - // singleton +public: SoundContext(); virtual ~SoundContext(); + SoundContext(const SoundContext &) = delete; SoundContext(SoundContext &&) = delete; SoundContext & operator=(const SoundContext &) = delete; SoundContext & operator=(SoundContext &&) = delete; + /** + * \brief Play a sample + * + * Plays a Sound from the beginning of the sample and returns a handle to control it later. + * + * \param resource Sound instance to play + * + * \returns Handle to control this voice + */ + virtual SoundHandle play(Sound & resource); + /** + * \brief Stop a voice immediately if it is still playing + * + * \note This function does nothing if the handle is invalid or if the sound is already + * stopped / finished playing. + * + * \param handle Voice handle returned by SoundContext::play + */ + virtual void stop(const SoundHandle & handle); + /** + * \brief Change the volume of a voice + * + * \note This function does nothing if the handle is invalid or if the sound is already + * stopped / finished playing. + * + * \param handle Voice handle returned by SoundContext::play + * \param volume New gain value (0=silent, 1=default) + */ + virtual void set_volume(const SoundHandle & handle, float volume); + /** + * \brief Set the looping behavior of a voice + * + * \note This function does nothing if the handle is invalid or if the sound is already + * stopped / finished playing. + * + * \param handle Voice handle returned by SoundContext::play + * \param loop Looping behavior (false=oneshot, true=loop) + */ + virtual void set_loop(const SoundHandle & handle, bool loop); + private: - static SoundContext & get_instance(); + //! Abstracted class SoLoud::Soloud engine; - friend class Sound; + + //! Config reference + Config & config = Config::get_instance(); + + //! Sound handle registry + std::unordered_map<SoundHandle, SoLoud::handle> registry; + //! Unique handle counter + SoundHandle next_handle = 0; }; } // namespace crepe diff --git a/src/crepe/facade/SoundHandle.h b/src/crepe/facade/SoundHandle.h new file mode 100644 index 0000000..b7925fc --- /dev/null +++ b/src/crepe/facade/SoundHandle.h @@ -0,0 +1,12 @@ +#pragma once + +#include <cstddef> + +namespace crepe { + +/** + * \brief Voice handle returned by + */ +typedef size_t SoundHandle; + +} // namespace crepe diff --git a/src/crepe/facade/Texture.cpp b/src/crepe/facade/Texture.cpp new file mode 100644 index 0000000..b63403d --- /dev/null +++ b/src/crepe/facade/Texture.cpp @@ -0,0 +1,28 @@ +#include "../util/Log.h" +#include "facade/SDLContext.h" +#include "manager/Mediator.h" + +#include "Resource.h" +#include "Texture.h" +#include "types.h" + +using namespace crepe; +using namespace std; + +Texture::Texture(const Asset & src, Mediator & mediator) : Resource(src, mediator) { + dbg_trace(); + SDLContext & ctx = mediator.sdl_context; + this->texture = ctx.texture_from_path(src.get_path()); + this->size = ctx.get_size(*this); + this->aspect_ratio = static_cast<float>(this->size.x) / this->size.y; +} + +Texture::~Texture() { + dbg_trace(); + this->texture.reset(); +} + +const ivec2 & Texture::get_size() const noexcept { return this->size; } +const float & Texture::get_ratio() const noexcept { return this->aspect_ratio; } + +SDL_Texture * Texture::get_img() const noexcept { return this->texture.get(); } diff --git a/src/crepe/facade/Texture.h b/src/crepe/facade/Texture.h new file mode 100644 index 0000000..cdacac4 --- /dev/null +++ b/src/crepe/facade/Texture.h @@ -0,0 +1,69 @@ +#pragma once + +#include <SDL2/SDL_render.h> +#include <memory> + +#include "../Resource.h" + +#include "types.h" + +namespace crepe { + +class Mediator; +class Asset; + +/** + * \class Texture + * \brief Manages texture loading and properties. + * + * The Texture class is responsible for loading an image from a source and providing access to + * its dimensions. Textures can be used for rendering. + */ +class Texture : public Resource { + +public: + /** + * \brief Constructs a Texture from an Asset resource. + * \param src Asset with texture data to load. + * \param mediator use the SDLContext reference to load the image + */ + Texture(const Asset & src, Mediator & mediator); + + /** + * \brief Destroys the Texture instance + */ + ~Texture(); + + /** + * \brief get width and height of image in pixels + * \return pixel size width and height + * + */ + const ivec2 & get_size() const noexcept; + + /** + * \brief aspect_ratio of image + * \return ratio + * + */ + const float & get_ratio() const noexcept; + + /** + * \brief get the image texture + * \return SDL_Texture + * + */ + SDL_Texture * get_img() const noexcept; + +private: + //! The texture of the class from the library + std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> texture; + + // texture size in pixel + ivec2 size; + + //! ratio of image + float aspect_ratio; +}; + +} // namespace crepe diff --git a/src/crepe/manager/CMakeLists.txt b/src/crepe/manager/CMakeLists.txt index 517b8a2..f73e165 100644 --- a/src/crepe/manager/CMakeLists.txt +++ b/src/crepe/manager/CMakeLists.txt @@ -4,6 +4,8 @@ target_sources(crepe PUBLIC Manager.cpp SaveManager.cpp SceneManager.cpp + LoopTimerManager.cpp + ResourceManager.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -16,5 +18,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES SaveManager.h SceneManager.h SceneManager.hpp + LoopTimerManager.h + ResourceManager.h + ResourceManager.hpp ) diff --git a/src/crepe/manager/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp index 80cf8b4..df30d27 100644 --- a/src/crepe/manager/ComponentManager.cpp +++ b/src/crepe/manager/ComponentManager.cpp @@ -1,4 +1,5 @@ #include "../api/GameObject.h" +#include "../api/Metadata.h" #include "../types.h" #include "../util/Log.h" @@ -61,3 +62,13 @@ GameObject ComponentManager::new_object(const string & name, const string & tag, void ComponentManager::set_persistent(game_object_id_t id, bool persistent) { this->persistent[id] = persistent; } + +set<game_object_id_t> ComponentManager::get_objects_by_name(const string & name) const { + return this->get_objects_by_predicate<Metadata>( + [name](const Metadata & data) { return data.name == name; }); +} + +set<game_object_id_t> ComponentManager::get_objects_by_tag(const string & tag) const { + return this->get_objects_by_predicate<Metadata>( + [tag](const Metadata & data) { return data.tag == tag; }); +} diff --git a/src/crepe/manager/ComponentManager.h b/src/crepe/manager/ComponentManager.h index 44429d9..19a8e81 100644 --- a/src/crepe/manager/ComponentManager.h +++ b/src/crepe/manager/ComponentManager.h @@ -1,6 +1,7 @@ #pragma once #include <memory> +#include <set> #include <typeindex> #include <unordered_map> #include <vector> @@ -134,8 +135,77 @@ public: */ template <typename T> RefVector<T> get_components_by_type() const; + /** + * \brief Get all components of a specific type on a GameObject with name \c name + * + * \tparam T The type of the component + * \param name Metadata::name for the same game_object_id as the returned components + * \return Components matching criteria + */ + template <typename T> + RefVector<T> get_components_by_name(const std::string & name) const; + /** + * \brief Get all components of a specific type on a GameObject with tag \c tag + * + * \tparam T The type of the component + * \param name Metadata::tag for the same game_object_id as the returned components + * \return Components matching criteria + */ + template <typename T> + RefVector<T> get_components_by_tag(const std::string & tag) const; + +private: + /** + * \brief Get object IDs by predicate function + * + * This function calls the predicate function \c pred for all components matching type \c T, + * and adds their parent game_object_id to a \c std::set if the predicate returns true. + * + * \tparam T The type of the component to check the predicate against + * \param pred Predicate function + * + * \note The predicate function may be called for multiple components with the same \c + * game_object_id. In this case, the ID is added if *any* call returns \c true. + * + * \returns game_object_id for all components where the predicate returned true + */ + template <typename T> + std::set<game_object_id_t> + get_objects_by_predicate(const std::function<bool(const T &)> & pred) const; + + /** + * \brief Get components of type \c T for multiple game object IDs + * + * \tparam T The type of the components to return + * \param ids The object IDs + * + * \return All components matching type \c T and one of the IDs in \c ids + */ + template <typename T> + RefVector<T> get_components_by_ids(const std::set<game_object_id_t> & ids) const; + + /** + * \brief Get object IDs for objects with name \c name + * + * \param name Object name to match + * \returns Object IDs where Metadata::name is equal to \c name + */ + std::set<game_object_id_t> get_objects_by_name(const std::string & name) const; + /** + * \brief Get object IDs for objects with tag \c tag + * + * \param tag Object tag to match + * \returns Object IDs where Metadata::tag is equal to \c tag + */ + std::set<game_object_id_t> get_objects_by_tag(const std::string & tag) const; private: + //! By Component \c std::type_index (readability helper type) + template <typename T> + using by_type = std::unordered_map<std::type_index, T>; + //! By \c game_object_id index (readability helper type) + template <typename T> + using by_id_index = std::vector<T>; /** * \brief The components * @@ -146,8 +216,7 @@ private: * The first vector is for the ids of the GameObjects and the second vector is for the * components (because a GameObject might have multiple components). */ - std::unordered_map<std::type_index, std::vector<std::vector<std::unique_ptr<Component>>>> - components; + by_type<by_id_index<std::vector<std::unique_ptr<Component>>>> components; //! Persistent flag for each GameObject std::unordered_map<game_object_id_t, bool> persistent; diff --git a/src/crepe/manager/ComponentManager.hpp b/src/crepe/manager/ComponentManager.hpp index ffb38ec..9e70865 100644 --- a/src/crepe/manager/ComponentManager.hpp +++ b/src/crepe/manager/ComponentManager.hpp @@ -95,32 +95,25 @@ template <typename T> RefVector<T> ComponentManager::get_components_by_id(game_object_id_t id) const { using namespace std; - // Determine the type of T (this is used as the key of the unordered_map<>) - type_index type = typeid(T); - - // Create an empty vector<> - RefVector<T> component_vector; - - if (this->components.find(type) == this->components.end()) return component_vector; - - // Get the correct vector<> - const vector<vector<unique_ptr<Component>>> & component_array = this->components.at(type); - - // Make sure that the id (that we are looking for) is within the boundaries of the vector<> - if (id >= component_array.size()) return component_vector; - - // Loop trough the whole vector<> - for (const unique_ptr<Component> & component_ptr : component_array[id]) { - // Cast the unique_ptr to a raw pointer - T * casted_component = static_cast<T *>(component_ptr.get()); - - if (casted_component == nullptr) continue; + static_assert(is_base_of<Component, T>::value, + "get_components_by_id must recieve a derivative class of Component"); - // Add the dereferenced raw pointer to the vector<> - component_vector.push_back(*casted_component); + type_index type = typeid(T); + if (!this->components.contains(type)) return {}; + + const by_id_index<vector<unique_ptr<Component>>> & components_by_id + = this->components.at(type); + if (id >= components_by_id.size()) return {}; + + RefVector<T> out = {}; + const vector<unique_ptr<Component>> & components = components_by_id.at(id); + for (auto & component_ptr : components) { + if (component_ptr == nullptr) continue; + Component & component = *component_ptr.get(); + out.push_back(static_cast<T &>(component)); } - return component_vector; + return out; } template <typename T> @@ -158,4 +151,46 @@ RefVector<T> ComponentManager::get_components_by_type() const { return component_vector; } +template <typename T> +std::set<game_object_id_t> +ComponentManager::get_objects_by_predicate(const std::function<bool(const T &)> & pred) const { + using namespace std; + + set<game_object_id_t> objects = {}; + RefVector<T> components = this->get_components_by_type<T>(); + + for (const T & component : components) { + game_object_id_t id = dynamic_cast<const Component &>(component).game_object_id; + if (objects.contains(id)) continue; + if (!pred(component)) continue; + objects.insert(id); + } + + return objects; +} + +template <typename T> +RefVector<T> +ComponentManager::get_components_by_ids(const std::set<game_object_id_t> & ids) const { + using namespace std; + + RefVector<T> out = {}; + for (game_object_id_t id : ids) { + RefVector<T> components = get_components_by_id<T>(id); + out.insert(out.end(), components.begin(), components.end()); + } + + return out; +} + +template <typename T> +RefVector<T> ComponentManager::get_components_by_name(const std::string & name) const { + return this->get_components_by_ids<T>(this->get_objects_by_name(name)); +} + +template <typename T> +RefVector<T> ComponentManager::get_components_by_tag(const std::string & tag) const { + return this->get_components_by_ids<T>(this->get_objects_by_tag(tag)); +} + } // namespace crepe diff --git a/src/crepe/manager/EventManager.cpp b/src/crepe/manager/EventManager.cpp index 20f0dd3..6aa49ee 100644 --- a/src/crepe/manager/EventManager.cpp +++ b/src/crepe/manager/EventManager.cpp @@ -3,11 +3,9 @@ using namespace crepe; using namespace std; -EventManager & EventManager::get_instance() { - static EventManager instance; - return instance; +EventManager::EventManager(Mediator & mediator) : Manager(mediator) { + this->mediator.event_manager = *this; } - void EventManager::dispatch_events() { for (auto & event : this->events_queue) { this->handle_event(event.type, event.channel, *event.event.get()); diff --git a/src/crepe/manager/EventManager.h b/src/crepe/manager/EventManager.h index ba5e98b..639e37f 100644 --- a/src/crepe/manager/EventManager.h +++ b/src/crepe/manager/EventManager.h @@ -8,6 +8,8 @@ #include "../api/Event.h" #include "../api/EventHandler.h" +#include "Manager.h" + namespace crepe { //! Event listener unique ID @@ -22,27 +24,19 @@ typedef size_t subscription_t; typedef size_t event_channel_t; /** - * \class EventManager * \brief Manages event subscriptions, triggers, and queues, enabling decoupled event handling. * * The `EventManager` acts as a centralized event system. It allows for registering callbacks * for specific event types, triggering events synchronously, queueing events for later * processing, and managing subscriptions via unique identifiers. */ -class EventManager { +class EventManager : public Manager { public: static constexpr const event_channel_t CHANNEL_ALL = -1; - /** - * \brief Get the singleton instance of the EventManager. - * - * This method returns the unique instance of the EventManager, creating it if it - * doesn't already exist. Ensures only one instance is active in the program. - * - * \return Reference to the singleton instance of the EventManager. + * \param mediator A reference to a Mediator object used for transfering managers. */ - static EventManager & get_instance(); - + EventManager(Mediator & mediator); /** * \brief Subscribe to a specific event type. * @@ -108,13 +102,6 @@ public: private: /** - * \brief Default constructor for the EventManager. - * - * Constructor is private to enforce the singleton pattern. - */ - EventManager() = default; - - /** * \struct QueueEntry * \brief Represents an entry in the event queue. */ diff --git a/src/crepe/manager/LoopTimerManager.cpp b/src/crepe/manager/LoopTimerManager.cpp new file mode 100644 index 0000000..9819632 --- /dev/null +++ b/src/crepe/manager/LoopTimerManager.cpp @@ -0,0 +1,91 @@ +#include <chrono> +#include <thread> + +#include "../util/Log.h" + +#include "LoopTimerManager.h" + +using namespace crepe; +using namespace std::chrono; +using namespace std::chrono_literals; + +LoopTimerManager::LoopTimerManager(Mediator & mediator) : Manager(mediator) { + this->mediator.loop_timer = *this; + dbg_trace(); +} + +void LoopTimerManager::start() { + this->last_frame_time = std::chrono::steady_clock::now(); + + this->elapsed_time = elapsed_time_t{0}; + this->elapsed_fixed_time = elapsed_time_t{0}; + this->delta_time = duration_t{0}; +} + +void LoopTimerManager::update() { + time_point_t current_frame_time = std::chrono::steady_clock::now(); + // Convert to duration in seconds for delta time + this->delta_time = current_frame_time - last_frame_time; + + if (this->delta_time > this->maximum_delta_time) { + this->delta_time = this->maximum_delta_time; + } + if (this->delta_time > 0s) { + this->actual_fps = 1.0 / duration_cast<seconds>(this->delta_time).count(); + } else { + this->actual_fps = 0; + } + this->elapsed_time += duration_cast<elapsed_time_t>(this->delta_time); + this->last_frame_time = current_frame_time; +} + +duration_t LoopTimerManager::get_delta_time() const { + return this->delta_time * this->time_scale; +} + +elapsed_time_t LoopTimerManager::get_elapsed_time() const { return this->elapsed_time; } + +void LoopTimerManager::advance_fixed_elapsed_time() { + this->elapsed_fixed_time + += std::chrono::duration_cast<elapsed_time_t>(this->fixed_delta_time); +} + +void LoopTimerManager::set_target_framerate(unsigned fps) { + this->target_fps = fps; + //check if fps is lower or equals 0 + if (fps <= 0) return; + // target time per frame in seconds + this->frame_target_time = duration_t(1s) / this->target_fps; +} + +unsigned LoopTimerManager::get_fps() const { return this->actual_fps; } + +void LoopTimerManager::set_time_scale(double value) { this->time_scale = value; } + +float LoopTimerManager::get_time_scale() const { return this->time_scale; } + +void LoopTimerManager::enforce_frame_rate() { + time_point_t current_frame_time = std::chrono::steady_clock::now(); + duration_t frame_duration = current_frame_time - this->last_frame_time; + // Check if frame duration is less than the target frame time + if (frame_duration < this->frame_target_time) { + duration_t delay_time = this->frame_target_time - frame_duration; + if (delay_time > 0s) { + std::this_thread::sleep_for(delay_time); + } + } +} + +duration_t LoopTimerManager::get_lag() const { + return this->elapsed_time - this->elapsed_fixed_time; +} + +duration_t LoopTimerManager::get_scaled_fixed_delta_time() const { + return this->fixed_delta_time * this->time_scale; +} + +void LoopTimerManager::set_fixed_delta_time(float seconds) { + this->fixed_delta_time = duration_t(seconds); +} + +duration_t LoopTimerManager::get_fixed_delta_time() const { return this->fixed_delta_time; } diff --git a/src/crepe/manager/LoopTimerManager.h b/src/crepe/manager/LoopTimerManager.h new file mode 100644 index 0000000..91403e4 --- /dev/null +++ b/src/crepe/manager/LoopTimerManager.h @@ -0,0 +1,175 @@ +#pragma once + +#include <chrono> + +#include "Manager.h" + +namespace crepe { + +typedef std::chrono::duration<double> duration_t; +typedef std::chrono::duration<unsigned long long, std::micro> elapsed_time_t; + +/** + * \brief Manages timing and frame rate for the game loop. + * + * The LoopTimerManager class is responsible for calculating and managing timing functions + * such as delta time, frames per second (FPS), fixed time steps, and time scaling. It ensures + * consistent frame updates and supports game loop operations, such as handling fixed updates + * for physics and other time-sensitive operations. + */ +class LoopTimerManager : public Manager { +public: + /** + * \param mediator A reference to a Mediator object used for transfering managers. + */ + LoopTimerManager(Mediator & mediator); + /** + * \brief Get the current delta time for the current frame. + * + * This value represents the estimated frame duration of the current frame. + * This value can be used in the frame_update to convert pixel based values to time based values. + * + * \return Delta time in seconds since the last frame. + */ + duration_t get_delta_time() const; + + /** + * \brief Get the current elapsed time (total time passed ) + * + * \note The current game time may vary from real-world elapsed time. It is the cumulative + * sum of each frame's delta time. + * + * \return Elapsed game time in seconds. + */ + elapsed_time_t get_elapsed_time() const; + + /** + * \brief Set the target frames per second (FPS). + * + * \param fps The desired frames rendered per second. + */ + void set_target_framerate(unsigned fps); + + /** + * \brief Get the current frames per second (FPS). + * + * \return Current FPS. + */ + unsigned get_fps() const; + + /** + * \brief Get the current time scale. + * + * \return The current time scale, where (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up). + * up the game. + */ + float get_time_scale() const; + + /** + * \brief Set the time scale. + * + * time_scale is a value that changes the delta time that can be retrieved using get_delta_time function. + * + * \param time_scale The desired time scale (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up). + */ + void set_time_scale(double time_scale); + + /** + * \brief Get the fixed delta time in seconds without scaling by the time scale. + * + * This value is used in the LoopManager to determine how many times + * the fixed_update should be called within a given interval. + * + * \return The unscaled fixed delta time in seconds. + */ + duration_t get_fixed_delta_time() const; + + /** + * \brief Set the fixed_delta_time in seconds. + * + * \param seconds fixed_delta_time in seconds. + * + * The fixed_delta_time value is used to determine how many times per second the fixed_update and process_input functions are called. + * + */ + void set_fixed_delta_time(float seconds); + + /** + * \brief Retrieves the scaled fixed delta time in seconds. + * + * The scaled fixed delta time is the timing value used within the `fixed_update` function. + * It is adjusted by the time_scale to account for any changes in the simulation's + * speed. + * + * \return The fixed delta time, scaled by the current time scale, in seconds. + */ + duration_t get_scaled_fixed_delta_time() const; + +private: + //! Friend relation to use start,enforce_frame_rate,get_lag,update,advance_fixed_update. + friend class LoopManager; + /** + * \brief Start the loop timer. + * + * Initializes the timer to begin tracking frame times. + */ + void start(); + /** + * \brief Enforce the frame rate limit. + * + * Ensures that the game loop does not exceed the target FPS by delaying frame updates as + * necessary. + */ + void enforce_frame_rate(); + /** + * \brief Get the accumulated lag in the game loop. + * + * Lag represents the difference between the target frame time and the actual frame time, + * useful for managing fixed update intervals. + * + * \return Accumulated lag in seconds. + */ + duration_t get_lag() const; + + /** + * \brief Update the timer to the current frame. + * + * Calculates and updates the delta time for the current frame and adds it to the cumulative + * game time. + */ + void update(); + + /** + * \brief Progress the elapsed fixed time by the fixed delta time interval. + * + * This method advances the game's fixed update loop by adding the fixed_delta_time + * to elapsed_fixed_time, ensuring the fixed update catches up with the elapsed time. + */ + void advance_fixed_elapsed_time(); + +private: + //! Target frames per second. + unsigned target_fps = 60; + //! Actual frames per second. + unsigned actual_fps = 0; + //! Time scale for speeding up or slowing down the game (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up). + float time_scale = 1; + //! Maximum delta time in seconds to avoid large jumps. + duration_t maximum_delta_time{0.25}; + //! Delta time for the current frame in seconds. + duration_t delta_time{0.0}; + //! Target time per frame in seconds + duration_t frame_target_time{1.0 / target_fps}; + //! Fixed delta time for fixed updates in seconds. + duration_t fixed_delta_time{1.0 / 50.0}; + //! Total elapsed game time in microseconds. + elapsed_time_t elapsed_time{0}; + //! Total elapsed time for fixed updates in microseconds. + elapsed_time_t elapsed_fixed_time{0}; + + typedef std::chrono::steady_clock::time_point time_point_t; + //! Time of the last frame. + time_point_t last_frame_time; +}; + +} // namespace crepe diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h index 8094d80..a336410 100644 --- a/src/crepe/manager/Mediator.h +++ b/src/crepe/manager/Mediator.h @@ -2,16 +2,15 @@ #include "../util/OptionalRef.h" -// TODO: remove these singletons: -#include "../facade/SDLContext.h" -#include "EventManager.h" -#include "SaveManager.h" -#include "api/LoopTimer.h" - namespace crepe { class ComponentManager; class SceneManager; +class EventManager; +class LoopTimerManager; +class SaveManager; +class ResourceManager; +class SDLContext; /** * Struct to pass references to classes that would otherwise need to be singletons down to @@ -26,12 +25,13 @@ class SceneManager; * \warning This class should never be directly accessible from the API */ struct Mediator { + OptionalRef<SDLContext> sdl_context; OptionalRef<ComponentManager> component_manager; OptionalRef<SceneManager> scene_manager; - OptionalRef<SaveManager> save_manager = SaveManager::get_instance(); - OptionalRef<EventManager> event_manager = EventManager::get_instance(); - OptionalRef<SDLContext> sdl_context = SDLContext::get_instance(); - OptionalRef<LoopTimer> timer = LoopTimer::get_instance(); + OptionalRef<EventManager> event_manager; + OptionalRef<LoopTimerManager> loop_timer; + OptionalRef<SaveManager> save_manager; + OptionalRef<ResourceManager> resource_manager; }; } // namespace crepe diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp new file mode 100644 index 0000000..a141a46 --- /dev/null +++ b/src/crepe/manager/ResourceManager.cpp @@ -0,0 +1,30 @@ +#include "util/Log.h" + +#include "ResourceManager.h" + +using namespace crepe; +using namespace std; + +ResourceManager::ResourceManager(Mediator & mediator) : Manager(mediator) { + dbg_trace(); + mediator.resource_manager = *this; +} +ResourceManager::~ResourceManager() { dbg_trace(); } + +void ResourceManager::clear() { + std::erase_if(this->resources, [](const pair<const Asset, CacheEntry> & pair) { + const CacheEntry & entry = pair.second; + return entry.persistent == false; + }); +} + +void ResourceManager::clear_all() { this->resources.clear(); } + +void ResourceManager::set_persistent(const Asset & asset, bool persistent) { + this->get_entry(asset).persistent = persistent; +} + +ResourceManager::CacheEntry & ResourceManager::get_entry(const Asset & asset) { + if (!this->resources.contains(asset)) this->resources[asset] = {}; + return this->resources.at(asset); +} diff --git a/src/crepe/manager/ResourceManager.h b/src/crepe/manager/ResourceManager.h new file mode 100644 index 0000000..84b275d --- /dev/null +++ b/src/crepe/manager/ResourceManager.h @@ -0,0 +1,78 @@ +#pragma once + +#include <memory> +#include <unordered_map> + +#include "../Resource.h" +#include "../api/Asset.h" + +#include "Manager.h" + +namespace crepe { + +/** + * \brief Owner of concrete Resource instances + * + * ResourceManager caches concrete Resource instances per Asset. Concrete resources are + * destroyed at the end of scenes by default, unless the game programmer marks them as + * persistent. + */ +class ResourceManager : public Manager { +public: + ResourceManager(Mediator & mediator); + virtual ~ResourceManager(); // dbg_trace + +private: + //! Cache entry + struct CacheEntry { + //! Concrete resource instance + std::unique_ptr<Resource> resource = nullptr; + //! Prevent ResourceManager::clear from removing this entry + bool persistent = false; + }; + //! Internal cache + std::unordered_map<const Asset, CacheEntry> resources; + /** + * \brief Ensure a cache entry exists for this asset and return a mutable reference to it + * + * \param asset Asset the concrete resource is instantiated from + * + * \returns Mutable reference to cache entry + */ + CacheEntry & get_entry(const Asset & asset); + +public: + /** + * \brief Mark a resource as persistent (i.e. used across multiple scenes) + * + * \param asset Asset the concrete resource is instantiated from + * \param persistent Whether this resource is persistent (true=keep, false=destroy) + */ + void set_persistent(const Asset & asset, bool persistent); + + /** + * \brief Retrieve reference to concrete Resource by Asset + * + * \param asset Asset the concrete resource is instantiated from + * \tparam Resource Concrete derivative of Resource + * + * This class instantiates the concrete resource if it is not yet stored in the internal + * cache, or returns a reference to the cached resource if it already exists. + * + * \returns Reference to concrete resource + * + * \throws std::runtime_error if the \c Resource parameter does not match with the actual + * type of the resource stored in the cache for this Asset + */ + template <typename Resource> + Resource & get(const Asset & asset); + + //! Clear non-persistent resources from cache + void clear(); + //! Clear all resources from cache regardless of persistence + void clear_all(); +}; + +} // namespace crepe + +#include "ResourceManager.hpp" diff --git a/src/crepe/manager/ResourceManager.hpp b/src/crepe/manager/ResourceManager.hpp new file mode 100644 index 0000000..cf5c949 --- /dev/null +++ b/src/crepe/manager/ResourceManager.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include <format> + +#include "ResourceManager.h" + +namespace crepe { + +template <typename T> +T & ResourceManager::get(const Asset & asset) { + using namespace std; + static_assert(is_base_of<Resource, T>::value, + "cache must recieve a derivative class of Resource"); + + CacheEntry & entry = this->get_entry(asset); + if (entry.resource == nullptr) entry.resource = make_unique<T>(asset, this->mediator); + + T * concrete_resource = dynamic_cast<T *>(entry.resource.get()); + if (concrete_resource == nullptr) + throw runtime_error(format("ResourceManager: mismatch between requested type and " + "actual type of resource ({})", + asset.get_path())); + + return *concrete_resource; +} + +} // namespace crepe diff --git a/src/crepe/manager/SaveManager.cpp b/src/crepe/manager/SaveManager.cpp index d4ed1c1..691ea2f 100644 --- a/src/crepe/manager/SaveManager.cpp +++ b/src/crepe/manager/SaveManager.cpp @@ -1,13 +1,25 @@ #include "../ValueBroker.h" #include "../api/Config.h" #include "../facade/DB.h" -#include "../util/Log.h" #include "SaveManager.h" using namespace std; using namespace crepe; +SaveManager::SaveManager(Mediator & mediator) : Manager(mediator) { + mediator.save_manager = *this; +} + +DB & SaveManager::get_db() { + if (this->db == nullptr) { + Config & cfg = Config::get_instance(); + this->db + = {new DB(cfg.savemgr.location), [](void * db) { delete static_cast<DB *>(db); }}; + } + return *static_cast<DB *>(this->db.get()); +} + template <> string SaveManager::serialize(const string & value) const noexcept { return value; @@ -90,22 +102,6 @@ int32_t SaveManager::deserialize(const string & value) const noexcept { return deserialize<int64_t>(value); } -SaveManager::SaveManager() { dbg_trace(); } - -SaveManager & SaveManager::get_instance() { - dbg_trace(); - static SaveManager instance; - return instance; -} - -DB & SaveManager::get_db() { - Config & cfg = Config::get_instance(); - // TODO: make this path relative to XDG_DATA_HOME on Linux and whatever the - // default equivalent is on Windows using some third party library - static DB db(cfg.savemgr.location); - return db; -} - bool SaveManager::has(const string & key) { DB & db = this->get_db(); return db.has(key); @@ -155,7 +151,8 @@ ValueBroker<T> SaveManager::get(const string & key) { return { [this, key](const T & target) { this->set<T>(key, target); }, [this, key, value]() mutable -> const T & { - value = this->deserialize<T>(this->get_db().get(key)); + DB & db = this->get_db(); + value = this->deserialize<T>(db.get(key)); return value; }, }; diff --git a/src/crepe/manager/SaveManager.h b/src/crepe/manager/SaveManager.h index 3d8c852..61a978d 100644 --- a/src/crepe/manager/SaveManager.h +++ b/src/crepe/manager/SaveManager.h @@ -1,9 +1,12 @@ #pragma once +#include <functional> #include <memory> #include "../ValueBroker.h" +#include "Manager.h" + namespace crepe { class DB; @@ -18,7 +21,7 @@ class DB; * * The underlying database is a key-value store. */ -class SaveManager { +class SaveManager : public Manager { public: /** * \brief Get a read/write reference to a value and initialize it if it does not yet exist @@ -63,8 +66,8 @@ public: */ bool has(const std::string & key); -private: - SaveManager(); +public: + SaveManager(Mediator & mediator); virtual ~SaveManager() = default; private: @@ -89,26 +92,13 @@ private: template <typename T> T deserialize(const std::string & value) const noexcept; -public: - // singleton - static SaveManager & get_instance(); - SaveManager(const SaveManager &) = delete; - SaveManager(SaveManager &&) = delete; - SaveManager & operator=(const SaveManager &) = delete; - SaveManager & operator=(SaveManager &&) = delete; +protected: + //! Create or return DB + virtual DB & get_db(); private: - /** - * \brief Create an instance of DB and return its reference - * - * \returns DB instance - * - * This function exists because DB is a facade class, which can't directly be used in the API - * without workarounds - * - * TODO: better solution - */ - static DB & get_db(); + //! Database + std::unique_ptr<void, std::function<void(void *)>> db = nullptr; }; } // namespace crepe diff --git a/src/crepe/manager/SceneManager.cpp b/src/crepe/manager/SceneManager.cpp index 50a9fbb..d4ca90b 100644 --- a/src/crepe/manager/SceneManager.cpp +++ b/src/crepe/manager/SceneManager.cpp @@ -32,4 +32,7 @@ void SceneManager::load_next_scene() { // Load the new scene scene->load_scene(); + + //clear the next scene + next_scene.clear(); } diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp new file mode 100644 index 0000000..d231c7c --- /dev/null +++ b/src/crepe/system/AISystem.cpp @@ -0,0 +1,188 @@ +#include <algorithm> +#include <cmath> + +#include "manager/ComponentManager.h" +#include "manager/LoopTimerManager.h" +#include "manager/Mediator.h" + +#include "AISystem.h" + +using namespace crepe; +using namespace std::chrono; + +void AISystem::update() { + const Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + LoopTimerManager & timer = mediator.loop_timer; + RefVector<AI> ai_components = mgr.get_components_by_type<AI>(); + LoopTimerManager & loop_timer = mediator.loop_timer; + + //TODO: Use fixed loop dt (this is not available at master at the moment) + duration_t dt = loop_timer.get_delta_time(); + + // Loop through all AI components + for (AI & ai : ai_components) { + if (!ai.active) { + continue; + } + + RefVector<Rigidbody> rigidbodies + = mgr.get_components_by_id<Rigidbody>(ai.game_object_id); + if (rigidbodies.empty()) { + throw std::runtime_error( + "AI component must be attached to a GameObject with a Rigidbody component"); + } + Rigidbody & rigidbody = rigidbodies.front().get(); + if (!rigidbody.active) { + continue; + } + if (rigidbody.data.mass <= 0) { + throw std::runtime_error("Mass must be greater than 0"); + } + + // Calculate the force to apply to the entity + vec2 force = this->calculate(ai, rigidbody); + // Calculate the acceleration (using the above calculated force) + vec2 acceleration = force / rigidbody.data.mass; + // Finally, update Rigidbody's velocity + rigidbody.data.linear_velocity += acceleration * duration_cast<seconds>(dt).count(); + } +} + +vec2 AISystem::calculate(AI & ai, const Rigidbody & rigidbody) { + const Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + RefVector<Transform> transforms = mgr.get_components_by_id<Transform>(ai.game_object_id); + Transform & transform = transforms.front().get(); + + vec2 force; + + // Run all the behaviors that are on, and stop if the force gets too high + if (ai.on(AI::BehaviorTypeMask::FLEE)) { + vec2 force_to_add = this->flee(ai, rigidbody, transform); + + if (!this->accumulate_force(ai, force, force_to_add)) { + return force; + } + } + if (ai.on(AI::BehaviorTypeMask::ARRIVE)) { + vec2 force_to_add = this->arrive(ai, rigidbody, transform); + + if (!this->accumulate_force(ai, force, force_to_add)) { + return force; + } + } + if (ai.on(AI::BehaviorTypeMask::SEEK)) { + vec2 force_to_add = this->seek(ai, rigidbody, transform); + + if (!this->accumulate_force(ai, force, force_to_add)) { + return force; + } + } + if (ai.on(AI::BehaviorTypeMask::PATH_FOLLOW)) { + vec2 force_to_add = this->path_follow(ai, rigidbody, transform); + + if (!this->accumulate_force(ai, force, force_to_add)) { + return force; + } + } + + return force; +} + +bool AISystem::accumulate_force(const AI & ai, vec2 & running_total, vec2 & force_to_add) { + float magnitude = running_total.length(); + float magnitude_remaining = ai.max_force - magnitude; + + if (magnitude_remaining <= 0.0f) { + // If the force is already at/above the max force, return false + return false; + } + + float magnitude_to_add = force_to_add.length(); + if (magnitude_to_add < magnitude_remaining) { + // If the force to add is less than the remaining force, add it + running_total += force_to_add; + } else { + // If the force to add is greater than the remaining force, add a fraction of it + force_to_add.normalize(); + running_total += force_to_add * magnitude_remaining; + } + + return true; +} + +vec2 AISystem::seek(const AI & ai, const Rigidbody & rigidbody, + const Transform & transform) const { + // Calculate the desired velocity + vec2 desired_velocity = ai.seek_target - transform.position; + desired_velocity.normalize(); + desired_velocity *= rigidbody.data.max_linear_velocity; + + return desired_velocity - rigidbody.data.linear_velocity; +} + +vec2 AISystem::flee(const AI & ai, const Rigidbody & rigidbody, + const Transform & transform) const { + // Calculate the desired velocity if the entity is within the panic distance + vec2 desired_velocity = transform.position - ai.flee_target; + if (desired_velocity.length_squared() > ai.square_flee_panic_distance) { + return vec2{0, 0}; + } + desired_velocity.normalize(); + desired_velocity *= rigidbody.data.max_linear_velocity; + + return desired_velocity - rigidbody.data.linear_velocity; +} + +vec2 AISystem::arrive(const AI & ai, const Rigidbody & rigidbody, + const Transform & transform) const { + // Calculate the desired velocity (taking into account the deceleration rate) + vec2 to_target = ai.arrive_target - transform.position; + float distance = to_target.length(); + if (distance > 0.0f) { + if (ai.arrive_deceleration <= 0.0f) { + throw std::runtime_error("Deceleration rate must be greater than 0"); + } + + float speed = distance / ai.arrive_deceleration; + speed = std::min(speed, rigidbody.data.max_linear_velocity.length()); + vec2 desired_velocity = to_target * (speed / distance); + + return desired_velocity - rigidbody.data.linear_velocity; + } + + return vec2{0, 0}; +} + +vec2 AISystem::path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform) { + if (ai.path.empty()) { + return vec2{0, 0}; + } + + // Get the target node + vec2 target = ai.path.at(ai.path_index); + // Calculate the force to apply to the entity + vec2 to_target = target - transform.position; + if (to_target.length_squared() > ai.path_node_distance * ai.path_node_distance) { + // If the entity is not close enough to the target node, seek it + ai.seek_target = target; + ai.arrive_target = target; + } else { + // If the entity is close enough to the target node, move to the next node + ai.path_index++; + if (ai.path_index >= ai.path.size()) { + if (ai.path_loop) { + // If the path is looping, reset the path index + ai.path_index = 0; + } else { + // If the path is not looping, arrive at the last node + ai.path_index = ai.path.size() - 1; + return this->arrive(ai, rigidbody, transform); + } + } + } + + // Seek the target node + return this->seek(ai, rigidbody, transform); +} diff --git a/src/crepe/system/AISystem.h b/src/crepe/system/AISystem.h new file mode 100644 index 0000000..d5f8a8e --- /dev/null +++ b/src/crepe/system/AISystem.h @@ -0,0 +1,81 @@ +#pragma once + +#include "api/AI.h" +#include "api/Rigidbody.h" + +#include "System.h" +#include "api/Transform.h" +#include "types.h" + +namespace crepe { + +/** + * \brief The AISystem is used to control the movement of entities using AI. + * + * The AISystem is used to control the movement of entities using AI. The AISystem can be used to + * implement different behaviors such as seeking, fleeing, arriving, and path following. + */ +class AISystem : public System { +public: + using System::System; + + //! Update the AI system + void update() override; + +private: + /** + * \brief Calculate the total force to apply to the entity + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + */ + vec2 calculate(AI & ai, const Rigidbody & rigidbody); + /** + * \brief Accumulate the force to apply to the entity + * + * \param ai The AI component + * \param running_total The running total of the force + * \param force_to_add The force to add + * \return true if the force was added, false otherwise + */ + bool accumulate_force(const AI & ai, vec2 & running_total, vec2 & force_to_add); + + /** + * \brief Calculate the seek force + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + * \param transform The Transform component + * \return The seek force + */ + vec2 seek(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; + /** + * \brief Calculate the flee force + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + * \param transform The Transform component + * \return The flee force + */ + vec2 flee(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; + /** + * \brief Calculate the arrive force + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + * \param transform The Transform component + * \return The arrive force + */ + vec2 arrive(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; + /** + * \brief Calculate the path follow force + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + * \param transform The Transform component + * \return The path follow force + */ + vec2 path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform); +}; + +} // namespace crepe diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index 549c35d..31eb85c 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -2,7 +2,7 @@ #include "../api/Animator.h" #include "../manager/ComponentManager.h" -#include "api/LoopTimer.h" +#include "../manager/LoopTimerManager.h" #include "AnimatorSystem.h" @@ -10,10 +10,10 @@ using namespace crepe; void AnimatorSystem::update() { ComponentManager & mgr = this->mediator.component_manager; - LoopTimer & timer = this->mediator.timer; + LoopTimerManager & timer = this->mediator.loop_timer; RefVector<Animator> animations = mgr.get_components_by_type<Animator>(); - double elapsed_time = timer.get_current_time(); + unsigned long long elapsed_time = timer.get_elapsed_time().count(); for (Animator & a : animations) { if (!a.active) continue; @@ -23,7 +23,7 @@ void AnimatorSystem::update() { int last_frame = ctx.row; - int cycle_end = (ctx.cycle_end == -1) ? a.max_rows : ctx.cycle_end; + int cycle_end = (ctx.cycle_end == -1) ? a.grid_size.x : ctx.cycle_end; int total_frames = cycle_end - ctx.cycle_start; int curr_frame = static_cast<int>(elapsed_time / frame_duration) % total_frames; diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp new file mode 100644 index 0000000..b1aa0f8 --- /dev/null +++ b/src/crepe/system/AudioSystem.cpp @@ -0,0 +1,62 @@ +#include "AudioSystem.h" + +#include "../manager/ComponentManager.h" +#include "../manager/ResourceManager.h" +#include "../types.h" + +using namespace crepe; +using namespace std; + +void AudioSystem::update() { + ComponentManager & component_manager = this->mediator.component_manager; + ResourceManager & resource_manager = this->mediator.resource_manager; + RefVector<AudioSource> components + = component_manager.get_components_by_type<AudioSource>(); + + for (AudioSource & component : components) { + Sound & resource = resource_manager.get<Sound>(component.source); + + this->diff_update(component, resource); + + this->update_last(component); + } +} + +void AudioSystem::diff_update(AudioSource & component, Sound & resource) { + SoundContext & context = this->get_context(); + + if (component.active != component.last_active) { + if (!component.active) { + context.stop(component.voice); + return; + } + if (component.play_on_awake) component.oneshot_play = true; + } + if (!component.active) return; + + if (component.oneshot_play) { + component.voice = context.play(resource); + component.oneshot_play = false; + } + if (component.oneshot_stop) { + context.stop(component.voice); + component.oneshot_stop = false; + } + if (component.volume != component.last_volume) { + context.set_volume(component.voice, component.volume); + } + if (component.loop != component.last_loop) { + context.set_loop(component.voice, component.loop); + } +} + +void AudioSystem::update_last(AudioSource & component) { + component.last_active = component.active; + component.last_loop = component.loop; + component.last_volume = component.volume; +} + +SoundContext & AudioSystem::get_context() { + if (this->context == nullptr) this->context = make_unique<SoundContext>(); + return *this->context.get(); +} diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h new file mode 100644 index 0000000..2ddc443 --- /dev/null +++ b/src/crepe/system/AudioSystem.h @@ -0,0 +1,51 @@ +#pragma once + +#include "../api/AudioSource.h" +#include "../facade/Sound.h" +#include "../facade/SoundContext.h" + +#include "System.h" + +namespace crepe { + +class AudioSystem : public System { +public: + using System::System; + void update() override; + +private: + /** + * \brief Update `last_*` members of \c component + * + * Copies all component properties stored for comparison between AudioSystem::update() calls + * + * \param component AudioSource component to update + */ + void update_last(AudioSource & component); + + /** + * \brief Compare update component + * + * Compares properties of \c component and \c data, and calls SoundContext functions where + * applicable. + * + * \param component AudioSource component to update + * \param resource Sound instance for AudioSource's Asset + */ + void diff_update(AudioSource & component, Sound & resource); + +protected: + /** + * \brief Get SoundContext + * + * SoundContext is retrieved through this function instead of being a direct member of + * AudioSystem to aid with testability. + */ + virtual SoundContext & get_context(); + +private: + //! SoundContext + std::unique_ptr<SoundContext> context = nullptr; +}; + +} // namespace crepe diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index 95f6e33..0e2db76 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -5,8 +5,10 @@ target_sources(crepe PUBLIC PhysicsSystem.cpp CollisionSystem.cpp RenderSystem.cpp + AudioSystem.cpp AnimatorSystem.cpp InputSystem.cpp + AISystem.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -15,6 +17,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES PhysicsSystem.h CollisionSystem.h RenderSystem.h + AudioSystem.h AnimatorSystem.h InputSystem.h + AISystem.h ) diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp index e76b1ec..459feb3 100644 --- a/src/crepe/system/InputSystem.cpp +++ b/src/crepe/system/InputSystem.cpp @@ -2,6 +2,9 @@ #include "../api/Button.h" #include "../manager/ComponentManager.h" #include "../manager/EventManager.h" +#include "facade/SDLContext.h" +#include "util/Log.h" + #include "InputSystem.h" using namespace crepe; @@ -9,7 +12,8 @@ using namespace crepe; void InputSystem::update() { ComponentManager & mgr = this->mediator.component_manager; EventManager & event_mgr = this->mediator.event_manager; - std::vector<SDLContext::EventData> event_list = SDLContext::get_instance().get_events(); + SDLContext & context = this->mediator.sdl_context; + std::vector<SDLContext::EventData> event_list = context.get_events(); RefVector<Button> buttons = mgr.get_components_by_type<Button>(); RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); OptionalRef<Camera> curr_cam_ref; diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index 26f2c85..afd9548 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -10,9 +10,12 @@ #include "../api/Sprite.h" #include "../api/Transform.h" #include "../facade/SDLContext.h" +#include "../facade/Texture.h" #include "../manager/ComponentManager.h" +#include "../manager/ResourceManager.h" #include "RenderSystem.h" +#include "types.h" using namespace crepe; using namespace std; @@ -27,7 +30,7 @@ void RenderSystem::present_screen() { ctx.present_screen(); } -SDLContext::CameraValues RenderSystem::update_camera() { +void RenderSystem::update_camera() { ComponentManager & mgr = this->mediator.component_manager; SDLContext & ctx = this->mediator.sdl_context; RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); @@ -38,9 +41,9 @@ SDLContext::CameraValues RenderSystem::update_camera() { if (!cam.active) continue; const Transform & transform = mgr.get_components_by_id<Transform>(cam.game_object_id).front().get(); - SDLContext::CameraValues cam_val = ctx.set_camera(cam); - cam_val.cam_pos = transform.position + cam.data.postion_offset; - return cam_val; + vec2 new_camera_pos = transform.position + cam.data.postion_offset; + ctx.update_camera_view(cam, new_camera_pos); + return; } throw std::runtime_error("No active cameras in current scene"); } @@ -67,11 +70,12 @@ void RenderSystem::update() { this->present_screen(); } -bool RenderSystem::render_particle(const Sprite & sprite, const SDLContext::CameraValues & cam, - const double & scale) { +bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) { ComponentManager & mgr = this->mediator.component_manager; SDLContext & ctx = this->mediator.sdl_context; + ResourceManager & resource_manager = this->mediator.resource_manager; + Texture & res = resource_manager.get<Texture>(sprite.source); vector<reference_wrapper<ParticleEmitter>> emitters = mgr.get_components_by_id<ParticleEmitter>(sprite.game_object_id); @@ -88,7 +92,7 @@ bool RenderSystem::render_particle(const Sprite & sprite, const SDLContext::Came ctx.draw(SDLContext::RenderContext{ .sprite = sprite, - .cam = cam, + .texture = res, .pos = p.position, .angle = p.angle, .scale = scale, @@ -97,12 +101,14 @@ bool RenderSystem::render_particle(const Sprite & sprite, const SDLContext::Came } return rendering_particles; } -void RenderSystem::render_normal(const Sprite & sprite, const SDLContext::CameraValues & cam, - const Transform & tm) { +void RenderSystem::render_normal(const Sprite & sprite, const Transform & tm) { SDLContext & ctx = this->mediator.sdl_context; + ResourceManager & resource_manager = this->mediator.resource_manager; + const Texture & res = resource_manager.get<Texture>(sprite.source); + ctx.draw(SDLContext::RenderContext{ .sprite = sprite, - .cam = cam, + .texture = res, .pos = tm.position, .angle = tm.rotation, .scale = tm.scale, @@ -111,7 +117,7 @@ void RenderSystem::render_normal(const Sprite & sprite, const SDLContext::Camera void RenderSystem::render() { ComponentManager & mgr = this->mediator.component_manager; - const SDLContext::CameraValues & cam = this->update_camera(); + this->update_camera(); RefVector<Sprite> sprites = mgr.get_components_by_type<Sprite>(); RefVector<Sprite> sorted_sprites = this->sort(sprites); @@ -121,10 +127,10 @@ void RenderSystem::render() { const Transform & transform = mgr.get_components_by_id<Transform>(sprite.game_object_id).front().get(); - bool rendered_particles = this->render_particle(sprite, cam, transform.scale); + bool rendered_particles = this->render_particle(sprite, transform.scale); if (rendered_particles) continue; - this->render_normal(sprite, cam, transform); + this->render_normal(sprite, transform); } } diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h index e270a6b..fc7b46e 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -2,8 +2,6 @@ #include <cmath> -#include "facade/SDLContext.h" - #include "System.h" #include "types.h" @@ -14,7 +12,6 @@ class Sprite; class Transform; /** - * \class RenderSystem * \brief Manages rendering operations for all game objects. * * RenderSystem is responsible for rendering, clearing and presenting the screen, and @@ -37,7 +34,7 @@ private: void present_screen(); //! Updates the active camera used for rendering. - SDLContext::CameraValues update_camera(); + void update_camera(); //! Renders the whole screen void render(); @@ -52,8 +49,7 @@ private: * constructor is now protected i cannot make tmp inside * \return true if particles have been rendered */ - bool render_particle(const Sprite & sprite, const SDLContext::CameraValues & cam, - const double & scale); + bool render_particle(const Sprite & sprite, const double & scale); /** * \brief renders a sprite with a Transform component on the screen @@ -61,8 +57,7 @@ private: * \param sprite the sprite component that holds all the data * \param tm the Transform component that holds the position,rotation and scale */ - void render_normal(const Sprite & sprite, const SDLContext::CameraValues & cam, - const Transform & tm); + void render_normal(const Sprite & sprite, const Transform & tm); /** * \brief sort a vector sprite objects with diff --git a/src/crepe/system/System.cpp b/src/crepe/system/System.cpp index f68549b..ecc740d 100644 --- a/src/crepe/system/System.cpp +++ b/src/crepe/system/System.cpp @@ -1,7 +1,5 @@ -#include "../util/Log.h" - #include "System.h" using namespace crepe; -System::System(const Mediator & mediator) : mediator(mediator) { dbg_trace(); } +System::System(const Mediator & mediator) : mediator(mediator) {} diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h index 3201667..1b2cb3f 100644 --- a/src/crepe/util/OptionalRef.h +++ b/src/crepe/util/OptionalRef.h @@ -25,7 +25,7 @@ public: */ OptionalRef<T> & operator=(T & ref); /** - * \brief Retrieve this reference + * \brief Retrieve this reference (cast) * * \returns Internal reference if it is set * @@ -33,6 +33,14 @@ public: */ operator T &() const; /** + * \brief Retrieve this reference (member access) + * + * \returns Internal reference if it is set + * + * \throws std::runtime_error if this function is called while the reference it not set + */ + T * operator->() const; + /** * \brief Check if this reference is not empty * * \returns `true` if reference is set, or `false` if it is not diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp index 4608c9e..5e36b3a 100644 --- a/src/crepe/util/OptionalRef.hpp +++ b/src/crepe/util/OptionalRef.hpp @@ -19,6 +19,13 @@ OptionalRef<T>::operator T &() const { } template <typename T> +T * OptionalRef<T>::operator->() const { + if (this->ref == nullptr) + throw std::runtime_error("OptionalRef: attempt to dereference nullptr"); + return this->ref; +} + +template <typename T> OptionalRef<T> & OptionalRef<T>::operator=(T & ref) { this->ref = &ref; return *this; |