diff options
Diffstat (limited to 'src/crepe/api')
57 files changed, 3321 insertions, 50 deletions
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 new file mode 100644 index 0000000..203cef3 --- /dev/null +++ b/src/crepe/api/Animator.cpp @@ -0,0 +1,57 @@ + +#include "util/dbg.h" + +#include "Animator.h" +#include "Component.h" +#include "Sprite.h" + +using namespace crepe; + +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), + grid_size(grid_size), + data(data) { + dbg_trace(); + + 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; + + this->spritesheet.aspect_ratio + = static_cast<float>(single_frame_size.x) / single_frame_size.y; +} + +Animator::~Animator() { dbg_trace(); } + +void Animator::loop() { this->data.looping = true; } + +void Animator::play() { this->active = true; } + +void Animator::pause() { this->active = false; } + +void Animator::stop() { + this->active = false; + this->data.col = 0; + this->data.row = 0; +} +void Animator::set_fps(int fps) { this->data.fps = fps; } + +void Animator::set_cycle_range(int start, int end) { + this->data.cycle_start = start, this->data.cycle_end = end; +} + +void Animator::set_anim(int col) { + Animator::Data & ctx = this->data; + this->spritesheet.mask.x = ctx.row = 0; + ctx.col = col; + this->spritesheet.mask.y = ctx.col * this->spritesheet.mask.h; +} + +void Animator::next_anim() { + Animator::Data & ctx = this->data; + 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 new file mode 100644 index 0000000..5918800 --- /dev/null +++ b/src/crepe/api/Animator.h @@ -0,0 +1,104 @@ +#pragma once + +#include "../types.h" + +#include "Component.h" +#include "Sprite.h" + +namespace crepe { + +class AnimatorSystem; +class SDLContext; + +/** + * \brief The Animator component is used to animate sprites by managing the movement and frame + * changes within a sprite sheet. + * + * This component allows for controlling sprite animation through rows and columns of a sprite + * sheet. It can be used to play animations, loop them, or stop them. + */ +class Animator : public Component { +public: + struct Data { + //! frames per second for animation + unsigned int fps = 1; + //! The current col being animated. + unsigned int col = 0; + //! The current row being animated. + unsigned int row = 0; + //! should the animation loop + bool looping = false; + //! starting frame for cycling + unsigned int cycle_start = 0; + //! end frame for cycling (-1 = use last frame) + int cycle_end = -1; + }; + +public: + //! Animator will repeat the animation + void loop(); + //! starts the animation + void play(); + //! pauses the animation + void pause(); + /** + * \brief stops the animation + * + * sets the active on false and resets all the current rows and columns + */ + void stop(); + /** + * \brief set frames per second + * + * \param fps frames per second + */ + void set_fps(int fps); + /** + * \brief set the range in the row + * + * \param start of row animation + * \param end of row animation + */ + void set_cycle_range(int start, int end); + /** + * \brief select which column to animate from + * + * \param col animation column + */ + void set_anim(int col); + //! will go to the next animaiton of current row + void next_anim(); + +public: + /** + * \brief Constructs an Animator object that will control animations for a sprite sheet. + * + * \param id The unique identifier for the component, typically assigned automatically. + * \param spritesheet the reference to the 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, const ivec2 & single_frame_size, + const uvec2 & grid_size, const Animator::Data & data); + ~Animator(); // dbg_trace + +public: + 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; +}; + +} // namespace crepe diff --git a/src/crepe/api/Asset.cpp b/src/crepe/api/Asset.cpp new file mode 100644 index 0000000..e148367 --- /dev/null +++ b/src/crepe/api/Asset.cpp @@ -0,0 +1,54 @@ +#include <filesystem> +#include <stdexcept> +#include <whereami.h> + +#include "api/Config.h" + +#include "Asset.h" + +using namespace crepe; +using namespace std; + +Asset::Asset(const string & src) : src(find_asset(src)) {} +Asset::Asset(const char * src) : src(find_asset(src)) {} + +const string & Asset::get_path() const noexcept { return this->src; } + +string Asset::find_asset(const string & src) const { + auto & cfg = Config::get_instance(); + string & root_pattern = cfg.asset.root_pattern; + + // if root_pattern is empty, find_asset must return all paths as-is + if (root_pattern.empty()) return src; + + // absolute paths do not need to be resolved, only canonicalized + filesystem::path path = src; + if (path.is_absolute()) return filesystem::canonical(path); + + // find directory matching root_pattern + filesystem::path root = this->whereami(); + while (1) { + if (filesystem::exists(root / root_pattern)) break; + if (!root.has_parent_path()) + throw runtime_error(format("Asset: Cannot find root pattern ({})", root_pattern)); + root = root.parent_path(); + } + + // join path to root (base directory) and canonicalize + return filesystem::canonical(root / path); +} + +string Asset::whereami() const noexcept { + string path; + size_t path_length = wai_getExecutablePath(NULL, 0, NULL); + path.resize(path_length + 1); // wai writes null byte + wai_getExecutablePath(path.data(), path_length, NULL); + path.resize(path_length); + return path; +} + +bool Asset::operator==(const Asset & other) const noexcept { return this->src == other.src; } + +size_t std::hash<const Asset>::operator()(const Asset & asset) const noexcept { + return std::hash<string>{}(asset.get_path()); +}; diff --git a/src/crepe/api/Asset.h b/src/crepe/api/Asset.h new file mode 100644 index 0000000..bfd0ac7 --- /dev/null +++ b/src/crepe/api/Asset.h @@ -0,0 +1,84 @@ +#pragma once + +#include <string> + +namespace crepe { + +/** + * \brief Asset location helper + * + * This class is used to locate game asset files, and should *always* be used + * instead of reading file paths directly. + */ +class Asset { +public: + /** + * \param src Unique identifier to asset + */ + Asset(const std::string & src); + /** + * \param src Unique identifier to asset + */ + Asset(const char * src); + +public: + /** + * \brief Get the path to this asset + * \return path to this asset + */ + const std::string & get_path() const noexcept; + + /** + * \brief Comparison operator + * \param other Possibly different instance of \c Asset to test equality against + * \return True if \c this and \c other are equal + */ + bool operator==(const Asset & other) const noexcept; + +private: + //! path to asset + const std::string src; + +private: + /** + * \brief Locate asset path, or throw exception if it cannot be found + * + * This function resolves asset locations relative to crepe::Config::root_pattern if it is + * set and \p src is a relative path. If \p src is an absolute path, it is canonicalized. + * This function only returns if the file can be found. + * + * \param src Arbitrary path to resource file + * + * \returns \p src if crepe::Config::root_pattern is empty + * \returns Canonical path to \p src + * + * \throws std::runtime_error if root_pattern cannot be found + * \throws std::filesystem::filesystem_error if the resolved path does not exist + * \throws std::filesystem::filesystem_error if the path cannot be canonicalized + */ + std::string find_asset(const std::string & src) const; + /** + * \returns The path to the current executable + */ + std::string whereami() const noexcept; +}; + +} // namespace crepe + +namespace std { + +//! Hash helper struct +template <> +struct hash<const crepe::Asset> { + /** + * \brief Hash operator for crepe::Asset + * + * This function hashes a crepe::Asset instance, allowing it to be used as a key in an \c + * std::unordered_map. + * + * \returns Hash value + */ + size_t operator()(const crepe::Asset & asset) const noexcept; +}; + +} // namespace std diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp index b512d27..7b05cb1 100644 --- a/src/crepe/api/AudioSource.cpp +++ b/src/crepe/api/AudioSource.cpp @@ -1,22 +1,15 @@ #include "AudioSource.h" -#include "../Sound.h" -#include <memory> +using namespace crepe; +using namespace std; -using namespace crepe::api; - -AudioSource::AudioSource(std::unique_ptr<Asset> audio_clip) { - this->sound = std::make_unique<crepe::Sound>(std::move(audio_clip)); -} - -void AudioSource::play() { return this->play(false); } +AudioSource::AudioSource(game_object_id_t id, const Asset & src) + : Component(id), + source(src) {} void AudioSource::play(bool looping) { - this->sound->set_looping(looping); - this->sound->play(); + this->loop = looping; + this->oneshot_play = true; } -void AudioSource::stop() { - this->sound->pause(); - this->sound->rewind(); -} +void AudioSource::stop() { this->oneshot_stop = true; } diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h index 2d26cda..b20e490 100644 --- a/src/crepe/api/AudioSource.h +++ b/src/crepe/api/AudioSource.h @@ -1,41 +1,74 @@ #pragma once -#include <memory> +#include "../Component.h" +#include "../facade/SoundHandle.h" +#include "../types.h" #include "Asset.h" -#include "Component.h" +#include "GameObject.h" namespace crepe { -class Sound; -} -namespace crepe::api { +class AudioSystem; //! Audio source component -class AudioSource : 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: - AudioSource(std::unique_ptr<Asset> audio_clip); + // std::unique_ptr needs to be able to destoy this component virtual ~AudioSource() = default; public: - //! Start or resume this audio source - void play(); - void play(bool looping); + //! Start this audio source + void play(bool looping = false); //! Stop this audio source void stop(); public: - //! Sample file location - std::unique_ptr<Asset> audio_clip; - //! TODO: ????? - bool play_on_awake; + //! Play when this component becomes active + bool play_on_awake = false; //! Repeat the current audio clip during playback - bool loop; + bool loop = false; //! Normalized volume (0.0 - 1.0) - float volume; + float volume = 1.0; private: - std::unique_ptr<crepe::Sound> sound; + //! 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::api +} // namespace crepe diff --git a/src/crepe/api/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp index 2dd933e..af7572c 100644 --- a/src/crepe/api/BehaviorScript.cpp +++ b/src/crepe/api/BehaviorScript.cpp @@ -1,10 +1,15 @@ -#include "../util/log.h" - #include "BehaviorScript.h" +#include "Component.h" +#include "GameObject.h" -using namespace crepe::api; +using namespace crepe; -BehaviorScript::BehaviorScript() { - dbg_trace(); -} +BehaviorScript::BehaviorScript(game_object_id_t id, Mediator & mediator) + : Component(id), + mediator(mediator) {} +template <> +BehaviorScript & GameObject::add_component<BehaviorScript>() { + 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 ba60a8c..3909b96 100644 --- a/src/crepe/api/BehaviorScript.h +++ b/src/crepe/api/BehaviorScript.h @@ -1,20 +1,74 @@ #pragma once -#include "../Script.h" +#include <memory> + #include "../Component.h" -namespace crepe::api { +#include "GameObject.h" + +namespace crepe { -class BehaviorScript : public Script, public Component { +class ScriptSystem; +class ComponentManager; +class Script; + +/** + * \brief Script component + * + * This class acts as a (component) wrapper around an instance of (a class derivatived from) \c + * Script. \c BehaviorScript is the only ECS component that stores member function + * implementations as data. + */ +class BehaviorScript : public Component { protected: - // only allow ComponentManager to instantiate scripts + /** + * \param id Parent \c GameObject id + * \param mediator Mediator reference + * + * \note Calls to this constructor (should) always pass through \c GameObject::add_component, + * which has an exception for this specific component type. This was done so the user does + * not have to pass references used within \c Script to each \c BehaviorScript instance. + */ + BehaviorScript(game_object_id_t id, Mediator & mediator); + //! Only ComponentManager is allowed to instantiate BehaviorScript friend class ComponentManager; - BehaviorScript(); + public: - // but allow uniqe_ptr to call the destructor (THIS IS VERY IMPORTANT) - virtual ~BehaviorScript() = default; + /** + * \brief Set the concrete script of this component + * + * \tparam T Concrete script type (derived from \c crepe::Script) + * \tparam Args Arguments for concrete script constructor + * + * \param args Arguments for concrete script constructor (forwarded using perfect forwarding) + * + * \returns Reference to BehaviorScript component (`*this`) + */ + template <class T, typename... Args> + BehaviorScript & set_script(Args &&... args); +protected: + //! Script instance + std::unique_ptr<Script> script = nullptr; + //! ScriptSystem needs direct access to the script instance + friend class ScriptSystem; + +protected: + //! Reference mediator + Mediator & mediator; }; -} +/** + * \brief Add a BehaviorScript component to this game object + * + * The \c BehaviorScript class is the only exception to the ECS harmony, and requires a + * reference to the component manager passed to its constructor in order to function normally. + * This is because the \c BehaviorScript (and \c Script) classes are the only component-related + * classes that store implemented member functions as data. + */ +template <> +BehaviorScript & GameObject::add_component<BehaviorScript>(); + +} // namespace crepe +#include "BehaviorScript.hpp" diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp new file mode 100644 index 0000000..353d5e2 --- /dev/null +++ b/src/crepe/api/BehaviorScript.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include <type_traits> + +#include "BehaviorScript.h" +#include "Script.h" + +namespace crepe { + +template <class T, typename... Args> +BehaviorScript & BehaviorScript::set_script(Args &&... args) { + static_assert(std::is_base_of<Script, T>::value); + this->script = std::unique_ptr<Script>(new T(std::forward<Args>(args)...)); + + this->script->game_object_id = this->game_object_id; + this->script->active = this->active; + this->script->mediator = this->mediator; + + return *this; +} + +} // namespace crepe diff --git a/src/crepe/api/BoxCollider.cpp b/src/crepe/api/BoxCollider.cpp new file mode 100644 index 0000000..a893d41 --- /dev/null +++ b/src/crepe/api/BoxCollider.cpp @@ -0,0 +1,10 @@ +#include "BoxCollider.h" + +#include "../Collider.h" + +using namespace crepe; + +BoxCollider::BoxCollider(game_object_id_t game_object_id, const vec2 & dimensions, + const vec2 & offset) + : Collider(game_object_id, offset), + dimensions(dimensions) {} diff --git a/src/crepe/api/BoxCollider.h b/src/crepe/api/BoxCollider.h new file mode 100644 index 0000000..d643e7f --- /dev/null +++ b/src/crepe/api/BoxCollider.h @@ -0,0 +1,23 @@ +#pragma once + +#include "../Collider.h" +#include "Vector2.h" +#include "types.h" + +namespace crepe { + +/** + * \brief A class representing a box-shaped collider. + * + * This class is used for collision detection with other colliders (e.g., CircleCollider). + */ +class BoxCollider : public Collider { +public: + BoxCollider(game_object_id_t game_object_id, const vec2 & dimensions, + const vec2 & offset = {0, 0}); + + //! Width and height of the box collider + vec2 dimensions; +}; + +} // namespace crepe diff --git a/src/crepe/api/Button.cpp b/src/crepe/api/Button.cpp new file mode 100644 index 0000000..40153c9 --- /dev/null +++ b/src/crepe/api/Button.cpp @@ -0,0 +1,8 @@ +#include "Button.h" + +namespace crepe { + +Button::Button(game_object_id_t id, const vec2 & dimensions, const vec2 & offset) + : UIObject(id, dimensions, offset) {} + +} // namespace crepe diff --git a/src/crepe/api/Button.h b/src/crepe/api/Button.h new file mode 100644 index 0000000..d42527e --- /dev/null +++ b/src/crepe/api/Button.h @@ -0,0 +1,48 @@ +#pragma once + +#include <functional> + +#include "Event.h" +#include "UIObject.h" + +namespace crepe { + +/** + * \brief Button component. + * + * This component creates a clickable surface at the transform location with the specified width and height. + * + * The Button can be used in scripts by subscribing a EventHandler to the following events: + * - ButtonPressEvent + * - ButtonEnterEvent + * - ButtonExitEvent + * \see EventManager + * + */ +class Button : public UIObject { +public: + /** + * \brief Constructs a Button with the specified game object ID and dimensions. + * + * \param id The unique ID of the game object associated with this button. + * \param dimensions The width and height of the UIObject + * \param offset The offset relative this GameObjects Transform + */ + Button(game_object_id_t id, const vec2 & dimensions, const vec2 & offset); + /** + * \brief Get the maximum number of instances for this component + * + * Since the button Event transfers the GameObject Metadata it will be the same for each button so only one button is allowed per GameObject + * + * \return 1 + */ + virtual int get_instances_max() const { return 1; } + +private: + //! friend relation hover variable + friend class InputSystem; + //! Indicates whether the mouse is currently hovering over the button + bool hover = false; +}; + +} // namespace crepe diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 86623de..2bee3fb 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -1,10 +1,57 @@ target_sources(crepe PUBLIC - # AudioSource.cpp + AudioSource.cpp BehaviorScript.cpp + GameObject.cpp + Rigidbody.cpp + ParticleEmitter.cpp + Transform.cpp + Color.cpp + Sprite.cpp + Config.cpp + Metadata.cpp + Camera.cpp + Animator.cpp + BoxCollider.cpp + CircleCollider.cpp + Engine.cpp + Asset.cpp + EventHandler.cpp + Script.cpp + Button.cpp + UIObject.cpp + AI.cpp + Text.cpp + Scene.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES - # AudioSource.h + AudioSource.h BehaviorScript.h + Config.h + Script.h + Script.hpp + GameObject.h + GameObject.hpp + Rigidbody.h + Sprite.h + Vector2.h + Vector2.hpp + Color.h + Scene.h + Scene.hpp + Metadata.h + Camera.h + Animator.h + BoxCollider.h + CircleCollider.h + EventHandler.h + EventHandler.hpp + Event.h + Engine.h + Engine.hpp + Asset.h + Button.h + UIObject.h + AI.h + Text.h ) - diff --git a/src/crepe/api/Camera.cpp b/src/crepe/api/Camera.cpp new file mode 100644 index 0000000..19a3296 --- /dev/null +++ b/src/crepe/api/Camera.cpp @@ -0,0 +1,18 @@ +#include "util/dbg.h" + +#include "Camera.h" +#include "Component.h" +#include "types.h" + +using namespace crepe; + +Camera::Camera(game_object_id_t id, const ivec2 & screen, const vec2 & viewport_size, + const Data & data) + : Component(id), + screen(screen), + viewport_size(viewport_size), + data(data) { + dbg_trace(); +} + +Camera::~Camera() { dbg_trace(); } diff --git a/src/crepe/api/Camera.h b/src/crepe/api/Camera.h new file mode 100644 index 0000000..54d9a73 --- /dev/null +++ b/src/crepe/api/Camera.h @@ -0,0 +1,67 @@ +#pragma once + +#include "Color.h" +#include "Component.h" +#include "types.h" + +namespace crepe { + +/** + * \class Camera + * \brief Represents a camera component for rendering in the game. + * + * The Camera class defines the view parameters, including background color, aspect ratio, + * position, and zoom level. It controls what part of the game world is visible on the screen. + */ +class Camera : public Component { +public: + struct Data { + /** + * \bg_color background color of the game + * + * This will make the background the same color as the given value. + */ + const Color bg_color = Color::BLACK; + + /** + * \zoom Zooming level of the game + * + * zoom = 1 --> no zoom. + * zoom < 1 --> zoom out + * zoom > 1 --> zoom in + */ + double zoom = 1; + + //! offset postion from the game object transform component + vec2 postion_offset; + }; + +public: + /** + * \brief Constructs a Camera with the specified ID and background color. + * \param id Unique identifier for the camera component. + * \param screen is the actual screen size in pixels + * \param viewport_size is the view of the world in game units + * \param data the camera component data + */ + Camera(game_object_id_t id, const ivec2 & screen, const vec2 & viewport_size, + const Camera::Data & data); + ~Camera(); // dbg_trace only + +public: + Camera::Data data; + + //! screen the display size in pixels ( output resolution ) + const ivec2 screen; + + //! viewport is the area of the world visible through the camera (in world units) + const vec2 viewport_size; + +public: + /** + * \brief Gets the maximum number of camera instances allowed. + * \return Maximum instance count as an integer. + */ + virtual int get_instances_max() const { return 1; } +}; +} // namespace crepe diff --git a/src/crepe/api/CircleCollider.cpp b/src/crepe/api/CircleCollider.cpp new file mode 100644 index 0000000..90ab5e7 --- /dev/null +++ b/src/crepe/api/CircleCollider.cpp @@ -0,0 +1,8 @@ +#include "CircleCollider.h" + +using namespace crepe; + +CircleCollider::CircleCollider(game_object_id_t game_object_id, float radius, + const vec2 & offset) + : Collider(game_object_id, offset), + radius(radius) {} diff --git a/src/crepe/api/CircleCollider.h b/src/crepe/api/CircleCollider.h new file mode 100644 index 0000000..22da836 --- /dev/null +++ b/src/crepe/api/CircleCollider.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Vector2.h" + +#include "../Collider.h" + +namespace crepe { + +/** + * \brief A class representing a circle-shaped collider. + * + * This class is used for collision detection with other colliders (e.g., BoxCollider). + */ +class CircleCollider : public Collider { +public: + CircleCollider(game_object_id_t game_object_id, float radius, + const vec2 & offset = {0, 0}); + + //! Radius of the circle collider. + float radius; +}; + +} // namespace crepe diff --git a/src/crepe/api/Color.cpp b/src/crepe/api/Color.cpp new file mode 100644 index 0000000..29bd77a --- /dev/null +++ b/src/crepe/api/Color.cpp @@ -0,0 +1,12 @@ +#include "Color.h" + +using namespace crepe; + +const Color Color::WHITE{0xff, 0xff, 0xff}; +const Color Color::RED{0xff, 0x00, 0x00}; +const Color Color::GREEN{0x00, 0xff, 0x00}; +const Color Color::BLUE{0x00, 0x00, 0xff}; +const Color Color::BLACK{0x00, 0x00, 0x00}; +const Color Color::CYAN{0x00, 0xff, 0xff}; +const Color Color::YELLOW{0xff, 0xff, 0x00}; +const Color Color::MAGENTA{0xff, 0x00, 0xff}; diff --git a/src/crepe/api/Color.h b/src/crepe/api/Color.h new file mode 100644 index 0000000..84edb5c --- /dev/null +++ b/src/crepe/api/Color.h @@ -0,0 +1,23 @@ +#pragma once + +#include <cstdint> + +namespace crepe { + +struct Color { + uint8_t r = 0x00; + uint8_t g = 0x00; + uint8_t b = 0x00; + uint8_t a = 0xff; + + static const Color WHITE; + static const Color RED; + static const Color GREEN; + static const Color BLUE; + static const Color CYAN; + static const Color MAGENTA; + static const Color YELLOW; + static const Color BLACK; +}; + +} // namespace crepe diff --git a/src/crepe/api/Config.cpp b/src/crepe/api/Config.cpp new file mode 100644 index 0000000..0100bcc --- /dev/null +++ b/src/crepe/api/Config.cpp @@ -0,0 +1,8 @@ +#include "Config.h" + +using namespace crepe; + +Config & Config::get_instance() { + static Config instance; + return instance; +} diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h new file mode 100644 index 0000000..6b9e3ca --- /dev/null +++ b/src/crepe/api/Config.h @@ -0,0 +1,103 @@ +#pragma once + +#include <string> + +#include "../util/Log.h" + +#include "types.h" + +namespace crepe { + +/** + * \brief Global configuration interface + * + * This class stores engine default settings. Properties on this class are only supposed to be + * modified *before* execution is handed over from the game programmer to the engine (i.e. the + * main loop is started). + */ +struct Config final { + //! Retrieve handle to global Config instance + static Config & get_instance(); + + //! Logging-related settings + struct { + /** + * \brief Log level + * + * Only messages with equal or higher priority than this value will be logged. + */ + Log::Level level = Log::Level::INFO; + /** + * \brief Colored log output + * + * Enables log coloring using ANSI escape codes. + */ + bool color = true; + } log; + + //! Save manager + struct { + /** + * \brief Save file location + * + * This location is used by the constructor of SaveManager, and should be set before save + * manager functionality is attempted to be used. + */ + std::string location = "save.crepe.db"; + } savemgr; + + //! physics-related settings + struct { + /** + * \brief gravity value of physics system + * + * Gravity value of game. + */ + float gravity = 10; + } physics; + + //! default window settings + struct { + //! default screen size in pixels + ivec2 default_size = {1280, 720}; + std::string window_title = "Jetpack joyride clone"; + } window_settings; + + //! Asset loading options + struct { + /** + * \brief Pattern to match for Asset base directory + * + * All non-absolute paths resolved using \c Asset will be made relative to + * the first parent directory relative to the calling executable where + * appending this pattern results in a path that exists. If this string is + * empty, path resolution is disabled, and Asset will return all paths + * as-is. + */ + std::string root_pattern = ".crepe-root"; + } asset; + //! Default font options + struct { + /** + * \brief Default font size + * + * Using the SDL_ttf library the font size needs to be set when loading the font. + * This config option is the font size at which all fonts will be loaded initially. + * + */ + unsigned int size = 16; + } font; + //! Configuration for click tolerance. + struct { + //! The maximum number of pixels the mouse can move between MouseDown and MouseUp events to be considered a click. + int click_tolerance = 5; + } input; + + //! Audio system settings + struct { + //! Max amount of simultanious voices + unsigned int voices = 32; + } audio; +}; + +} // namespace crepe diff --git a/src/crepe/api/Engine.cpp b/src/crepe/api/Engine.cpp new file mode 100644 index 0000000..2e9d35a --- /dev/null +++ b/src/crepe/api/Engine.cpp @@ -0,0 +1,63 @@ +#include "../util/Log.h" + +#include "Engine.h" + +using namespace crepe; +using namespace std; + +int Engine::main() noexcept { + try { + this->setup(); + } catch (const exception & e) { + Log::logf(Log::Level::ERROR, "Uncaught exception in setup: {}\n", e.what()); + return EXIT_FAILURE; + } + + try { + this->loop(); + } catch (const exception & e) { + Log::logf(Log::Level::ERROR, "Uncaught exception in main loop: {}\n", e.what()); + this->event_manager.trigger_event<ShutDownEvent>(); + } + + return EXIT_SUCCESS; +} + +void Engine::setup() { + this->loop_timer.start(); + this->scene_manager.load_next_scene(); + + this->event_manager.subscribe<ShutDownEvent>([this](const ShutDownEvent & event) { + this->game_running = false; + + // propagate to possible user ShutDownEvent listeners + return false; + }); +} + +void Engine::loop() { + LoopTimerManager & timer = this->loop_timer; + SystemManager & systems = this->system_manager; + + while (this->game_running) { + timer.update(); + + while (timer.get_lag() >= timer.get_fixed_delta_time()) { + try { + systems.fixed_update(); + } catch (const exception & e) { + Log::logf(Log::Level::WARNING, + "Uncaught exception in fixed update function: {}\n", e.what()); + } + timer.advance_fixed_elapsed_time(); + } + + try { + systems.frame_update(); + } catch (const exception & e) { + Log::logf(Log::Level::WARNING, "Uncaught exception in frame update function: {}\n", + e.what()); + } + timer.enforce_frame_rate(); + } +} diff --git a/src/crepe/api/Engine.h b/src/crepe/api/Engine.h new file mode 100644 index 0000000..700a0cd --- /dev/null +++ b/src/crepe/api/Engine.h @@ -0,0 +1,81 @@ +#pragma once + +#include "../facade/SDLContext.h" +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/ReplayManager.h" +#include "../manager/ResourceManager.h" +#include "../manager/SaveManager.h" +#include "../manager/SceneManager.h" +#include "../manager/SystemManager.h" + +namespace crepe { + +/** + * \brief Main game entrypoint + * + * This class is responsible for managing the game loop, including initialization and updating. + */ +class Engine { +public: + /** + * \brief Engine entrypoint + * + * This function is called by the game programmer after registering all scenes + * + * \returns process exit code + */ + int main() noexcept; + + //! \copydoc SceneManager::add_scene + template <typename T> + void add_scene(); + +private: + /** + * \brief Setup function for one-time initialization. + * + * This function initializes necessary components for the game. + */ + void setup(); + /** + * \brief Main game loop function. + * + * This function runs the main loop, handling game updates and rendering. + */ + void loop(); + + //! Game loop condition + bool game_running = true; + +private: + //! Global context + Mediator mediator; + + //! SystemManager + SystemManager system_manager{mediator}; + + //! SDLContext instance + SDLContext sdl_context{mediator}; + + //! Resource manager instance + ResourceManager resource_manager{mediator}; + + //! Component manager instance + ComponentManager component_manager{mediator}; + //! Scene manager instance + SceneManager scene_manager{mediator}; + //! LoopTimerManager instance + LoopTimerManager loop_timer{mediator}; + //! EventManager instance + EventManager event_manager{mediator}; + //! Save manager instance + SaveManager save_manager{mediator}; + //! ReplayManager instance + ReplayManager replay_manager{mediator}; +}; + +} // namespace crepe + +#include "Engine.hpp" diff --git a/src/crepe/api/Engine.hpp b/src/crepe/api/Engine.hpp new file mode 100644 index 0000000..f2fdc0a --- /dev/null +++ b/src/crepe/api/Engine.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "Engine.h" + +namespace crepe { + +template <class T> +void Engine::add_scene() { + this->scene_manager.add_scene<T>(); +} + +} // namespace crepe diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h new file mode 100644 index 0000000..8e38280 --- /dev/null +++ b/src/crepe/api/Event.h @@ -0,0 +1,154 @@ +#pragma once +// TODO discussing the location of these events + +#include <string> + +#include "types.h" + +#include "KeyCodes.h" + +namespace crepe { + +/** + * \brief Base class for all event types in the system. + */ +class Event {}; + +/** + * \brief Event triggered when a key is pressed. + */ +class KeyPressEvent : public Event { +public: + //! false if first time press, true if key is repeated + bool repeat = false; + + //! The key that was pressed. + Keycode key = Keycode::NONE; +}; + +/** + * \brief Event triggered when a key is released. + */ +class KeyReleaseEvent : public Event { +public: + //! The key that was released. + Keycode key = Keycode::NONE; +}; + +/** + * \brief Event triggered when a mouse button is pressed. + */ +class MousePressEvent : public Event { +public: + //! mouse position in world coordinates (game units). + vec2 mouse_pos = {0, 0}; + + //! The mouse button that was pressed. + MouseButton button = MouseButton::NONE; +}; + +/** + * \brief Event triggered when a mouse button is clicked (press and release). + */ +class MouseClickEvent : public Event { +public: + //! mouse position in world coordinates (game units). + vec2 mouse_pos = {0, 0}; + + //! The mouse button that was clicked. + MouseButton button = MouseButton::NONE; +}; + +/** + * \brief Event triggered when a mouse button is released. + */ +class MouseReleaseEvent : public Event { +public: + //! mouse position in world coordinates (game units). + vec2 mouse_pos = {0, 0}; + + //! The mouse button that was released. + MouseButton button = MouseButton::NONE; +}; + +/** + * \brief Event triggered when the mouse is moved. + */ +class MouseMoveEvent : public Event { +public: + //! mouse position in world coordinates (game units). + vec2 mouse_pos = {0, 0}; + //! The change in mouse position relative to the last position (in pixels). + ivec2 mouse_delta = {0, 0}; +}; + +/** + * \brief Event triggered when the mouse is moved. + */ +class MouseScrollEvent : public Event { +public: + //! mouse position in world coordinates (game units) when the scroll happened. + vec2 mouse_pos = {0, 0}; + //! scroll direction (-1 = down, 1 = up) + int scroll_direction = 0; + //! scroll amount in y axis (from and away from the person). + float scroll_delta = 0; +}; + +/** + * \brief Event triggered to indicate the application is shutting down. + */ +class ShutDownEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window is overlapped by another window. + * + * When two windows overlap the bottom window gets distorted and that window has to be redrawn. + */ +class WindowExposeEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window is resized. + */ +class WindowResizeEvent : public Event { +public: + //! new window dimensions + ivec2 dimensions = {0, 0}; +}; + +/** + * \brief Event triggered to indicate the window is moved. + */ +class WindowMoveEvent : public Event { +public: + //! The change in position relative to the last position (in pixels). + ivec2 delta_move = {0, 0}; +}; + +/** + * \brief Event triggered to indicate the window is minimized. + */ +class WindowMinimizeEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window is maximized + */ +class WindowMaximizeEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window gained focus + * + * This event is triggered when the window receives focus, meaning it becomes the active window + * for user interaction. + */ +class WindowFocusGainEvent : public Event {}; + +/** + * \brief Event triggered to indicate the window lost focus + * + * This event is triggered when the window loses focus, meaning it is no longer the active window + * for user interaction. + */ +class WindowFocusLostEvent : public Event {}; + +} // namespace crepe diff --git a/src/crepe/api/EventHandler.cpp b/src/crepe/api/EventHandler.cpp new file mode 100644 index 0000000..4dc232f --- /dev/null +++ b/src/crepe/api/EventHandler.cpp @@ -0,0 +1,5 @@ +#include "EventHandler.h" + +using namespace crepe; + +bool IEventHandlerWrapper::exec(const Event & e) { return this->call(e); } diff --git a/src/crepe/api/EventHandler.h b/src/crepe/api/EventHandler.h new file mode 100644 index 0000000..7bb501b --- /dev/null +++ b/src/crepe/api/EventHandler.h @@ -0,0 +1,96 @@ +#pragma once + +#include <functional> +#include <string> + +#include "Event.h" + +namespace crepe { +/** + * \brief A type alias for an event handler function. + * + * The EventHandler is a std::function that takes an EventType reference and returns a boolean value + * indicating whether the event is handled. + * + * \tparam EventType The type of event this handler will handle. + * + * Returning \c false from an event handler results in the event being propogated to other listeners for the same event type, while returning \c true stops propogation altogether. + */ +template <typename EventType> +using EventHandler = std::function<bool(const EventType & e)>; + +/** + * \class IEventHandlerWrapper + * \brief An abstract base class for event handler wrappers. + * + * This class provides the interface for handling events. Derived classes must implement the + * `call()` method to process events + */ +class IEventHandlerWrapper { +public: + /** + * \brief Virtual destructor for IEventHandlerWrapper. + */ + virtual ~IEventHandlerWrapper() = default; + + /** + * \brief Executes the handler with the given event. + * + * This method calls the `call()` method of the derived class, passing the event to the handler. + * + * \param e The event to be processed. + * \return A boolean value indicating whether the event is handled. + */ + bool exec(const Event & e); + +private: + /** + * \brief The method responsible for handling the event. + * + * This method is implemented by derived classes to process the event. + * + * \param e The event to be processed. + * \return A boolean value indicating whether the event is handled. + */ + virtual bool call(const Event & e) = 0; +}; + +/** + * \class EventHandlerWrapper + * \brief A wrapper for event handler functions. + * + * This class wraps an event handler function of a specific event type. It implements the + * `call()` and `get_type()` methods to allow the handler to be executed and its type to be + * queried. + * + * \tparam EventType The type of event this handler will handle. + */ +template <typename EventType> +class EventHandlerWrapper : public IEventHandlerWrapper { +public: + /** + * \brief Constructs an EventHandlerWrapper with a given handler. + * + * The constructor takes an event handler function and stores it in the wrapper. + * + * \param handler The event handler function. + */ + explicit EventHandlerWrapper(const EventHandler<EventType> & handler); + +private: + /** + * \brief Calls the stored event handler with the event. + * + * This method casts the event to the appropriate type and calls the handler. + * + * \param e The event to be handled. + * \return A boolean value indicating whether the event is handled. + */ + bool call(const Event & e) override; + //! The event handler function. + EventHandler<EventType> handler; +}; + +} // namespace crepe + +#include "EventHandler.hpp" diff --git a/src/crepe/api/EventHandler.hpp b/src/crepe/api/EventHandler.hpp new file mode 100644 index 0000000..050e57e --- /dev/null +++ b/src/crepe/api/EventHandler.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include <typeindex> + +#include "EventHandler.h" + +namespace crepe { + +// Implementation of EventHandlerWrapper constructor +template <typename EventType> +EventHandlerWrapper<EventType>::EventHandlerWrapper(const EventHandler<EventType> & handler) + : handler(handler) {} + +// Implementation of EventHandlerWrapper::call +template <typename EventType> +bool EventHandlerWrapper<EventType>::call(const Event & e) { + return this->handler(static_cast<const EventType &>(e)); +} + +} //namespace crepe diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp new file mode 100644 index 0000000..9b94cad --- /dev/null +++ b/src/crepe/api/GameObject.cpp @@ -0,0 +1,35 @@ +#include "api/Transform.h" + +#include "BehaviorScript.h" +#include "GameObject.h" +#include "Metadata.h" + +using namespace crepe; +using namespace std; + +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), + mediator(mediator), + transform(mediator.component_manager->add_component<Transform>(this->id, position, + rotation, scale)), + metadata(mediator.component_manager->add_component<Metadata>(this->id, name, tag)) {} + +void GameObject::set_parent(const GameObject & parent) { + ComponentManager & mgr = this->mediator.component_manager; + + // Set parent on own Metadata component + RefVector<Metadata> this_metadata = mgr.get_components_by_id<Metadata>(this->id); + this_metadata.at(0).get().parent = parent.id; + + // Add own id to children list of parent's Metadata component + RefVector<Metadata> parent_metadata = mgr.get_components_by_id<Metadata>(parent.id); + parent_metadata.at(0).get().children.push_back(this->id); +} + +void GameObject::set_persistent(bool persistent) { + 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 new file mode 100644 index 0000000..572ce3a --- /dev/null +++ b/src/crepe/api/GameObject.h @@ -0,0 +1,86 @@ +#pragma once + +#include <string> + +#include "types.h" + +namespace crepe { + +class Mediator; +class Transform; +class Metadata; + +/** + * \brief Represents a GameObject + * + * This class represents a GameObject. The GameObject class is only used as an interface for + * the game programmer. The actual implementation is done in the ComponentManager. + */ +class GameObject { +private: + /** + * This constructor creates a new GameObject. It creates a new Transform and Metadata + * component and adds them to the ComponentManager. + * + * \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 + * \param position The position of the GameObject + * \param rotation The rotation of the GameObject + * \param scale The scale of the GameObject + */ + GameObject(Mediator & mediator, game_object_id_t id, const std::string & name, + const std::string & tag, const vec2 & position, double rotation, double scale); + //! ComponentManager instances GameObject + friend class ComponentManager; + +public: + //! The id of the GameObject + const game_object_id_t id; + //! This entity's transform + Transform & transform; + //! This entity's metadata + Metadata & metadata; + +public: + /** + * \brief Set the parent of this GameObject + * + * This method sets the parent of this GameObject. It sets the parent in the Metadata + * component of this GameObject and adds this GameObject to the children list of the parent + * GameObject. + * + * \param parent The parent GameObject + */ + void set_parent(const GameObject & parent); + /** + * \brief Add a component to the GameObject + * + * This method adds a component to the GameObject. It forwards the arguments to the + * ComponentManager. + * + * \tparam T The type of the component + * \tparam Args The types of the arguments + * \param args The arguments to create the component + * \return The created component + */ + template <typename T, typename... Args> + T & add_component(Args &&... args); + /** + * \brief Components will not be deleted if this method is called + * + * This method sets the persistent flag of the GameObject to true. If the persistent + * flag is set to true, the GameObject will not be deleted when the scene is changed. + * + * \param persistent The persistent flag + */ + void set_persistent(bool persistent = true); + +protected: + Mediator & mediator; +}; + +} // namespace crepe + +#include "GameObject.hpp" diff --git a/src/crepe/api/GameObject.hpp b/src/crepe/api/GameObject.hpp new file mode 100644 index 0000000..69f7d73 --- /dev/null +++ b/src/crepe/api/GameObject.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "../manager/ComponentManager.h" + +#include "GameObject.h" + +namespace crepe { + +template <typename T, typename... Args> +T & GameObject::add_component(Args &&... args) { + ComponentManager & mgr = this->mediator.component_manager; + return mgr.add_component<T>(this->id, std::forward<Args>(args)...); +} + +} // namespace crepe diff --git a/src/crepe/api/KeyCodes.h b/src/crepe/api/KeyCodes.h new file mode 100644 index 0000000..1b9573a --- /dev/null +++ b/src/crepe/api/KeyCodes.h @@ -0,0 +1,160 @@ +#pragma once + +#include <unordered_map> + +namespace crepe { + +//! Enumeration for mouse button inputs, including standard and extended buttons. +enum class MouseButton { + NONE = 0, //!< No mouse button input. + LEFT_MOUSE = 1, //!< Left mouse button. + RIGHT_MOUSE = 2, //!< Right mouse button. + MIDDLE_MOUSE = 3, //!< Middle mouse button (scroll wheel press). + X1_MOUSE = 4, //!< First extended mouse button. + X2_MOUSE = 5, //!< Second extended mouse button. + SCROLL_UP = 6, //!< Scroll wheel upward movement. + SCROLL_DOWN = 7, //!< Scroll wheel downward movement. +}; + +//! Enumeration for keyboard key inputs, including printable characters, function keys, and keypad keys. +enum class Keycode { + NONE = 0, //!< No key input. + SPACE = 32, //!< Spacebar. + APOSTROPHE = 39, //!< Apostrophe ('). + COMMA = 44, //!< Comma (,). + MINUS = 45, //!< Minus (-). + PERIOD = 46, //!< Period (.). + SLASH = 47, //!< Slash (/). + D0 = 48, //!< Digit 0. + D1 = 49, //!< Digit 1. + D2 = 50, //!< Digit 2. + D3 = 51, //!< Digit 3. + D4 = 52, //!< Digit 4. + D5 = 53, //!< Digit 5. + D6 = 54, //!< Digit 6. + D7 = 55, //!< Digit 7. + D8 = 56, //!< Digit 8. + D9 = 57, //!< Digit 9. + SEMICOLON = 59, //!< Semicolon (;). + EQUAL = 61, //!< Equal sign (=). + A = 65, //!< Key 'A'. + B = 66, //!< Key 'B'. + C = 67, //!< Key 'C'. + D = 68, //!< Key 'D'. + E = 69, //!< Key 'E'. + F = 70, //!< Key 'F'. + G = 71, //!< Key 'G'. + H = 72, //!< Key 'H'. + I = 73, //!< Key 'I'. + J = 74, //!< Key 'J'. + K = 75, //!< Key 'K'. + L = 76, //!< Key 'L'. + M = 77, //!< Key 'M'. + N = 78, //!< Key 'N'. + O = 79, //!< Key 'O'. + P = 80, //!< Key 'P'. + Q = 81, //!< Key 'Q'. + R = 82, //!< Key 'R'. + S = 83, //!< Key 'S'. + T = 84, //!< Key 'T'. + U = 85, //!< Key 'U'. + V = 86, //!< Key 'V'. + W = 87, //!< Key 'W'. + X = 88, //!< Key 'X'. + Y = 89, //!< Key 'Y'. + Z = 90, //!< Key 'Z'. + LEFT_BRACKET = 91, //!< Left bracket ([). + BACKSLASH = 92, //!< Backslash (\). + RIGHT_BRACKET = 93, //!< Right bracket (]). + GRAVE_ACCENT = 96, //!< Grave accent (`). + WORLD1 = 161, //!< Non-US key #1. + WORLD2 = 162, //!< Non-US key #2. + ESCAPE = 256, //!< Escape key. + ENTER = 257, //!< Enter key. + TAB = 258, //!< Tab key. + BACKSPACE = 259, //!< Backspace key. + INSERT = 260, //!< Insert key. + DELETE = 261, //!< Delete key. + RIGHT = 262, //!< Right arrow key. + LEFT = 263, //!< Left arrow key. + DOWN = 264, //!< Down arrow key. + UP = 265, //!< Up arrow key. + PAGE_UP = 266, //!< Page Up key. + PAGE_DOWN = 267, //!< Page Down key. + HOME = 268, //!< Home key. + END = 269, //!< End key. + CAPS_LOCK = 280, //!< Caps Lock key. + SCROLL_LOCK = 281, //!< Scroll Lock key. + NUM_LOCK = 282, //!< Num Lock key. + PRINT_SCREEN = 283, //!< Print Screen key. + PAUSE = 284, //!< Pause key. + /** + * \name Function keys (F1-F25). + * \{ + */ + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + F13 = 302, + F14 = 303, + F15 = 304, + F16 = 305, + F17 = 306, + F18 = 307, + F19 = 308, + F20 = 309, + F21 = 310, + F22 = 311, + F23 = 312, + F24 = 313, + F25 = 314, + /// \} + /** + * \name Keypad digits and operators. + * \{ + */ + KP0 = 320, + KP1 = 321, + KP2 = 322, + KP3 = 323, + KP4 = 324, + KP5 = 325, + KP6 = 326, + KP7 = 327, + KP8 = 328, + KP9 = 329, + KP_DECIMAL = 330, + KP_DIVIDE = 331, + KP_MULTIPLY = 332, + KP_SUBTRACT = 333, + KP_ADD = 334, + KP_ENTER = 335, + KP_EQUAL = 336, + /// \} + /** + * \name Modifier keys. + * \{ + */ + LEFT_SHIFT = 340, + LEFT_CONTROL = 341, + LEFT_ALT = 342, + LEFT_SUPER = 343, + RIGHT_SHIFT = 344, + RIGHT_CONTROL = 345, + RIGHT_ALT = 346, + RIGHT_SUPER = 347, + /// \} + MENU = 348, //!< Menu key. +}; +//! Typedef for keyboard state. +typedef std::unordered_map<Keycode, bool> keyboard_state_t; +} // namespace crepe diff --git a/src/crepe/api/Metadata.cpp b/src/crepe/api/Metadata.cpp new file mode 100644 index 0000000..d421de5 --- /dev/null +++ b/src/crepe/api/Metadata.cpp @@ -0,0 +1,9 @@ +#include "Metadata.h" + +using namespace crepe; +using namespace std; + +Metadata::Metadata(game_object_id_t id, const string & name, const string & tag) + : Component(id), + name(name), + tag(tag) {} diff --git a/src/crepe/api/Metadata.h b/src/crepe/api/Metadata.h new file mode 100644 index 0000000..f404703 --- /dev/null +++ b/src/crepe/api/Metadata.h @@ -0,0 +1,42 @@ +#pragma once + +#include <string> +#include <vector> + +#include "../Component.h" + +namespace crepe { + +/** + * \brief Metadata component + * + * This class represents the Metadata component. It stores the name, tag, parent and children + * of a GameObject. + */ +class Metadata : public Component { +public: + /** + * \param game_object_id The id of the GameObject this component belongs to + * \param name The name of the GameObject + * \param tag The tag of the GameObject + */ + Metadata(game_object_id_t id, const std::string & name, const std::string & tag); + /** + * \brief Get the maximum number of instances for this component + * + * \return The maximum number of instances for this component + */ + virtual int get_instances_max() const { return 1; } + +public: + //! The name of the GameObject + const std::string name; + //! The tag of the GameObject + const std::string tag; + //! The id of the parent GameObject (-1 if no parent) + game_object_id_t parent = -1; + //! The ids of the children GameObjects + std::vector<game_object_id_t> children; +}; + +} // namespace crepe diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp new file mode 100644 index 0000000..9a70334 --- /dev/null +++ b/src/crepe/api/ParticleEmitter.cpp @@ -0,0 +1,28 @@ +#include "ParticleEmitter.h" +#include "api/Sprite.h" + +using namespace crepe; +using namespace std; + +ParticleEmitter::ParticleEmitter(game_object_id_t game_object_id, const Sprite & sprite, + const Data & data) + : Component(game_object_id), + sprite(sprite), + data(data) { + for (size_t i = 0; i < this->data.max_particles; i++) { + this->particles.emplace_back(); + } +} + +unique_ptr<Component> ParticleEmitter::save() const { + return unique_ptr<Component>{new ParticleEmitter(*this)}; +} + +void ParticleEmitter::restore(const Component & snapshot) { + *this = static_cast<const ParticleEmitter &>(snapshot); +} + +ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter & other) { + this->particles = other.particles; + return *this; +} diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h new file mode 100644 index 0000000..1edd2b5 --- /dev/null +++ b/src/crepe/api/ParticleEmitter.h @@ -0,0 +1,104 @@ +#pragma once + +#include <cmath> +#include <vector> + +#include "system/ParticleSystem.h" +#include "system/RenderSystem.h" + +#include "Component.h" +#include "Particle.h" +#include "types.h" + +namespace crepe { + +class Sprite; + +/** + * \brief Data holder for particle emission parameters. + * + * The ParticleEmitter class stores configuration data for particle properties, defining the + * characteristics and boundaries of particle emissions. + */ +class ParticleEmitter : public Component { +public: + /** + * \brief Defines the boundary within which particles are constrained. + * + * This structure specifies the boundary's size and offset, as well as the behavior of + * particles upon reaching the boundary limits. + */ + struct Boundary { + //! boundary width (midpoint is emitter location) + float width = INFINITY; + //! boundary height (midpoint is emitter location) + float height = INFINITY; + //! boundary offset from particle emitter location + vec2 offset; + //! reset on exit or stop velocity and set max postion + bool reset_on_exit = false; + }; + + //! sprite reference of displayed sprite + const Sprite & sprite; + + /** + * \brief Holds parameters that control particle emission. + * + * Contains settings for the emitter’s position, particle speed, angle, lifespan, boundary, + * and the sprite used for rendering particles. + */ + struct Data { + //! offset of the emitter relative to transform + vec2 offset; + //! maximum number of particles + const unsigned int max_particles = 256; + //! rate of particle emission per second + float emission_rate = 50; + //! min speed of the particles + float min_speed = 100; + //! min speed of the particles + float max_speed = 100; + //! min angle of particle emission + float min_angle = 0; + //! max angle of particle emission + float max_angle = 0; + //! begin Lifespan of particle in seconds (only visual) + float begin_lifespan = 0.0; + //! end Lifespan of particle in seconds + float end_lifespan = 10.0; + //! force over time (physics) + vec2 force_over_time; + //! particle boundary + Boundary boundary; + }; + +public: + /** + * \param game_object_id Identifier for the game object using this emitter. + * \param data Configuration data defining particle properties. + */ + ParticleEmitter(game_object_id_t game_object_id, const Sprite & sprite, const Data & data); + +public: + //! Configuration data for particle emission settings. + Data data; + +protected: + virtual std::unique_ptr<Component> save() const; + ParticleEmitter(const ParticleEmitter &) = default; + virtual void restore(const Component & snapshot); + virtual ParticleEmitter & operator=(const ParticleEmitter &); + +private: + //! Only ParticleSystem can move and read particles + friend ParticleSystem; + //! Only RenderSystem can read particles + friend RenderSystem; + //! Saves time left over from last update event. + float spawn_accumulator = 0; + //! collection of particles + std::vector<Particle> particles; +}; + +} // namespace crepe diff --git a/src/crepe/api/Rigidbody.cpp b/src/crepe/api/Rigidbody.cpp new file mode 100644 index 0000000..8213afb --- /dev/null +++ b/src/crepe/api/Rigidbody.cpp @@ -0,0 +1,13 @@ +#include "Rigidbody.h" + +using namespace crepe; + +crepe::Rigidbody::Rigidbody(game_object_id_t id, const Data & data) + : Component(id), + data(data) {} + +void crepe::Rigidbody::add_force_linear(const vec2 & force) { + this->data.linear_velocity += force; +} + +void crepe::Rigidbody::add_force_angular(float force) { this->data.angular_velocity += force; } diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h new file mode 100644 index 0000000..6900295 --- /dev/null +++ b/src/crepe/api/Rigidbody.h @@ -0,0 +1,178 @@ +#pragma once + +#include <cmath> +#include <set> + +#include "../Component.h" + +#include "types.h" + +namespace crepe { + +/** + * \brief Rigidbody class + * + * This class is used by the physics sytem and collision system. It configures how to system + * interact with the gameobject for movement and collisions. + */ +class Rigidbody : public Component { +public: + /** + * \brief BodyType enum + * + * This enum provides three bodytypes the physics sytem and collision system use. + */ + enum class BodyType { + //! Does not move (e.g. walls, ground ...) + STATIC, + //! Moves and responds to forces (e.g. player, physics objects ...) + DYNAMIC, + //! Moves but does not respond to forces (e.g. moving platforms ...) + KINEMATIC, + }; + /** + * \brief PhysicsConstraints to constrain movement + * + * This struct configures the movement constraint for this object. If a constraint is enabled + * the systems will not move the object. + */ + struct PhysicsConstraints { + //! Prevent movement along X axis + bool x = false; + //! Prevent movement along Y axis + bool y = false; + //! Prevent rotation + bool rotation = false; + }; + +public: + /** + * \brief struct for Rigidbody data + * + * This struct holds the data for the Rigidbody. + */ + struct Data { + //! objects mass + float mass = 1; + /** + * \brief Gravity scale factor. + * + * The `gravity_scale` controls how much gravity affects the object. It is a multiplier applied to the default + * gravity force, allowing for fine-grained control over how the object responds to gravity. + * + */ + float gravity_scale = 0; + + //! Defines the type of the physics body, which determines how the physics system interacts with the object. + BodyType body_type = BodyType::DYNAMIC; + + /** + * \name Linear (positional) motion + * + * These variables define the linear motion (movement along the position) of an object. + * The linear velocity is applied to the object's position in each update of the PhysicsSystem. + * The motion is affected by the object's linear velocity, its maximum velocity, and a coefficient + * that can scale the velocity over time. + * + * \{ + */ + //! Linear velocity of the object (speed and direction). + vec2 linear_velocity; + //! Maximum linear velocity of the object. This limits the object's speed. + float max_linear_velocity = INFINITY; + //! Linear velocity coefficient. This scales the object's velocity for adjustment or damping. + vec2 linear_velocity_coefficient = {1, 1}; + //! \} + + /** + * \name Angular (rotational) motion + * + * These variables define the angular motion (rotation) of an object. + * The angular velocity determines how quickly the object rotates, while the maximum angular velocity + * sets a limit on the rotation speed. The angular velocity coefficient applies damping or scaling + * to the angular velocity, which can be used to simulate friction or other effects that slow down rotation. + * + * \{ + */ + //! Angular velocity of the object, representing the rate of rotation (in degrees). + float angular_velocity = 0; + //! Maximum angular velocity of the object. This limits the maximum rate of rotation. + float max_angular_velocity = INFINITY; + //! Angular velocity coefficient. This scales the object's angular velocity, typically used for damping. + float angular_velocity_coefficient = 1; + //! \} + + /** + * \brief Movement constraints for an object. + * + * The `PhysicsConstraints` struct defines the constraints that restrict an object's movement + * in certain directions or prevent rotation. These constraints effect only the physics system + * to prevent the object from moving or rotating in specified ways. + * + */ + PhysicsConstraints constraints; + + /** + * \brief Elasticity factor of the material (bounce factor). + * + * The `elasticity_coefficient` controls how much of the object's velocity is retained after a collision. + * It represents the material's ability to bounce or recover velocity upon impact. The coefficient is a value + * above 0.0. + * + */ + float elastisity_coefficient = 0.0; + + /** + * \brief Offset of all colliders relative to the object's transform position. + * + * The `offset` defines a positional shift applied to all colliders associated with the object, relative to the object's + * transform position. This allows for the colliders to be placed at a different position than the object's actual + * position, without modifying the object's transform itself. + * + */ + vec2 offset; + + /** + * \brief Defines the collision layers of a GameObject. + * + * The `collision_layers` specifies the layers that the GameObject will collide with. + * Each element represents a layer ID, and the GameObject will only detect + * collisions with other GameObjects that belong to these layers. + */ + std::set<int> collision_layers = {0}; + }; + +public: + /** + * \param game_object_id id of the gameobject the rigibody is added to. + * \param data struct to configure the rigidbody. + */ + Rigidbody(game_object_id_t id, const Data & data); + //! struct to hold data of rigidbody + Data data; + +public: + /** + * \brief add a linear force to the Rigidbody. + * + * \param force Vector2 that is added to the linear force. + */ + void add_force_linear(const vec2 & force); + /** + * \brief add a angular force to the Rigidbody. + * + * \param force Vector2 that is added to the angular force. + */ + void add_force_angular(float force); + +protected: + /** + * Ensures there is at most one Rigidbody component per entity. + * \return Always returns 1, indicating this constraint. + */ + virtual int get_instances_max() const { return 1; } + //! ComponentManager instantiates all components + friend class ComponentManager; +}; + +} // namespace crepe diff --git a/src/crepe/api/Scene.cpp b/src/crepe/api/Scene.cpp new file mode 100644 index 0000000..ad729d2 --- /dev/null +++ b/src/crepe/api/Scene.cpp @@ -0,0 +1,15 @@ +#include "Scene.h" + +using namespace crepe; + +SaveManager & Scene::get_save_manager() const { return mediator->save_manager; } + +GameObject Scene::new_object(const std::string & name, const std::string & tag, + const vec2 & position, double rotation, double scale) { + // Forward the call to ComponentManager's new_object method + return mediator->component_manager->new_object(name, tag, position, rotation, scale); +} + +void Scene::set_persistent(const Asset & asset, bool persistent) { + mediator->resource_manager->set_persistent(asset, persistent); +} diff --git a/src/crepe/api/Scene.h b/src/crepe/api/Scene.h new file mode 100644 index 0000000..dcca9d4 --- /dev/null +++ b/src/crepe/api/Scene.h @@ -0,0 +1,94 @@ +#pragma once + +#include <string> + +#include "../manager/ComponentManager.h" +#include "../manager/Mediator.h" +#include "../manager/ResourceManager.h" +#include "../util/Log.h" +#include "../util/OptionalRef.h" + +#include "GameObject.h" + +namespace crepe { + +class SceneManager; +class ComponentManager; +class Asset; + +/** + * \brief Represents a Scene + * + * This class represents a Scene. The Scene class is only used as an interface for the game + * programmer. + */ +class Scene { +protected: + // NOTE: This must be the only constructor on Scene, see "Late references" below + Scene() = default; + //! SceneManager instances Scene + friend class SceneManager; + +public: + virtual ~Scene() = default; + +public: + //! Load the scene + virtual void load_scene() = 0; + /** + * \brief Get the scene's name + * \return The scene's name + */ + virtual std::string get_name() const = 0; + + // TODO: Late references should ALWAYS be private! This is currently kept as-is so unit tests + // keep passing, but this reference should not be directly accessible by the user!!! + +private: + /** + * \name Late references + * + * These references are set by SceneManager immediately after calling the constructor of Scene. + * + * \note Scene must have a constructor without arguments so the game programmer doesn't need to + * manually add `using Scene::Scene` to their concrete scene class, if they want to add a + * constructor with arguments (e.g. for passing references to their own concrete Scene classes). + * + * \{ + */ + //! Mediator reference + OptionalRef<Mediator> mediator; + //! \} + +protected: + /** + * \brief Retrieve the reference to the SaveManager instance + * + * \returns A reference to the SaveManager instance held by the Mediator. + */ + SaveManager & get_save_manager() const; + + //! \copydoc ComponentManager::new_object + GameObject new_object(const std::string & name, const std::string & tag = "", + const vec2 & position = {0, 0}, double rotation = 0, + double scale = 1); + + //! \copydoc ResourceManager::set_persistent + void set_persistent(const Asset & asset, bool persistent); + /** + * \name Logging functions + * \see Log + * \{ + */ + //! \copydoc Log::logf + template <class... Args> + void logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args); + //! \copydoc Log::logf + template <class... Args> + void logf(std::format_string<Args...> fmt, Args &&... args); + //! \} +}; + +} // namespace crepe + +#include "Scene.hpp" diff --git a/src/crepe/api/Scene.hpp b/src/crepe/api/Scene.hpp new file mode 100644 index 0000000..14635df --- /dev/null +++ b/src/crepe/api/Scene.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "../util/Log.h" + +#include "Scene.h" + +namespace crepe { + +template <class... Args> +void Scene::logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args) { + Log::logf(level, fmt, std::forward<Args>(args)...); +} + +template <class... Args> +void Scene::logf(std::format_string<Args...> fmt, Args &&... args) { + Log::logf(fmt, std::forward<Args>(args)...); +} + +} // namespace crepe diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp new file mode 100644 index 0000000..b147252 --- /dev/null +++ b/src/crepe/api/Script.cpp @@ -0,0 +1,63 @@ +#include <string> + +#include "../facade/SDLContext.h" +#include "../manager/SceneManager.h" + +#include "Script.h" + +using namespace crepe; +using namespace std; + +Script::~Script() { + EventManager & mgr = this->mediator->event_manager; + for (auto id : this->listeners) { + mgr.unsubscribe(id); + } +} + +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback) { + this->subscribe_internal(callback, this->game_object_id); +} + +void Script::set_next_scene(const string & name) { + SceneManager & mgr = this->mediator->scene_manager; + mgr.set_next_scene(name); +} + +SaveManager & Script::get_save_manager() const { return this->mediator->save_manager; } + +LoopTimerManager & Script::get_loop_timer() const { return this->mediator->loop_timer; } + +void Script::replay::record_start() { + ReplayManager & mgr = this->mediator->replay_manager; + return mgr.record_start(); +} + +recording_t Script::replay::record_end() { + ReplayManager & mgr = this->mediator->replay_manager; + return mgr.record_end(); +} + +void Script::replay::play(recording_t recording) { + ReplayManager & mgr = this->mediator->replay_manager; + return mgr.play(recording); +} + +void Script::replay::release(recording_t recording) { + ReplayManager & mgr = this->mediator->replay_manager; + return mgr.release(recording); +} + +const keyboard_state_t & Script::get_keyboard_state() const { + SDLContext & sdl_context = this->mediator->sdl_context; + return sdl_context.get_keyboard_state(); +} + +bool Script::get_key_state(Keycode key) const noexcept { + try { + return this->get_keyboard_state().at(key); + } catch (...) { + return false; + } +} diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h new file mode 100644 index 0000000..5f68928 --- /dev/null +++ b/src/crepe/api/Script.h @@ -0,0 +1,276 @@ +#pragma once + +#include <vector> + +#include "../api/KeyCodes.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/Mediator.h" +#include "../manager/ReplayManager.h" +#include "../system/CollisionSystem.h" +#include "../types.h" +#include "../util/Log.h" +#include "../util/OptionalRef.h" + +namespace crepe { + +class ScriptSystem; +class BehaviorScript; +class ComponentManager; + +/** + * \brief Script interface + * + * This class is used as a base class for user-defined scripts that can be added to game + * objects using the \c BehaviorScript component. + * + * \note Additional *events* (like Unity's OnDisable and OnEnable) should be implemented as + * member or lambda methods in derivative user script classes and registered in \c init(). + * + * \warning Concrete scripts are allowed do create a custom constructor, but the utility + * functions should not be called inside the constructor as they rely on late references that + * are only available after the constructor returns. + * + * \see feature_script + */ +class Script { +protected: + /** + * \name Interface functions + * \{ + */ + /** + * \brief Script initialization function (empty by default) + * + * This function is called during the ScriptSystem::update() routine *before* + * Script::update() if it (a) has not yet been called and (b) the \c BehaviorScript component + * holding this script instance is active. + */ + virtual void init() {} + /** + * \brief Script fixed update function (empty by default) + * + * \param delta_time Time since last fixed update + * + * \note This function is called during the ScriptSystem::update() routine if the \c + * BehaviorScript component holding this script instance is active. + */ + virtual void fixed_update(duration_t delta_time) {} + /** + * \brief Script frame update function (empty by default) + * + * \param delta_time Time since last frame update + * + * \note This function is called during the ScriptSystem::update() routine if the \c + * BehaviorScript component holding this script instance is active. + */ + virtual void frame_update(duration_t delta_time) {} + //! \} + + //! ScriptSystem calls \c init() and \c update() + friend class crepe::ScriptSystem; + +protected: + /** + * \name Component query functions + * \see ComponentManager + * \{ + */ + /** + * \brief Get single component of type \c T on this game object + * \tparam T Type of component + * \returns Reference to component + * \throws std::runtime_error if this game object does not have a component with type \c T + */ + template <typename T> + T & get_component() const; + /** + * \brief Get all components of type \c T on this game object + * \tparam T Type of component + * \returns List of component references + */ + template <typename T> + RefVector<T> get_components() const; + //! \copydoc ComponentManager::get_components_by_id + template <typename T> + RefVector<T> get_components_by_id(game_object_id_t id) const; + //! \copydoc ComponentManager::get_components_by_name + template <typename T> + RefVector<T> get_components_by_name(const std::string & name) const; + //! \copydoc ComponentManager::get_components_by_tag + template <typename T> + RefVector<T> get_components_by_tag(const std::string & tag) const; + //! \} + + /** + * \name Logging functions + * \see Log + * \{ + */ + //! \copydoc Log::logf + template <class... Args> + void logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args); + //! \copydoc Log::logf + template <class... Args> + void logf(std::format_string<Args...> fmt, Args &&... args); + // \} + + /** + * \name Event manager functions + * \see EventManager + * \{ + */ + //! \copydoc EventManager::subscribe + template <typename EventType> + void subscribe(const EventHandler<EventType> & callback, event_channel_t channel); + //! \copydoc EventManager::subscribe + template <typename EventType> + void subscribe(const EventHandler<EventType> & callback); + //! \copydoc EventManager::trigger_event + template <typename EventType> + void trigger_event(const EventType & event = {}, + event_channel_t channel = EventManager::CHANNEL_ALL); + //! \copydoc EventManager::queue_event + template <typename EventType> + void queue_event(const EventType & event = {}, + event_channel_t channel = EventManager::CHANNEL_ALL); + //! \} + + /** + * \name Scene-related functions + * \see SceneManager + * \{ + */ + //! \copydoc SceneManager::set_next_scene + void set_next_scene(const std::string & name); + //! \} + + /** + * \name Save data management functions + * \see SaveManager + * \{ + */ + //! Retrieve SaveManager reference + SaveManager & get_save_manager() const; + //! \} + + /** + * \name Timing functions + * \see LoopTimerManager + * \{ + */ + //! Retrieve LoopTimerManager reference + LoopTimerManager & get_loop_timer() const; + //! \} + + //! Replay management functions + struct replay { // NOLINT + //! \copydoc ReplayManager::record_start + void record_start(); + //! \copydoc ReplayManager::record_end + recording_t record_end(); + //! \copydoc ReplayManager::play + void play(recording_t); + //! \copydoc ReplayManager::release + void release(recording_t); + + private: + OptionalRef<Mediator> & mediator; + replay(OptionalRef<Mediator> & mediator) : mediator(mediator) {} + friend class Script; + } replay{mediator}; + + /** + * \brief Utility function to retrieve the keyboard state + * \see SDLContext::get_keyboard_state + * + * \return current keyboard state map with Keycode as key and bool as value(true = pressed, false = not pressed) + */ + const keyboard_state_t & get_keyboard_state() const; + /** + * \brief Utility function to retrieve a single key state. + * \see SDLContext::get_keyboard_state + * + * \return Keycode state (true if pressed, false if not pressed). + */ + bool get_key_state(Keycode key) const noexcept; + +private: + /** + * \brief Internal subscribe function + * + * This function exists so certain template specializations of Script::subscribe can be + * explicitly deleted, and does the following: + * - Wrap the user-provided callback in a check that tests if the parent BehaviorScript + * component is still active + * - Store the subscriber handle returned by the event manager so this listener is + * automatically unsubscribed at the end of this Script instance's life + * + * \tparam EventType concrete Event class + * \param callback User-provided callback function + * \param channel Event channel (may have been overridden by template specializations) + */ + template <typename EventType> + void subscribe_internal(const EventHandler<EventType> & callback, event_channel_t channel); + +protected: + // NOTE: This must be the only constructor on Script, see "Late references" below + Script() = default; + //! Only \c BehaviorScript instantiates Script + friend class BehaviorScript; + +public: + // std::unique_ptr destroys script + virtual ~Script(); + +private: + Script(const Script &) = delete; + Script(Script &&) = delete; + Script & operator=(const Script &) = delete; + Script & operator=(Script &&) = delete; + +private: + /** + * \name Late references + * + * These references are set by BehaviorScript immediately after calling the constructor of + * Script. + * + * \note Script must have a constructor without arguments so the game programmer doesn't need + * to manually add `using Script::Script` to their concrete script class if they want to + * implement a non-default constructor (e.g. for passing references to their own concrete + * Script classes). + * + * \{ + */ + //! Game object ID of game object parent BehaviorScript is attached to + game_object_id_t game_object_id; + //! Reference to parent component + OptionalRef<bool> active; + //! Mediator reference + OptionalRef<Mediator> mediator; + //! \} + +private: + //! Flag to indicate if \c init() has been called already + bool initialized = false; + //! List of subscribed events + std::vector<subscription_t> listeners; +}; + +/** + * \brief Subscribe to CollisionEvent for the current GameObject + * + * This is a template specialization for Script::subscribe which automatically sets the event + * channel so the callback handler is only called for CollisionEvent events that apply to the + * current GameObject the parent BehaviorScript is attached to. + */ +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback); +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback, event_channel_t) + = delete; + +} // namespace crepe + +#include "Script.hpp" diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp new file mode 100644 index 0000000..4462a41 --- /dev/null +++ b/src/crepe/api/Script.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include "../manager/ComponentManager.h" +#include "../manager/ReplayManager.h" + +#include "BehaviorScript.h" +#include "Script.h" + +namespace crepe { + +template <typename T> +T & Script::get_component() const { + using namespace std; + RefVector<T> all_components = this->get_components<T>(); + if (all_components.size() < 1) + throw runtime_error( + format("Script: no component found with type = {}", typeid(T).name())); + + return all_components.back().get(); +} + +template <typename T> +RefVector<T> Script::get_components() const { + return this->get_components_by_id<T>(this->game_object_id); +} + +template <class... Args> +void Script::logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args) { + Log::logf(level, fmt, std::forward<Args>(args)...); +} + +template <class... Args> +void Script::logf(std::format_string<Args...> fmt, Args &&... args) { + Log::logf(fmt, std::forward<Args>(args)...); +} + +template <typename EventType> +void Script::subscribe_internal(const EventHandler<EventType> & callback, + event_channel_t channel) { + EventManager & mgr = this->mediator->event_manager; + subscription_t listener = mgr.subscribe<EventType>( + [this, callback](const EventType & data) -> bool { + // check if (parent) BehaviorScript component is active + bool & active = this->active; + if (!active) return false; + + // check if replay manager is playing (if initialized) + try { + ReplayManager & replay = this->mediator->replay_manager; + if (replay.get_state() == ReplayManager::PLAYING) return false; + } catch (const std::runtime_error &) { + } + + // call user-provided callback + return callback(data); + }, + channel); + this->listeners.push_back(listener); +} + +template <typename EventType> +void Script::subscribe(const EventHandler<EventType> & callback, event_channel_t channel) { + this->subscribe_internal(callback, channel); +} + +template <typename EventType> +void Script::subscribe(const EventHandler<EventType> & callback) { + this->subscribe_internal(callback, EventManager::CHANNEL_ALL); +} + +template <typename EventType> +void Script::trigger_event(const EventType & event, event_channel_t channel) { + EventManager & mgr = this->mediator->event_manager; + mgr.trigger_event(event, channel); +} + +template <typename EventType> +void Script::queue_event(const EventType & event, event_channel_t channel) { + EventManager & mgr = this->mediator->event_manager; + mgr.queue_event(event, channel); +} + +template <typename T> +RefVector<T> Script::get_components_by_id(game_object_id_t id) const { + Mediator & mediator = this->mediator; + 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 new file mode 100644 index 0000000..0107c7b --- /dev/null +++ b/src/crepe/api/Sprite.cpp @@ -0,0 +1,21 @@ +#include <cmath> + +#include "../util/dbg.h" +#include "api/Asset.h" + +#include "Component.h" +#include "Sprite.h" +#include "types.h" + +using namespace std; +using namespace crepe; + +Sprite::Sprite(game_object_id_t id, const Asset & texture, const Sprite::Data & data) + : Component(id), + source(texture), + data(data) { + + dbg_trace(); +} + +Sprite::~Sprite() { dbg_trace(); } diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h new file mode 100644 index 0000000..a2409c2 --- /dev/null +++ b/src/crepe/api/Sprite.h @@ -0,0 +1,114 @@ +#pragma once + +#include "../Component.h" +#include "api/Asset.h" + +#include "Color.h" +#include "types.h" + +namespace crepe { + +class SDLContext; +class Animator; +class AnimatorSystem; + +/** + * \brief Represents a renderable sprite component. + * + * A renderable sprite that can be displayed in the game. It includes a texture, color, and + * flip settings, and is managed in layers with defined sorting orders. + */ +class Sprite : public Component { +public: + //! settings to flip the image + struct FlipSettings { + //! horizantal flip + bool flip_x = false; + //! vertical flip + bool flip_y = false; + }; + + //! Sprite data that does not have to be set in the constructor + struct Data { + /** + * \brief Sprite tint (multiplied) + * + * The sprite texture's pixels are multiplied by this color before being displayed + * (including alpha channel for transparency). + */ + Color color = Color::WHITE; + + //! Flip settings for the sprite + FlipSettings flip; + + //! Layer sorting level of the sprite + const int sorting_in_layer = 0; + + //! Order within the sorting layer + const int order_in_layer = 0; + + /** + * \brief width and height of the sprite in game units + * + * - if exclusively width is specified, the height is calculated using the texture's aspect + * ratio + * - if exclusively height is specified, the width is calculated using the texture's aspect + * ratio + * - if both are specified the texture is streched to fit the specified size + */ + vec2 size = {0, 0}; + + //! independent sprite angle. rotating clockwise direction in degrees + float angle_offset = 0; + + //! independent sprite scale multiplier + float scale_offset = 1; + + //! independent sprite offset position + vec2 position_offset; + }; + +public: + /** + * \param game_id Unique identifier for the game object this sprite belongs to. + * \param texture asset of the image + * \param ctx all the sprite data + */ + Sprite(game_object_id_t id, const Asset & texture, const Data & data); + ~Sprite(); + + //! Texture used for the sprite + const Asset source; + + Data data; + +private: + //! Reads the mask of sprite + friend class SDLContext; + + //! Reads the all the variables plus the mask + friend class Animator; + + //! 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; + int x = 0; + int y = 0; + }; + //! Render area of the sprite this will also be adjusted by the AnimatorSystem if an Animator + // object is present in GameObject. this is in sprite pixels + Rect mask; +}; + +} // namespace crepe diff --git a/src/crepe/api/Text.cpp b/src/crepe/api/Text.cpp new file mode 100644 index 0000000..54a4370 --- /dev/null +++ b/src/crepe/api/Text.cpp @@ -0,0 +1,12 @@ +#include "../facade/FontFacade.h" + +#include "Text.h" + +using namespace crepe; + +Text::Text(game_object_id_t id, const vec2 & dimensions, const vec2 & offset, + const std::string & font_family, const Data & data, const std::string & text) + : UIObject(id, dimensions, offset), + text(text), + data(data), + font_family(font_family) {} diff --git a/src/crepe/api/Text.h b/src/crepe/api/Text.h new file mode 100644 index 0000000..c30dc80 --- /dev/null +++ b/src/crepe/api/Text.h @@ -0,0 +1,66 @@ +#pragma once + +#include <optional> +#include <string> + +#include "../Component.h" + +#include "Asset.h" +#include "Color.h" +#include "UIObject.h" + +namespace crepe { +/** + * \brief Text UIObject component for displaying text + * + * This class can be used to display text on screen. By setting the font_family to a font already stored on the current device it will automatically be loaded in. + */ +class Text : public UIObject { +public: + //! Text data that does not have to be set in the constructor + struct Data { + /** + * \brief fontsize for text rendering + * + * \note this is not the actual font size that is loaded in. + * + * Since SDL_TTF requires the font size when loading in the font it is not possible to switch the font size. + * The default font size that is loaded is set in the Config. + * Instead this value is used to upscale the font texture which can cause blurring or distorted text when upscaling or downscaling too much. + */ + unsigned int font_size = 16; + + //! Layer sorting level of the text + const int sorting_in_layer = 0; + + //! Order within the sorting text + const int order_in_layer = 0; + + //! Label text color. + Color text_color = Color::BLACK; + }; + +public: + /** + * + * \param dimensions Width and height of the UIObject. + * \param offset Offset of the UIObject relative to its transform + * \param text The text to be displayed. + * \param font_family The font style name to be displayed. + * \param data Data struct containing extra text parameters. + * \param font Optional font asset that can be passed or left empty. + */ + Text(game_object_id_t id, const vec2 & dimensions, const vec2 & offset, + const std::string & font_family, const Data & data, const std::string & text = ""); + + //! Label text. + std::string text = ""; + //! font family name + std::string font_family = ""; + //! Font asset variable if this is not set, it will use the font_family to create an asset. + std::optional<Asset> font; + //! Data instance + Data data; +}; + +} // namespace crepe diff --git a/src/crepe/api/Transform.cpp b/src/crepe/api/Transform.cpp new file mode 100644 index 0000000..fcfce14 --- /dev/null +++ b/src/crepe/api/Transform.cpp @@ -0,0 +1,22 @@ +#include "../util/dbg.h" + +#include "Transform.h" + +using namespace crepe; +using namespace std; + +Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, double scale) + : Component(id), + position(point), + rotation(rotation), + scale(scale) { + dbg_trace(); +} + +unique_ptr<Component> Transform::save() const { + return unique_ptr<Component>{new Transform(*this)}; +} + +void Transform::restore(const Component & snapshot) { + *this = static_cast<const Transform &>(snapshot); +} diff --git a/src/crepe/api/Transform.h b/src/crepe/api/Transform.h new file mode 100644 index 0000000..a6f3486 --- /dev/null +++ b/src/crepe/api/Transform.h @@ -0,0 +1,46 @@ +#pragma once + +#include "Component.h" +#include "types.h" + +namespace crepe { + +/** + * \brief Transform component + * + * This class represents the Transform component. It stores the position, rotation and scale of + * a GameObject. + */ +class Transform : public Component { +public: + //! Translation (shift) + vec2 position = {0, 0}; + //! Rotation, in degrees clockwise + float rotation = 0; + //! Multiplication factor + float scale = 0; + +protected: + /** + * \param id The id of the GameObject this component belongs to + * \param point The position of the GameObject + * \param rotation The rotation of the GameObject + * \param scale The scale of the GameObject + */ + Transform(game_object_id_t id, const vec2 & point, double rotation, double scale); + /** + * There is always exactly one transform component per entity + * \return 1 + */ + virtual int get_instances_max() const { return 1; } + //! ComponentManager instantiates all components + friend class ComponentManager; + +protected: + virtual std::unique_ptr<Component> save() const; + Transform(const Transform &) = default; + virtual void restore(const Component & snapshot); + virtual Transform & operator=(const Transform &) = default; +}; + +} // namespace crepe diff --git a/src/crepe/api/UIObject.cpp b/src/crepe/api/UIObject.cpp new file mode 100644 index 0000000..d239b89 --- /dev/null +++ b/src/crepe/api/UIObject.cpp @@ -0,0 +1,8 @@ +#include "UIObject.h" + +using namespace crepe; + +UIObject::UIObject(game_object_id_t id, const vec2 & dimensions, const vec2 & offset) + : Component(id), + dimensions(dimensions), + offset(offset) {} diff --git a/src/crepe/api/UIObject.h b/src/crepe/api/UIObject.h new file mode 100644 index 0000000..f7f4fba --- /dev/null +++ b/src/crepe/api/UIObject.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../Component.h" + +namespace crepe { + +/** + * \brief Represents a UI object in the game, derived from the Component class. + */ +class UIObject : public Component { +public: + /** + * \brief Constructs a UiObject with the specified game object ID. + * \param id The unique ID of the game object associated with this UI object. + * \param dimensions width and height of the UIObject + * \param offset Offset relative to the GameObject Transform + */ + UIObject(game_object_id_t id, const vec2 & dimensions, const vec2 & offset); + //! Width and height of the UIObject + vec2 dimensions; + //! Position offset relative to this GameObjects Transform + vec2 offset; +}; + +} // namespace crepe diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h new file mode 100644 index 0000000..bf9d124 --- /dev/null +++ b/src/crepe/api/Vector2.h @@ -0,0 +1,97 @@ +#pragma once + +namespace crepe { + +//! 2D vector +template <class T> +struct Vector2 { + //! X component of the vector + T x = 0; + //! Y component of the vector + T y = 0; + + //! Subtracts another vector from this vector and returns the result. + Vector2 operator-(const Vector2<T> & other) const; + + //! Subtracts a scalar value from both components of this vector and returns the result. + Vector2 operator-(T scalar) const; + + //! Adds another vector to this vector and returns the result. + Vector2 operator+(const Vector2<T> & other) const; + + //! Adds a scalar value to both components of this vector and returns the result. + Vector2 operator+(T scalar) const; + + //! Multiplies this vector by another vector element-wise and returns the result. + Vector2 operator*(const Vector2<T> & other) const; + + //! Multiplies this vector by a scalar and returns the result. + Vector2 operator*(T scalar) const; + + //! Divides this vector by another vector element-wise and returns the result. + Vector2 operator/(const Vector2<T> & other) const; + + //! Divides this vector by a scalar and returns the result. + Vector2 operator/(T scalar) const; + + //! Adds another vector to this vector and updates this vector. + Vector2 & operator+=(const Vector2<T> & other); + + //! Adds a scalar value to both components of this vector and updates this vector. + Vector2 & operator+=(T other); + + //! Subtracts another vector from this vector and updates this vector. + Vector2 & operator-=(const Vector2<T> & other); + + //! Subtracts a scalar value from both components of this vector and updates this vector. + Vector2 & operator-=(T other); + + //! Multiplies this vector by another vector element-wise and updates this vector. + Vector2 & operator*=(const Vector2<T> & other); + + //! Multiplies this vector by a scalar and updates this vector. + Vector2 & operator*=(T other); + + //! Divides this vector by another vector element-wise and updates this vector. + Vector2 & operator/=(const Vector2<T> & other); + + //! Divides this vector by a scalar and updates this vector. + Vector2 & operator/=(T other); + + //! Returns the negation of this vector. + Vector2 operator-() const; + + //! Checks if this vector is equal to another vector. + bool operator==(const Vector2<T> & other) const; + + //! 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 + +#include "Vector2.hpp" diff --git a/src/crepe/api/Vector2.hpp b/src/crepe/api/Vector2.hpp new file mode 100644 index 0000000..ff53cb0 --- /dev/null +++ b/src/crepe/api/Vector2.hpp @@ -0,0 +1,166 @@ +#pragma once + +#include <cmath> + +#include "Vector2.h" + +namespace crepe { + +template <class T> +Vector2<T> Vector2<T>::operator-(const Vector2<T> & other) const { + return {x - other.x, y - other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator-(T scalar) const { + return {x - scalar, y - scalar}; +} + +template <class T> +Vector2<T> Vector2<T>::operator+(const Vector2<T> & other) const { + return {x + other.x, y + other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator+(T scalar) const { + return {x + scalar, y + scalar}; +} + +template <class T> +Vector2<T> Vector2<T>::operator*(const Vector2<T> & other) const { + return {x * other.x, y * other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator*(T scalar) const { + return {x * scalar, y * scalar}; +} + +template <class T> +Vector2<T> Vector2<T>::operator/(const Vector2<T> & other) const { + return {x / other.x, y / other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator/(T scalar) const { + return {x / scalar, y / scalar}; +} + +template <class T> +Vector2<T> & Vector2<T>::operator+=(const Vector2<T> & other) { + x += other.x; + y += other.y; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator+=(T other) { + x += other; + y += other; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator-=(const Vector2<T> & other) { + x -= other.x; + y -= other.y; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator-=(T other) { + x -= other; + y -= other; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator*=(const Vector2<T> & other) { + x *= other.x; + y *= other.y; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator*=(T other) { + x *= other; + y *= other; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator/=(const Vector2<T> & other) { + x /= other.x; + y /= other.y; + return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator/=(T other) { + x /= other; + y /= other; + return *this; +} + +template <class T> +Vector2<T> Vector2<T>::operator-() const { + return {-x, -y}; +} + +template <class T> +bool Vector2<T>::operator==(const Vector2<T> & other) const { + return x == other.x && y == other.y; +} + +template <class T> +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 |