diff options
Diffstat (limited to 'src/crepe/system')
25 files changed, 2501 insertions, 0 deletions
diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp new file mode 100644 index 0000000..0f35010 --- /dev/null +++ b/src/crepe/system/AISystem.cpp @@ -0,0 +1,186 @@ +#include <algorithm> +#include <cmath> + +#include "manager/ComponentManager.h" +#include "manager/LoopTimerManager.h" +#include "manager/Mediator.h" + +#include "AISystem.h" + +using namespace crepe; +using namespace std::chrono; + +void AISystem::fixed_update() { + const Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + LoopTimerManager & loop_timer = mediator.loop_timer; + RefVector<AI> ai_components = mgr.get_components_by_type<AI>(); + + float dt = loop_timer.get_scaled_fixed_delta_time().count(); + + // Loop through all AI components + for (AI & ai : ai_components) { + if (!ai.active) { + continue; + } + + RefVector<Rigidbody> rigidbodies + = mgr.get_components_by_id<Rigidbody>(ai.game_object_id); + if (rigidbodies.empty()) { + throw std::runtime_error( + "AI component must be attached to a GameObject with a Rigidbody component"); + } + Rigidbody & rigidbody = rigidbodies.front().get(); + if (!rigidbody.active) { + continue; + } + if (rigidbody.data.mass <= 0) { + throw std::runtime_error("Mass must be greater than 0"); + } + + // Calculate the force to apply to the entity + vec2 force = this->calculate(ai, rigidbody); + // Calculate the acceleration (using the above calculated force) + vec2 acceleration = force / rigidbody.data.mass; + // Finally, update Rigidbody's velocity + rigidbody.data.linear_velocity += acceleration * dt; + } +} + +vec2 AISystem::calculate(AI & ai, const Rigidbody & rigidbody) { + const Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + RefVector<Transform> transforms = mgr.get_components_by_id<Transform>(ai.game_object_id); + Transform & transform = transforms.front().get(); + + vec2 force; + + // Run all the behaviors that are on, and stop if the force gets too high + if (ai.on(AI::BehaviorTypeMask::FLEE)) { + vec2 force_to_add = this->flee(ai, rigidbody, transform); + + if (!this->accumulate_force(ai, force, force_to_add)) { + return force; + } + } + if (ai.on(AI::BehaviorTypeMask::ARRIVE)) { + vec2 force_to_add = this->arrive(ai, rigidbody, transform); + + if (!this->accumulate_force(ai, force, force_to_add)) { + return force; + } + } + if (ai.on(AI::BehaviorTypeMask::SEEK)) { + vec2 force_to_add = this->seek(ai, rigidbody, transform); + + if (!this->accumulate_force(ai, force, force_to_add)) { + return force; + } + } + if (ai.on(AI::BehaviorTypeMask::PATH_FOLLOW)) { + vec2 force_to_add = this->path_follow(ai, rigidbody, transform); + + if (!this->accumulate_force(ai, force, force_to_add)) { + return force; + } + } + + return force; +} + +bool AISystem::accumulate_force(const AI & ai, vec2 & running_total, vec2 & force_to_add) { + float magnitude = running_total.length(); + float magnitude_remaining = ai.max_force - magnitude; + + if (magnitude_remaining <= 0.0f) { + // If the force is already at/above the max force, return false + return false; + } + + float magnitude_to_add = force_to_add.length(); + if (magnitude_to_add < magnitude_remaining) { + // If the force to add is less than the remaining force, add it + running_total += force_to_add; + } else { + // If the force to add is greater than the remaining force, add a fraction of it + force_to_add.normalize(); + running_total += force_to_add * magnitude_remaining; + } + + return true; +} + +vec2 AISystem::seek(const AI & ai, const Rigidbody & rigidbody, + const Transform & transform) const { + // Calculate the desired velocity + vec2 desired_velocity = ai.seek_target - transform.position; + desired_velocity.normalize(); + desired_velocity *= rigidbody.data.max_linear_velocity; + + return desired_velocity - rigidbody.data.linear_velocity; +} + +vec2 AISystem::flee(const AI & ai, const Rigidbody & rigidbody, + const Transform & transform) const { + // Calculate the desired velocity if the entity is within the panic distance + vec2 desired_velocity = transform.position - ai.flee_target; + if (desired_velocity.length_squared() > ai.square_flee_panic_distance) { + return vec2{0, 0}; + } + desired_velocity.normalize(); + desired_velocity *= rigidbody.data.max_linear_velocity; + + return desired_velocity - rigidbody.data.linear_velocity; +} + +vec2 AISystem::arrive(const AI & ai, const Rigidbody & rigidbody, + const Transform & transform) const { + // Calculate the desired velocity (taking into account the deceleration rate) + vec2 to_target = ai.arrive_target - transform.position; + float distance = to_target.length(); + if (distance > 0.0f) { + if (ai.arrive_deceleration <= 0.0f) { + throw std::runtime_error("Deceleration rate must be greater than 0"); + } + + float speed = distance / ai.arrive_deceleration; + speed = std::min(speed, rigidbody.data.max_linear_velocity); + vec2 desired_velocity = to_target * (speed / distance); + + return desired_velocity - rigidbody.data.linear_velocity; + } + + return vec2{0, 0}; +} + +vec2 AISystem::path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform) { + if (ai.path.empty()) { + return vec2{0, 0}; + } + + // Get the target node + vec2 target = ai.path.at(ai.path_index); + // Calculate the force to apply to the entity + vec2 to_target = target - transform.position; + if (to_target.length_squared() > ai.path_node_distance * ai.path_node_distance) { + // If the entity is not close enough to the target node, seek it + ai.seek_target = target; + ai.arrive_target = target; + } else { + // If the entity is close enough to the target node, move to the next node + ai.path_index++; + if (ai.path_index >= ai.path.size()) { + if (ai.path_loop) { + // If the path is looping, reset the path index + ai.path_index = 0; + } else { + // If the path is not looping, arrive at the last node + ai.path_index = ai.path.size() - 1; + return this->arrive(ai, rigidbody, transform); + } + } + } + + // Seek the target node + return this->seek(ai, rigidbody, transform); +} diff --git a/src/crepe/system/AISystem.h b/src/crepe/system/AISystem.h new file mode 100644 index 0000000..04807cf --- /dev/null +++ b/src/crepe/system/AISystem.h @@ -0,0 +1,81 @@ +#pragma once + +#include "api/AI.h" +#include "api/Rigidbody.h" + +#include "System.h" +#include "api/Transform.h" +#include "types.h" + +namespace crepe { + +/** + * \brief The AISystem is used to control the movement of entities using AI. + * + * The AISystem is used to control the movement of entities using AI. The AISystem can be used to + * implement different behaviors such as seeking, fleeing, arriving, and path following. + */ +class AISystem : public System { +public: + using System::System; + + //! Update the AI system + void fixed_update() override; + +private: + /** + * \brief Calculate the total force to apply to the entity + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + */ + vec2 calculate(AI & ai, const Rigidbody & rigidbody); + /** + * \brief Accumulate the force to apply to the entity + * + * \param ai The AI component + * \param running_total The running total of the force + * \param force_to_add The force to add + * \return true if the force was added, false otherwise + */ + bool accumulate_force(const AI & ai, vec2 & running_total, vec2 & force_to_add); + + /** + * \brief Calculate the seek force + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + * \param transform The Transform component + * \return The seek force + */ + vec2 seek(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; + /** + * \brief Calculate the flee force + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + * \param transform The Transform component + * \return The flee force + */ + vec2 flee(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; + /** + * \brief Calculate the arrive force + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + * \param transform The Transform component + * \return The arrive force + */ + vec2 arrive(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; + /** + * \brief Calculate the path follow force + * + * \param ai The AI component + * \param rigidbody The Rigidbody component + * \param transform The Transform component + * \return The path follow force + */ + vec2 path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform); +}; + +} // namespace crepe diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp new file mode 100644 index 0000000..e5ab2fa --- /dev/null +++ b/src/crepe/system/AnimatorSystem.cpp @@ -0,0 +1,40 @@ +#include "../api/Animator.h" +#include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h" +#include <chrono> + +#include "AnimatorSystem.h" + +using namespace crepe; +using namespace std::chrono; + +void AnimatorSystem::frame_update() { + ComponentManager & mgr = this->mediator.component_manager; + LoopTimerManager & timer = this->mediator.loop_timer; + RefVector<Animator> animations = mgr.get_components_by_type<Animator>(); + + float elapsed_time = duration_cast<duration<float>>(timer.get_elapsed_time()).count(); + + for (Animator & a : animations) { + if (!a.active) continue; + if (a.data.fps == 0) continue; + + Animator::Data & ctx = a.data; + float frame_duration = 1.0f / ctx.fps; + + int last_frame = ctx.row; + + int cycle_end = (ctx.cycle_end == -1) ? a.grid_size.x : ctx.cycle_end; + int total_frames = cycle_end - ctx.cycle_start; + + int curr_frame = static_cast<int>(elapsed_time / frame_duration) % total_frames; + + ctx.row = ctx.cycle_start + curr_frame; + a.spritesheet.mask.x = ctx.row * a.spritesheet.mask.w; + a.spritesheet.mask.y = (ctx.col * a.spritesheet.mask.h); + + if (!ctx.looping && curr_frame == ctx.cycle_start && last_frame == total_frames - 1) { + a.active = false; + } + } +} diff --git a/src/crepe/system/AnimatorSystem.h b/src/crepe/system/AnimatorSystem.h new file mode 100644 index 0000000..092e131 --- /dev/null +++ b/src/crepe/system/AnimatorSystem.h @@ -0,0 +1,28 @@ +#pragma once + +#include "System.h" + +namespace crepe { + +/** + * \brief The AnimatorSystem is responsible for managing and updating all Animator components. + * + * This system is responsible for controlling the behavior of the animations for all entities + * that have the Animator component attached. It updates the animations by controlling their + * frame changes, looping behavior, and overall animation state. + */ +class AnimatorSystem : public System { + +public: + using System::System; + /** + * \brief Updates the Animator components. + * + * This method is called to update the state of all + * Animator components, moving the animations forward and managing their behavior (e.g., + * looping). + */ + void frame_update() override; +}; + +} // namespace crepe diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp new file mode 100644 index 0000000..d4e8b9f --- /dev/null +++ b/src/crepe/system/AudioSystem.cpp @@ -0,0 +1,62 @@ +#include "AudioSystem.h" + +#include "../manager/ComponentManager.h" +#include "../manager/ResourceManager.h" +#include "../types.h" + +using namespace crepe; +using namespace std; + +void AudioSystem::fixed_update() { + ComponentManager & component_manager = this->mediator.component_manager; + ResourceManager & resource_manager = this->mediator.resource_manager; + RefVector<AudioSource> components + = component_manager.get_components_by_type<AudioSource>(); + + for (AudioSource & component : components) { + Sound & resource = resource_manager.get<Sound>(component.source); + + this->diff_update(component, resource); + + this->update_last(component); + } +} + +void AudioSystem::diff_update(AudioSource & component, Sound & resource) { + SoundContext & context = this->get_context(); + + if (component.active != component.last_active) { + if (!component.active) { + context.stop(component.voice); + return; + } + if (component.play_on_awake) component.oneshot_play = true; + } + if (!component.active) return; + + if (component.oneshot_play) { + component.voice = context.play(resource); + component.oneshot_play = false; + } + if (component.oneshot_stop) { + context.stop(component.voice); + component.oneshot_stop = false; + } + if (component.volume != component.last_volume) { + context.set_volume(component.voice, component.volume); + } + if (component.loop != component.last_loop) { + context.set_loop(component.voice, component.loop); + } +} + +void AudioSystem::update_last(AudioSource & component) { + component.last_active = component.active; + component.last_loop = component.loop; + component.last_volume = component.volume; +} + +SoundContext & AudioSystem::get_context() { + if (this->context == nullptr) this->context = make_unique<SoundContext>(); + return *this->context.get(); +} diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h new file mode 100644 index 0000000..56fc98c --- /dev/null +++ b/src/crepe/system/AudioSystem.h @@ -0,0 +1,51 @@ +#pragma once + +#include "../api/AudioSource.h" +#include "../facade/Sound.h" +#include "../facade/SoundContext.h" + +#include "System.h" + +namespace crepe { + +class AudioSystem : public System { +public: + using System::System; + void fixed_update() override; + +private: + /** + * \brief Update `last_*` members of \c component + * + * Copies all component properties stored for comparison between AudioSystem::update() calls + * + * \param component AudioSource component to update + */ + void update_last(AudioSource & component); + + /** + * \brief Compare update component + * + * Compares properties of \c component and \c data, and calls SoundContext functions where + * applicable. + * + * \param component AudioSource component to update + * \param resource Sound instance for AudioSource's Asset + */ + void diff_update(AudioSource & component, Sound & resource); + +protected: + /** + * \brief Get SoundContext + * + * SoundContext is retrieved through this function instead of being a direct member of + * AudioSystem to aid with testability. + */ + virtual SoundContext & get_context(); + +private: + //! SoundContext + std::unique_ptr<SoundContext> context = nullptr; +}; + +} // namespace crepe diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt new file mode 100644 index 0000000..52369d0 --- /dev/null +++ b/src/crepe/system/CMakeLists.txt @@ -0,0 +1,28 @@ +target_sources(crepe PUBLIC + System.cpp + ParticleSystem.cpp + ScriptSystem.cpp + PhysicsSystem.cpp + CollisionSystem.cpp + RenderSystem.cpp + AudioSystem.cpp + AnimatorSystem.cpp + InputSystem.cpp + EventSystem.cpp + ReplaySystem.cpp + AISystem.cpp +) + +target_sources(crepe PUBLIC FILE_SET HEADERS FILES + System.h + ScriptSystem.h + PhysicsSystem.h + CollisionSystem.h + RenderSystem.h + AudioSystem.h + AnimatorSystem.h + InputSystem.h + EventSystem.h + ReplaySystem.h + AISystem.h +) diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp new file mode 100644 index 0000000..9d88d9f --- /dev/null +++ b/src/crepe/system/CollisionSystem.cpp @@ -0,0 +1,575 @@ +#include <algorithm> +#include <cmath> +#include <cstddef> +#include <functional> +#include <optional> +#include <utility> +#include <variant> + +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "api/BoxCollider.h" +#include "api/CircleCollider.h" +#include "api/Event.h" +#include "api/Metadata.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "api/Vector2.h" + +#include "Collider.h" +#include "CollisionSystem.h" +#include "types.h" +#include "util/OptionalRef.h" + +using namespace crepe; + +void CollisionSystem::fixed_update() { + std::vector<CollisionInternal> all_colliders; + game_object_id_t id = 0; + ComponentManager & mgr = this->mediator.component_manager; + RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>(); + // Collisions can only happen on object with a rigidbody + for (Rigidbody & rigidbody : rigidbodies) { + if (!rigidbody.active) continue; + id = rigidbody.game_object_id; + Transform & transform = mgr.get_components_by_id<Transform>(id).front().get(); + // Check if the boxcollider is active and has the same id as the rigidbody. + RefVector<BoxCollider> boxcolliders = mgr.get_components_by_type<BoxCollider>(); + for (BoxCollider & boxcollider : boxcolliders) { + if (boxcollider.game_object_id != id) continue; + if (!boxcollider.active) continue; + all_colliders.push_back({.id = id, + .collider = collider_variant{boxcollider}, + .transform = transform, + .rigidbody = rigidbody}); + } + // Check if the circlecollider is active and has the same id as the rigidbody. + RefVector<CircleCollider> circlecolliders + = mgr.get_components_by_type<CircleCollider>(); + for (CircleCollider & circlecollider : circlecolliders) { + if (circlecollider.game_object_id != id) continue; + if (!circlecollider.active) continue; + all_colliders.push_back({.id = id, + .collider = collider_variant{circlecollider}, + .transform = transform, + .rigidbody = rigidbody}); + } + } + + // Check between all colliders if there is a collision + std::vector<std::pair<CollisionInternal, CollisionInternal>> collided + = this->gather_collisions(all_colliders); + + // For both objects call the collision handler + for (auto & collision_pair : collided) { + this->collision_handler_request(collision_pair.first, collision_pair.second); + this->collision_handler_request(collision_pair.second, collision_pair.first); + } +} + +void CollisionSystem::collision_handler_request(CollisionInternal & this_data, + CollisionInternal & other_data) { + + CollisionInternalType type + = this->get_collider_type(this_data.collider, other_data.collider); + std::pair<vec2, CollisionSystem::Direction> resolution_data + = this->collision_handler(this_data, other_data, type); + ComponentManager & mgr = this->mediator.component_manager; + OptionalRef<Metadata> this_metadata + = mgr.get_components_by_id<Metadata>(this_data.id).front().get(); + OptionalRef<Metadata> other_metadata + = mgr.get_components_by_id<Metadata>(other_data.id).front().get(); + OptionalRef<Collider> this_collider; + OptionalRef<Collider> other_collider; + switch (type) { + case CollisionInternalType::BOX_BOX: { + this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider); + other_collider + = std::get<std::reference_wrapper<BoxCollider>>(other_data.collider); + break; + } + case CollisionInternalType::BOX_CIRCLE: { + this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider); + other_collider + = std::get<std::reference_wrapper<CircleCollider>>(other_data.collider); + break; + } + case CollisionInternalType::CIRCLE_BOX: { + this_collider + = std::get<std::reference_wrapper<CircleCollider>>(this_data.collider); + other_collider + = std::get<std::reference_wrapper<BoxCollider>>(other_data.collider); + break; + } + case CollisionInternalType::CIRCLE_CIRCLE: { + this_collider + = std::get<std::reference_wrapper<CircleCollider>>(this_data.collider); + other_collider + = std::get<std::reference_wrapper<CircleCollider>>(other_data.collider); + break; + } + } + + // collision info + crepe::CollisionSystem::CollisionInfo collision_info{ + .this_collider = this_collider, + .this_transform = this_data.transform, + .this_rigidbody = this_data.rigidbody, + .this_metadata = this_metadata, + .other_collider = other_collider, + .other_transform = other_data.transform, + .other_rigidbody = other_data.rigidbody, + .other_metadata = other_metadata, + .resolution = resolution_data.first, + .resolution_direction = resolution_data.second, + }; + + // Determine if static needs to be called + this->determine_collision_handler(collision_info); +} + +std::pair<vec2, CollisionSystem::Direction> +CollisionSystem::collision_handler(CollisionInternal & data1, CollisionInternal & data2, + CollisionInternalType type) { + vec2 resolution; + switch (type) { + case CollisionInternalType::BOX_BOX: { + const BoxCollider & collider1 + = std::get<std::reference_wrapper<BoxCollider>>(data1.collider); + const BoxCollider & collider2 + = std::get<std::reference_wrapper<BoxCollider>>(data2.collider); + vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, + data1.rigidbody); + vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, + data2.rigidbody); + resolution = this->get_box_box_resolution(collider1, collider2, collider_pos1, + collider_pos2); + break; + } + case CollisionInternalType::BOX_CIRCLE: { + const BoxCollider & collider1 + = std::get<std::reference_wrapper<BoxCollider>>(data1.collider); + const CircleCollider & collider2 + = std::get<std::reference_wrapper<CircleCollider>>(data2.collider); + vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, + data1.rigidbody); + vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, + data2.rigidbody); + resolution = -this->get_circle_box_resolution(collider2, collider1, collider_pos2, + collider_pos1); + break; + } + case CollisionInternalType::CIRCLE_CIRCLE: { + const CircleCollider & collider1 + = std::get<std::reference_wrapper<CircleCollider>>(data1.collider); + const CircleCollider & collider2 + = std::get<std::reference_wrapper<CircleCollider>>(data2.collider); + vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, + data1.rigidbody); + vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, + data2.rigidbody); + resolution = this->get_circle_circle_resolution(collider1, collider2, + collider_pos1, collider_pos2); + break; + } + case CollisionInternalType::CIRCLE_BOX: { + const CircleCollider & collider1 + = std::get<std::reference_wrapper<CircleCollider>>(data1.collider); + const BoxCollider & collider2 + = std::get<std::reference_wrapper<BoxCollider>>(data2.collider); + vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, + data1.rigidbody); + vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, + data2.rigidbody); + resolution = this->get_circle_box_resolution(collider1, collider2, collider_pos1, + collider_pos2); + break; + } + } + + Direction resolution_direction = Direction::NONE; + if (resolution.x != 0 && resolution.y != 0) { + resolution_direction = Direction::BOTH; + } else if (resolution.x != 0) { + resolution_direction = Direction::X_DIRECTION; + //checks if the other velocity has a value and if this object moved + if (data1.rigidbody.data.linear_velocity.x != 0 + && data1.rigidbody.data.linear_velocity.y != 0) + resolution.y = -data1.rigidbody.data.linear_velocity.y + * (resolution.x / data1.rigidbody.data.linear_velocity.x); + } else if (resolution.y != 0) { + resolution_direction = Direction::Y_DIRECTION; + //checks if the other velocity has a value and if this object moved + if (data1.rigidbody.data.linear_velocity.x != 0 + && data1.rigidbody.data.linear_velocity.y != 0) + resolution.x = -data1.rigidbody.data.linear_velocity.x + * (resolution.y / data1.rigidbody.data.linear_velocity.y); + } + + return std::make_pair(resolution, resolution_direction); +} + +vec2 CollisionSystem::get_box_box_resolution(const BoxCollider & box_collider1, + const BoxCollider & box_collider2, + const vec2 & final_position1, + const vec2 & final_position2) const { + vec2 resolution; // Default resolution vector + vec2 delta = final_position2 - final_position1; + + // Compute half-dimensions of the boxes + float half_width1 = box_collider1.dimensions.x / 2.0; + float half_height1 = box_collider1.dimensions.y / 2.0; + float half_width2 = box_collider2.dimensions.x / 2.0; + float half_height2 = box_collider2.dimensions.y / 2.0; + + // Calculate overlaps along X and Y axes + float overlap_x = (half_width1 + half_width2) - std::abs(delta.x); + float overlap_y = (half_height1 + half_height2) - std::abs(delta.y); + + // Check if there is a collision should always be true + if (overlap_x > 0 && overlap_y > 0) { + // Determine the direction of resolution + if (overlap_x < overlap_y) { + // Resolve along the X-axis (smallest overlap) + resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; + } else if (overlap_y < overlap_x) { + // Resolve along the Y-axis (smallest overlap) + resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; + } else { + // Equal overlap, resolve both directions with preference + resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; + resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; + } + } + + return resolution; +} + +vec2 CollisionSystem::get_circle_circle_resolution(const CircleCollider & circle_collider1, + const CircleCollider & circle_collider2, + const vec2 & final_position1, + const vec2 & final_position2) const { + vec2 delta = final_position2 - final_position1; + + // Compute the distance between the two circle centers + float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y); + + // Compute the combined radii of the two circles + float combined_radius = circle_collider1.radius + circle_collider2.radius; + + // Compute the penetration depth + float penetration_depth = combined_radius - distance; + + // Normalize the delta vector to get the collision direction + vec2 collision_normal = delta / distance; + + // Compute the resolution vector + vec2 resolution = -collision_normal * penetration_depth; + + return resolution; +} + +vec2 CollisionSystem::get_circle_box_resolution(const CircleCollider & circle_collider, + const BoxCollider & box_collider, + const vec2 & circle_position, + const vec2 & box_position) const { + vec2 delta = circle_position - box_position; + + // Compute half-dimensions of the box + float half_width = box_collider.dimensions.x / 2.0f; + float half_height = box_collider.dimensions.y / 2.0f; + + // Clamp circle center to the nearest point on the box + vec2 closest_point; + closest_point.x = std::clamp(delta.x, -half_width, half_width); + closest_point.y = std::clamp(delta.y, -half_height, half_height); + + // Find the vector from the circle center to the closest point + vec2 closest_delta = delta - closest_point; + + // Normalize the delta to get the collision direction + float distance + = std::sqrt(closest_delta.x * closest_delta.x + closest_delta.y * closest_delta.y); + vec2 collision_normal = closest_delta / distance; + + // Compute penetration depth + float penetration_depth = circle_collider.radius - distance; + + // Compute the resolution vector + vec2 resolution = collision_normal * penetration_depth; + + return resolution; +} + +void CollisionSystem::determine_collision_handler(CollisionInfo & info) { + // Check rigidbody type for static + if (info.this_rigidbody.data.body_type == Rigidbody::BodyType::STATIC) return; + // If second body is static perform the static collision handler in this system + if (info.other_rigidbody.data.body_type == Rigidbody::BodyType::STATIC) { + this->static_collision_handler(info); + }; + // Call collision event for user + CollisionEvent data(info); + EventManager & emgr = this->mediator.event_manager; + emgr.trigger_event<CollisionEvent>(data, info.this_collider.game_object_id); +} + +void CollisionSystem::static_collision_handler(CollisionInfo & info) { + // Move object back using calculate move back value + info.this_transform.position += info.resolution; + + switch (info.resolution_direction) { + case Direction::BOTH: + //bounce + if (info.this_rigidbody.data.elastisity_coefficient > 0) { + info.this_rigidbody.data.linear_velocity + = -info.this_rigidbody.data.linear_velocity + * info.this_rigidbody.data.elastisity_coefficient; + } + //stop movement + else { + info.this_rigidbody.data.linear_velocity = {0, 0}; + } + break; + case Direction::Y_DIRECTION: + // Bounce + if (info.this_rigidbody.data.elastisity_coefficient > 0) { + info.this_rigidbody.data.linear_velocity.y + = -info.this_rigidbody.data.linear_velocity.y + * info.this_rigidbody.data.elastisity_coefficient; + } + // Stop movement + else { + info.this_rigidbody.data.linear_velocity.y = 0; + info.this_transform.position.x -= info.resolution.x; + } + break; + case Direction::X_DIRECTION: + // Bounce + if (info.this_rigidbody.data.elastisity_coefficient > 0) { + info.this_rigidbody.data.linear_velocity.x + = -info.this_rigidbody.data.linear_velocity.x + * info.this_rigidbody.data.elastisity_coefficient; + } + // Stop movement + else { + info.this_rigidbody.data.linear_velocity.x = 0; + info.this_transform.position.y -= info.resolution.y; + } + break; + case Direction::NONE: + // Not possible + break; + } +} + +std::vector<std::pair<CollisionSystem::CollisionInternal, CollisionSystem::CollisionInternal>> +CollisionSystem::gather_collisions(std::vector<CollisionInternal> & colliders) { + + // TODO: + // If no colliders skip + // Check if colliders has rigidbody if not skip + + // TODO: + // If amount is higer than lets say 16 for now use quadtree otwerwise skip + // Quadtree code + // Quadtree is placed over the input vector + + // Return data of collided colliders which are variants + std::vector<std::pair<CollisionInternal, CollisionInternal>> collisions_ret; + //using visit to visit the variant to access the active and id. + for (size_t i = 0; i < colliders.size(); ++i) { + for (size_t j = i + 1; j < colliders.size(); ++j) { + if (colliders[i].id == colliders[j].id) continue; + if (!have_common_layer(colliders[i].rigidbody.data.collision_layers, + colliders[j].rigidbody.data.collision_layers)) + continue; + CollisionInternalType type + = get_collider_type(colliders[i].collider, colliders[j].collider); + if (!get_collision( + { + .collider = colliders[i].collider, + .transform = colliders[i].transform, + .rigidbody = colliders[i].rigidbody, + }, + { + .collider = colliders[j].collider, + .transform = colliders[j].transform, + .rigidbody = colliders[j].rigidbody, + }, + type)) + continue; + collisions_ret.emplace_back(colliders[i], colliders[j]); + } + } + + return collisions_ret; +} + +bool CollisionSystem::have_common_layer(const std::set<int> & layers1, + const std::set<int> & layers2) { + + // Check if any number is equal in the layers + for (int num : layers1) { + if (layers2.contains(num)) { + // Common layer found + return true; + break; + } + } + // No common layer found + return false; +} + +CollisionSystem::CollisionInternalType +CollisionSystem::get_collider_type(const collider_variant & collider1, + const collider_variant & collider2) const { + if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider1)) { + if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { + return CollisionInternalType::CIRCLE_CIRCLE; + } else { + return CollisionInternalType::CIRCLE_BOX; + } + } else { + if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { + return CollisionInternalType::BOX_CIRCLE; + } else { + return CollisionInternalType::BOX_BOX; + } + } +} + +bool CollisionSystem::get_collision(const CollisionInternal & first_info, + const CollisionInternal & second_info, + CollisionInternalType type) const { + switch (type) { + case CollisionInternalType::BOX_BOX: { + const BoxCollider & box_collider1 + = std::get<std::reference_wrapper<BoxCollider>>(first_info.collider); + const BoxCollider & box_collider2 + = std::get<std::reference_wrapper<BoxCollider>>(second_info.collider); + return this->get_box_box_collision(box_collider1, box_collider2, + first_info.transform, second_info.transform, + second_info.rigidbody, second_info.rigidbody); + } + case CollisionInternalType::BOX_CIRCLE: { + const BoxCollider & box_collider + = std::get<std::reference_wrapper<BoxCollider>>(first_info.collider); + const CircleCollider & circle_collider + = std::get<std::reference_wrapper<CircleCollider>>(second_info.collider); + return this->get_box_circle_collision( + box_collider, circle_collider, first_info.transform, second_info.transform, + second_info.rigidbody, second_info.rigidbody); + } + case CollisionInternalType::CIRCLE_CIRCLE: { + const CircleCollider & circle_collider1 + = std::get<std::reference_wrapper<CircleCollider>>(first_info.collider); + const CircleCollider & circle_collider2 + = std::get<std::reference_wrapper<CircleCollider>>(second_info.collider); + return this->get_circle_circle_collision( + circle_collider1, circle_collider2, first_info.transform, + second_info.transform, second_info.rigidbody, second_info.rigidbody); + } + case CollisionInternalType::CIRCLE_BOX: { + const CircleCollider & circle_collider + = std::get<std::reference_wrapper<CircleCollider>>(first_info.collider); + const BoxCollider & box_collider + = std::get<std::reference_wrapper<BoxCollider>>(second_info.collider); + return this->get_box_circle_collision( + box_collider, circle_collider, first_info.transform, second_info.transform, + second_info.rigidbody, second_info.rigidbody); + } + } + return false; +} + +bool CollisionSystem::get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, + const Transform & transform1, + const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const { + // Get current positions of colliders + vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1); + vec2 final_position2 = this->get_current_position(box2.offset, transform2, rigidbody2); + + // Calculate half-extents (half width and half height) + float half_width1 = box1.dimensions.x / 2.0; + float half_height1 = box1.dimensions.y / 2.0; + float half_width2 = box2.dimensions.x / 2.0; + float half_height2 = box2.dimensions.y / 2.0; + + // Check if the boxes overlap along the X and Y axes + return (final_position1.x + half_width1 > final_position2.x - half_width2 + && final_position1.x - half_width1 < final_position2.x + half_width2 + && final_position1.y + half_height1 > final_position2.y - half_height2 + && final_position1.y - half_height1 < final_position2.y + half_height2); +} + +bool CollisionSystem::get_box_circle_collision(const BoxCollider & box1, + const CircleCollider & circle2, + const Transform & transform1, + const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const { + // Get current positions of colliders + vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1); + vec2 final_position2 = this->get_current_position(circle2.offset, transform2, rigidbody2); + + // Calculate box half-extents + float half_width = box1.dimensions.x / 2.0; + float half_height = box1.dimensions.y / 2.0; + + // Find the closest point on the box to the circle's center + float closest_x = std::max(final_position1.x - half_width, + std::min(final_position2.x, final_position1.x + half_width)); + float closest_y = std::max(final_position1.y - half_height, + std::min(final_position2.y, final_position1.y + half_height)); + + // Calculate the distance squared between the circle's center and the closest point on the box + float distance_x = final_position2.x - closest_x; + float distance_y = final_position2.y - closest_y; + float distance_squared = distance_x * distance_x + distance_y * distance_y; + + // Compare distance squared with the square of the circle's radius + return distance_squared < circle2.radius * circle2.radius; +} + +bool CollisionSystem::get_circle_circle_collision(const CircleCollider & circle1, + const CircleCollider & circle2, + const Transform & transform1, + const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const { + // Get current positions of colliders + vec2 final_position1 = this->get_current_position(circle1.offset, transform1, rigidbody1); + vec2 final_position2 = this->get_current_position(circle2.offset, transform2, rigidbody2); + + float distance_x = final_position1.x - final_position2.x; + float distance_y = final_position1.y - final_position2.y; + float distance_squared = distance_x * distance_x + distance_y * distance_y; + + // Calculate the sum of the radii + float radius_sum = circle1.radius + circle2.radius; + + // Check if the distance between the centers is less than or equal to the sum of the radii + return distance_squared < radius_sum * radius_sum; +} + +vec2 CollisionSystem::get_current_position(const vec2 & collider_offset, + const Transform & transform, + const Rigidbody & rigidbody) const { + // Get the rotation in radians + float radians1 = transform.rotation * (M_PI / 180.0); + + // Calculate total offset with scale + vec2 total_offset = (rigidbody.data.offset + collider_offset) * transform.scale; + + // Rotate + float rotated_total_offset_x1 + = total_offset.x * cos(radians1) - total_offset.y * sin(radians1); + float rotated_total_offset_y1 + = total_offset.x * sin(radians1) + total_offset.y * cos(radians1); + + // Final positions considering scaling and rotation + return (transform.position + vec2(rotated_total_offset_x1, rotated_total_offset_y1)); +} diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h new file mode 100644 index 0000000..48a8f86 --- /dev/null +++ b/src/crepe/system/CollisionSystem.h @@ -0,0 +1,311 @@ +#pragma once + +#include <optional> +#include <variant> +#include <vector> + +#include "api/BoxCollider.h" +#include "api/CircleCollider.h" +#include "api/Event.h" +#include "api/Metadata.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "api/Vector2.h" + +#include "Collider.h" +#include "System.h" + +namespace crepe { + +//! A system responsible for detecting and handling collisions between colliders. +class CollisionSystem : public System { +public: + using System::System; + +private: + //! A variant type that can hold either a BoxCollider or a CircleCollider. + using collider_variant = std::variant<std::reference_wrapper<BoxCollider>, + std::reference_wrapper<CircleCollider>>; + + //! Enum representing the types of collider pairs for collision detection. + enum class CollisionInternalType { + BOX_BOX, + CIRCLE_CIRCLE, + BOX_CIRCLE, + CIRCLE_BOX, + }; + + /** + * \brief A structure to store the collision data of a single collider. + * + * This structure all components and id that are for needed within this system when calculating or handeling collisions. + * The transform and rigidbody are mostly needed for location and rotation. + * In rigidbody additional info is written about what the body of the object is, + * and how it should respond on a collision. + */ + struct CollisionInternal { + game_object_id_t id = 0; + collider_variant collider; + Transform & transform; + Rigidbody & rigidbody; + }; + + //! Enum representing movement directions during collision resolution. + enum class Direction { + //! No movement required. + NONE, + //! Movement in the X direction. + X_DIRECTION, + //! Movement in the Y direction. + Y_DIRECTION, + //! Movement in both X and Y directions. + BOTH + }; + +public: + /** + * \brief Structure representing detailed collision information between two colliders. + * + * Includes information about the colliding objects and the resolution data for handling the collision. + */ + struct CollisionInfo { + Collider & this_collider; + Transform & this_transform; + Rigidbody & this_rigidbody; + Metadata & this_metadata; + Collider & other_collider; + Transform & other_transform; + Rigidbody & other_rigidbody; + Metadata & other_metadata; + //! The resolution vector for the collision. + vec2 resolution; + //! The direction of movement for resolving the collision. + Direction resolution_direction = Direction::NONE; + }; + +public: + //! Updates the collision system by checking for collisions between colliders and handling them. + void fixed_update() override; + +private: + /** + * \brief Determines the type of collider pair from two colliders. + * + * Uses std::holds_alternative to identify the types of the provided colliders. + * + * \param collider1 First collider variant (BoxCollider or CircleCollider). + * \param collider2 Second collider variant (BoxCollider or CircleCollider). + * \return The combined type of the two colliders. + */ + CollisionInternalType get_collider_type(const collider_variant & collider1, + const collider_variant & collider2) const; + + /** + * \brief Calculates the current position of a collider. + * + * Combines the Collider offset, Transform position, and Rigidbody offset to compute the position of the collider. + * + * \param collider_offset The offset of the collider. + * \param transform The Transform of the associated game object. + * \param rigidbody The Rigidbody of the associated game object. + * \return The calculated position of the collider. + */ + vec2 get_current_position(const vec2 & collider_offset, const Transform & transform, + const Rigidbody & rigidbody) const; + +private: + /** + * \brief Handles collision resolution between two colliders. + * + * Processes collision data and adjusts objects to resolve collisions and/or calls the user oncollision script function. + * + * \param data1 Collision data for the first collider. + * \param data2 Collision data for the second collider. + */ + void collision_handler_request(CollisionInternal & data1, CollisionInternal & data2); + + /** + * \brief Resolves collision between two colliders and calculates the movement required. + * + * Determines the displacement and direction needed to separate colliders based on their types. + * + * \param data1 Collision data for the first collider. + * \param data2 Collision data for the second collider. + * \param type The type of collider pair. + * \return A pair containing the resolution vector and direction for the first collider. + */ + std::pair<vec2, Direction> collision_handler(CollisionInternal & data1, + CollisionInternal & data2, + CollisionInternalType type); + + /** + * \brief Calculates the resolution vector for two BoxColliders. + * + * Computes the displacement required to separate two overlapping BoxColliders. + * + * \param box_collider1 The first BoxCollider. + * \param box_collider2 The second BoxCollider. + * \param position1 The position of the first BoxCollider. + * \param position2 The position of the second BoxCollider. + * \return The resolution vector for the collision. + */ + vec2 get_box_box_resolution(const BoxCollider & box_collider1, + const BoxCollider & box_collider2, const vec2 & position1, + const vec2 & position2) const; + + /** + * \brief Calculates the resolution vector for two CircleCollider. + * + * Computes the displacement required to separate two overlapping CircleCollider. + * + * \param circle_collider1 The first CircleCollider. + * \param circle_collider2 The second CircleCollider. + * \param final_position1 The position of the first CircleCollider. + * \param final_position2 The position of the second CircleCollider. + * \return The resolution vector for the collision. + */ + vec2 get_circle_circle_resolution(const CircleCollider & circle_collider1, + const CircleCollider & circle_collider2, + const vec2 & final_position1, + const vec2 & final_position2) const; + + /** + * \brief Calculates the resolution vector for two CircleCollider. + * + * Computes the displacement required to separate two overlapping CircleCollider. + * + * \param circle_collider The first CircleCollider. + * \param box_collider The second CircleCollider. + * \param circle_position The position of the CircleCollider. + * \param box_position The position of the BoxCollider. + * \return The resolution vector for the collision. + */ + vec2 get_circle_box_resolution(const CircleCollider & circle_collider, + const BoxCollider & box_collider, + const vec2 & circle_position, + const vec2 & box_position) const; + + /** + * \brief Determines the appropriate collision handler for a collision. + * + * Decides the correct resolution process based on the dynamic or static nature of the colliders involved. + * + * \param info Collision information containing data about both colliders. + */ + void determine_collision_handler(CollisionInfo & info); + + /** + * \brief Handles collisions involving static objects. + * + * Resolves collisions by adjusting positions and modifying velocities if bounce is enabled. + * + * \param info Collision information containing data about both colliders. + */ + void static_collision_handler(CollisionInfo & info); + +private: + /** + * \brief Checks for collisions between colliders. + * + * Identifies collisions and generates pairs of colliding objects for further processing. + * + * \param colliders A collection of all active colliders. + * \return A list of collision pairs with their associated data. + */ + std::vector<std::pair<CollisionInternal, CollisionInternal>> + gather_collisions(std::vector<CollisionInternal> & colliders); + + /** + * \brief Checks if two collision layers have at least one common layer. + * + * This function checks if there is any overlapping layer between the two inputs. + * It compares each layer from the first input to see + * if it exists in the second input. If at least one common layer is found, + * the function returns true, indicating that the two colliders share a common + * collision layer. + * + * \param layers1 all collision layers for the first collider. + * \param layers2 all collision layers for the second collider. + * \return Returns true if there is at least one common layer, false otherwise. + */ + + bool have_common_layer(const std::set<int> & layers1, const std::set<int> & layers2); + + /** + * \brief Checks for collision between two colliders. + * + * Calls the appropriate collision detection function based on the collider types. + * + * \param first_info Collision data for the first collider. + * \param second_info Collision data for the second collider. + * \param type The type of collider pair. + * \return True if a collision is detected, otherwise false. + */ + bool get_collision(const CollisionInternal & first_info, + const CollisionInternal & second_info, + CollisionInternalType type) const; + + /** + * \brief Detects collisions between two BoxColliders. + * + * \param box1 The first BoxCollider. + * \param box2 The second BoxCollider. + * \param transform1 Transform of the first object. + * \param transform2 Transform of the second object. + * \param rigidbody1 Rigidbody of the first object. + * \param rigidbody2 Rigidbody of the second object. + * \return True if a collision is detected, otherwise false. + */ + bool get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, + const Transform & transform1, const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const; + + /** + * \brief Check collision for box on circle collider + * + * \param box1 The BoxCollider + * \param circle2 The CircleCollider + * \param transform1 Transform of the first object. + * \param transform2 Transform of the second object. + * \param rigidbody1 Rigidbody of the first object. + * \param rigidbody2 Rigidbody of the second object. + * \return True if a collision is detected, otherwise false. + */ + bool get_box_circle_collision(const BoxCollider & box1, const CircleCollider & circle2, + const Transform & transform1, const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const; + + /** + * \brief Check collision for circle on circle collider + * + * \param circle1 First CircleCollider + * \param circle2 Second CircleCollider + * \param transform1 Transform of the first object. + * \param transform2 Transform of the second object. + * \param rigidbody1 Rigidbody of the first object. + * \param rigidbody2 Rigidbody of the second object. + * \return True if a collision is detected, otherwise false. + * + * \return status of collision + */ + bool get_circle_circle_collision(const CircleCollider & circle1, + const CircleCollider & circle2, + const Transform & transform1, + const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const; +}; + +/** + * \brief Event triggered during a collision between objects. + */ +class CollisionEvent : public Event { +public: + crepe::CollisionSystem::CollisionInfo info; + CollisionEvent(const crepe::CollisionSystem::CollisionInfo & collisionInfo) + : info(collisionInfo) {} +}; + +} // namespace crepe diff --git a/src/crepe/system/EventSystem.cpp b/src/crepe/system/EventSystem.cpp new file mode 100644 index 0000000..7e168ab --- /dev/null +++ b/src/crepe/system/EventSystem.cpp @@ -0,0 +1,9 @@ +#include "EventSystem.h" +#include "../manager/EventManager.h" + +using namespace crepe; + +void EventSystem::fixed_update() { + EventManager & ev = this->mediator.event_manager; + ev.dispatch_events(); +} diff --git a/src/crepe/system/EventSystem.h b/src/crepe/system/EventSystem.h new file mode 100644 index 0000000..0ae48d2 --- /dev/null +++ b/src/crepe/system/EventSystem.h @@ -0,0 +1,21 @@ +#pragma once + +#include "System.h" + +namespace crepe { + +/** + * \brief EventManager dispatch helper system + */ +class EventSystem : public System { +public: + using System::System; + + /** + * \brief Dispatch queued events + * \see EventManager::dispatch_events + */ + void fixed_update() override; +}; + +} // namespace crepe diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp new file mode 100644 index 0000000..d209282 --- /dev/null +++ b/src/crepe/system/InputSystem.cpp @@ -0,0 +1,209 @@ +#include "../api/Button.h" +#include "../facade/SDLContext.h" +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "util/Log.h" + +#include "InputSystem.h" + +using namespace crepe; + +void InputSystem::fixed_update() { + ComponentManager & mgr = this->mediator.component_manager; + SDLContext & context = this->mediator.sdl_context; + std::vector<EventData> event_list = context.get_events(); + RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); + OptionalRef<Camera> curr_cam_ref; + + // Find the active camera + for (Camera & cam : cameras) { + if (!cam.active) continue; + curr_cam_ref = cam; + break; + } + if (!curr_cam_ref) return; + + Camera & current_cam = curr_cam_ref; + RefVector<Transform> transform_vec + = mgr.get_components_by_id<Transform>(current_cam.game_object_id); + Transform & cam_transform = transform_vec.front().get(); + + vec2 camera_origin = cam_transform.position + current_cam.data.postion_offset + - (current_cam.viewport_size / 2); + + for (const EventData & event : event_list) { + // Only calculate mouse coordinates for relevant events + if (event.event_type == EventType::MOUSE_DOWN + || event.event_type == EventType::MOUSE_UP + || event.event_type == EventType::MOUSE_MOVE + || event.event_type == EventType::MOUSE_WHEEL) { + this->handle_mouse_event(event, camera_origin, current_cam); + + } else { + this->handle_non_mouse_event(event); + } + } +} + +void InputSystem::handle_mouse_event(const EventData & event, const vec2 & camera_origin, + const Camera & current_cam) { + EventManager & event_mgr = this->mediator.event_manager; + vec2 adjusted_mouse; + adjusted_mouse.x = event.data.mouse_data.mouse_position.x + camera_origin.x; + adjusted_mouse.x = event.data.mouse_data.mouse_position.y + camera_origin.y; + // Check if the mouse is within the viewport + if ((adjusted_mouse.x < camera_origin.x + || adjusted_mouse.x > camera_origin.x + current_cam.viewport_size.x + || adjusted_mouse.y < camera_origin.y + || adjusted_mouse.y > camera_origin.y + current_cam.viewport_size.y)) + return; + + // Handle mouse-specific events + switch (event.event_type) { + case EventType::MOUSE_DOWN: + event_mgr.queue_event<MousePressEvent>({ + .mouse_pos = adjusted_mouse, + .button = event.data.mouse_data.mouse_button, + }); + this->last_mouse_down_position = adjusted_mouse; + this->last_mouse_button = event.data.mouse_data.mouse_button; + break; + + case EventType::MOUSE_UP: { + event_mgr.queue_event<MouseReleaseEvent>({ + .mouse_pos = adjusted_mouse, + .button = event.data.mouse_data.mouse_button, + }); + vec2 delta_move = adjusted_mouse - this->last_mouse_down_position; + int click_tolerance = Config::get_instance().input.click_tolerance; + if (this->last_mouse_button == event.data.mouse_data.mouse_button + && std::abs(delta_move.x) <= click_tolerance + && std::abs(delta_move.y) <= click_tolerance) { + event_mgr.queue_event<MouseClickEvent>({ + .mouse_pos = adjusted_mouse, + .button = event.data.mouse_data.mouse_button, + }); + this->handle_click(event.data.mouse_data.mouse_button, adjusted_mouse); + } + break; + } + + case EventType::MOUSE_MOVE: + event_mgr.queue_event<MouseMoveEvent>({ + .mouse_pos = adjusted_mouse, + .mouse_delta = event.data.mouse_data.rel_mouse_move, + }); + this->handle_move(event, adjusted_mouse); + break; + + case EventType::MOUSE_WHEEL: + event_mgr.queue_event<MouseScrollEvent>({ + .mouse_pos = adjusted_mouse, + .scroll_direction = event.data.mouse_data.scroll_direction, + .scroll_delta = event.data.mouse_data.scroll_delta, + }); + break; + + default: + break; + } +} + +void InputSystem::handle_non_mouse_event(const EventData & event) { + EventManager & event_mgr = this->mediator.event_manager; + switch (event.event_type) { + case EventType::KEY_DOWN: + + event_mgr.queue_event<KeyPressEvent>( + {.repeat = event.data.key_data.key_repeat, .key = event.data.key_data.key}); + break; + case EventType::KEY_UP: + event_mgr.queue_event<KeyReleaseEvent>({.key = event.data.key_data.key}); + break; + case EventType::SHUTDOWN: + event_mgr.queue_event<ShutDownEvent>({}); + break; + case EventType::WINDOW_EXPOSE: + event_mgr.queue_event<WindowExposeEvent>({}); + break; + case EventType::WINDOW_RESIZE: + event_mgr.queue_event<WindowResizeEvent>( + WindowResizeEvent{.dimensions = event.data.window_data.resize_dimension}); + break; + case EventType::WINDOW_MOVE: + event_mgr.queue_event<WindowMoveEvent>( + {.delta_move = event.data.window_data.move_delta}); + break; + case EventType::WINDOW_MINIMIZE: + event_mgr.queue_event<WindowMinimizeEvent>({}); + break; + case EventType::WINDOW_MAXIMIZE: + event_mgr.queue_event<WindowMaximizeEvent>({}); + break; + case EventType::WINDOW_FOCUS_GAIN: + event_mgr.queue_event<WindowFocusGainEvent>({}); + break; + case EventType::WINDOW_FOCUS_LOST: + event_mgr.queue_event<WindowFocusLostEvent>({}); + break; + default: + break; + } +} + +void InputSystem::handle_move(const EventData & event_data, const vec2 & mouse_pos) { + ComponentManager & mgr = this->mediator.component_manager; + EventManager & event_mgr = this->mediator.event_manager; + RefVector<Button> buttons = mgr.get_components_by_type<Button>(); + + for (Button & button : buttons) { + if (!button.active) continue; + Metadata & metadata + = mgr.get_components_by_id<Metadata>(button.game_object_id).front(); + Transform & transform + = mgr.get_components_by_id<Transform>(button.game_object_id).front(); + bool was_hovering = button.hover; + if (this->is_mouse_inside_button(mouse_pos, button, transform)) { + button.hover = true; + if (!was_hovering) { + event_mgr.trigger_event<ButtonEnterEvent>(metadata); + } + } else { + button.hover = false; + if (was_hovering) { + event_mgr.trigger_event<ButtonExitEvent>(metadata); + } + } + } +} + +void InputSystem::handle_click(const MouseButton & mouse_button, const vec2 & mouse_pos) { + ComponentManager & mgr = this->mediator.component_manager; + EventManager & event_mgr = this->mediator.event_manager; + RefVector<Button> buttons = mgr.get_components_by_type<Button>(); + + for (Button & button : buttons) { + if (!button.active) continue; + Metadata & metadata + = mgr.get_components_by_id<Metadata>(button.game_object_id).front(); + Transform & transform + = mgr.get_components_by_id<Transform>(button.game_object_id).front(); + + if (this->is_mouse_inside_button(mouse_pos, button, transform)) { + event_mgr.trigger_event<ButtonPressEvent>(metadata); + } + } +} + +bool InputSystem::is_mouse_inside_button(const vec2 & mouse_pos, const Button & button, + const Transform & transform) { + int actual_x = transform.position.x + button.offset.x; + int actual_y = transform.position.y + button.offset.y; + + int half_width = button.dimensions.x / 2; + int half_height = button.dimensions.y / 2; + + // Check if the mouse is within the button's boundaries + return mouse_pos.x >= actual_x - half_width && mouse_pos.x <= actual_x + half_width + && mouse_pos.y >= actual_y - half_height && mouse_pos.y <= actual_y + half_height; +} diff --git a/src/crepe/system/InputSystem.h b/src/crepe/system/InputSystem.h new file mode 100644 index 0000000..0f1bfa1 --- /dev/null +++ b/src/crepe/system/InputSystem.h @@ -0,0 +1,132 @@ +#pragma once + +#include "../api/Config.h" +#include "../facade/EventData.h" + +#include "../api/Event.h" +#include "../api/Metadata.h" +#include "../types.h" +#include "../util/OptionalRef.h" + +#include "System.h" + +namespace crepe { + +class Camera; +class Button; +class Transform; +//! Event triggered when a button is clicked +class ButtonPressEvent : public Event { +public: + //! Metadata of the button. + const Metadata & metadata; + /** + * \param metadata Metadata of the button pressed + */ + ButtonPressEvent(const Metadata & metadata) : metadata(metadata){}; +}; +//! Event triggered when the mouse enters a button +class ButtonEnterEvent : public Event { +public: + //! Metadata of the button. + const Metadata & metadata; + /** + * \param metadata Metadata of the button pressed + */ + ButtonEnterEvent(const Metadata & metadata) : metadata(metadata){}; +}; +//! Event triggered when the mouse leaves a button +class ButtonExitEvent : public Event { +public: + //! Metadata of the button. + const Metadata & metadata; + /** + * \param metadata Metadata of the button pressed + */ + ButtonExitEvent(const Metadata & metadata) : metadata(metadata){}; +}; + +/** + * \brief Handles the processing of input events created by SDLContext + * + * This system processes events such as mouse clicks, mouse movement, and keyboard + * actions. It is responsible for detecting interactions with UI buttons and + * passing the corresponding events to the registered listeners. + */ +class InputSystem : public System { +public: + using System::System; + + /** + * \brief Updates the system, processing all input events. + * This method processes all events and triggers corresponding actions. + */ + void fixed_update() override; + +private: + //! Stores the last position of the mouse when the button was pressed. + vec2 last_mouse_down_position; + // TODO: specify world/hud space and make regular `vec2` + + //! Stores the last mouse button pressed. + MouseButton last_mouse_button = MouseButton::NONE; + /** + * \brief Handles mouse-related events. + * \param event The event data for the mouse event. + * \param camera_origin The origin position of the camera in world space. + * \param current_cam The currently active camera. + * + * This method processes mouse events, adjusts the mouse position to world coordinates, + * and triggers the appropriate mouse-specific event handling logic. + */ + void handle_mouse_event(const EventData & event, const vec2 & camera_origin, + const Camera & current_cam); + /** + * \brief Handles non-mouse-related events. + * \param event The event data for the non-mouse event. + * + * This method processes events that do not involve the mouse, such as keyboard events, + * window events, and shutdown events, and triggers the corresponding event actions. + */ + void handle_non_mouse_event(const EventData & event); + /** + * \brief Handles the mouse click event. + * \param mouse_button The mouse button involved in the click. + * \param world_mouse_x The X coordinate of the mouse in world space. + * \param world_mouse_y The Y coordinate of the mouse in world space. + * + * This method processes the mouse click event and triggers the corresponding button action. + */ + void handle_click(const MouseButton & mouse_button, const vec2 & mouse_pos); + + /** + * \brief Handles the mouse movement event. + * \param event_data The event data containing information about the mouse movement. + * \param world_mouse_x The X coordinate of the mouse in world space. + * \param world_mouse_y The Y coordinate of the mouse in world space. + * + * This method processes the mouse movement event and updates the button hover state. + */ + void handle_move(const EventData & event_data, const vec2 & mouse_pos); + + /** + * \brief Checks if the mouse position is inside the bounds of the button. + * \param world_mouse_x The X coordinate of the mouse in world space. + * \param world_mouse_y The Y coordinate of the mouse in world space. + * \param button The button to check. + * \param transform The transform component of the button. + * \return True if the mouse is inside the button, false otherwise. + */ + bool is_mouse_inside_button(const vec2 & mouse_pos, const Button & button, + const Transform & transform); + + /** + * \brief Handles the button press event, calling the on_click callback if necessary. + * \param button The button being pressed. + * + * This method triggers the on_click action for the button when it is pressed. + */ + void handle_button_press(Button & button); +}; + +} // namespace crepe diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp new file mode 100644 index 0000000..bbc7366 --- /dev/null +++ b/src/crepe/system/ParticleSystem.cpp @@ -0,0 +1,121 @@ +#include <chrono> +#include <cmath> +#include <cstdlib> +#include <ctime> + +#include "../api/ParticleEmitter.h" +#include "../api/Transform.h" +#include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h" + +#include "ParticleSystem.h" + +using namespace crepe; + +void ParticleSystem::fixed_update() { + // Get all emitters + const Mediator & mediator = this->mediator; + LoopTimerManager & loop_timer = mediator.loop_timer; + ComponentManager & mgr = mediator.component_manager; + float dt = loop_timer.get_scaled_fixed_delta_time().count(); + + RefVector<ParticleEmitter> emitters = mgr.get_components_by_type<ParticleEmitter>(); + + for (ParticleEmitter & emitter : emitters) { + // Get transform linked to emitter + const Transform & transform + = mgr.get_components_by_id<Transform>(emitter.game_object_id).front().get(); + + // Emit particles based on emission_rate + emitter.spawn_accumulator += emitter.data.emission_rate * dt; + while (emitter.spawn_accumulator >= 1.0) { + this->emit_particle(emitter, transform); + emitter.spawn_accumulator -= 1.0; + } + + // Update all particles + for (Particle & particle : emitter.particles) { + if (particle.active) { + particle.update(dt); + } + } + + // Check if within boundary + this->check_bounds(emitter, transform); + } +} + +void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform & transform) { + constexpr float DEG_TO_RAD = M_PI / 180.0; + + vec2 initial_position = emitter.data.offset + transform.position; + float random_angle + = this->generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); + + float random_speed + = this->generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); + float angle_radians = random_angle * DEG_TO_RAD; + + vec2 velocity + = {random_speed * std::cos(angle_radians), random_speed * std::sin(angle_radians)}; + + for (Particle & particle : emitter.particles) { + if (!particle.active) { + particle.reset(emitter.data.end_lifespan, initial_position, velocity, + random_angle); + break; + } + } +} + +void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform & transform) { + vec2 offset = emitter.data.boundary.offset + transform.position + emitter.data.offset; + float half_width = emitter.data.boundary.width / 2.0; + float half_height = emitter.data.boundary.height / 2.0; + + float left = offset.x - half_width; + float right = offset.x + half_width; + float top = offset.y - half_height; + float bottom = offset.y + half_height; + + for (Particle & particle : emitter.particles) { + const vec2 & position = particle.position; + bool within_bounds = (position.x >= left && position.x <= right && position.y >= top + && position.y <= bottom); + //if not within bounds do a reset or stop velocity + if (!within_bounds) { + if (emitter.data.boundary.reset_on_exit) { + particle.active = false; + } else { + particle.velocity = {0, 0}; + if (position.x < left) particle.position.x = left; + else if (position.x > right) particle.position.x = right; + if (position.y < top) particle.position.y = top; + else if (position.y > bottom) particle.position.y = bottom; + } + } + } +} + +float ParticleSystem::generate_random_angle(float min_angle, float max_angle) const { + if (min_angle == max_angle) { + return min_angle; + } else if (min_angle < max_angle) { + return min_angle + + static_cast<float>(std::rand() % static_cast<int>(max_angle - min_angle)); + } else { + float angle_offset = (360 - min_angle) + max_angle; + float random_angle + = min_angle + static_cast<float>(std::rand() % static_cast<int>(angle_offset)); + return (random_angle >= 360) ? random_angle - 360 : random_angle; + } +} + +float ParticleSystem::generate_random_speed(float min_speed, float max_speed) const { + if (min_speed == max_speed) { + return min_speed; + } else { + return min_speed + + static_cast<float>(std::rand() % static_cast<int>(max_speed - min_speed)); + } +} diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h new file mode 100644 index 0000000..4296ff3 --- /dev/null +++ b/src/crepe/system/ParticleSystem.h @@ -0,0 +1,62 @@ +#pragma once + +#include <cstdint> + +#include "System.h" + +namespace crepe { + +class ParticleEmitter; +class Transform; + +/** + * \brief ParticleSystem class responsible for managing particle emission, updates, and bounds + * checking. + */ +class ParticleSystem : public System { +public: + using System::System; + /** + * \brief Updates all particle emitters by emitting particles, updating particle states, and + * checking bounds. + */ + void fixed_update() override; + +private: + /** + * \brief Emits a particle from the specified emitter based on its emission properties. + * + * \param emitter Reference to the ParticleEmitter. + * \param transform Const reference to the Transform component associated with the emitter. + */ + void emit_particle(ParticleEmitter & emitter, const Transform & transform); + + /** + * \brief Checks whether particles are within the emitter’s boundary, resets or stops + * particles if they exit. + * + * \param emitter Reference to the ParticleEmitter. + * \param transform Const reference to the Transform component associated with the emitter. + */ + void check_bounds(ParticleEmitter & emitter, const Transform & transform); + + /** + * \brief Generates a random angle for particle emission within the specified range. + * + * \param min_angle Minimum emission angle in degrees. + * \param max_angle Maximum emission angle in degrees. + * \return Random angle in degrees. + */ + float generate_random_angle(float min_angle, float max_angle) const; + + /** + * \brief Generates a random speed for particle emission within the specified range. + * + * \param min_speed Minimum emission speed. + * \param max_speed Maximum emission speed. + * \return Random speed. + */ + float generate_random_speed(float min_speed, float max_speed) const; +}; + +} // namespace crepe diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp new file mode 100644 index 0000000..62f8132 --- /dev/null +++ b/src/crepe/system/PhysicsSystem.cpp @@ -0,0 +1,106 @@ +#include <cmath> + +#include "../api/Config.h" +#include "../api/Rigidbody.h" +#include "../api/Transform.h" +#include "../api/Vector2.h" +#include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/Mediator.h" + +#include "PhysicsSystem.h" + +using namespace crepe; + +void PhysicsSystem::fixed_update() { + const Mediator & mediator = this->mediator; + ComponentManager & mgr = mediator.component_manager; + LoopTimerManager & loop_timer = mediator.loop_timer; + RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>(); + float dt = loop_timer.get_scaled_fixed_delta_time().count(); + + float gravity = Config::get_instance().physics.gravity; + for (Rigidbody & rigidbody : rigidbodies) { + if (!rigidbody.active) continue; + Transform & transform + = mgr.get_components_by_id<Transform>(rigidbody.game_object_id).front().get(); + + switch (rigidbody.data.body_type) { + case Rigidbody::BodyType::DYNAMIC: + if (transform.game_object_id == rigidbody.game_object_id) { + // Add gravity + + if (rigidbody.data.mass <= 0) { + throw std::runtime_error("Mass must be greater than 0"); + } + + if (gravity <= 0) { + throw std::runtime_error("Config Gravity must be greater than 0"); + } + + if (rigidbody.data.gravity_scale > 0 && !rigidbody.data.constraints.y) { + rigidbody.data.linear_velocity.y + += (rigidbody.data.mass * rigidbody.data.gravity_scale * gravity + * dt); + } + // Add coefficient rotation + if (rigidbody.data.angular_velocity_coefficient > 0) { + rigidbody.data.angular_velocity + *= std::pow(rigidbody.data.angular_velocity_coefficient, dt); + } + + // Add coefficient movement horizontal + if (rigidbody.data.linear_velocity_coefficient.x > 0 + && !rigidbody.data.constraints.x) { + rigidbody.data.linear_velocity.x + *= std::pow(rigidbody.data.linear_velocity_coefficient.x, dt); + } + + // Add coefficient movement horizontal + if (rigidbody.data.linear_velocity_coefficient.y > 0 + && !rigidbody.data.constraints.y) { + rigidbody.data.linear_velocity.y + *= std::pow(rigidbody.data.linear_velocity_coefficient.y, dt); + } + + // Max velocity check + if (rigidbody.data.angular_velocity + > rigidbody.data.max_angular_velocity) { + rigidbody.data.angular_velocity = rigidbody.data.max_angular_velocity; + } else if (rigidbody.data.angular_velocity + < -rigidbody.data.max_angular_velocity) { + rigidbody.data.angular_velocity = -rigidbody.data.max_angular_velocity; + } + + // Set max velocity to maximum length + if (rigidbody.data.linear_velocity.length() + > rigidbody.data.max_linear_velocity) { + rigidbody.data.linear_velocity.normalize(); + rigidbody.data.linear_velocity *= rigidbody.data.max_linear_velocity; + } + + // Move object + if (!rigidbody.data.constraints.rotation) { + transform.rotation += rigidbody.data.angular_velocity * dt; + transform.rotation = std::fmod(transform.rotation, 360.0); + if (transform.rotation < 0) { + transform.rotation += 360.0; + } + } + if (!rigidbody.data.constraints.x) { + transform.position.x += rigidbody.data.linear_velocity.x * dt; + } + if (!rigidbody.data.constraints.y) { + transform.position.y += rigidbody.data.linear_velocity.y * dt; + } + } + break; + case Rigidbody::BodyType::KINEMATIC: + break; //(scripts) + case Rigidbody::BodyType::STATIC: + break; //(unmoveable objects) + default: + break; + } + } +} diff --git a/src/crepe/system/PhysicsSystem.h b/src/crepe/system/PhysicsSystem.h new file mode 100644 index 0000000..5ed624f --- /dev/null +++ b/src/crepe/system/PhysicsSystem.h @@ -0,0 +1,24 @@ +#pragma once + +#include "System.h" + +namespace crepe { + +/** + * \brief System that controls all physics + * + * This class is a physics system that uses a rigidbody and transform to add physics to a game + * object. + */ +class PhysicsSystem : public System { +public: + using System::System; + /** + * \brief updates the physics system. + * + * It calculates new velocties and changes the postion in the transform. + */ + void fixed_update() override; +}; + +} // namespace crepe diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp new file mode 100644 index 0000000..33218f6 --- /dev/null +++ b/src/crepe/system/RenderSystem.cpp @@ -0,0 +1,160 @@ +#include <algorithm> +#include <cassert> +#include <cmath> +#include <functional> +#include <optional> +#include <stdexcept> +#include <vector> + +#include "../api/Camera.h" +#include "../api/ParticleEmitter.h" +#include "../api/Sprite.h" +#include "../api/Text.h" +#include "../api/Transform.h" +#include "../facade/Font.h" +#include "../facade/SDLContext.h" +#include "../facade/Texture.h" +#include "../manager/ComponentManager.h" +#include "../manager/ResourceManager.h" + +#include "RenderSystem.h" +#include "types.h" + +using namespace crepe; +using namespace std; + +void RenderSystem::clear_screen() { + SDLContext & ctx = this->mediator.sdl_context; + ctx.clear_screen(); +} + +void RenderSystem::present_screen() { + SDLContext & ctx = this->mediator.sdl_context; + ctx.present_screen(); +} + +void RenderSystem::update_camera() { + ComponentManager & mgr = this->mediator.component_manager; + SDLContext & ctx = this->mediator.sdl_context; + RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); + + if (cameras.size() == 0) throw std::runtime_error("No cameras in current scene"); + + for (Camera & cam : cameras) { + if (!cam.active) continue; + const Transform & transform + = mgr.get_components_by_id<Transform>(cam.game_object_id).front().get(); + vec2 new_camera_pos = transform.position + cam.data.postion_offset; + ctx.update_camera_view(cam, new_camera_pos); + return; + } + throw std::runtime_error("No active cameras in current scene"); +} + +bool sorting_comparison(const Sprite & a, const Sprite & b) { + if (a.data.sorting_in_layer != b.data.sorting_in_layer) + return a.data.sorting_in_layer < b.data.sorting_in_layer; + if (a.data.order_in_layer != b.data.order_in_layer) + return a.data.order_in_layer < b.data.order_in_layer; + + return false; +} + +RefVector<Sprite> RenderSystem::sort(RefVector<Sprite> & objs) const { + RefVector<Sprite> sorted_objs(objs); + std::sort(sorted_objs.begin(), sorted_objs.end(), sorting_comparison); + + return sorted_objs; +} + +void RenderSystem::frame_update() { + this->clear_screen(); + this->render(); + this->present_screen(); +} + +bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) { + + ComponentManager & mgr = this->mediator.component_manager; + SDLContext & ctx = this->mediator.sdl_context; + ResourceManager & resource_manager = this->mediator.resource_manager; + Texture & res = resource_manager.get<Texture>(sprite.source); + + vector<reference_wrapper<ParticleEmitter>> emitters + = mgr.get_components_by_id<ParticleEmitter>(sprite.game_object_id); + + bool rendering_particles = false; + + for (const ParticleEmitter & em : emitters) { + if (&em.sprite != &sprite) continue; + rendering_particles = true; + if (!em.active) continue; + + for (const Particle & p : em.particles) { + if (!p.active) continue; + + ctx.draw(SDLContext::RenderContext{ + .sprite = sprite, + .texture = res, + .pos = p.position, + .angle = p.angle, + .scale = scale, + }); + } + } + return rendering_particles; +} +void RenderSystem::render_normal(const Sprite & sprite, const Transform & tm) { + SDLContext & ctx = this->mediator.sdl_context; + ResourceManager & resource_manager = this->mediator.resource_manager; + const Texture & res = resource_manager.get<Texture>(sprite.source); + + ctx.draw(SDLContext::RenderContext{ + .sprite = sprite, + .texture = res, + .pos = tm.position, + .angle = tm.rotation, + .scale = tm.scale, + }); +} + +void RenderSystem::render() { + ComponentManager & mgr = this->mediator.component_manager; + this->update_camera(); + + RefVector<Sprite> sprites = mgr.get_components_by_type<Sprite>(); + ResourceManager & resource_manager = this->mediator.resource_manager; + RefVector<Sprite> sorted_sprites = this->sort(sprites); + RefVector<Text> text_components = mgr.get_components_by_type<Text>(); + for (Text & text : text_components) { + const Transform & transform + = mgr.get_components_by_id<Transform>(text.game_object_id).front().get(); + this->render_text(text, transform); + } + for (const Sprite & sprite : sorted_sprites) { + if (!sprite.active) continue; + const Transform & transform + = mgr.get_components_by_id<Transform>(sprite.game_object_id).front().get(); + + bool rendered_particles = this->render_particle(sprite, transform.scale); + + if (rendered_particles) continue; + + this->render_normal(sprite, transform); + } +} +void RenderSystem::render_text(Text & text, const Transform & tm) { + SDLContext & ctx = this->mediator.sdl_context; + + if (!text.font.has_value()) { + text.font.emplace(ctx.get_font_from_name(text.font_family)); + } + + ResourceManager & resource_manager = this->mediator.resource_manager; + + if (!text.font.has_value()) { + return; + } + const Asset & font_asset = text.font.value(); + const Font & res = resource_manager.get<Font>(font_asset); +} diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h new file mode 100644 index 0000000..656ad5b --- /dev/null +++ b/src/crepe/system/RenderSystem.h @@ -0,0 +1,83 @@ +#pragma once + +#include <cmath> + +#include "System.h" +#include "types.h" + +namespace crepe { + +class Camera; +class Sprite; +class Transform; +class Text; +/** + * \brief Manages rendering operations for all game objects. + * + * RenderSystem is responsible for rendering, clearing and presenting the screen, and + * managing the active camera. + */ +class RenderSystem : public System { +public: + using System::System; + /** + * \brief Updates the RenderSystem for the current frame. + * This method is called to perform all rendering operations for the current game frame. + */ + void frame_update() override; + +private: + //! Clears the screen in preparation for rendering. + void clear_screen(); + + //! Presents the rendered frame to the display. + void present_screen(); + + //! Updates the active camera used for rendering. + void update_camera(); + + //! Renders the whole screen + void render(); + + /** + * \brief Renders all the particles on the screen from a given sprite. + * + * \param sprite renders the particles with given texture + * \param tm the Transform component for scale. This is not a const reference because each + * particle has a position and rotation that needs to overwrite the transform position and + * rotation without overwriting the current transform. and because the transform + * constructor is now protected i cannot make tmp inside + * \return true if particles have been rendered + */ + bool render_particle(const Sprite & sprite, const double & scale); + /** + * \brief Renders all Text components + * + * \param text The text component to be rendered. + * \param tm the Transform component that holds the position,rotation and scale + */ + void render_text(Text & text, const Transform & tm); + /** + * \brief renders a sprite with a Transform component on the screen + * + * \param sprite the sprite component that holds all the data + * \param tm the Transform component that holds the position,rotation and scale + */ + void render_normal(const Sprite & sprite, const Transform & tm); + + /** + * \brief sort a vector sprite objects with + * + * \param objs the vector that will do a sorting algorithm on + * \return returns a sorted reference vector + */ + RefVector<Sprite> sort(RefVector<Sprite> & objs) const; + + /** + * \todo Add text rendering using SDL_ttf for text components. + * \todo Implement a text component and a button component. + * \todo Consider adding text input functionality. + */ +}; + +} // namespace crepe diff --git a/src/crepe/system/ReplaySystem.cpp b/src/crepe/system/ReplaySystem.cpp new file mode 100644 index 0000000..efc3be4 --- /dev/null +++ b/src/crepe/system/ReplaySystem.cpp @@ -0,0 +1,54 @@ +#include "../manager/ReplayManager.h" +#include "../manager/SystemManager.h" + +#include "EventSystem.h" +#include "RenderSystem.h" +#include "ReplaySystem.h" + +using namespace crepe; +using namespace std; + +void ReplaySystem::fixed_update() { + ReplayManager & replay = this->mediator.replay_manager; + ReplayManager::State state = replay.get_state(); + ReplayManager::State last_state = this->last_state; + this->last_state = state; + + switch (state) { + case ReplayManager::IDLE: + break; + case ReplayManager::RECORDING: { + replay.frame_record(); + break; + } + case ReplayManager::PLAYING: { + if (last_state != ReplayManager::PLAYING) this->playback_begin(); + bool last = replay.frame_step(); + if (last) this->playback_end(); + break; + } + } +} + +void ReplaySystem::playback_begin() { + SystemManager & systems = this->mediator.system_manager; + ComponentManager & components = this->mediator.component_manager; + + this->playback = { + .components = components.save(), + .systems = systems.save(), + }; + + systems.disable_all(); + systems.get_system<RenderSystem>().active = true; + systems.get_system<ReplaySystem>().active = true; + systems.get_system<EventSystem>().active = true; +} + +void ReplaySystem::playback_end() { + SystemManager & systems = this->mediator.system_manager; + ComponentManager & components = this->mediator.component_manager; + + components.restore(this->playback.components); + systems.restore(this->playback.systems); +} diff --git a/src/crepe/system/ReplaySystem.h b/src/crepe/system/ReplaySystem.h new file mode 100644 index 0000000..bbc8d76 --- /dev/null +++ b/src/crepe/system/ReplaySystem.h @@ -0,0 +1,44 @@ +#pragma once + +#include "../manager/ReplayManager.h" +#include "../manager/SystemManager.h" + +#include "System.h" + +namespace crepe { + +/** + * \brief ReplayManager helper system + * + * This system records and replays recordings using ReplayManager. + */ +class ReplaySystem : public System { +public: + using System::System; + + void fixed_update() override; + +private: + //! Last ReplayManager state + ReplayManager::State last_state = ReplayManager::IDLE; + + /** + * \brief Playback snapshot + * + * When starting playback, the component state is saved and most systems are disabled. This + * struct stores the engine state before ReplayManager::play is called. + */ + struct Snapshot { + ComponentManager::Snapshot components; + SystemManager::Snapshot systems; + }; + //! Before playback snapshot + Snapshot playback; + + //! Snapshot state and disable systems during playback + void playback_begin(); + //! Restore state from before \c playback_begin() + void playback_end(); +}; + +} // namespace crepe diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp new file mode 100644 index 0000000..93b4853 --- /dev/null +++ b/src/crepe/system/ScriptSystem.cpp @@ -0,0 +1,40 @@ +#include "../api/BehaviorScript.h" +#include "../api/Script.h" +#include "../manager/ComponentManager.h" + +#include "ScriptSystem.h" + +using namespace std; +using namespace crepe; + +void ScriptSystem::fixed_update() { + LoopTimerManager & timer = this->mediator.loop_timer; + duration_t delta_time = timer.get_scaled_fixed_delta_time(); + this->update(&Script::fixed_update, delta_time); +} + +void ScriptSystem::frame_update() { + LoopTimerManager & timer = this->mediator.loop_timer; + duration_t delta_time = timer.get_delta_time(); + this->update(&Script::frame_update, delta_time); +} + +void ScriptSystem::update(void (Script::*update_function)(duration_t), + const duration_t & delta_time) { + ComponentManager & mgr = this->mediator.component_manager; + RefVector<BehaviorScript> behavior_scripts = mgr.get_components_by_type<BehaviorScript>(); + + for (BehaviorScript & behavior_script : behavior_scripts) { + if (!behavior_script.active) continue; + + Script * script = behavior_script.script.get(); + if (script == nullptr) continue; + + if (!script->initialized) { + script->init(); + script->initialized = true; + } + + (*script.*update_function)(delta_time); + } +} diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h new file mode 100644 index 0000000..257b615 --- /dev/null +++ b/src/crepe/system/ScriptSystem.h @@ -0,0 +1,37 @@ +#pragma once + +#include "../manager/LoopTimerManager.h" + +#include "System.h" + +namespace crepe { + +class Script; + +/** + * \brief Script system + * + * The script system is responsible for all \c BehaviorScript components, and calls the methods + * on classes derived from \c Script. + */ +class ScriptSystem : public System { +public: + using System::System; + +public: + //! Call Script::fixed_update() on all active \c BehaviorScript instances + void fixed_update() override; + //! Call Script::frame_update() on all active \c BehaviorScript instances + void frame_update() override; + +private: + /** + * \brief Call Script `*_update` member function on all active \c BehaviorScript instances + * + * \note This routine also calls Script::init() if this has not been done before on the \c + * BehaviorScript instance. + */ + void update(void (Script::*update_function)(duration_t), const duration_t & delta_time); +}; + +} // namespace crepe diff --git a/src/crepe/system/System.cpp b/src/crepe/system/System.cpp new file mode 100644 index 0000000..ecc740d --- /dev/null +++ b/src/crepe/system/System.cpp @@ -0,0 +1,5 @@ +#include "System.h" + +using namespace crepe; + +System::System(const Mediator & mediator) : mediator(mediator) {} diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h new file mode 100644 index 0000000..e2ce7eb --- /dev/null +++ b/src/crepe/system/System.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../manager/Mediator.h" + +namespace crepe { + +class ComponentManager; + +/** + * \brief Base ECS system class + * + * This class is used as the base for all system classes. Classes derived from System must + * implement the System::update() method and copy Script::Script with the `using`-syntax. + */ +class System { +public: + //! Code that runs in the fixed loop + virtual void fixed_update() {}; + //! Code that runs in the frame loop + virtual void frame_update() {}; + //! Indicates that the update functions of this system should be run + bool active = true; + +public: + System(const Mediator & m); + virtual ~System() = default; + +protected: + const Mediator & mediator; +}; + +} // namespace crepe |