Add basic game loop
parent
78c07da39f
commit
7e954cd4de
|
|
@ -1,11 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Entity.hpp"
|
#include "Entity.hpp"
|
||||||
|
#include "GameContext.hpp"
|
||||||
|
|
||||||
#include "raylib.h"
|
#include "raylib.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
struct TransformComponent : public Component {
|
struct TransformComponent : public Component {
|
||||||
float x = 0.0f;
|
float x = 0.0f;
|
||||||
|
|
@ -51,7 +53,6 @@ struct GravityWellComponent : public Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use buffered input
|
|
||||||
active = IsMouseButtonDown(MOUSE_BUTTON_LEFT);
|
active = IsMouseButtonDown(MOUSE_BUTTON_LEFT);
|
||||||
|
|
||||||
const Vector2 mouse = GetMousePosition();
|
const Vector2 mouse = GetMousePosition();
|
||||||
|
|
@ -63,44 +64,35 @@ struct GravityWellComponent : public Component {
|
||||||
void Cleanup() override {}
|
void Cleanup() override {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GravityReceiverComponent : public Component {
|
struct ScrollComponent : public Component {
|
||||||
Entity *well = nullptr;
|
float scrollX = 0.0f;
|
||||||
bool inVoid = false;
|
float speed = 2.0f;
|
||||||
|
float accel = 0.018f;
|
||||||
void Setup() override {}
|
void Setup() override {}
|
||||||
void Update(float dt) override {
|
void Update(float dt) override {
|
||||||
(void)inVoid;
|
speed += accel * dt;
|
||||||
|
scrollX += speed * dt * 60.0f;
|
||||||
if (!well) {
|
if (context) {
|
||||||
return;
|
context->scrollX = scrollX;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto myTransform = entity->GetComponent<TransformComponent>();
|
|
||||||
auto physics = entity->GetComponent<PhysicsComponent>();
|
|
||||||
auto wellTransform = well->GetComponent<TransformComponent>();
|
|
||||||
auto wellGravity = well->GetComponent<GravityWellComponent>();
|
|
||||||
if (!myTransform || !physics || !wellTransform || !wellGravity) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
void Cleanup() override {}
|
||||||
|
};
|
||||||
|
|
||||||
if (!wellGravity->get().active) {
|
struct MeterComponent : public Component {
|
||||||
return;
|
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 {}
|
||||||
|
};
|
||||||
|
|
||||||
const float dx = wellTransform->get().x - myTransform->get().x;
|
struct NullZoneComponent : public Component {
|
||||||
const float dy = wellTransform->get().y - myTransform->get().y;
|
float width = 70.0f;
|
||||||
const float dist = std::sqrt(dx * dx + dy * dy);
|
void Setup() override {}
|
||||||
if (dist <= 0.0001f) {
|
void Update(float) override {}
|
||||||
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 {}
|
void Cleanup() override {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -111,22 +103,21 @@ struct ColliderComponent : public Component {
|
||||||
void Cleanup() override {}
|
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 {
|
struct ScrollableComponent : public Component {
|
||||||
float worldX = 0.0f;
|
float worldX = 0.0f;
|
||||||
void Setup() override {}
|
void Setup() override {}
|
||||||
void Update(float) override {}
|
void Update(float) override {
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto transform = entity->GetComponent<TransformComponent>();
|
||||||
|
if (!transform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform->get().x = worldX - context->scrollX;
|
||||||
|
}
|
||||||
void Cleanup() override {}
|
void Cleanup() override {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -149,10 +140,212 @@ struct ProjectionComponent : public Component {
|
||||||
void Cleanup() override {}
|
void Cleanup() override {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MeterComponent : public Component {
|
struct CollectibleComponent : public Component {
|
||||||
float value = 60.0f;
|
bool collected = false;
|
||||||
void Setup() override {}
|
void Setup() override {}
|
||||||
void Update(float) override {}
|
void Update(float) override {
|
||||||
|
if (collected || !context || !context->probeEntity || entity == context->probeEntity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto selfTransform = entity->GetComponent<TransformComponent>();
|
||||||
|
auto selfCollider = entity->GetComponent<ColliderComponent>();
|
||||||
|
auto probeTransform = context->probeEntity->GetComponent<TransformComponent>();
|
||||||
|
auto probeCollider = context->probeEntity->GetComponent<ColliderComponent>();
|
||||||
|
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<MeterComponent>();
|
||||||
|
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<TransformComponent>();
|
||||||
|
auto selfCollider = entity->GetComponent<ColliderComponent>();
|
||||||
|
auto probeTransform = context->probeEntity->GetComponent<TransformComponent>();
|
||||||
|
auto probeCollider = context->probeEntity->GetComponent<ColliderComponent>();
|
||||||
|
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<TransformComponent>();
|
||||||
|
auto physics = entity->GetComponent<PhysicsComponent>();
|
||||||
|
auto wellTransform = well->GetComponent<TransformComponent>();
|
||||||
|
auto wellGravity = well->GetComponent<GravityWellComponent>();
|
||||||
|
if (!myTransform || !physics || !wellTransform || !wellGravity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MeterComponent *meterPtr = nullptr;
|
||||||
|
if (context->hudEntity) {
|
||||||
|
auto meter = context->hudEntity->GetComponent<MeterComponent>();
|
||||||
|
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<NullZoneComponent>();
|
||||||
|
auto zoneTransform = other->GetComponent<TransformComponent>();
|
||||||
|
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<TransformComponent>();
|
||||||
|
auto physics = entity->GetComponent<PhysicsComponent>();
|
||||||
|
if (!transform || !physics) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transform->get().y < -20.0f ||
|
||||||
|
transform->get().y > static_cast<float>(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<MeterComponent>();
|
||||||
|
if (meter) {
|
||||||
|
meter->get().value = 60.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->entities) {
|
||||||
|
for (auto &other : *context->entities) {
|
||||||
|
if (!other) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto collectible = other->GetComponent<CollectibleComponent>();
|
||||||
|
if (collectible) {
|
||||||
|
collectible->get().collected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context->resetRequested = false;
|
||||||
|
}
|
||||||
void Cleanup() override {}
|
void Cleanup() override {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -163,21 +356,14 @@ struct HudComponent : public Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RenderComponent : public Component {
|
struct RenderComponent : public Component {
|
||||||
virtual void Draw() {}
|
std::function<void()> draw;
|
||||||
void Setup() override {}
|
|
||||||
void Update(float) override {}
|
|
||||||
void Cleanup() override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NullZoneComponent : public Component {
|
virtual void Draw() {
|
||||||
float width = 70.0f;
|
if (draw) {
|
||||||
void Setup() override {}
|
draw();
|
||||||
void Update(float) override {}
|
}
|
||||||
void Cleanup() override {}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
struct CollectibleComponent : public Component {
|
|
||||||
bool collected = false;
|
|
||||||
void Setup() override {}
|
void Setup() override {}
|
||||||
void Update(float) override {}
|
void Update(float) override {}
|
||||||
void Cleanup() override {}
|
void Cleanup() override {}
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,25 @@ std::shared_ptr<Entity> CreateProbe() {
|
||||||
physics.vy = 0.0f;
|
physics.vy = 0.0f;
|
||||||
physics.speedCap = 8192.0f;
|
physics.speedCap = 8192.0f;
|
||||||
|
|
||||||
e->AddComponent<ColliderComponent>();
|
auto &collider = e->AddComponent<ColliderComponent>();
|
||||||
|
collider.radius = 7.0f;
|
||||||
|
|
||||||
|
auto &probeState = e->AddComponent<ProbeStateComponent>();
|
||||||
|
probeState.spawnX = 96.0f;
|
||||||
|
probeState.spawnY = 230.0f;
|
||||||
|
probeState.spawnVx = 165.0f;
|
||||||
|
probeState.spawnVy = 0.0f;
|
||||||
|
|
||||||
e->AddComponent<TrailComponent>();
|
e->AddComponent<TrailComponent>();
|
||||||
e->AddComponent<ProjectionComponent>();
|
e->AddComponent<ProjectionComponent>();
|
||||||
e->AddComponent<RenderComponent>();
|
auto &render = e->AddComponent<RenderComponent>();
|
||||||
|
render.draw = [e]() {
|
||||||
|
auto transform = e->GetComponent<TransformComponent>();
|
||||||
|
if (!transform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DrawCircleV({transform->get().x, transform->get().y}, 7.0f, Color{250, 244, 227, 255});
|
||||||
|
};
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,7 +50,15 @@ std::shared_ptr<Entity> CreateGravityWell() {
|
||||||
well.minDist = 28.0f;
|
well.minDist = 28.0f;
|
||||||
well.followLerp = 12.0f;
|
well.followLerp = 12.0f;
|
||||||
|
|
||||||
e->AddComponent<RenderComponent>();
|
auto &render = e->AddComponent<RenderComponent>();
|
||||||
|
render.draw = [e]() {
|
||||||
|
auto transform = e->GetComponent<TransformComponent>();
|
||||||
|
if (!transform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DrawCircleLines(static_cast<int>(transform->get().x), static_cast<int>(transform->get().y),
|
||||||
|
18.0f, Color{86, 197, 255, 255});
|
||||||
|
};
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,9 +69,18 @@ std::shared_ptr<Entity> CreateStar(float x, float y) {
|
||||||
t.y = y;
|
t.y = y;
|
||||||
auto &scrollable = e->AddComponent<ScrollableComponent>();
|
auto &scrollable = e->AddComponent<ScrollableComponent>();
|
||||||
scrollable.worldX = x;
|
scrollable.worldX = x;
|
||||||
e->AddComponent<ColliderComponent>();
|
auto &collider = e->AddComponent<ColliderComponent>();
|
||||||
|
collider.radius = 6.0f;
|
||||||
|
|
||||||
e->AddComponent<CollectibleComponent>();
|
e->AddComponent<CollectibleComponent>();
|
||||||
e->AddComponent<RenderComponent>();
|
auto &render = e->AddComponent<RenderComponent>();
|
||||||
|
render.draw = [e]() {
|
||||||
|
auto transform = e->GetComponent<TransformComponent>();
|
||||||
|
if (!transform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DrawCircleV({transform->get().x, transform->get().y}, 6.0f, Color{255, 223, 86, 255});
|
||||||
|
};
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,8 +91,19 @@ std::shared_ptr<Entity> CreateAsteroid(float x, float y) {
|
||||||
t.y = y;
|
t.y = y;
|
||||||
auto &scrollable = e->AddComponent<ScrollableComponent>();
|
auto &scrollable = e->AddComponent<ScrollableComponent>();
|
||||||
scrollable.worldX = x;
|
scrollable.worldX = x;
|
||||||
e->AddComponent<ColliderComponent>();
|
auto &collider = e->AddComponent<ColliderComponent>();
|
||||||
e->AddComponent<RenderComponent>();
|
collider.radius = 13.0f;
|
||||||
|
|
||||||
|
e->AddComponent<HazardComponent>();
|
||||||
|
|
||||||
|
auto &render = e->AddComponent<RenderComponent>();
|
||||||
|
render.draw = [e]() {
|
||||||
|
auto transform = e->GetComponent<TransformComponent>();
|
||||||
|
if (!transform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DrawCircleV({transform->get().x, transform->get().y}, 13.0f, Color{116, 126, 142, 255});
|
||||||
|
};
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,7 +116,17 @@ std::shared_ptr<Entity> CreateNullZone(float x, float width) {
|
||||||
|
|
||||||
auto &nullZone = e->AddComponent<NullZoneComponent>();
|
auto &nullZone = e->AddComponent<NullZoneComponent>();
|
||||||
nullZone.width = width;
|
nullZone.width = width;
|
||||||
e->AddComponent<RenderComponent>();
|
auto &render = e->AddComponent<RenderComponent>();
|
||||||
|
render.draw = [e]() {
|
||||||
|
auto transform = e->GetComponent<TransformComponent>();
|
||||||
|
auto zone = e->GetComponent<NullZoneComponent>();
|
||||||
|
if (!transform || !zone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawRectangle(static_cast<int>(transform->get().x), 0, static_cast<int>(zone->get().width),
|
||||||
|
GetScreenHeight(), Color{96, 64, 146, 80});
|
||||||
|
};
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,6 +141,20 @@ std::shared_ptr<Entity> CreateHUD() {
|
||||||
auto e = std::make_shared<Entity>();
|
auto e = std::make_shared<Entity>();
|
||||||
e->AddComponent<MeterComponent>();
|
e->AddComponent<MeterComponent>();
|
||||||
e->AddComponent<HudComponent>();
|
e->AddComponent<HudComponent>();
|
||||||
e->AddComponent<RenderComponent>();
|
auto &render = e->AddComponent<RenderComponent>();
|
||||||
|
render.draw = [e]() {
|
||||||
|
auto meter = e->GetComponent<MeterComponent>();
|
||||||
|
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<int>((w - 2) * t), h - 2, Color{60, 199, 178, 255});
|
||||||
|
};
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "GameContext.hpp"
|
||||||
|
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
@ -9,9 +11,17 @@
|
||||||
struct Entity;
|
struct Entity;
|
||||||
class Component;
|
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 {
|
class Component {
|
||||||
public:
|
public:
|
||||||
Entity *entity = nullptr;
|
Entity *entity = nullptr;
|
||||||
|
GameContext *context = nullptr;
|
||||||
|
|
||||||
virtual void Setup() = 0;
|
virtual void Setup() = 0;
|
||||||
virtual void Update(float dt) = 0;
|
virtual void Update(float dt) = 0;
|
||||||
|
|
@ -22,14 +32,45 @@ class Component {
|
||||||
|
|
||||||
struct Entity {
|
struct Entity {
|
||||||
std::vector<std::shared_ptr<Component>> components;
|
std::vector<std::shared_ptr<Component>> 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 <std::derived_from<Component> T> T &AddComponent() {
|
template <std::derived_from<Component> T> T &AddComponent() {
|
||||||
auto &ptr = components.emplace_back(std::make_shared<T>());
|
auto &ptr = components.emplace_back(std::make_shared<T>());
|
||||||
ptr->entity = this;
|
ptr->entity = this;
|
||||||
|
ptr->context = context;
|
||||||
ptr->Setup();
|
ptr->Setup();
|
||||||
return static_cast<T &>(*ptr);
|
return static_cast<T &>(*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 <std::derived_from<Component> T>
|
template <std::derived_from<Component> T>
|
||||||
std::optional<std::reference_wrapper<T>> GetComponent() const {
|
std::optional<std::reference_wrapper<T>> GetComponent() const {
|
||||||
for (auto &c : components) {
|
for (auto &c : components) {
|
||||||
|
|
@ -41,6 +82,12 @@ struct Entity {
|
||||||
return {};
|
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) {
|
void Update(float dt) {
|
||||||
for (auto &c : components) {
|
for (auto &c : components) {
|
||||||
c->Update(dt);
|
c->Update(dt);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<std::shared_ptr<Entity>> *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;
|
||||||
|
};
|
||||||
|
|
@ -2,47 +2,26 @@
|
||||||
|
|
||||||
#include "Components.hpp"
|
#include "Components.hpp"
|
||||||
#include "Entity.hpp"
|
#include "Entity.hpp"
|
||||||
|
#include "GameContext.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
void UpdateAllSystems(std::vector<std::shared_ptr<Entity>> &entities, float deltaTime) {
|
void UpdateAllSystems(std::vector<std::shared_ptr<Entity>> &entities, GameContext &context,
|
||||||
std::shared_ptr<Entity> worldEntity;
|
float deltaTime) {
|
||||||
std::shared_ptr<Entity> wellEntity;
|
context.entities = &entities;
|
||||||
float scrollX = 0.0f;
|
context.worldEntity = (entities.size() > 0 && entities[0]) ? entities[0].get() : nullptr;
|
||||||
|
context.wellEntity = (entities.size() > 1 && entities[1]) ? entities[1].get() : nullptr;
|
||||||
if (!entities.empty()) {
|
context.probeEntity = (entities.size() > 2 && entities[2]) ? entities[2].get() : nullptr;
|
||||||
worldEntity = entities[0];
|
context.hudEntity = (!entities.empty() && entities.back()) ? entities.back().get() : nullptr;
|
||||||
}
|
|
||||||
if (entities.size() > 1) {
|
|
||||||
wellEntity = entities[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (worldEntity) {
|
|
||||||
worldEntity->Update(deltaTime);
|
|
||||||
auto scroll = worldEntity->GetComponent<ScrollComponent>();
|
|
||||||
if (scroll) {
|
|
||||||
scrollX = scroll->get().scrollX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wellEntity) {
|
|
||||||
wellEntity->Update(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto &entity : entities) {
|
for (auto &entity : entities) {
|
||||||
if (entity == worldEntity || entity == wellEntity || !entity) {
|
if (!entity) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto receiver = entity->GetComponent<GravityReceiverComponent>();
|
if (entity->context != &context) {
|
||||||
if (receiver) {
|
entity->SetContext(&context);
|
||||||
receiver->get().well = wellEntity.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto transform = entity->GetComponent<TransformComponent>();
|
|
||||||
auto scrollable = entity->GetComponent<ScrollableComponent>();
|
|
||||||
if (transform && scrollable) {
|
|
||||||
transform->get().x = scrollable->get().worldX - scrollX;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entity->Update(deltaTime);
|
entity->Update(deltaTime);
|
||||||
|
|
|
||||||
40
as6/main.cpp
40
as6/main.cpp
|
|
@ -2,6 +2,7 @@
|
||||||
#include "Draw.hpp"
|
#include "Draw.hpp"
|
||||||
#include "Entities.hpp"
|
#include "Entities.hpp"
|
||||||
#include "Entity.hpp"
|
#include "Entity.hpp"
|
||||||
|
#include "GameContext.hpp"
|
||||||
#include "Systems.hpp"
|
#include "Systems.hpp"
|
||||||
|
|
||||||
#include "raylib-cpp.hpp"
|
#include "raylib-cpp.hpp"
|
||||||
|
|
@ -14,6 +15,8 @@ int main() {
|
||||||
std::vector<std::shared_ptr<Entity>> entities;
|
std::vector<std::shared_ptr<Entity>> entities;
|
||||||
entities.reserve(20);
|
entities.reserve(20);
|
||||||
|
|
||||||
|
GameContext context;
|
||||||
|
|
||||||
entities.push_back(CreateWorld());
|
entities.push_back(CreateWorld());
|
||||||
entities.push_back(CreateGravityWell());
|
entities.push_back(CreateGravityWell());
|
||||||
entities.push_back(CreateProbe());
|
entities.push_back(CreateProbe());
|
||||||
|
|
@ -25,15 +28,11 @@ int main() {
|
||||||
while (!window.ShouldClose()) {
|
while (!window.ShouldClose()) {
|
||||||
float dt = window.GetFrameTime();
|
float dt = window.GetFrameTime();
|
||||||
|
|
||||||
UpdateAllSystems(entities, dt);
|
UpdateAllSystems(entities, context, dt);
|
||||||
|
|
||||||
auto worldScroll = entities[0]->GetComponent<ScrollComponent>();
|
auto worldScroll = entities[0]->GetComponent<ScrollComponent>();
|
||||||
auto wellTransform = entities[1]->GetComponent<TransformComponent>();
|
|
||||||
auto probeTransform = entities[2]->GetComponent<TransformComponent>();
|
auto probeTransform = entities[2]->GetComponent<TransformComponent>();
|
||||||
auto starTransform = entities[3]->GetComponent<TransformComponent>();
|
auto hudMeter = entities.back()->GetComponent<MeterComponent>();
|
||||||
auto asteroidTransform = entities[4]->GetComponent<TransformComponent>();
|
|
||||||
auto nullTransform = entities[5]->GetComponent<TransformComponent>();
|
|
||||||
auto nullZone = entities[5]->GetComponent<NullZoneComponent>();
|
|
||||||
|
|
||||||
window.BeginDrawing();
|
window.BeginDrawing();
|
||||||
window.ClearBackground(raylib::Color(11, 15, 26, 255));
|
window.ClearBackground(raylib::Color(11, 15, 26, 255));
|
||||||
|
|
@ -50,32 +49,29 @@ int main() {
|
||||||
|
|
||||||
if (probeTransform) {
|
if (probeTransform) {
|
||||||
const auto &probe = probeTransform->get();
|
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::DrawText(TextFormat("probe: (%.1f, %.1f)", probe.x, probe.y), 14, 60, 16,
|
||||||
raylib::Color(235, 215, 125, 255));
|
raylib::Color(235, 215, 125, 255));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wellTransform) {
|
if (hudMeter) {
|
||||||
const auto &well = wellTransform->get();
|
raylib::DrawText(TextFormat("meter: %.1f", hudMeter->get().value), 14, 80, 16,
|
||||||
::DrawCircleLines(static_cast<int>(well.x), static_cast<int>(well.y), 18.0f,
|
raylib::Color(120, 210, 190, 255));
|
||||||
raylib::Color(86, 197, 255, 255));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (starTransform) {
|
for (auto &entity : entities) {
|
||||||
const auto &star = starTransform->get();
|
if (!entity) {
|
||||||
::DrawCircleV({star.x, star.y}, 6.0f, raylib::Color(255, 223, 86, 255));
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asteroidTransform) {
|
auto collectible = entity->GetComponent<CollectibleComponent>();
|
||||||
const auto &asteroid = asteroidTransform->get();
|
if (collectible && collectible->get().collected) {
|
||||||
::DrawCircleV({asteroid.x, asteroid.y}, 13.0f, raylib::Color(116, 126, 142, 255));
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nullTransform && nullZone) {
|
auto render = entity->GetComponent<RenderComponent>();
|
||||||
const auto &zone = nullTransform->get();
|
if (render) {
|
||||||
const int width = static_cast<int>(nullZone->get().width);
|
render->get().Draw();
|
||||||
::DrawRectangle(static_cast<int>(zone.x), 0, width, GetScreenHeight(),
|
}
|
||||||
raylib::Color(96, 64, 146, 80));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.EndDrawing();
|
window.EndDrawing();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue