mapcreator git · master
SDL3 2.5D game and engine using assets, with map editor
C++ 99.3%git clone https://git.christianimmanuel.de/sdl-graphics/mapcreator.gitwget https://git.christianimmanuel.de/sdl-graphics/mapcreator/archive/mapcreator.tar.gzsrc/end_screen/end_screen.cpp raw
#include "end_screen.hpp"
// ─────────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────────
float EndScreen::Lerp(float a, float b, float t) {
return a + (b - a) * t;
}
// All images are 460×460 and share the same centred offset.
SDL_FPoint EndScreen::LayerOffset() const {
if (!assets[0].surface) return {0.0f, 0.0f};
float ox = (screen_w - assets[0].surface->w) / 2.0f;
float oy = (screen_h - assets[0].surface->h) / 2.0f;
return {ox, oy};
}
// ─────────────────────────────────────────────────────────────────────────────
// Construction / Destruction
// ─────────────────────────────────────────────────────────────────────────────
EndScreen::EndScreen(SDL_Renderer* r) : renderer(r) { Init(); }
EndScreen::~EndScreen() {
for (auto& a : assets)
if (a.surface) SDL_DestroySurface(a.surface);
}
// ─────────────────────────────────────────────────────────────────────────────
// Init
// ─────────────────────────────────────────────────────────────────────────────
void EndScreen::Init() {
SDL_GetRenderOutputSize(renderer, &screen_w, &screen_h);
const char* paths[NUM_LAYERS] = {
"quit/quit_text_to.png",
"quit/quit_text_be.png",
"quit/quit_text_continued.png",
"quit/quit_text_nope.png",
};
for (int i = 0; i < NUM_LAYERS; ++i) {
assets[i].surface = IMG_Load(paths[i]);
layer_state[i] = LayerState::WAITING;
layer_start_tick[i]= 0;
layer_due[i] = 0;
}
rng.seed(std::random_device{}());
last_time = SDL_GetTicks();
// Extract particles for every layer before starting
for (int i = 0; i < NUM_LAYERS; ++i)
BuildParticles(i);
// Fire layer 0 immediately
StartLayer(0);
}
// ─────────────────────────────────────────────────────────────────────────────
// BuildParticles — extract visible pixels from a surface into EndParticles.
// current is initialised to screen-centre; origin is the final screen pos.
// ─────────────────────────────────────────────────────────────────────────────
void EndScreen::BuildParticles(int idx) {
auto& surf = assets[idx].surface;
if (!surf) return;
const SDL_PixelFormatDetails* details =
SDL_GetPixelFormatDetails(surf->format);
Uint32* pixels = static_cast<Uint32*>(surf->pixels);
int pitch = surf->pitch / (int)sizeof(Uint32);
int w = surf->w, h = surf->h;
SDL_FPoint off = LayerOffset(); // same for every layer
auto& parts = layer_particles[idx];
parts.clear();
parts.reserve(static_cast<size_t>(w * h / 4)); // rough upper bound
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
Uint32 p = pixels[y * pitch + x];
Uint8 r, g, b, a;
SDL_GetRGBA(p, details, nullptr, &r, &g, &b, &a);
if (a > 100) {
EndParticle ep;
ep.origin = { off.x + static_cast<float>(x),
off.y + static_cast<float>(y) };
ep.current = { screen_w / 2.0f, screen_h / 2.0f };
parts.push_back(ep);
}
}
}
}
// ─────────────────────────────────────────────────────────────────────────────
// StartLayer — begin the spread-in animation for one layer
// ─────────────────────────────────────────────────────────────────────────────
void EndScreen::StartLayer(int idx) {
if (idx < 0 || idx >= NUM_LAYERS) return;
layer_state[idx] = LayerState::SPREADING_IN;
layer_start_tick[idx] = SDL_GetTicks();
// All particles burst from screen centre
for (auto& ep : layer_particles[idx])
ep.current = { screen_w / 2.0f, screen_h / 2.0f };
}
// ─────────────────────────────────────────────────────────────────────────────
// UpdateScreen — call on window resize
// ─────────────────────────────────────────────────────────────────────────────
void EndScreen::UpdateScreen() {
SDL_GetRenderOutputSize(renderer, &screen_w, &screen_h);
for (int i = 0; i < NUM_LAYERS; ++i)
BuildParticles(i);
// Re-centre any currently-spreading layer
for (int i = 0; i < NUM_LAYERS; ++i) {
if (layer_state[i] == LayerState::SPREADING_IN) {
for (auto& ep : layer_particles[i])
ep.current = { screen_w / 2.0f, screen_h / 2.0f };
}
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Update
// ─────────────────────────────────────────────────────────────────────────────
void EndScreen::Update() {
Uint64 now = SDL_GetTicks();
last_time = now;
// ── Advance any spreading layer ──────────────────────────────────────────
for (int i = 0; i < NUM_LAYERS; ++i) {
if (layer_state[i] != LayerState::SPREADING_IN) continue;
float t = static_cast<float>(now - layer_start_tick[i])
/ static_cast<float>(SPREAD_DURATION_MS);
if (t >= 1.0f) {
// Snap to final positions
for (auto& ep : layer_particles[i])
ep.current = ep.origin;
layer_state[i] = LayerState::VISIBLE;
// Schedule the next layer
if (i + 1 < NUM_LAYERS) {
Uint64 gap = 0;
if (i == 0) gap = GAP_AFTER_0_MS;
else if (i == 1) gap = GAP_AFTER_1_MS;
else if (i == 2) gap = GAP_AFTER_2_MS;
layer_due[i + 1] = now + gap;
}
} else {
float cx = screen_w / 2.0f;
float cy = screen_h / 2.0f;
for (auto& ep : layer_particles[i]) {
ep.current.x = Lerp(cx, ep.origin.x, t);
ep.current.y = Lerp(cy, ep.origin.y, t);
}
}
}
// ── Trigger any WAITING layer whose due time has arrived ─────────────────
for (int i = 1; i < NUM_LAYERS; ++i) {
if (layer_state[i] == LayerState::WAITING
&& layer_due[i] > 0
&& now >= layer_due[i])
{
StartLayer(i);
}
}
// ── End condition: last layer fully visible ───────────────────────────────
if (layer_state[NUM_LAYERS - 1] == LayerState::VISIBLE) {
if (!end_timer_started) {
end_timer_started = true;
end_timer_start = now;
} else if (now - end_timer_start >= END_PAUSE_MS) {
running = false;
}
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Render
// Strategy: every non-WAITING layer is drawn as white 1×1 particle rects.
// VISIBLE layers have all particles snapped to their origins, so they look
// like a solid rendered image. No SDL_RenderTexture is used at all, which
// means there is zero risk of one layer's texture rect overwriting another.
// ─────────────────────────────────────────────────────────────────────────────
void EndScreen::Render() {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
for (int i = 0; i < NUM_LAYERS; ++i) {
if (layer_state[i] == LayerState::WAITING) continue;
// Draw every particle — works for both SPREADING_IN and VISIBLE states.
for (const auto& ep : layer_particles[i]) {
SDL_FRect r { ep.current.x, ep.current.y, 1.0f, 1.0f };
SDL_RenderFillRect(renderer, &r);
}
}
SDL_RenderPresent(renderer);
}
// ─────────────────────────────────────────────────────────────────────────────
// Event loop
// ─────────────────────────────────────────────────────────────────────────────
namespace EndScreen_Event {
void event_Loop(SDL_Renderer* renderer) {
EndScreen* scene = new EndScreen(renderer);
while (scene->running) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT)
scene->running = false;
if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE)
scene->running = false;
if (e.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED ||
e.type == SDL_EVENT_WINDOW_RESIZED)
scene->UpdateScreen();
}
scene->Update();
scene->Render();
}
delete scene;
}
}