Refactor components and scenes into header/impl files
parent
728f84325c
commit
4911bfe8d5
|
|
@ -12,7 +12,33 @@ add_subdirectory(../raylib-cpp raylib)
|
||||||
|
|
||||||
include(../assets/includeable.cmake)
|
include(../assets/includeable.cmake)
|
||||||
|
|
||||||
add_executable(as6-gravity-surfing main.cpp)
|
add_executable(as6-gravity-surfing
|
||||||
|
main.cpp
|
||||||
|
Draw.cpp
|
||||||
|
Entities.cpp
|
||||||
|
Systems.cpp
|
||||||
|
components/TransformComponent.cpp
|
||||||
|
components/PhysicsComponent.cpp
|
||||||
|
components/GravityWellComponent.cpp
|
||||||
|
components/ScrollComponent.cpp
|
||||||
|
components/MeterComponent.cpp
|
||||||
|
components/NullZoneComponent.cpp
|
||||||
|
components/ColliderComponent.cpp
|
||||||
|
components/ScrollableComponent.cpp
|
||||||
|
components/SpawnComponent.cpp
|
||||||
|
components/TrailComponent.cpp
|
||||||
|
components/ProjectionComponent.cpp
|
||||||
|
components/LifetimeComponent.cpp
|
||||||
|
components/CollectibleComponent.cpp
|
||||||
|
components/HazardComponent.cpp
|
||||||
|
components/GravityReceiverComponent.cpp
|
||||||
|
components/ProbeStateComponent.cpp
|
||||||
|
components/HudComponent.cpp
|
||||||
|
components/RenderComponent.cpp
|
||||||
|
scene/StartMenuScene.cpp
|
||||||
|
scene/GameplayScene.cpp
|
||||||
|
scene/DeathScene.cpp
|
||||||
|
)
|
||||||
target_link_libraries(as6-gravity-surfing PUBLIC raylib raylib_cpp buffered-raylib)
|
target_link_libraries(as6-gravity-surfing PUBLIC raylib raylib_cpp buffered-raylib)
|
||||||
|
|
||||||
target_include_directories(as6-gravity-surfing PRIVATE
|
target_include_directories(as6-gravity-surfing PRIVATE
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GameContext.hpp"
|
||||||
|
|
||||||
|
struct Entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base interface that every concrete gameplay component derives from.
|
||||||
|
*
|
||||||
|
* Each component is attached to one entity and receives the shared scene
|
||||||
|
* `GameContext` pointer so it can read global gameplay state.
|
||||||
|
*/
|
||||||
|
class Component {
|
||||||
|
public:
|
||||||
|
Entity *entity = nullptr;
|
||||||
|
GameContext *context = nullptr;
|
||||||
|
|
||||||
|
virtual void Setup() = 0;
|
||||||
|
virtual void Update(float dt) = 0;
|
||||||
|
virtual void Cleanup() = 0;
|
||||||
|
|
||||||
|
virtual ~Component() = default;
|
||||||
|
};
|
||||||
|
|
@ -1,401 +1,20 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Entity.hpp"
|
#include "components/CollectibleComponent.hpp"
|
||||||
#include "GameContext.hpp"
|
#include "components/ColliderComponent.hpp"
|
||||||
|
#include "components/GravityReceiverComponent.hpp"
|
||||||
#include "raylib.h"
|
#include "components/GravityWellComponent.hpp"
|
||||||
|
#include "components/HazardComponent.hpp"
|
||||||
#include <algorithm>
|
#include "components/HudComponent.hpp"
|
||||||
#include <cmath>
|
#include "components/LifetimeComponent.hpp"
|
||||||
#include <functional>
|
#include "components/MeterComponent.hpp"
|
||||||
#include <vector>
|
#include "components/NullZoneComponent.hpp"
|
||||||
|
#include "components/PhysicsComponent.hpp"
|
||||||
struct TransformComponent : public Component {
|
#include "components/ProbeStateComponent.hpp"
|
||||||
float x = 0.0f;
|
#include "components/ProjectionComponent.hpp"
|
||||||
float y = 0.0f;
|
#include "components/RenderComponent.hpp"
|
||||||
void Setup() override {}
|
#include "components/ScrollComponent.hpp"
|
||||||
void Update(float) override {}
|
#include "components/ScrollableComponent.hpp"
|
||||||
void Cleanup() override {}
|
#include "components/SpawnComponent.hpp"
|
||||||
};
|
#include "components/TrailComponent.hpp"
|
||||||
|
#include "components/TransformComponent.hpp"
|
||||||
struct PhysicsComponent : public Component {
|
|
||||||
float vx = 0.0f;
|
|
||||||
float vy = 0.0f;
|
|
||||||
float speedCap = 400.0f;
|
|
||||||
void Setup() override {}
|
|
||||||
void Update(float dt) override {
|
|
||||||
auto transform = entity->GetComponent<TransformComponent>();
|
|
||||||
if (!transform) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float speed = std::sqrt(vx * vx + vy * vy);
|
|
||||||
if (speed > speedCap && speed > 0.0f) {
|
|
||||||
const float scale = speedCap / speed;
|
|
||||||
vx *= scale;
|
|
||||||
vy *= scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
transform->get().x += vx * dt;
|
|
||||||
transform->get().y += vy * dt;
|
|
||||||
}
|
|
||||||
void Cleanup() override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GravityWellComponent : public Component {
|
|
||||||
float mass = 150000.0f;
|
|
||||||
float minDist = 30.0f;
|
|
||||||
bool active = false;
|
|
||||||
float followLerp = 12.0f;
|
|
||||||
void Setup() override {}
|
|
||||||
void Update(float dt) override {
|
|
||||||
auto transform = entity->GetComponent<TransformComponent>();
|
|
||||||
if (!transform) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
active = IsMouseButtonDown(MOUSE_BUTTON_LEFT);
|
|
||||||
|
|
||||||
const Vector2 mouse = GetMousePosition();
|
|
||||||
auto &t = transform->get();
|
|
||||||
const float blend = std::clamp(followLerp * dt, 0.0f, 1.0f);
|
|
||||||
t.x += (mouse.x - t.x) * blend;
|
|
||||||
t.y += (mouse.y - t.y) * blend;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
if (context) {
|
|
||||||
context->scrollX = scrollX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 SetValue(float newValue) {
|
|
||||||
const float oldValue = value;
|
|
||||||
value = newValue;
|
|
||||||
if (context && oldValue != value) {
|
|
||||||
context->EmitMeterChanged(oldValue, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddValue(float delta) { SetValue(std::clamp(value + delta, 0.0f, maxValue)); }
|
|
||||||
|
|
||||||
void Drain(float amount) { AddValue(-amount); }
|
|
||||||
|
|
||||||
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;
|
|
||||||
bool monitoring = false;
|
|
||||||
bool monitorable = true;
|
|
||||||
std::vector<std::function<void(Entity &)>> onCollision;
|
|
||||||
void Setup() override {}
|
|
||||||
|
|
||||||
void EmitCollision(Entity &other) {
|
|
||||||
for (auto &callback : onCollision) {
|
|
||||||
callback(other);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddCollisionListener(std::function<void(Entity &)> callback) {
|
|
||||||
onCollision.push_back(std::move(callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Update(float) override {
|
|
||||||
if (!monitoring || !context || !context->entities || entity->queuedForFree) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto selfTransform = entity->GetComponent<TransformComponent>();
|
|
||||||
if (!selfTransform) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto &other : *context->entities) {
|
|
||||||
if (!other || other.get() == entity || other->queuedForFree) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto otherCollider = other->GetComponent<ColliderComponent>();
|
|
||||||
auto otherTransform = other->GetComponent<TransformComponent>();
|
|
||||||
if (!otherCollider || !otherTransform || !otherCollider->get().monitorable) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float dx = selfTransform->get().x - otherTransform->get().x;
|
|
||||||
const float dy = selfTransform->get().y - otherTransform->get().y;
|
|
||||||
const float r = radius + otherCollider->get().radius;
|
|
||||||
if ((dx * dx + dy * dy) <= (r * r)) {
|
|
||||||
EmitCollision(*other);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void Cleanup() override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ScrollableComponent : public Component {
|
|
||||||
float worldX = 0.0f;
|
|
||||||
void Setup() 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 {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SpawnComponent : public Component {
|
|
||||||
float cursorWX = 0.0f;
|
|
||||||
void Setup() override {}
|
|
||||||
void Update(float) override {}
|
|
||||||
void Cleanup() override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TrailComponent : public Component {
|
|
||||||
void Setup() override {}
|
|
||||||
void Update(float) override {}
|
|
||||||
void Cleanup() override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ProjectionComponent : public Component {
|
|
||||||
void Setup() override {}
|
|
||||||
void Update(float) override {}
|
|
||||||
void Cleanup() override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LifetimeComponent : public Component {
|
|
||||||
float remaining = 0.0f;
|
|
||||||
void Setup() override {}
|
|
||||||
void Update(float dt) override {
|
|
||||||
remaining -= dt;
|
|
||||||
if (remaining <= 0.0f) {
|
|
||||||
entity->QueueFree();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void Cleanup() override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CollectibleComponent : public Component {
|
|
||||||
void Setup() override {
|
|
||||||
auto selfCollider = entity->GetComponent<ColliderComponent>();
|
|
||||||
if (!selfCollider) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
selfCollider->get().monitoring = true;
|
|
||||||
|
|
||||||
selfCollider->get().AddCollisionListener([this](Entity &other) {
|
|
||||||
if (entity->queuedForFree || !context || !context->probeEntity ||
|
|
||||||
&other != context->probeEntity) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
context->EmitCollectiblePicked(*entity);
|
|
||||||
entity->QueueFree();
|
|
||||||
|
|
||||||
if (!context->hudEntity) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto meter = context->hudEntity->GetComponent<MeterComponent>();
|
|
||||||
if (!meter) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
meter->get().AddValue(meter->get().gainPerStar);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Update(float) override {}
|
|
||||||
|
|
||||||
void Cleanup() override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HazardComponent : public Component {
|
|
||||||
void Setup() override {
|
|
||||||
auto selfCollider = entity->GetComponent<ColliderComponent>();
|
|
||||||
if (!selfCollider) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
selfCollider->get().monitoring = true;
|
|
||||||
|
|
||||||
selfCollider->get().AddCollisionListener([this](Entity &other) {
|
|
||||||
if (!context || !context->probeEntity || &other != context->probeEntity) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context->onPlayerDeath) {
|
|
||||||
context->onPlayerDeath();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
void Update(float) override {}
|
|
||||||
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->Drain(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>();
|
|
||||||
if (!transform) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transform->get().y < -20.0f ||
|
|
||||||
transform->get().y > static_cast<float>(GetScreenHeight() + 20) ||
|
|
||||||
transform->get().x < -20.0f) {
|
|
||||||
if (context->onPlayerDeath) {
|
|
||||||
context->onPlayerDeath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void Cleanup() override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HudComponent : public Component {
|
|
||||||
void Setup() override {}
|
|
||||||
void Update(float) override {}
|
|
||||||
void Cleanup() override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderComponent : public Component {
|
|
||||||
std::function<void()> draw;
|
|
||||||
|
|
||||||
virtual void Draw() {
|
|
||||||
if (draw) {
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Setup() override {}
|
|
||||||
void Update(float) override {}
|
|
||||||
void Cleanup() override {}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#include "Draw.hpp"
|
||||||
|
|
||||||
|
void DrawSceneOutline() {
|
||||||
|
DrawRectangleLines(0, 0, GetScreenWidth(), GetScreenHeight(), raylib::Color::DarkGray());
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,4 @@
|
||||||
|
|
||||||
#include "raylib-cpp.hpp"
|
#include "raylib-cpp.hpp"
|
||||||
|
|
||||||
// placeholder for draw helpers
|
void DrawSceneOutline();
|
||||||
void DrawSceneOutline() {
|
|
||||||
DrawRectangleLines(0, 0, GetScreenWidth(), GetScreenHeight(), raylib::Color::DarkGray());
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
#include "Entities.hpp"
|
||||||
|
|
||||||
|
#include "Components.hpp"
|
||||||
|
|
||||||
|
#include "raylib.h"
|
||||||
|
|
||||||
|
std::shared_ptr<Entity> CreateProbe() {
|
||||||
|
auto e = std::make_shared<Entity>();
|
||||||
|
auto &transform = e->AddComponent<TransformComponent>();
|
||||||
|
transform.x = 96.0f;
|
||||||
|
transform.y = 230.0f;
|
||||||
|
|
||||||
|
e->AddComponent<GravityReceiverComponent>();
|
||||||
|
|
||||||
|
auto &physics = e->AddComponent<PhysicsComponent>();
|
||||||
|
physics.vx = 108.0f;
|
||||||
|
physics.vy = 0.0f;
|
||||||
|
physics.speedCap = 8192.0f;
|
||||||
|
|
||||||
|
auto &collider = e->AddComponent<ColliderComponent>();
|
||||||
|
collider.radius = 7.0f;
|
||||||
|
collider.monitorable = true;
|
||||||
|
|
||||||
|
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<ProjectionComponent>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Entity> CreateGravityWell() {
|
||||||
|
auto e = std::make_shared<Entity>();
|
||||||
|
auto &transform = e->AddComponent<TransformComponent>();
|
||||||
|
transform.x = 96.0f;
|
||||||
|
transform.y = 230.0f;
|
||||||
|
|
||||||
|
auto &well = e->AddComponent<GravityWellComponent>();
|
||||||
|
well.mass = static_cast<float>(1 << 22);
|
||||||
|
well.minDist = 28.0f;
|
||||||
|
well.followLerp = 12.0f;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Entity> CreateStar(float x, float y) {
|
||||||
|
auto e = std::make_shared<Entity>();
|
||||||
|
auto &t = e->AddComponent<TransformComponent>();
|
||||||
|
t.x = x;
|
||||||
|
t.y = y;
|
||||||
|
auto &scrollable = e->AddComponent<ScrollableComponent>();
|
||||||
|
scrollable.worldX = x;
|
||||||
|
auto &collider = e->AddComponent<ColliderComponent>();
|
||||||
|
collider.radius = 6.0f;
|
||||||
|
|
||||||
|
e->AddComponent<CollectibleComponent>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Entity> CreateAsteroid(float x, float y) {
|
||||||
|
auto e = std::make_shared<Entity>();
|
||||||
|
auto &t = e->AddComponent<TransformComponent>();
|
||||||
|
t.x = x;
|
||||||
|
t.y = y;
|
||||||
|
auto &scrollable = e->AddComponent<ScrollableComponent>();
|
||||||
|
scrollable.worldX = x;
|
||||||
|
auto &collider = e->AddComponent<ColliderComponent>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Entity> CreateDebris(float x, float y, float vx, float vy, float ttl) {
|
||||||
|
auto e = std::make_shared<Entity>();
|
||||||
|
auto &t = e->AddComponent<TransformComponent>();
|
||||||
|
t.x = x;
|
||||||
|
t.y = y;
|
||||||
|
|
||||||
|
auto &physics = e->AddComponent<PhysicsComponent>();
|
||||||
|
physics.vx = vx;
|
||||||
|
physics.vy = vy;
|
||||||
|
physics.speedCap = 260.0f;
|
||||||
|
|
||||||
|
auto &lifetime = e->AddComponent<LifetimeComponent>();
|
||||||
|
lifetime.remaining = ttl;
|
||||||
|
|
||||||
|
auto &render = e->AddComponent<RenderComponent>();
|
||||||
|
render.draw = [e]() {
|
||||||
|
auto transform = e->GetComponent<TransformComponent>();
|
||||||
|
if (!transform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DrawCircleV({transform->get().x, transform->get().y}, 3.0f, Color{245, 196, 104, 220});
|
||||||
|
};
|
||||||
|
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Entity> CreateNullZone(float x, float width) {
|
||||||
|
auto e = std::make_shared<Entity>();
|
||||||
|
auto &t = e->AddComponent<TransformComponent>();
|
||||||
|
t.x = x;
|
||||||
|
auto &scrollable = e->AddComponent<ScrollableComponent>();
|
||||||
|
scrollable.worldX = x;
|
||||||
|
|
||||||
|
auto &nullZone = e->AddComponent<NullZoneComponent>();
|
||||||
|
nullZone.width = width;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Entity> CreateWorld() {
|
||||||
|
auto e = std::make_shared<Entity>();
|
||||||
|
e->AddComponent<ScrollComponent>();
|
||||||
|
e->AddComponent<SpawnComponent>();
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Entity> CreateHUD() {
|
||||||
|
auto e = std::make_shared<Entity>();
|
||||||
|
e->AddComponent<MeterComponent>();
|
||||||
|
e->AddComponent<HudComponent>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
191
as6/Entities.hpp
191
as6/Entities.hpp
|
|
@ -1,187 +1,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Components.hpp"
|
|
||||||
#include "Entity.hpp"
|
#include "Entity.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
std::shared_ptr<Entity> CreateProbe() {
|
std::shared_ptr<Entity> CreateProbe();
|
||||||
auto e = std::make_shared<Entity>();
|
std::shared_ptr<Entity> CreateGravityWell();
|
||||||
auto &transform = e->AddComponent<TransformComponent>();
|
std::shared_ptr<Entity> CreateStar(float x, float y);
|
||||||
transform.x = 96.0f;
|
std::shared_ptr<Entity> CreateAsteroid(float x, float y);
|
||||||
transform.y = 230.0f;
|
std::shared_ptr<Entity> CreateDebris(float x, float y, float vx, float vy, float ttl = 1.25f);
|
||||||
|
std::shared_ptr<Entity> CreateNullZone(float x, float width);
|
||||||
e->AddComponent<GravityReceiverComponent>();
|
std::shared_ptr<Entity> CreateWorld();
|
||||||
|
std::shared_ptr<Entity> CreateHUD();
|
||||||
auto &physics = e->AddComponent<PhysicsComponent>();
|
|
||||||
physics.vx = 108.0f;
|
|
||||||
physics.vy = 0.0f;
|
|
||||||
physics.speedCap = 8192.0f;
|
|
||||||
|
|
||||||
auto &collider = e->AddComponent<ColliderComponent>();
|
|
||||||
collider.radius = 7.0f;
|
|
||||||
collider.monitorable = true;
|
|
||||||
|
|
||||||
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<ProjectionComponent>();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Entity> CreateGravityWell() {
|
|
||||||
auto e = std::make_shared<Entity>();
|
|
||||||
auto &transform = e->AddComponent<TransformComponent>();
|
|
||||||
transform.x = 96.0f;
|
|
||||||
transform.y = 230.0f;
|
|
||||||
|
|
||||||
auto &well = e->AddComponent<GravityWellComponent>();
|
|
||||||
well.mass = (float)(1 << 22);
|
|
||||||
well.minDist = 28.0f;
|
|
||||||
well.followLerp = 12.0f;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Entity> CreateStar(float x, float y) {
|
|
||||||
auto e = std::make_shared<Entity>();
|
|
||||||
auto &t = e->AddComponent<TransformComponent>();
|
|
||||||
t.x = x;
|
|
||||||
t.y = y;
|
|
||||||
auto &scrollable = e->AddComponent<ScrollableComponent>();
|
|
||||||
scrollable.worldX = x;
|
|
||||||
auto &collider = e->AddComponent<ColliderComponent>();
|
|
||||||
collider.radius = 6.0f;
|
|
||||||
|
|
||||||
e->AddComponent<CollectibleComponent>();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Entity> CreateAsteroid(float x, float y) {
|
|
||||||
auto e = std::make_shared<Entity>();
|
|
||||||
auto &t = e->AddComponent<TransformComponent>();
|
|
||||||
t.x = x;
|
|
||||||
t.y = y;
|
|
||||||
auto &scrollable = e->AddComponent<ScrollableComponent>();
|
|
||||||
scrollable.worldX = x;
|
|
||||||
auto &collider = e->AddComponent<ColliderComponent>();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Entity> CreateDebris(float x, float y, float vx, float vy, float ttl = 1.25f) {
|
|
||||||
auto e = std::make_shared<Entity>();
|
|
||||||
auto &t = e->AddComponent<TransformComponent>();
|
|
||||||
t.x = x;
|
|
||||||
t.y = y;
|
|
||||||
|
|
||||||
auto &physics = e->AddComponent<PhysicsComponent>();
|
|
||||||
physics.vx = vx;
|
|
||||||
physics.vy = vy;
|
|
||||||
physics.speedCap = 260.0f;
|
|
||||||
|
|
||||||
auto &lifetime = e->AddComponent<LifetimeComponent>();
|
|
||||||
lifetime.remaining = ttl;
|
|
||||||
|
|
||||||
auto &render = e->AddComponent<RenderComponent>();
|
|
||||||
render.draw = [e]() {
|
|
||||||
auto transform = e->GetComponent<TransformComponent>();
|
|
||||||
if (!transform) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DrawCircleV({transform->get().x, transform->get().y}, 3.0f, Color{245, 196, 104, 220});
|
|
||||||
};
|
|
||||||
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Entity> CreateNullZone(float x, float width) {
|
|
||||||
auto e = std::make_shared<Entity>();
|
|
||||||
auto &t = e->AddComponent<TransformComponent>();
|
|
||||||
t.x = x;
|
|
||||||
auto &scrollable = e->AddComponent<ScrollableComponent>();
|
|
||||||
scrollable.worldX = x;
|
|
||||||
|
|
||||||
auto &nullZone = e->AddComponent<NullZoneComponent>();
|
|
||||||
nullZone.width = width;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Entity> CreateWorld() {
|
|
||||||
auto e = std::make_shared<Entity>();
|
|
||||||
e->AddComponent<ScrollComponent>();
|
|
||||||
e->AddComponent<SpawnComponent>();
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Entity> CreateHUD() {
|
|
||||||
auto e = std::make_shared<Entity>();
|
|
||||||
e->AddComponent<MeterComponent>();
|
|
||||||
e->AddComponent<HudComponent>();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "GameContext.hpp"
|
#include "Component.hpp"
|
||||||
|
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
@ -9,26 +9,6 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct Entity;
|
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;
|
|
||||||
virtual void Cleanup() = 0;
|
|
||||||
|
|
||||||
virtual ~Component() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Entity {
|
struct Entity {
|
||||||
std::vector<std::shared_ptr<Component>> components;
|
std::vector<std::shared_ptr<Component>> components;
|
||||||
|
|
|
||||||
|
|
@ -1,264 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Draw.hpp"
|
#include "scene/DeathScene.hpp"
|
||||||
#include "Entities.hpp"
|
#include "scene/GameplayScene.hpp"
|
||||||
#include "Systems.hpp"
|
#include "scene/Scene.hpp"
|
||||||
|
#include "scene/SceneManager.hpp"
|
||||||
#include "raylib.h"
|
#include "scene/StartMenuScene.hpp"
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class SceneManager;
|
|
||||||
|
|
||||||
class Scene {
|
|
||||||
public:
|
|
||||||
explicit Scene(SceneManager &owner) : manager(owner) {}
|
|
||||||
virtual ~Scene() = default;
|
|
||||||
|
|
||||||
virtual void Enter() {}
|
|
||||||
virtual void Exit() {}
|
|
||||||
virtual void Update(float dt) = 0;
|
|
||||||
virtual void Draw() = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
SceneManager &manager;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages the current active scene, using an object oriented state machine approach.
|
|
||||||
*/
|
|
||||||
class SceneManager {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Changes the current scene, invoking `Exit` on the old scene and `Enter`
|
|
||||||
* on the new scene. The new scene is constructed in-place with the provided arguments.
|
|
||||||
*
|
|
||||||
* @tparam T The type of the new scene, must derive from `Scene`.
|
|
||||||
* @param args Arguments forwarded to the constructor of the new scene.
|
|
||||||
*/
|
|
||||||
template <typename T, typename... Args> void ChangeScene(Args &&...args);
|
|
||||||
template <typename T, typename... Args> void QueueSceneChange(Args &&...args);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Changes to the provided next scene, invoking `Exit` on the old scene and
|
|
||||||
* `Enter` on the new scene.
|
|
||||||
*
|
|
||||||
* @param nextScene The new scene to switch to.
|
|
||||||
*/
|
|
||||||
void ChangeScene(std::unique_ptr<Scene> nextScene);
|
|
||||||
void QueueSceneChange(std::unique_ptr<Scene> nextScene);
|
|
||||||
void Update(float dt);
|
|
||||||
void Draw();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// use unique_ptr to enforce single ownership and ensure proper cleanup on
|
|
||||||
// scene change
|
|
||||||
std::unique_ptr<Scene> current;
|
|
||||||
std::unique_ptr<Scene> queued;
|
|
||||||
};
|
|
||||||
|
|
||||||
class StartMenuScene;
|
|
||||||
class GameplayScene;
|
|
||||||
class DeathScene;
|
|
||||||
|
|
||||||
class StartMenuScene : public Scene {
|
|
||||||
public:
|
|
||||||
explicit StartMenuScene(SceneManager &owner) : Scene(owner) {}
|
|
||||||
void Update(float dt) override;
|
|
||||||
void Draw() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GameplayScene : public Scene {
|
|
||||||
public:
|
|
||||||
explicit GameplayScene(SceneManager &owner) : Scene(owner) {}
|
|
||||||
void Enter() override;
|
|
||||||
void Update(float dt) override;
|
|
||||||
void Draw() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<std::shared_ptr<Entity>> entities;
|
|
||||||
GameContext context;
|
|
||||||
bool wantsDeathScene = false;
|
|
||||||
int collectedCount = 0;
|
|
||||||
float meterValue = 60.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DeathScene : public Scene {
|
|
||||||
public:
|
|
||||||
explicit DeathScene(SceneManager &owner) : Scene(owner) {}
|
|
||||||
void Update(float dt) override;
|
|
||||||
void Draw() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline void SceneManager::ChangeScene(std::unique_ptr<Scene> nextScene) {
|
|
||||||
if (current) {
|
|
||||||
current->Exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
current = std::move(nextScene);
|
|
||||||
if (current) {
|
|
||||||
current->Enter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void SceneManager::QueueSceneChange(std::unique_ptr<Scene> nextScene) {
|
|
||||||
queued = std::move(nextScene);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void SceneManager::Update(float dt) {
|
|
||||||
if (queued) {
|
|
||||||
ChangeScene(std::move(queued));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current) {
|
|
||||||
current->Update(dt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queued) {
|
|
||||||
ChangeScene(std::move(queued));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void SceneManager::Draw() {
|
|
||||||
if (current) {
|
|
||||||
current->Draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void StartMenuScene::Update(float) {
|
|
||||||
if (IsKeyPressed(KEY_ENTER) || IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
|
|
||||||
manager.QueueSceneChange<GameplayScene>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void StartMenuScene::Draw() {
|
|
||||||
DrawSceneOutline();
|
|
||||||
DrawText("Gravity Surfing", 24, 28, 36, Color{230, 238, 255, 255});
|
|
||||||
DrawText("Click or press Enter to start", 24, 78, 22, Color{140, 198, 220, 255});
|
|
||||||
DrawText("Left mouse controls gravity well", 24, 130, 18, Color{170, 185, 205, 255});
|
|
||||||
DrawText("Collect stars and dodge hazards", 24, 156, 18, Color{170, 185, 205, 255});
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void GameplayScene::Enter() {
|
|
||||||
entities.clear();
|
|
||||||
entities.reserve(20);
|
|
||||||
context = {};
|
|
||||||
wantsDeathScene = false;
|
|
||||||
collectedCount = 0;
|
|
||||||
meterValue = 60.0f;
|
|
||||||
|
|
||||||
context.onPlayerDeath = [this]() { wantsDeathScene = true; };
|
|
||||||
context.AddCollectiblePickedListener([this](Entity &collectible) {
|
|
||||||
++collectedCount;
|
|
||||||
|
|
||||||
auto transform = collectible.GetComponent<TransformComponent>();
|
|
||||||
if (!transform || !context.entities) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.entities->push_back(
|
|
||||||
CreateDebris(transform->get().x - 4.0f, transform->get().y, -22.0f, -36.0f));
|
|
||||||
context.entities->push_back(
|
|
||||||
CreateDebris(transform->get().x + 4.0f, transform->get().y, 24.0f, -28.0f));
|
|
||||||
});
|
|
||||||
context.AddMeterChangedListener([this](float, float newValue) { meterValue = newValue; });
|
|
||||||
|
|
||||||
entities.push_back(CreateWorld());
|
|
||||||
entities.push_back(CreateGravityWell());
|
|
||||||
entities.push_back(CreateProbe());
|
|
||||||
entities.push_back(CreateStar(900.0f, 120.0f));
|
|
||||||
entities.push_back(CreateAsteroid(1100.0f, 330.0f));
|
|
||||||
entities.push_back(CreateNullZone(1280.0f, 70.0f));
|
|
||||||
entities.push_back(CreateHUD());
|
|
||||||
|
|
||||||
if (auto meter = entities.back()->GetComponent<MeterComponent>()) {
|
|
||||||
meterValue = meter->get().value;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
entity->SetContext(&context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void GameplayScene::Update(float dt) {
|
|
||||||
UpdateAllSystems(entities, dt);
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (wantsDeathScene) {
|
|
||||||
manager.QueueSceneChange<DeathScene>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void GameplayScene::Draw() {
|
|
||||||
DrawSceneOutline();
|
|
||||||
DrawText("Gravity Surfing", 14, 12, 20, Color{230, 238, 255, 255});
|
|
||||||
|
|
||||||
auto worldScroll = entities[0]->GetComponent<ScrollComponent>();
|
|
||||||
auto probeTransform = entities[2]->GetComponent<TransformComponent>();
|
|
||||||
|
|
||||||
if (worldScroll) {
|
|
||||||
DrawText(TextFormat("scrollX: %.2f", worldScroll->get().scrollX), 14, 40, 16,
|
|
||||||
Color{125, 225, 205, 255});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (probeTransform) {
|
|
||||||
const auto &probe = probeTransform->get();
|
|
||||||
DrawText(TextFormat("probe: (%.1f, %.1f)", probe.x, probe.y), 14, 60, 16,
|
|
||||||
Color{235, 215, 125, 255});
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawText(TextFormat("meter: %.1f", meterValue), 14, 80, 16, Color{120, 210, 190, 255});
|
|
||||||
DrawText(TextFormat("stars: %i", collectedCount), 14, 100, 16, Color{255, 223, 86, 255});
|
|
||||||
|
|
||||||
for (auto &entity : entities) {
|
|
||||||
if (!entity || entity->queuedForFree) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto render = entity->GetComponent<RenderComponent>();
|
|
||||||
if (render) {
|
|
||||||
render->get().Draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void DeathScene::Update(float) {
|
|
||||||
if (IsKeyPressed(KEY_ENTER) || IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
|
|
||||||
manager.QueueSceneChange<GameplayScene>();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsKeyPressed(KEY_ESCAPE)) {
|
|
||||||
manager.QueueSceneChange<StartMenuScene>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void DeathScene::Draw() {
|
|
||||||
DrawSceneOutline();
|
|
||||||
DrawText("PROBE LOST", 24, 28, 36, Color{255, 208, 208, 255});
|
|
||||||
DrawText("Press Enter or click to restart", 24, 84, 22, Color{255, 170, 170, 255});
|
|
||||||
DrawText("Press Esc for menu", 24, 116, 20, Color{200, 188, 188, 255});
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T, typename... Args> inline void SceneManager::ChangeScene(Args &&...args) {
|
|
||||||
ChangeScene(std::make_unique<T>(*this, std::forward<Args>(args)...));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T, typename... Args> inline void SceneManager::QueueSceneChange(Args &&...args) {
|
|
||||||
QueueSceneChange(std::make_unique<T>(*this, std::forward<Args>(args)...));
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
#include "Systems.hpp"
|
||||||
|
|
||||||
|
void UpdateAllSystems(std::vector<std::shared_ptr<Entity>> &entities, float deltaTime) {
|
||||||
|
for (auto &entity : entities) {
|
||||||
|
if (!entity) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity->Update(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto remover = [](const std::shared_ptr<Entity> &entity) {
|
||||||
|
if (!entity) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity->queuedForFree) {
|
||||||
|
entity->Cleanup();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
entities.erase(std::remove_if(entities.begin(), entities.end(), remover), entities.end());
|
||||||
|
}
|
||||||
|
|
@ -6,27 +6,4 @@
|
||||||
#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, float deltaTime);
|
||||||
for (auto &entity : entities) {
|
|
||||||
if (!entity) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
entity->Update(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto remover = [](const std::shared_ptr<Entity> &entity) {
|
|
||||||
if (!entity) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity->queuedForFree) {
|
|
||||||
entity->Cleanup();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
entities.erase(std::remove_if(entities.begin(), entities.end(), remover), entities.end());
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
#include "components/CollectibleComponent.hpp"
|
||||||
|
|
||||||
|
#include "Entity.hpp"
|
||||||
|
#include "components/ColliderComponent.hpp"
|
||||||
|
#include "components/MeterComponent.hpp"
|
||||||
|
|
||||||
|
void CollectibleComponent::Setup() {
|
||||||
|
auto selfCollider = entity->GetComponent<ColliderComponent>();
|
||||||
|
if (!selfCollider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selfCollider->get().monitoring = true;
|
||||||
|
|
||||||
|
selfCollider->get().AddCollisionListener([this](Entity &other) {
|
||||||
|
if (entity->queuedForFree || !context || !context->probeEntity ||
|
||||||
|
&other != context->probeEntity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context->EmitCollectiblePicked(*entity);
|
||||||
|
entity->QueueFree();
|
||||||
|
|
||||||
|
if (!context->hudEntity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto meter = context->hudEntity->GetComponent<MeterComponent>();
|
||||||
|
if (!meter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
meter->get().AddValue(meter->get().gainPerStar);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectibleComponent::Update(float) {}
|
||||||
|
|
||||||
|
void CollectibleComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles collectible pickup by probe and applies meter gain.
|
||||||
|
*/
|
||||||
|
struct CollectibleComponent : public Component {
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
#include "components/ColliderComponent.hpp"
|
||||||
|
|
||||||
|
#include "Entity.hpp"
|
||||||
|
#include "components/TransformComponent.hpp"
|
||||||
|
|
||||||
|
void ColliderComponent::Setup() {}
|
||||||
|
|
||||||
|
void ColliderComponent::EmitCollision(Entity &other) {
|
||||||
|
for (auto &callback : onCollision) {
|
||||||
|
callback(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColliderComponent::AddCollisionListener(std::function<void(Entity &)> callback) {
|
||||||
|
onCollision.push_back(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColliderComponent::Update(float) {
|
||||||
|
if (!monitoring || !context || !context->entities || entity->queuedForFree) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto selfTransform = entity->GetComponent<TransformComponent>();
|
||||||
|
if (!selfTransform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &other : *context->entities) {
|
||||||
|
if (!other || other.get() == entity || other->queuedForFree) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto otherCollider = other->GetComponent<ColliderComponent>();
|
||||||
|
auto otherTransform = other->GetComponent<TransformComponent>();
|
||||||
|
if (!otherCollider || !otherTransform || !otherCollider->get().monitorable) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float dx = selfTransform->get().x - otherTransform->get().x;
|
||||||
|
const float dy = selfTransform->get().y - otherTransform->get().y;
|
||||||
|
const float r = radius + otherCollider->get().radius;
|
||||||
|
if ((dx * dx + dy * dy) <= (r * r)) {
|
||||||
|
EmitCollision(*other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColliderComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distance-based circle collider with event callbacks.
|
||||||
|
*
|
||||||
|
* `monitoring` enables active overlap checks, similar to Godot's Area2D monitoring.
|
||||||
|
* `monitorable` lets this collider be seen by monitors.
|
||||||
|
*/
|
||||||
|
struct ColliderComponent : public Component {
|
||||||
|
/**
|
||||||
|
* Radius of the circular collider, used for distance checks against other colliders.
|
||||||
|
*/
|
||||||
|
float radius = 8.0f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this collider actively checks for overlaps and emits collision events.
|
||||||
|
*/
|
||||||
|
bool monitoring = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this collider can be detected by other colliders that have monitoring enabled.
|
||||||
|
*/
|
||||||
|
bool monitorable = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks to invoke when a collision with another entity is detected. The callback
|
||||||
|
* receives the other entity involved in the collision.
|
||||||
|
*/
|
||||||
|
std::vector<std::function<void(Entity &)>> onCollision;
|
||||||
|
|
||||||
|
void Setup() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits a collision event to all registered listeners with the given other entity.
|
||||||
|
*
|
||||||
|
* @param other The other entity that this collider has collided with.
|
||||||
|
*/
|
||||||
|
void EmitCollision(Entity &other);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a collision listener callback to be invoked when a collision is detected.
|
||||||
|
*
|
||||||
|
* @param callback The callback function that takes the other entity as an argument.
|
||||||
|
*/
|
||||||
|
void AddCollisionListener(std::function<void(Entity &)> callback);
|
||||||
|
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
#include "components/GravityReceiverComponent.hpp"
|
||||||
|
|
||||||
|
#include "Entity.hpp"
|
||||||
|
#include "components/GravityWellComponent.hpp"
|
||||||
|
#include "components/MeterComponent.hpp"
|
||||||
|
#include "components/NullZoneComponent.hpp"
|
||||||
|
#include "components/PhysicsComponent.hpp"
|
||||||
|
#include "components/TransformComponent.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
void GravityReceiverComponent::Setup() {}
|
||||||
|
|
||||||
|
void GravityReceiverComponent::Update(float dt) {
|
||||||
|
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->Drain(meterPtr->drainRate * dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GravityReceiverComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies gravity well force to the probe and drains meter while active.
|
||||||
|
*/
|
||||||
|
struct GravityReceiverComponent : public Component {
|
||||||
|
/**
|
||||||
|
* The gravity well entity that this receiver is affected by. If null, the receiver will not
|
||||||
|
* apply any forces.
|
||||||
|
*/
|
||||||
|
Entity *well = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the probe is inside a void zone, which disables the well's gravity.
|
||||||
|
*/
|
||||||
|
bool inVoid = false;
|
||||||
|
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
#include "components/GravityWellComponent.hpp"
|
||||||
|
|
||||||
|
#include "Entity.hpp"
|
||||||
|
#include "components/TransformComponent.hpp"
|
||||||
|
|
||||||
|
#include "raylib.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
void GravityWellComponent::Setup() {}
|
||||||
|
|
||||||
|
void GravityWellComponent::Update(float dt) {
|
||||||
|
auto transform = entity->GetComponent<TransformComponent>();
|
||||||
|
if (!transform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
active = IsMouseButtonDown(MOUSE_BUTTON_LEFT);
|
||||||
|
|
||||||
|
const Vector2 mouse = GetMousePosition();
|
||||||
|
auto &t = transform->get();
|
||||||
|
const float blend = std::clamp(followLerp * dt, 0.0f, 1.0f);
|
||||||
|
t.x += (mouse.x - t.x) * blend;
|
||||||
|
t.y += (mouse.y - t.y) * blend;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GravityWellComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents player-controlled gravity source that follows mouse input.
|
||||||
|
*/
|
||||||
|
struct GravityWellComponent : public Component {
|
||||||
|
/**
|
||||||
|
* Mass of the gravity well, affecting the strength of the gravitational pull on receivers.
|
||||||
|
*/
|
||||||
|
float mass = 150000.0f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum distance for gravity pull calculations to avoid singularities and excessive forces
|
||||||
|
* when very close to receivers.
|
||||||
|
*/
|
||||||
|
float minDist = 30.0f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the gravity well is currently active and should apply gravitational forces to
|
||||||
|
* receivers (e.g. tied to mouse button state).
|
||||||
|
*/
|
||||||
|
bool active = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lerp factor for how quickly the gravity well follows the mouse position.
|
||||||
|
*/
|
||||||
|
float followLerp = 12.0f;
|
||||||
|
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
#include "components/HazardComponent.hpp"
|
||||||
|
|
||||||
|
#include "Entity.hpp"
|
||||||
|
#include "components/ColliderComponent.hpp"
|
||||||
|
|
||||||
|
void HazardComponent::Setup() {
|
||||||
|
auto selfCollider = entity->GetComponent<ColliderComponent>();
|
||||||
|
if (!selfCollider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selfCollider->get().monitoring = true;
|
||||||
|
|
||||||
|
selfCollider->get().AddCollisionListener([this](Entity &other) {
|
||||||
|
if (!context || !context->probeEntity || &other != context->probeEntity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->onPlayerDeath) {
|
||||||
|
context->onPlayerDeath();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void HazardComponent::Update(float) {}
|
||||||
|
|
||||||
|
void HazardComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits player death event when the probe collides with this hazard.
|
||||||
|
*/
|
||||||
|
struct HazardComponent : public Component {
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#include "components/HudComponent.hpp"
|
||||||
|
|
||||||
|
void HudComponent::Setup() {}
|
||||||
|
void HudComponent::Update(float) {}
|
||||||
|
void HudComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker component for HUD entities.
|
||||||
|
*/
|
||||||
|
struct HudComponent : public Component {
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#include "components/LifetimeComponent.hpp"
|
||||||
|
|
||||||
|
#include "Entity.hpp"
|
||||||
|
|
||||||
|
void LifetimeComponent::Setup() {}
|
||||||
|
|
||||||
|
void LifetimeComponent::Update(float dt) {
|
||||||
|
remaining -= dt;
|
||||||
|
if (remaining <= 0.0f) {
|
||||||
|
entity->QueueFree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LifetimeComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees an entity after a fixed amount of remaining time.
|
||||||
|
*/
|
||||||
|
struct LifetimeComponent : public Component {
|
||||||
|
/**
|
||||||
|
* Time in seconds until the entity is freed.
|
||||||
|
*/
|
||||||
|
float remaining = 0.0f;
|
||||||
|
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
#include "components/MeterComponent.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
void MeterComponent::Setup() {}
|
||||||
|
|
||||||
|
void MeterComponent::SetValue(float newValue) {
|
||||||
|
const float oldValue = value;
|
||||||
|
value = newValue;
|
||||||
|
if (context && oldValue != value) {
|
||||||
|
context->EmitMeterChanged(oldValue, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeterComponent::AddValue(float delta) { SetValue(std::clamp(value + delta, 0.0f, maxValue)); }
|
||||||
|
|
||||||
|
void MeterComponent::Drain(float amount) { AddValue(-amount); }
|
||||||
|
|
||||||
|
void MeterComponent::Update(float) {}
|
||||||
|
|
||||||
|
void MeterComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores and mutates the player's gravity meter with change events.
|
||||||
|
*/
|
||||||
|
struct MeterComponent : public Component {
|
||||||
|
float value = 60.0f;
|
||||||
|
float maxValue = 100.0f;
|
||||||
|
float drainRate = 14.0f;
|
||||||
|
float gainPerStar = 28.0f;
|
||||||
|
|
||||||
|
void Setup() override;
|
||||||
|
void SetValue(float newValue);
|
||||||
|
void AddValue(float delta);
|
||||||
|
void Drain(float amount);
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#include "components/NullZoneComponent.hpp"
|
||||||
|
|
||||||
|
void NullZoneComponent::Setup() {}
|
||||||
|
void NullZoneComponent::Update(float) {}
|
||||||
|
void NullZoneComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a vertical strip where gravity pull is disabled.
|
||||||
|
*/
|
||||||
|
struct NullZoneComponent : public Component {
|
||||||
|
float width = 70.0f;
|
||||||
|
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
#include "components/PhysicsComponent.hpp"
|
||||||
|
|
||||||
|
#include "Entity.hpp"
|
||||||
|
#include "components/TransformComponent.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
void PhysicsComponent::Setup() {}
|
||||||
|
|
||||||
|
void PhysicsComponent::Update(float dt) {
|
||||||
|
auto transform = entity->GetComponent<TransformComponent>();
|
||||||
|
if (!transform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float speed = std::sqrt(vx * vx + vy * vy);
|
||||||
|
if (speed > speedCap && speed > 0.0f) {
|
||||||
|
const float scale = speedCap / speed;
|
||||||
|
vx *= scale;
|
||||||
|
vy *= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform->get().x += vx * dt;
|
||||||
|
transform->get().y += vy * dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integrates velocity into transform each frame with a speed cap.
|
||||||
|
*/
|
||||||
|
struct PhysicsComponent : public Component {
|
||||||
|
float vx = 0.0f;
|
||||||
|
float vy = 0.0f;
|
||||||
|
float speedCap = 400.0f;
|
||||||
|
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
#include "components/ProbeStateComponent.hpp"
|
||||||
|
|
||||||
|
#include "Entity.hpp"
|
||||||
|
#include "components/TransformComponent.hpp"
|
||||||
|
|
||||||
|
#include "raylib.h"
|
||||||
|
|
||||||
|
void ProbeStateComponent::Setup() {}
|
||||||
|
|
||||||
|
void ProbeStateComponent::Update(float) {
|
||||||
|
if (!context || !context->probeEntity || entity != context->probeEntity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto transform = entity->GetComponent<TransformComponent>();
|
||||||
|
if (!transform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transform->get().y < -20.0f ||
|
||||||
|
transform->get().y > static_cast<float>(GetScreenHeight() + 20) ||
|
||||||
|
transform->get().x < -20.0f) {
|
||||||
|
if (context->onPlayerDeath) {
|
||||||
|
context->onPlayerDeath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProbeStateComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors probe bounds and emits death when probe exits playable area.
|
||||||
|
*/
|
||||||
|
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 dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#include "components/ProjectionComponent.hpp"
|
||||||
|
|
||||||
|
void ProjectionComponent::Setup() {}
|
||||||
|
void ProjectionComponent::Update(float) {}
|
||||||
|
void ProjectionComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder for future trajectory projection behavior.
|
||||||
|
*/
|
||||||
|
struct ProjectionComponent : public Component {
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include "components/RenderComponent.hpp"
|
||||||
|
|
||||||
|
void RenderComponent::Draw() {
|
||||||
|
if (draw) {
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderComponent::Setup() {}
|
||||||
|
void RenderComponent::Update(float) {}
|
||||||
|
void RenderComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores draw callback used by scene render loop.
|
||||||
|
*/
|
||||||
|
struct RenderComponent : public Component {
|
||||||
|
/**
|
||||||
|
* Callback that performs drawing for this entity. This is invoked by the
|
||||||
|
* scene's render loop after all updates have been processed, so it can be
|
||||||
|
* used to draw the entity based on its final state for the frame.
|
||||||
|
*/
|
||||||
|
std::function<void()> draw;
|
||||||
|
|
||||||
|
void Draw();
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
#include "components/ScrollComponent.hpp"
|
||||||
|
|
||||||
|
void ScrollComponent::Setup() {}
|
||||||
|
|
||||||
|
void ScrollComponent::Update(float dt) {
|
||||||
|
speed += accel * dt;
|
||||||
|
scrollX += speed * dt * 60.0f;
|
||||||
|
if (context) {
|
||||||
|
context->scrollX = scrollX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScrollComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advances the gameplay scroll value over time.
|
||||||
|
*/
|
||||||
|
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 Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
#include "components/ScrollableComponent.hpp"
|
||||||
|
|
||||||
|
#include "Entity.hpp"
|
||||||
|
#include "components/TransformComponent.hpp"
|
||||||
|
|
||||||
|
void ScrollableComponent::Setup() {}
|
||||||
|
|
||||||
|
void ScrollableComponent::Update(float) {
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto transform = entity->GetComponent<TransformComponent>();
|
||||||
|
if (!transform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform->get().x = worldX - context->scrollX;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScrollableComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a stable world X position into camera-relative screen X.
|
||||||
|
*/
|
||||||
|
struct ScrollableComponent : public Component {
|
||||||
|
float worldX = 0.0f;
|
||||||
|
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#include "components/SpawnComponent.hpp"
|
||||||
|
|
||||||
|
void SpawnComponent::Setup() {}
|
||||||
|
void SpawnComponent::Update(float) {}
|
||||||
|
void SpawnComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder for spawn cursor/state while spawning system evolves.
|
||||||
|
*/
|
||||||
|
struct SpawnComponent : public Component {
|
||||||
|
float cursorWX = 0.0f;
|
||||||
|
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#include "components/TrailComponent.hpp"
|
||||||
|
|
||||||
|
void TrailComponent::Setup() {}
|
||||||
|
void TrailComponent::Update(float) {}
|
||||||
|
void TrailComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder for future probe trail visualization.
|
||||||
|
*/
|
||||||
|
struct TrailComponent : public Component {
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#include "components/TransformComponent.hpp"
|
||||||
|
|
||||||
|
void TransformComponent::Setup() {}
|
||||||
|
void TransformComponent::Update(float) {}
|
||||||
|
void TransformComponent::Cleanup() {}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Component.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores world-space position for an entity.
|
||||||
|
*/
|
||||||
|
struct TransformComponent : public Component {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
|
||||||
|
void Setup() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Cleanup() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
#include "scene/DeathScene.hpp"
|
||||||
|
|
||||||
|
#include "scene/GameplayScene.hpp"
|
||||||
|
#include "scene/SceneManager.hpp"
|
||||||
|
#include "scene/StartMenuScene.hpp"
|
||||||
|
|
||||||
|
void DeathScene::Update(float) {
|
||||||
|
if (IsKeyPressed(KEY_ENTER) || IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
|
||||||
|
manager.QueueSceneChange<GameplayScene>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyPressed(KEY_ESCAPE)) {
|
||||||
|
manager.QueueSceneChange<StartMenuScene>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeathScene::Draw() {
|
||||||
|
DrawSceneOutline();
|
||||||
|
DrawText("PROBE LOST", 24, 28, 36, Color{255, 208, 208, 255});
|
||||||
|
DrawText("Press Enter or click to restart", 24, 84, 22, Color{255, 170, 170, 255});
|
||||||
|
DrawText("Press Esc for menu", 24, 116, 20, Color{200, 188, 188, 255});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Draw.hpp"
|
||||||
|
#include "scene/Scene.hpp"
|
||||||
|
|
||||||
|
#include "raylib.h"
|
||||||
|
|
||||||
|
class GameplayScene;
|
||||||
|
class StartMenuScene;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-death scene that offers restart or return to menu.
|
||||||
|
*/
|
||||||
|
class DeathScene : public Scene {
|
||||||
|
public:
|
||||||
|
explicit DeathScene(SceneManager &owner) : Scene(owner) {}
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Draw() override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
#include "scene/GameplayScene.hpp"
|
||||||
|
|
||||||
|
#include "scene/DeathScene.hpp"
|
||||||
|
#include "scene/SceneManager.hpp"
|
||||||
|
|
||||||
|
void GameplayScene::Enter() {
|
||||||
|
entities.clear();
|
||||||
|
entities.reserve(20);
|
||||||
|
context = {};
|
||||||
|
wantsDeathScene = false;
|
||||||
|
collectedCount = 0;
|
||||||
|
meterValue = 60.0f;
|
||||||
|
|
||||||
|
context.onPlayerDeath = [this]() { wantsDeathScene = true; };
|
||||||
|
context.AddCollectiblePickedListener([this](Entity &collectible) {
|
||||||
|
++collectedCount;
|
||||||
|
|
||||||
|
auto transform = collectible.GetComponent<TransformComponent>();
|
||||||
|
if (!transform || !context.entities) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.entities->push_back(
|
||||||
|
CreateDebris(transform->get().x - 4.0f, transform->get().y, -22.0f, -36.0f));
|
||||||
|
context.entities->push_back(
|
||||||
|
CreateDebris(transform->get().x + 4.0f, transform->get().y, 24.0f, -28.0f));
|
||||||
|
});
|
||||||
|
context.AddMeterChangedListener([this](float, float newValue) { meterValue = newValue; });
|
||||||
|
|
||||||
|
entities.push_back(CreateWorld());
|
||||||
|
entities.push_back(CreateGravityWell());
|
||||||
|
entities.push_back(CreateProbe());
|
||||||
|
entities.push_back(CreateStar(900.0f, 120.0f));
|
||||||
|
entities.push_back(CreateAsteroid(1100.0f, 330.0f));
|
||||||
|
entities.push_back(CreateNullZone(1280.0f, 70.0f));
|
||||||
|
entities.push_back(CreateHUD());
|
||||||
|
|
||||||
|
if (auto meter = entities.back()->GetComponent<MeterComponent>()) {
|
||||||
|
meterValue = meter->get().value;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
entity->SetContext(&context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameplayScene::Update(float dt) {
|
||||||
|
UpdateAllSystems(entities, dt);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (wantsDeathScene) {
|
||||||
|
manager.QueueSceneChange<DeathScene>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameplayScene::Draw() {
|
||||||
|
DrawSceneOutline();
|
||||||
|
DrawText("Gravity Surfing", 14, 12, 20, Color{230, 238, 255, 255});
|
||||||
|
|
||||||
|
auto worldScroll = entities[0]->GetComponent<ScrollComponent>();
|
||||||
|
auto probeTransform = entities[2]->GetComponent<TransformComponent>();
|
||||||
|
|
||||||
|
if (worldScroll) {
|
||||||
|
DrawText(TextFormat("scrollX: %.2f", worldScroll->get().scrollX), 14, 40, 16,
|
||||||
|
Color{125, 225, 205, 255});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (probeTransform) {
|
||||||
|
const auto &probe = probeTransform->get();
|
||||||
|
DrawText(TextFormat("probe: (%.1f, %.1f)", probe.x, probe.y), 14, 60, 16,
|
||||||
|
Color{235, 215, 125, 255});
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawText(TextFormat("meter: %.1f", meterValue), 14, 80, 16, Color{120, 210, 190, 255});
|
||||||
|
DrawText(TextFormat("stars: %i", collectedCount), 14, 100, 16, Color{255, 223, 86, 255});
|
||||||
|
|
||||||
|
for (auto &entity : entities) {
|
||||||
|
if (!entity || entity->queuedForFree) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto render = entity->GetComponent<RenderComponent>();
|
||||||
|
if (render) {
|
||||||
|
render->get().Draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Components.hpp"
|
||||||
|
#include "Draw.hpp"
|
||||||
|
#include "Entities.hpp"
|
||||||
|
#include "GameContext.hpp"
|
||||||
|
#include "Systems.hpp"
|
||||||
|
#include "scene/Scene.hpp"
|
||||||
|
|
||||||
|
#include "raylib.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class DeathScene;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main gameplay state containing active entity list and gameplay context.
|
||||||
|
*/
|
||||||
|
class GameplayScene : public Scene {
|
||||||
|
public:
|
||||||
|
explicit GameplayScene(SceneManager &owner) : Scene(owner) {}
|
||||||
|
void Enter() override;
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Draw() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::shared_ptr<Entity>> entities;
|
||||||
|
GameContext context;
|
||||||
|
bool wantsDeathScene = false;
|
||||||
|
int collectedCount = 0;
|
||||||
|
float meterValue = 60.0f;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class SceneManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base state interface for scene manager states.
|
||||||
|
*/
|
||||||
|
class Scene {
|
||||||
|
public:
|
||||||
|
explicit Scene(SceneManager &owner) : manager(owner) {}
|
||||||
|
virtual ~Scene() = default;
|
||||||
|
|
||||||
|
virtual void Enter() {}
|
||||||
|
virtual void Exit() {}
|
||||||
|
virtual void Update(float dt) = 0;
|
||||||
|
virtual void Draw() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
SceneManager &manager;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "scene/Scene.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Owns current scene and supports immediate or queued transitions.
|
||||||
|
*/
|
||||||
|
class SceneManager {
|
||||||
|
public:
|
||||||
|
template <typename T, typename... Args> void ChangeScene(Args &&...args);
|
||||||
|
template <typename T, typename... Args> void QueueSceneChange(Args &&...args);
|
||||||
|
|
||||||
|
void ChangeScene(std::unique_ptr<Scene> nextScene);
|
||||||
|
void QueueSceneChange(std::unique_ptr<Scene> nextScene);
|
||||||
|
void Update(float dt);
|
||||||
|
void Draw();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Scene> current;
|
||||||
|
std::unique_ptr<Scene> queued;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void SceneManager::ChangeScene(std::unique_ptr<Scene> nextScene) {
|
||||||
|
if (current) {
|
||||||
|
current->Exit();
|
||||||
|
}
|
||||||
|
current = std::move(nextScene);
|
||||||
|
if (current) {
|
||||||
|
current->Enter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SceneManager::QueueSceneChange(std::unique_ptr<Scene> nextScene) {
|
||||||
|
queued = std::move(nextScene);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SceneManager::Update(float dt) {
|
||||||
|
if (queued) {
|
||||||
|
ChangeScene(std::move(queued));
|
||||||
|
}
|
||||||
|
if (current) {
|
||||||
|
current->Update(dt);
|
||||||
|
}
|
||||||
|
if (queued) {
|
||||||
|
ChangeScene(std::move(queued));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SceneManager::Draw() {
|
||||||
|
if (current) {
|
||||||
|
current->Draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename... Args> inline void SceneManager::ChangeScene(Args &&...args) {
|
||||||
|
ChangeScene(std::make_unique<T>(*this, std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename... Args> inline void SceneManager::QueueSceneChange(Args &&...args) {
|
||||||
|
QueueSceneChange(std::make_unique<T>(*this, std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
#include "scene/StartMenuScene.hpp"
|
||||||
|
|
||||||
|
#include "scene/GameplayScene.hpp"
|
||||||
|
#include "scene/SceneManager.hpp"
|
||||||
|
|
||||||
|
void StartMenuScene::Update(float) {
|
||||||
|
if (IsKeyPressed(KEY_ENTER) || IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
|
||||||
|
manager.QueueSceneChange<GameplayScene>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartMenuScene::Draw() {
|
||||||
|
DrawSceneOutline();
|
||||||
|
DrawText("Gravity Surfing", 24, 28, 36, Color{230, 238, 255, 255});
|
||||||
|
DrawText("Click or press Enter to start", 24, 78, 22, Color{140, 198, 220, 255});
|
||||||
|
DrawText("Left mouse controls gravity well", 24, 130, 18, Color{170, 185, 205, 255});
|
||||||
|
DrawText("Collect stars and dodge hazards", 24, 156, 18, Color{170, 185, 205, 255});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Draw.hpp"
|
||||||
|
#include "scene/Scene.hpp"
|
||||||
|
|
||||||
|
#include "raylib.h"
|
||||||
|
|
||||||
|
class GameplayScene;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Landing scene shown before gameplay starts.
|
||||||
|
*/
|
||||||
|
class StartMenuScene : public Scene {
|
||||||
|
public:
|
||||||
|
explicit StartMenuScene(SceneManager &owner) : Scene(owner) {}
|
||||||
|
void Update(float dt) override;
|
||||||
|
void Draw() override;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue