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.gzAdd end screen 100%ai
quit/quit_text_be.aseprite | Bin 0 -> 507 bytes quit/quit_text_be.png | Bin 0 -> 1222 bytes quit/quit_text_continued.aseprite | Bin 0 -> 1188 bytes quit/quit_text_continued.png | Bin 0 -> 1631 bytes quit/quit_text_nope.aseprite | Bin 0 -> 618 bytes quit/quit_text_nope.png | Bin 0 -> 1230 bytes quit/quit_text_to.aseprite | Bin 0 -> 461 bytes quit/quit_text_to.png | Bin 0 -> 1131 bytes src/end_screen/end_screen.cpp | 245 ++++++++++++++++++++++++++++++++++++++ src/end_screen/end_screen.hpp | 72 +++++++++++ src/game/game.cpp | 3 + 11 files changed, 320 insertions(+) diff --git a/quit/quit_text_be.aseprite b/quit/quit_text_be.aseprite new file mode 100644 index 0000000..d09355e Binary files /dev/null and b/quit/quit_text_be.aseprite differ diff --git a/quit/quit_text_be.png b/quit/quit_text_be.png new file mode 100644 index 0000000..5dee7a4 Binary files /dev/null and b/quit/quit_text_be.png differ diff --git a/quit/quit_text_continued.aseprite b/quit/quit_text_continued.aseprite new file mode 100644 index 0000000..9da6d84 Binary files /dev/null and b/quit/quit_text_continued.aseprite differ diff --git a/quit/quit_text_continued.png b/quit/quit_text_continued.png new file mode 100644 index 0000000..760528c Binary files /dev/null and b/quit/quit_text_continued.png differ diff --git a/quit/quit_text_nope.aseprite b/quit/quit_text_nope.aseprite new file mode 100644 index 0000000..2db5a98 Binary files /dev/null and b/quit/quit_text_nope.aseprite differ diff --git a/quit/quit_text_nope.png b/quit/quit_text_nope.png new file mode 100644 index 0000000..d7788a9 Binary files /dev/null and b/quit/quit_text_nope.png differ diff --git a/quit/quit_text_to.aseprite b/quit/quit_text_to.aseprite new file mode 100644 index 0000000..08654bf Binary files /dev/null and b/quit/quit_text_to.aseprite differ diff --git a/quit/quit_text_to.png b/quit/quit_text_to.png new file mode 100644 index 0000000..4c4cc34 Binary files /dev/null and b/quit/quit_text_to.png differ diff --git a/src/end_screen/end_screen.cpp b/src/end_screen/end_screen.cpp new file mode 100644 index 0000000..bac4e30 --- /dev/null +++ b/src/end_screen/end_screen.cpp @@ -0,0 +1,245 @@ +#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; + } +} diff --git a/src/end_screen/end_screen.hpp b/src/end_screen/end_screen.hpp new file mode 100644 index 0000000..3177c34 --- /dev/null +++ b/src/end_screen/end_screen.hpp @@ -0,0 +1,72 @@ +// end_screen.hpp +// Ending screen: 4 images revealed one by one as particle bursts from centre. +// All layers are ALWAYS rendered as particles — no texture overdraw possible. +// Clear color: black. Text is white. + +#pragma once + +#include <SDL3/SDL.h> +#include <SDL3_image/SDL_image.h> +#include <vector> +#include <array> +#include <random> +#include <cstdint> + +struct EndParticle { + SDL_FPoint origin; // final screen position (centred) + SDL_FPoint current; // animated position +}; + +class EndScreen { +public: + bool running = true; + + explicit EndScreen(SDL_Renderer* r); + ~EndScreen(); + + void Update(); + void Render(); + void UpdateScreen(); + +private: + enum class LayerState { WAITING, SPREADING_IN, VISIBLE }; + + // ── Timings (ms) ───────────────────────────────────────────────────────── + // Fast burst animation, long delays between words. + static constexpr Uint64 SPREAD_DURATION_MS = 420; // fast particle burst + static constexpr Uint64 GAP_AFTER_0_MS = 200; // "to" → "be" (quick follow-up) + static constexpr Uint64 GAP_AFTER_1_MS = 1000; // "be" → "continued" + static constexpr Uint64 GAP_AFTER_2_MS = 2000; // "continued" → "nope" + static constexpr Uint64 END_PAUSE_MS = 2000; // hold all visible before done + + void Init(); + void BuildParticles(int layer_idx); + void StartLayer(int idx); + SDL_FPoint LayerOffset() const; // same offset for all layers (same image size) + static float Lerp(float a, float b, float t); + + SDL_Renderer* renderer = nullptr; + int screen_w = 0, screen_h = 0; + + static constexpr int NUM_LAYERS = 4; + + struct LayerAsset { + SDL_Surface* surface = nullptr; + // No texture needed — we render entirely via particles + }; + std::array<LayerAsset, NUM_LAYERS> assets{}; + std::array<LayerState, NUM_LAYERS> layer_state{}; + std::array<Uint64, NUM_LAYERS> layer_start_tick{}; // when spreading began + std::array<Uint64, NUM_LAYERS> layer_due{}; // scheduled start tick + std::array<std::vector<EndParticle>, NUM_LAYERS> layer_particles{}; + + bool end_timer_started = false; + Uint64 end_timer_start = 0; + Uint64 last_time = 0; + + std::mt19937 rng; +}; + +namespace EndScreen_Event { + void event_Loop(SDL_Renderer* renderer); +} diff --git a/src/game/game.cpp b/src/game/game.cpp index eebb5ce..fdb19a1 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -25,6 +25,7 @@ #include "defaults/input.hpp" #include "start_screen/start_screen.hpp" #include "cursor/animated_cursor.hpp" +#include "end_screen/end_screen.hpp" // Game class encapsulating SDL initialization and loop @@ -224,6 +225,8 @@ void Game::Run() { Default::fps_timer -= 1.0f; } } + + EndScreen_Event::event_Loop(renderer); } void Game::handle_Events() {