diff options
Diffstat (limited to 'src/crepe')
43 files changed, 811 insertions, 280 deletions
diff --git a/src/crepe/Resource.cpp b/src/crepe/Resource.cpp index 27b4c4b..85913ed 100644 --- a/src/crepe/Resource.cpp +++ b/src/crepe/Resource.cpp @@ -1,5 +1,6 @@ #include "Resource.h" +#include "manager/Mediator.h" using namespace crepe; -Resource::Resource(const Asset & asset) {} +Resource::Resource(const Asset & asset, Mediator & mediator) {} diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h index eceb15b..d65206b 100644 --- a/src/crepe/Resource.h +++ b/src/crepe/Resource.h @@ -4,6 +4,7 @@ namespace crepe { class ResourceManager; class Asset; +class Mediator; /** * \brief Resource interface @@ -17,7 +18,7 @@ class Asset; */ class Resource { public: - Resource(const Asset & src); + Resource(const Asset & src, Mediator & mediator); virtual ~Resource() = default; Resource(const Resource &) = delete; 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/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp index d22afdf..af7572c 100644 --- a/src/crepe/api/BehaviorScript.cpp +++ b/src/crepe/api/BehaviorScript.cpp @@ -10,6 +10,6 @@ BehaviorScript::BehaviorScript(game_object_id_t id, Mediator & mediator) template <> BehaviorScript & GameObject::add_component<BehaviorScript>() { - ComponentManager & mgr = this->component_manager; - return mgr.add_component<BehaviorScript>(this->id, mgr.mediator); + ComponentManager & mgr = this->mediator.component_manager; + return mgr.add_component<BehaviorScript>(this->id, this->mediator); } diff --git a/src/crepe/api/BehaviorScript.h b/src/crepe/api/BehaviorScript.h index 3909b96..02d588a 100644 --- a/src/crepe/api/BehaviorScript.h +++ b/src/crepe/api/BehaviorScript.h @@ -9,6 +9,7 @@ namespace crepe { class ScriptSystem; +class Mediator; class ComponentManager; class Script; diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index a163faf..46deb67 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -6,7 +6,6 @@ target_sources(crepe PUBLIC ParticleEmitter.cpp Transform.cpp Color.cpp - Texture.cpp Sprite.cpp Config.cpp Metadata.cpp @@ -23,6 +22,7 @@ target_sources(crepe PUBLIC Script.cpp Button.cpp UIObject.cpp + AI.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -38,7 +38,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Vector2.h Vector2.hpp Color.h - Texture.h Scene.h Metadata.h Camera.h @@ -55,4 +54,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Asset.h Button.h UIObject.h + AI.h ) diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp index 9ef4682..ea9c425 100644 --- a/src/crepe/api/GameObject.cpp +++ b/src/crepe/api/GameObject.cpp @@ -7,20 +7,20 @@ using namespace crepe; using namespace std; -GameObject::GameObject(ComponentManager & component_manager, game_object_id_t id, +GameObject::GameObject(Mediator & mediator, game_object_id_t id, const std::string & name, const std::string & tag, const vec2 & position, double rotation, double scale) : id(id), - component_manager(component_manager) { + mediator(mediator) { // Add Transform and Metadata components - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; mgr.add_component<Transform>(this->id, position, rotation, scale); mgr.add_component<Metadata>(this->id, name, tag); } void GameObject::set_parent(const GameObject & parent) { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; // Set parent on own Metadata component RefVector<Metadata> this_metadata = mgr.get_components_by_id<Metadata>(this->id); @@ -32,7 +32,7 @@ void GameObject::set_parent(const GameObject & parent) { } void GameObject::set_persistent(bool persistent) { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; mgr.set_persistent(this->id, persistent); } diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h index ff80f49..a311c21 100644 --- a/src/crepe/api/GameObject.h +++ b/src/crepe/api/GameObject.h @@ -6,7 +6,7 @@ namespace crepe { -class ComponentManager; +class Mediator; /** * \brief Represents a GameObject @@ -20,7 +20,7 @@ private: * This constructor creates a new GameObject. It creates a new Transform and Metadata * component and adds them to the ComponentManager. * - * \param component_manager Reference to component_manager + * \param mediator Reference to mediator * \param id The id of the GameObject * \param name The name of the GameObject * \param tag The tag of the GameObject @@ -28,7 +28,7 @@ private: * \param rotation The rotation of the GameObject * \param scale The scale of the GameObject */ - GameObject(ComponentManager & component_manager, game_object_id_t id, + GameObject(Mediator & mediator, game_object_id_t id, const std::string & name, const std::string & tag, const vec2 & position, double rotation, double scale); //! ComponentManager instances GameObject @@ -73,7 +73,7 @@ public: const game_object_id_t id; protected: - ComponentManager & component_manager; + Mediator & mediator; }; } // namespace crepe diff --git a/src/crepe/api/GameObject.hpp b/src/crepe/api/GameObject.hpp index a6b45b0..69f7d73 100644 --- a/src/crepe/api/GameObject.hpp +++ b/src/crepe/api/GameObject.hpp @@ -8,7 +8,7 @@ namespace crepe { template <typename T, typename... Args> T & GameObject::add_component(Args &&... args) { - ComponentManager & mgr = this->component_manager; + ComponentManager & mgr = this->mediator.component_manager; return mgr.add_component<T>(this->id, std::forward<Args>(args)...); } diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index ff428fd..3511bca 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -1,3 +1,4 @@ +#include "../system/AISystem.h" #include "../system/AnimatorSystem.h" #include "../system/AudioSystem.h" #include "../system/CollisionSystem.h" @@ -15,6 +16,7 @@ using namespace std; LoopManager::LoopManager() { this->load_system<ScriptSystem>(); + this->load_system<AISystem>(); this->load_system<PhysicsSystem>(); this->load_system<CollisionSystem>(); this->load_system<AnimatorSystem>(); diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h index 4b1fc1e..28aa423 100644 --- a/src/crepe/api/LoopManager.h +++ b/src/crepe/api/LoopManager.h @@ -3,11 +3,12 @@ #include "../facade/SDLContext.h" #include "../manager/ComponentManager.h" #include "../manager/ResourceManager.h" -#include "../manager/SceneManager.h" #include "../manager/SaveManager.h" +#include "../manager/SceneManager.h" #include "../system/System.h" #include "LoopTimer.h" +#include "manager/ResourceManager.h" namespace crepe { @@ -71,11 +72,10 @@ private: ResourceManager resource_manager{mediator}; //! Save manager instance SaveManager save_manager{mediator}; - - //! SDL context \todo no more singletons! - SDLContext & sdl_context = mediator.sdl_context; - //! Loop timer \todo no more singletons! - LoopTimer & loop_timer = LoopTimer::get_instance(); + //! SDLContext instance + SDLContext sdl_context{mediator}; + //! LoopTimer instance + LoopTimer loop_timer{mediator}; private: /** diff --git a/src/crepe/api/LoopTimer.cpp b/src/crepe/api/LoopTimer.cpp index 15a0e3a..56e48d3 100644 --- a/src/crepe/api/LoopTimer.cpp +++ b/src/crepe/api/LoopTimer.cpp @@ -1,17 +1,16 @@ #include <chrono> -#include "../facade/SDLContext.h" #include "../util/Log.h" +#include "facade/SDLContext.h" +#include "manager/Manager.h" #include "LoopTimer.h" using namespace crepe; -LoopTimer::LoopTimer() { dbg_trace(); } - -LoopTimer & LoopTimer::get_instance() { - static LoopTimer instance; - return instance; +LoopTimer::LoopTimer(Mediator & mediator) : Manager(mediator) { + dbg_trace(); + mediator.timer = *this; } void LoopTimer::start() { @@ -67,7 +66,8 @@ void LoopTimer::enforce_frame_rate() { = 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()); + SDLContext & ctx = this->mediator.sdl_context; + ctx.delay(delay_time.count()); } } diff --git a/src/crepe/api/LoopTimer.h b/src/crepe/api/LoopTimer.h index 9393439..0a73a4c 100644 --- a/src/crepe/api/LoopTimer.h +++ b/src/crepe/api/LoopTimer.h @@ -1,19 +1,13 @@ #pragma once +#include "manager/Manager.h" #include <chrono> namespace crepe { -class LoopTimer { +class LoopTimer : public Manager { 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. @@ -102,7 +96,7 @@ private: * * Private constructor for singleton pattern to restrict instantiation outside the class. */ - LoopTimer(); + LoopTimer(Mediator & mediator); /** * \brief Update the timer to the current frame. 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 6becf60..1dbd6a5 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -18,23 +18,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 +57,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() { @@ -221,25 +218,19 @@ 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; } const CameraValues & cam = ctx.cam; @@ -260,15 +251,24 @@ 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, + .texture = ctx.texture, .cam = ctx.cam, .pos = ctx.pos, .img_scale = ctx.scale, @@ -276,9 +276,9 @@ void SDLContext::draw(const RenderContext & ctx) { 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) { @@ -368,7 +368,7 @@ 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; } @@ -435,6 +435,6 @@ std::vector<SDLContext::EventData> SDLContext::get_events() { return event_list; } 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 e232511..46b779f 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -14,14 +14,15 @@ #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 @@ -62,6 +63,7 @@ public: //! rendering data needed to render on screen struct RenderContext { const Sprite & sprite; + const Texture & texture; const CameraValues & cam; const vec2 & pos; const double & angle; @@ -92,20 +94,27 @@ public: float scroll_delta = INFINITY; ivec2 rel_mouse_move = {-1, -1}; }; - /** - * \brief Gets the singleton instance of SDLContext. - * \return Reference to the SDLContext instance. - */ - 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. * @@ -139,9 +148,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. @@ -157,23 +164,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. @@ -184,14 +175,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 @@ -207,33 +195,24 @@ private: /** * \brief sets the background of the camera (will be adjusted in future PR) * \param camera Reference to the Camera object. + * \return camera data the component cannot store */ CameraValues set_camera(const Camera & camera); -private: +public: //! the data needed to construct a sdl dst rectangle struct DestinationRectangleData { const Sprite & sprite; + const Texture & texture; const CameraValues & cam; const vec2 & pos; const double & img_scale; }; - /** - * \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; diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index ad50637..97e455e 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -6,7 +6,7 @@ using namespace crepe; using namespace std; -Sound::Sound(const Asset & src) : Resource(src) { +Sound::Sound(const Asset & src, Mediator & mediator) : Resource(src, mediator) { this->sample.load(src.get_path().c_str()); dbg_trace(); } diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index 85d141b..4a5d692 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -8,6 +8,7 @@ namespace crepe { class SoundContext; +class Mediator; /** * \brief Sound resource facade @@ -17,7 +18,7 @@ class SoundContext; */ class Sound : public Resource { public: - Sound(const Asset & src); + Sound(const Asset & src, Mediator & mediator); ~Sound(); // dbg_trace private: 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/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp index df30d27..24ba0d7 100644 --- a/src/crepe/manager/ComponentManager.cpp +++ b/src/crepe/manager/ComponentManager.cpp @@ -53,7 +53,7 @@ GameObject ComponentManager::new_object(const string & name, const string & tag, this->next_id++; } - GameObject object{*this, this->next_id, name, tag, position, rotation, scale}; + GameObject object{this->mediator, this->next_id, name, tag, position, rotation, scale}; this->next_id++; return object; diff --git a/src/crepe/manager/ComponentManager.h b/src/crepe/manager/ComponentManager.h index 94fd94f..dd7c154 100644 --- a/src/crepe/manager/ComponentManager.h +++ b/src/crepe/manager/ComponentManager.h @@ -143,7 +143,6 @@ public: RefVector<T> get_components_by_tag(const std::string & tag) const; struct SnapshotComponent { - by_type<by_id_index<std::vector<std::unique_ptr<Component>>>> components; Component component; }; struct Snapshot { diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h index 35ac181..628154a 100644 --- a/src/crepe/manager/Mediator.h +++ b/src/crepe/manager/Mediator.h @@ -3,9 +3,7 @@ #include "../util/OptionalRef.h" // TODO: remove these singletons: -#include "../facade/SDLContext.h" #include "EventManager.h" -#include "api/LoopTimer.h" namespace crepe { @@ -13,6 +11,8 @@ class ComponentManager; class SceneManager; class SaveManager; class ResourceManager; +class SDLContext; +class LoopTimer; /** * Struct to pass references to classes that would otherwise need to be singletons down to @@ -27,13 +27,13 @@ class ResourceManager; * \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; OptionalRef<EventManager> event_manager = EventManager::get_instance(); OptionalRef<ResourceManager> resource_manager; - OptionalRef<SDLContext> sdl_context = SDLContext::get_instance(); - OptionalRef<LoopTimer> timer = LoopTimer::get_instance(); + OptionalRef<LoopTimer> timer; }; } // namespace crepe diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp index 7c01808..a141a46 100644 --- a/src/crepe/manager/ResourceManager.cpp +++ b/src/crepe/manager/ResourceManager.cpp @@ -6,8 +6,8 @@ using namespace crepe; using namespace std; ResourceManager::ResourceManager(Mediator & mediator) : Manager(mediator) { - mediator.resource_manager = *this; dbg_trace(); + mediator.resource_manager = *this; } ResourceManager::~ResourceManager() { dbg_trace(); } diff --git a/src/crepe/manager/ResourceManager.hpp b/src/crepe/manager/ResourceManager.hpp index 5167d71..cf5c949 100644 --- a/src/crepe/manager/ResourceManager.hpp +++ b/src/crepe/manager/ResourceManager.hpp @@ -13,7 +13,7 @@ T & ResourceManager::get(const Asset & asset) { "cache must recieve a derivative class of Resource"); CacheEntry & entry = this->get_entry(asset); - if (entry.resource == nullptr) entry.resource = make_unique<T>(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) diff --git a/src/crepe/manager/SaveManager.cpp b/src/crepe/manager/SaveManager.cpp index 39b92d4..691ea2f 100644 --- a/src/crepe/manager/SaveManager.cpp +++ b/src/crepe/manager/SaveManager.cpp @@ -14,10 +14,8 @@ SaveManager::SaveManager(Mediator & mediator) : Manager(mediator) { 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); } - }; + this->db + = {new DB(cfg.savemgr.location), [](void * db) { delete static_cast<DB *>(db); }}; } return *static_cast<DB *>(this->db.get()); } diff --git a/src/crepe/manager/SaveManager.h b/src/crepe/manager/SaveManager.h index 27e625c..61a978d 100644 --- a/src/crepe/manager/SaveManager.h +++ b/src/crepe/manager/SaveManager.h @@ -1,7 +1,7 @@ #pragma once -#include <memory> #include <functional> +#include <memory> #include "../ValueBroker.h" @@ -95,9 +95,10 @@ private: protected: //! Create or return DB virtual DB & get_db(); + private: //! Database - std::unique_ptr<void, std::function<void(void*)>> db = nullptr; + std::unique_ptr<void, std::function<void(void *)>> db = nullptr; }; } // namespace crepe diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp new file mode 100644 index 0000000..3c3fd93 --- /dev/null +++ b/src/crepe/system/AISystem.cpp @@ -0,0 +1,186 @@ +#include <algorithm> +#include <cmath> + +#include "api/LoopTimer.h" +#include "manager/ComponentManager.h" +#include "manager/Mediator.h" + +#include "AISystem.h" + +using namespace crepe; + +void AISystem::fixed_update() { + const Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + LoopTimer & timer = mediator.timer; + RefVector<AI> ai_components = mgr.get_components_by_type<AI>(); + + //TODO: Use fixed loop dt (this is not available at master at the moment) + double dt = timer.get_delta_time(); + + // 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 * dt; + } +} + +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..04807cf --- /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 fixed_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 8d6d8fa..02647f1 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -21,7 +21,7 @@ void AnimatorSystem::frame_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 index 9b86069..d4e8b9f 100644 --- a/src/crepe/system/AudioSystem.cpp +++ b/src/crepe/system/AudioSystem.cpp @@ -30,8 +30,7 @@ void AudioSystem::diff_update(AudioSource & component, Sound & resource) { context.stop(component.voice); return; } - if (component.play_on_awake) - component.oneshot_play = true; + if (component.play_on_awake) component.oneshot_play = true; } if (!component.active) return; diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index 1336114..3473876 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(crepe PUBLIC InputSystem.cpp EventSystem.cpp ReplaySystem.cpp + AISystem.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -22,4 +23,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES AnimatorSystem.h InputSystem.h EventSystem.h + AISystem.h ) diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp index b31bd34..7796b47 100644 --- a/src/crepe/system/InputSystem.cpp +++ b/src/crepe/system/InputSystem.cpp @@ -1,6 +1,8 @@ #include "../api/Button.h" #include "../manager/ComponentManager.h" #include "../manager/EventManager.h" +#include "facade/SDLContext.h" +#include "util/Log.h" #include "InputSystem.h" @@ -9,7 +11,8 @@ using namespace crepe; void InputSystem::fixed_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 e147b9f..7e9dfc6 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -10,7 +10,9 @@ #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" @@ -72,6 +74,8 @@ bool RenderSystem::render_particle(const Sprite & sprite, const SDLContext::Came 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,6 +92,7 @@ bool RenderSystem::render_particle(const Sprite & sprite, const SDLContext::Came ctx.draw(SDLContext::RenderContext{ .sprite = sprite, + .texture = res, .cam = cam, .pos = p.position, .angle = p.angle, @@ -100,8 +105,12 @@ bool RenderSystem::render_particle(const Sprite & sprite, const SDLContext::Came void RenderSystem::render_normal(const Sprite & sprite, const SDLContext::CameraValues & cam, 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, + .texture = res, .cam = cam, .pos = tm.position, .angle = tm.rotation, |