diff --git a/as6/Components.hpp b/as6/Components.hpp index 455a56e..692c55d 100644 --- a/as6/Components.hpp +++ b/as6/Components.hpp @@ -1,11 +1,13 @@ #pragma once #include "Entity.hpp" +#include "GameContext.hpp" #include "raylib.h" #include #include +#include struct TransformComponent : public Component { float x = 0.0f; @@ -51,7 +53,6 @@ struct GravityWellComponent : public Component { return; } - // TODO: use buffered input active = IsMouseButtonDown(MOUSE_BUTTON_LEFT); const Vector2 mouse = GetMousePosition(); @@ -63,47 +64,38 @@ struct GravityWellComponent : public Component { void Cleanup() override {} }; -struct GravityReceiverComponent : public Component { - Entity *well = nullptr; - bool inVoid = false; +struct ScrollComponent : public Component { + float scrollX = 0.0f; + float speed = 2.0f; + float accel = 0.018f; void Setup() override {} void Update(float dt) override { - (void)inVoid; - - if (!well) { - return; + speed += accel * dt; + scrollX += speed * dt * 60.0f; + if (context) { + context->scrollX = scrollX; } - - auto myTransform = entity->GetComponent(); - auto physics = entity->GetComponent(); - auto wellTransform = well->GetComponent(); - auto wellGravity = well->GetComponent(); - if (!myTransform || !physics || !wellTransform || !wellGravity) { - return; - } - - if (!wellGravity->get().active) { - return; - } - - const float dx = wellTransform->get().x - myTransform->get().x; - const float dy = wellTransform->get().y - myTransform->get().y; - const float dist = std::sqrt(dx * dx + dy * dy); - if (dist <= 0.0001f) { - return; - } - - const float clampedDist = std::max(dist, wellGravity->get().minDist); - const float force = wellGravity->get().mass / (clampedDist * clampedDist); - const float nx = dx / dist; - const float ny = dy / dist; - - physics->get().vx += nx * force * dt; - physics->get().vy += ny * force * dt; } void Cleanup() override {} }; +struct MeterComponent : public Component { + float value = 60.0f; + float maxValue = 100.0f; + float drainRate = 14.0f; + float gainPerStar = 28.0f; + void Setup() override {} + void Update(float) override {} + void Cleanup() override {} +}; + +struct NullZoneComponent : public Component { + float width = 70.0f; + void Setup() override {} + void Update(float) override {} + void Cleanup() override {} +}; + struct ColliderComponent : public Component { float radius = 8.0f; void Setup() override {} @@ -111,22 +103,21 @@ struct ColliderComponent : public Component { void Cleanup() override {} }; -struct ScrollComponent : public Component { - float scrollX = 0.0f; - float speed = 2.0f; - float accel = 0.018f; - void Setup() override {} - void Update(float dt) override { - speed += accel * dt; - scrollX += speed * dt * 60.0f; - } - void Cleanup() override {} -}; - struct ScrollableComponent : public Component { float worldX = 0.0f; void Setup() override {} - void Update(float) override {} + void Update(float) override { + if (!context) { + return; + } + + auto transform = entity->GetComponent(); + if (!transform) { + return; + } + + transform->get().x = worldX - context->scrollX; + } void Cleanup() override {} }; @@ -149,10 +140,212 @@ struct ProjectionComponent : public Component { void Cleanup() override {} }; -struct MeterComponent : public Component { - float value = 60.0f; +struct CollectibleComponent : public Component { + bool collected = false; void Setup() override {} - void Update(float) override {} + void Update(float) override { + if (collected || !context || !context->probeEntity || entity == context->probeEntity) { + return; + } + + auto selfTransform = entity->GetComponent(); + auto selfCollider = entity->GetComponent(); + auto probeTransform = context->probeEntity->GetComponent(); + auto probeCollider = context->probeEntity->GetComponent(); + if (!selfTransform || !selfCollider || !probeTransform || !probeCollider) { + return; + } + + const float dx = selfTransform->get().x - probeTransform->get().x; + const float dy = selfTransform->get().y - probeTransform->get().y; + const float r = selfCollider->get().radius + probeCollider->get().radius; + if ((dx * dx + dy * dy) > (r * r)) { + return; + } + + collected = true; + + if (!context->hudEntity) { + return; + } + + auto meter = context->hudEntity->GetComponent(); + if (!meter) { + return; + } + + meter->get().value = + std::min(meter->get().maxValue, meter->get().value + meter->get().gainPerStar); + } + void Cleanup() override {} +}; + +struct HazardComponent : public Component { + void Setup() override {} + void Update(float) override { + if (!context || !context->probeEntity || entity == context->probeEntity) { + return; + } + + auto selfTransform = entity->GetComponent(); + auto selfCollider = entity->GetComponent(); + auto probeTransform = context->probeEntity->GetComponent(); + auto probeCollider = context->probeEntity->GetComponent(); + if (!selfTransform || !selfCollider || !probeTransform || !probeCollider) { + return; + } + + const float dx = selfTransform->get().x - probeTransform->get().x; + const float dy = selfTransform->get().y - probeTransform->get().y; + const float r = selfCollider->get().radius + probeCollider->get().radius; + if ((dx * dx + dy * dy) <= (r * r)) { + context->resetRequested = true; + } + } + void Cleanup() override {} +}; + +struct GravityReceiverComponent : public Component { + Entity *well = nullptr; + bool inVoid = false; + void Setup() override {} + void Update(float dt) override { + if (!context || !context->probeEntity || entity != context->probeEntity) { + return; + } + + if (!well && context->wellEntity) { + well = context->wellEntity; + } + if (!well) { + return; + } + + auto myTransform = entity->GetComponent(); + auto physics = entity->GetComponent(); + auto wellTransform = well->GetComponent(); + auto wellGravity = well->GetComponent(); + if (!myTransform || !physics || !wellTransform || !wellGravity) { + return; + } + + MeterComponent *meterPtr = nullptr; + if (context->hudEntity) { + auto meter = context->hudEntity->GetComponent(); + if (meter) { + meterPtr = &meter->get(); + } + } + + if (!wellGravity->get().active) { + return; + } + if (meterPtr && meterPtr->value <= 0.0f) { + return; + } + + inVoid = false; + if (context->entities) { + for (auto &other : *context->entities) { + if (!other || other.get() == entity) { + continue; + } + + auto zone = other->GetComponent(); + auto zoneTransform = other->GetComponent(); + if (!zone || !zoneTransform) { + continue; + } + + const float left = zoneTransform->get().x; + const float right = left + zone->get().width; + if (myTransform->get().x >= left && myTransform->get().x <= right) { + inVoid = true; + break; + } + } + } + if (inVoid) { + return; + } + + const float dx = wellTransform->get().x - myTransform->get().x; + const float dy = wellTransform->get().y - myTransform->get().y; + const float dist = std::sqrt(dx * dx + dy * dy); + if (dist <= 0.0001f) { + return; + } + + const float clampedDist = std::max(dist, wellGravity->get().minDist); + const float force = wellGravity->get().mass / (clampedDist * clampedDist); + const float nx = dx / dist; + const float ny = dy / dist; + + physics->get().vx += nx * force * dt; + physics->get().vy += ny * force * dt; + + if (meterPtr) { + meterPtr->value = std::max(0.0f, meterPtr->value - meterPtr->drainRate * dt); + } + } + void Cleanup() override {} +}; + +struct ProbeStateComponent : public Component { + float spawnX = 96.0f; + float spawnY = 230.0f; + float spawnVx = 165.0f; + float spawnVy = 0.0f; + + void Setup() override {} + void Update(float) override { + if (!context || !context->probeEntity || entity != context->probeEntity) { + return; + } + + auto transform = entity->GetComponent(); + auto physics = entity->GetComponent(); + if (!transform || !physics) { + return; + } + + if (transform->get().y < -20.0f || + transform->get().y > static_cast(GetScreenHeight() + 20) || + transform->get().x < -20.0f) { + context->resetRequested = true; + } + + if (!context->resetRequested) { + return; + } + + transform->get().x = spawnX; + transform->get().y = spawnY; + physics->get().vx = spawnVx; + physics->get().vy = spawnVy; + + if (context->hudEntity) { + auto meter = context->hudEntity->GetComponent(); + if (meter) { + meter->get().value = 60.0f; + } + } + + if (context->entities) { + for (auto &other : *context->entities) { + if (!other) { + continue; + } + + auto collectible = other->GetComponent(); + if (collectible) { + collectible->get().collected = false; + } + } + } + + context->resetRequested = false; + } void Cleanup() override {} }; @@ -163,21 +356,14 @@ struct HudComponent : public Component { }; struct RenderComponent : public Component { - virtual void Draw() {} - void Setup() override {} - void Update(float) override {} - void Cleanup() override {} -}; + std::function draw; -struct NullZoneComponent : public Component { - float width = 70.0f; - void Setup() override {} - void Update(float) override {} - void Cleanup() override {} -}; + virtual void Draw() { + if (draw) { + draw(); + } + } -struct CollectibleComponent : public Component { - bool collected = false; void Setup() override {} void Update(float) override {} void Cleanup() override {} diff --git a/as6/Entities.hpp b/as6/Entities.hpp index f2bb02b..e597fb7 100644 --- a/as6/Entities.hpp +++ b/as6/Entities.hpp @@ -17,10 +17,25 @@ std::shared_ptr CreateProbe() { physics.vy = 0.0f; physics.speedCap = 8192.0f; - e->AddComponent(); + auto &collider = e->AddComponent(); + collider.radius = 7.0f; + + auto &probeState = e->AddComponent(); + probeState.spawnX = 96.0f; + probeState.spawnY = 230.0f; + probeState.spawnVx = 165.0f; + probeState.spawnVy = 0.0f; + e->AddComponent(); e->AddComponent(); - e->AddComponent(); + auto &render = e->AddComponent(); + render.draw = [e]() { + auto transform = e->GetComponent(); + if (!transform) { + return; + } + DrawCircleV({transform->get().x, transform->get().y}, 7.0f, Color{250, 244, 227, 255}); + }; return e; } @@ -35,7 +50,15 @@ std::shared_ptr CreateGravityWell() { well.minDist = 28.0f; well.followLerp = 12.0f; - e->AddComponent(); + auto &render = e->AddComponent(); + render.draw = [e]() { + auto transform = e->GetComponent(); + if (!transform) { + return; + } + DrawCircleLines(static_cast(transform->get().x), static_cast(transform->get().y), + 18.0f, Color{86, 197, 255, 255}); + }; return e; } @@ -46,9 +69,18 @@ std::shared_ptr CreateStar(float x, float y) { t.y = y; auto &scrollable = e->AddComponent(); scrollable.worldX = x; - e->AddComponent(); + auto &collider = e->AddComponent(); + collider.radius = 6.0f; + e->AddComponent(); - e->AddComponent(); + auto &render = e->AddComponent(); + render.draw = [e]() { + auto transform = e->GetComponent(); + if (!transform) { + return; + } + DrawCircleV({transform->get().x, transform->get().y}, 6.0f, Color{255, 223, 86, 255}); + }; return e; } @@ -59,8 +91,19 @@ std::shared_ptr CreateAsteroid(float x, float y) { t.y = y; auto &scrollable = e->AddComponent(); scrollable.worldX = x; - e->AddComponent(); - e->AddComponent(); + auto &collider = e->AddComponent(); + collider.radius = 13.0f; + + e->AddComponent(); + + auto &render = e->AddComponent(); + render.draw = [e]() { + auto transform = e->GetComponent(); + if (!transform) { + return; + } + DrawCircleV({transform->get().x, transform->get().y}, 13.0f, Color{116, 126, 142, 255}); + }; return e; } @@ -73,7 +116,17 @@ std::shared_ptr CreateNullZone(float x, float width) { auto &nullZone = e->AddComponent(); nullZone.width = width; - e->AddComponent(); + auto &render = e->AddComponent(); + render.draw = [e]() { + auto transform = e->GetComponent(); + auto zone = e->GetComponent(); + if (!transform || !zone) { + return; + } + + DrawRectangle(static_cast(transform->get().x), 0, static_cast(zone->get().width), + GetScreenHeight(), Color{96, 64, 146, 80}); + }; return e; } @@ -88,6 +141,20 @@ std::shared_ptr CreateHUD() { auto e = std::make_shared(); e->AddComponent(); e->AddComponent(); - e->AddComponent(); + auto &render = e->AddComponent(); + render.draw = [e]() { + auto meter = e->GetComponent(); + if (!meter) { + return; + } + + const float t = std::clamp(meter->get().value / 100.0f, 0.0f, 1.0f); + const int x = 14; + const int y = 86; + const int w = 240; + const int h = 14; + DrawRectangleLines(x, y, w, h, Color{125, 140, 165, 255}); + DrawRectangle(x + 1, y + 1, static_cast((w - 2) * t), h - 2, Color{60, 199, 178, 255}); + }; return e; } diff --git a/as6/Entity.hpp b/as6/Entity.hpp index 672d490..06be90e 100644 --- a/as6/Entity.hpp +++ b/as6/Entity.hpp @@ -1,5 +1,7 @@ #pragma once +#include "GameContext.hpp" + #include #include #include @@ -9,9 +11,17 @@ struct Entity; class Component; +/** + * Base interface that every component derives from. Each component keeps a + * backref to its owning entity plus the shared game context so it can read + * global state (scroll offset, meter, entity pointers, reset flags, etc.). The + * lifecycle follows a `Setup` / `Update` / `Cleanup` pattern invoked by the + * owning entity. + */ class Component { public: Entity *entity = nullptr; + GameContext *context = nullptr; virtual void Setup() = 0; virtual void Update(float dt) = 0; @@ -22,14 +32,45 @@ class Component { struct Entity { std::vector> components; + GameContext *context = nullptr; + /** + * Injects the global game context/state so the entity and its components can + * stay in sync with the global scroll/meter/reset state and known + * entity references. + * + * @param ctx Current game-wide context containing globals like scroll, entity handles, and + * reset flags. + */ + void SetContext(GameContext *ctx) { + context = ctx; + for (auto &component : components) { + component->context = ctx; + } + } + + /** + * Adds a component of type T to the entity, wiring ownership, the game + * context, and calling `Setup` immediately. + * + * @tparam T Component subclass type to add. + * @return Reference to the newly created component. + */ template T> T &AddComponent() { auto &ptr = components.emplace_back(std::make_shared()); ptr->entity = this; + ptr->context = context; ptr->Setup(); return static_cast(*ptr); } + /** + * Finds the first component of type T in this entity and returns a + * reference_wrapper if found. + * + * @tparam T Component subclass to get. + * @return Optional ref wrapper to the component if found, otherwise empty. + */ template T> std::optional> GetComponent() const { for (auto &c : components) { @@ -41,6 +82,12 @@ struct Entity { return {}; } + /** + * Propagates the delta time to all owned components so they can run their + * per-frame logic while sharing the same global context state. + * + * @param dt Time elapsed since the last frame. + */ void Update(float dt) { for (auto &c : components) { c->Update(dt); diff --git a/as6/GameContext.hpp b/as6/GameContext.hpp new file mode 100644 index 0000000..77b028a --- /dev/null +++ b/as6/GameContext.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +struct Entity; + +/** + * Shared execution context that flows through every entity and component so + * they can read/write the global scroll, known entity handles, and reset state. + */ +struct GameContext { + /** + * Pointer to the global entity list stored in `main.cpp`, allowing systems + * or components to inspect/create entities. + */ + std::vector> *entities = nullptr; + + /** + * Entity that owns the static world geometry (terrain, background, etc.). + */ + Entity *worldEntity = nullptr; + + /** + * Entity representing the gravity well that drags collectibles/probe. + */ + Entity *wellEntity = nullptr; + + /** + * Entity for the player's probe input/state representation. + */ + Entity *probeEntity = nullptr; + + /** + * HUD entity that draws meter/score/probe status overlays. + */ + Entity *hudEntity = nullptr; + + /** + * Global horizontal scroll offset that components read to position + * themselves on screen. + */ + float scrollX = 0.0f; + + /** + * Flag that a component (usually reset button) can toggle to request the + * gameplay systems reset the probe/meter/collectibles. + */ + bool resetRequested = false; +}; diff --git a/as6/Systems.hpp b/as6/Systems.hpp index 68ae783..ccd4fd0 100644 --- a/as6/Systems.hpp +++ b/as6/Systems.hpp @@ -2,47 +2,26 @@ #include "Components.hpp" #include "Entity.hpp" +#include "GameContext.hpp" + #include #include -void UpdateAllSystems(std::vector> &entities, float deltaTime) { - std::shared_ptr worldEntity; - std::shared_ptr wellEntity; - float scrollX = 0.0f; - - if (!entities.empty()) { - worldEntity = entities[0]; - } - if (entities.size() > 1) { - wellEntity = entities[1]; - } - - if (worldEntity) { - worldEntity->Update(deltaTime); - auto scroll = worldEntity->GetComponent(); - if (scroll) { - scrollX = scroll->get().scrollX; - } - } - - if (wellEntity) { - wellEntity->Update(deltaTime); - } +void UpdateAllSystems(std::vector> &entities, GameContext &context, + float deltaTime) { + context.entities = &entities; + context.worldEntity = (entities.size() > 0 && entities[0]) ? entities[0].get() : nullptr; + context.wellEntity = (entities.size() > 1 && entities[1]) ? entities[1].get() : nullptr; + context.probeEntity = (entities.size() > 2 && entities[2]) ? entities[2].get() : nullptr; + context.hudEntity = (!entities.empty() && entities.back()) ? entities.back().get() : nullptr; for (auto &entity : entities) { - if (entity == worldEntity || entity == wellEntity || !entity) { + if (!entity) { continue; } - auto receiver = entity->GetComponent(); - if (receiver) { - receiver->get().well = wellEntity.get(); - } - - auto transform = entity->GetComponent(); - auto scrollable = entity->GetComponent(); - if (transform && scrollable) { - transform->get().x = scrollable->get().worldX - scrollX; + if (entity->context != &context) { + entity->SetContext(&context); } entity->Update(deltaTime); diff --git a/as6/main.cpp b/as6/main.cpp index 649a7d5..5faee34 100644 --- a/as6/main.cpp +++ b/as6/main.cpp @@ -2,6 +2,7 @@ #include "Draw.hpp" #include "Entities.hpp" #include "Entity.hpp" +#include "GameContext.hpp" #include "Systems.hpp" #include "raylib-cpp.hpp" @@ -14,6 +15,8 @@ int main() { std::vector> entities; entities.reserve(20); + GameContext context; + entities.push_back(CreateWorld()); entities.push_back(CreateGravityWell()); entities.push_back(CreateProbe()); @@ -25,15 +28,11 @@ int main() { while (!window.ShouldClose()) { float dt = window.GetFrameTime(); - UpdateAllSystems(entities, dt); + UpdateAllSystems(entities, context, dt); auto worldScroll = entities[0]->GetComponent(); - auto wellTransform = entities[1]->GetComponent(); auto probeTransform = entities[2]->GetComponent(); - auto starTransform = entities[3]->GetComponent(); - auto asteroidTransform = entities[4]->GetComponent(); - auto nullTransform = entities[5]->GetComponent(); - auto nullZone = entities[5]->GetComponent(); + auto hudMeter = entities.back()->GetComponent(); window.BeginDrawing(); window.ClearBackground(raylib::Color(11, 15, 26, 255)); @@ -50,32 +49,29 @@ int main() { if (probeTransform) { const auto &probe = probeTransform->get(); - ::DrawCircleV({probe.x, probe.y}, 7.0f, raylib::Color(250, 244, 227, 255)); raylib::DrawText(TextFormat("probe: (%.1f, %.1f)", probe.x, probe.y), 14, 60, 16, raylib::Color(235, 215, 125, 255)); } - if (wellTransform) { - const auto &well = wellTransform->get(); - ::DrawCircleLines(static_cast(well.x), static_cast(well.y), 18.0f, - raylib::Color(86, 197, 255, 255)); + if (hudMeter) { + raylib::DrawText(TextFormat("meter: %.1f", hudMeter->get().value), 14, 80, 16, + raylib::Color(120, 210, 190, 255)); } - if (starTransform) { - const auto &star = starTransform->get(); - ::DrawCircleV({star.x, star.y}, 6.0f, raylib::Color(255, 223, 86, 255)); - } + for (auto &entity : entities) { + if (!entity) { + continue; + } - if (asteroidTransform) { - const auto &asteroid = asteroidTransform->get(); - ::DrawCircleV({asteroid.x, asteroid.y}, 13.0f, raylib::Color(116, 126, 142, 255)); - } + auto collectible = entity->GetComponent(); + if (collectible && collectible->get().collected) { + continue; + } - if (nullTransform && nullZone) { - const auto &zone = nullTransform->get(); - const int width = static_cast(nullZone->get().width); - ::DrawRectangle(static_cast(zone.x), 0, width, GetScreenHeight(), - raylib::Color(96, 64, 146, 80)); + auto render = entity->GetComponent(); + if (render) { + render->get().Draw(); + } } window.EndDrawing();