Nimbin[12]?SDL & Graphics / mapcreator / src/end_screen/end_screen.cpp

mapcreator git · master

SDL3 2.5D game and engine using assets, with map editor

sdl3 c++ game engine map-editor cmake · first commit 2025-10-31 · last commit 2026-09-07 (1 month ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-spieleentwicklung/map_creator

C++ 99.3%
git clone https://git.christianimmanuel.de/sdl-graphics/mapcreator.gitwget https://git.christianimmanuel.de/sdl-graphics/mapcreator/archive/mapcreator.tar.gz
src/end_screen/end_screen.cpp 11.2 KB · 245 lines 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;
    }
}