Add main loop

master
John Montagu, the 4th Earl of Sandvich 2026-03-16 00:09:43 -07:00
parent a97770a8eb
commit 50066db8ef
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
4 changed files with 139 additions and 25 deletions

View File

@ -2,6 +2,7 @@
#include "Entity.hpp"
#include "components/GravityWellComponent.hpp"
#include "components/MeterComponent.hpp"
#include "components/NullZoneComponent.hpp"
#include "components/TransformComponent.hpp"
@ -85,6 +86,14 @@ bool PhysicsComponent::ComputeWellDeltaVelocity(double px, double py, double dt,
return false;
}
// Probe cannot receive well acceleration when meter is depleted.
if (context && entity == context->probeEntity && context->hudEntity) {
auto meter = context->hudEntity->GetComponent<MeterComponent>();
if (meter && meter->get().value <= 0.0f) {
return false;
}
}
if (IsInsideNullZone(px)) {
return false;
}

View File

@ -1,5 +1,87 @@
#include "components/SpawnComponent.hpp"
void SpawnComponent::Setup() {}
void SpawnComponent::Update(float) {}
#include "Entities.hpp"
#include "Entity.hpp"
#include "components/CollectibleComponent.hpp"
#include "components/HazardComponent.hpp"
#include "components/NullZoneComponent.hpp"
#include "components/ScrollComponent.hpp"
#include "components/ScrollableComponent.hpp"
#include "components/TransformComponent.hpp"
#include "raylib.h"
#include <algorithm>
void SpawnComponent::Setup() {
// Start a bit beyond the initial handcrafted segment.
cursorWX = 1500.0f;
}
void SpawnComponent::Update(float) {
if (!context || !context->entities) {
return;
}
auto scroll = entity->GetComponent<ScrollComponent>();
if (!scroll) {
return;
}
const float cameraX = scroll->get().scrollX;
const float spawnLimit = cameraX + spawnAheadDistance;
while (cursorWX < spawnLimit) {
const float r = static_cast<float>(GetRandomValue(0, 99));
if (r < 55.0f) {
const float y = static_cast<float>(GetRandomValue(48, GetScreenHeight() - 48));
auto star = CreateStar(cursorWX, y);
star->SetContext(context);
context->entities->push_back(star);
} else if (r < 88.0f) {
const float y = static_cast<float>(GetRandomValue(42, GetScreenHeight() - 42));
auto asteroid = CreateAsteroid(cursorWX, y);
asteroid->SetContext(context);
context->entities->push_back(asteroid);
} else {
const float width = static_cast<float>(GetRandomValue(70, 140));
auto zone = CreateNullZone(cursorWX, width);
zone->SetContext(context);
context->entities->push_back(zone);
}
const float gap =
static_cast<float>(GetRandomValue(static_cast<int>(minGap), static_cast<int>(maxGap)));
cursorWX += gap;
}
const float cullX = cameraX - despawnBehindDistance;
for (auto &candidate : *context->entities) {
if (!candidate || candidate.get() == entity || candidate->queuedForFree) {
continue;
}
const bool isSpawnedGameplayObject =
candidate->GetComponent<CollectibleComponent>().has_value() ||
candidate->GetComponent<HazardComponent>().has_value() ||
candidate->GetComponent<NullZoneComponent>().has_value();
if (!isSpawnedGameplayObject) {
continue;
}
auto scrollable = candidate->GetComponent<ScrollableComponent>();
if (scrollable) {
if (scrollable->get().worldX < cullX) {
candidate->QueueFree();
}
continue;
}
auto transform = candidate->GetComponent<TransformComponent>();
if (transform && transform->get().x < cullX) {
candidate->QueueFree();
}
}
}
void SpawnComponent::Cleanup() {}

View File

@ -3,10 +3,29 @@
#include "Component.hpp"
/**
* Placeholder for spawn cursor/state while spawning system evolves.
* Spawns and despawns gameplay objects to keep the run effectively infinite.
*/
struct SpawnComponent : public Component {
/**
* Next world-space X coordinate where a spawn roll will happen.
*/
float cursorWX = 0.0f;
/**
* How far ahead of current scroll we keep content generated.
*/
float spawnAheadDistance = 1200.0f;
/**
* How far behind the camera entities are culled.
*/
float despawnBehindDistance = 180.0f;
/**
* Minimum spacing between spawn rolls.
*/
float minGap = 160.0f;
/**
* Maximum spacing between spawn rolls.
*/
float maxGap = 320.0f;
void Setup() override;
void Update(float dt) override;

View File

@ -20,30 +20,32 @@ void GameplayScene::Enter() {
return;
}
context.entities->push_back(
context.entities->emplace_back(
CreateDebris(transform->get().x - 4.0f, transform->get().y, -22.0f, -36.0f));
context.entities->push_back(
context.entities->emplace_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, 120.0f));
entities.push_back(CreateHUD());
context.worldEntity = entities.emplace_back(CreateWorld()).get();
if (auto meter = entities.back()->GetComponent<MeterComponent>()) {
context.wellEntity = entities.emplace_back(CreateGravityWell()).get();
context.probeEntity = entities.emplace_back(CreateProbe()).get();
entities.emplace_back(CreateStar(900.0f, 230.0f));
entities.emplace_back(CreateNullZone(1280.0f, 120.0f));
context.hudEntity = entities.emplace_back(CreateHUD()).get();
if (context.hudEntity) {
auto meter = context.hudEntity->GetComponent<MeterComponent>();
if (meter) {
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) {
@ -56,11 +58,6 @@ void GameplayScene::Enter() {
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>();
}
@ -70,8 +67,15 @@ 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>();
std::optional<std::reference_wrapper<ScrollComponent>> worldScroll;
if (context.worldEntity) {
worldScroll = context.worldEntity->GetComponent<ScrollComponent>();
}
std::optional<std::reference_wrapper<TransformComponent>> probeTransform;
if (context.probeEntity) {
probeTransform = context.probeEntity->GetComponent<TransformComponent>();
}
if (worldScroll) {
DrawText(TextFormat("scrollX: %.2f", worldScroll->get().scrollX), 14, 40, 16,