Nimbin[12]?SDL & Graphics / mapcreator / src/start_screen/start_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/start_screen/start_screen.cpp 12.5 KB · 331 lines raw
#include "start_screen.hpp"
#include "audio/audio.hpp"

StartScreen::StartScreen(SDL_Renderer* r) : renderer(r) { Init(); }
StartScreen::~StartScreen() {
        if (char_texture)  SDL_DestroyTexture(char_texture);
        if (logo_surface)  SDL_DestroySurface(logo_surface);
        if (props_texture) SDL_DestroyTexture(props_texture);
        if (props_surface) SDL_DestroySurface(props_surface);
    }


void StartScreen::StartShowProps() {
    if (!props_surface) return;

    phase = Phase::SHOW_PROPS;
    phase_start = SDL_GetTicks();

    // Extract visible pixels
    const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(props_surface->format);
    Uint32* pixels = static_cast<Uint32*>(props_surface->pixels);
    int pitch = props_surface->pitch / (int)sizeof(Uint32);
    int w = props_surface->w, h = props_surface->h;

    particles.clear();
    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, NULL, &r, &g, &b, &a);
            if (a > 180) { // or check color if you want black only
                particles.push_back({ {static_cast<float>(x), static_cast<float>(y)},
                                      {screen_w/2.0f, screen_h/2.0f},  // start from center
                                      {screen_w/2.0f, screen_h/2.0f} });
            }
        }
    }
}

void StartScreen::Update() {
    Uint64 now = SDL_GetTicks();
    float dt = static_cast<float>(now - last_time) / 1000.0f;
    last_time = now;

    switch (phase) {
        case Phase::DOT:
            if (now - phase_start > 500) StartSpreadToLine();
            break;

        case Phase::SPREAD_TO_LINE: {
            float p = (now - phase_start) /  280.0f;
            if (p >= 1.0f) { p = 1.0f; phase = Phase::LINE_WAIT; phase_start = now; }
            for (auto& pt : particles) {
                SDL_FPoint from = { center_dot.x, center_dot.y };
                pt.current.x = Lerp(from.x, pt.target.x, p);
                pt.current.y = Lerp(from.y, pt.target.y, p);
            }
            break;
        }

        case Phase::LINE_WAIT:
                                    if (now - phase_start > 300) {
                                        phase = Phase::RUNNING;
                                        run_start_time = now;
                                        char_x = -300.0f;

                                        // Re-apply dynamic speed (in case resized just before running)
                                        char_speed = (static_cast<float>(screen_w) + extra_distance) / desired_cross_time;
                                    }
                                    break;

        case Phase::RUNNING: {
                                 char_x += char_speed * dt;
                                 Uint64 frame_ticks = now - run_start_time;
                                 char_frame = static_cast<int>((frame_ticks / 80ULL) % 6ULL);

                                 // End when character is fully off-screen right
                                 if (char_x > static_cast<float>(screen_w) + 1420.0f) {
                                     StartFadeToLogo();
                                 }
                                 break;
                             }

        case Phase::FADE_TO_LOGO: {
            float p = (now - phase_start) /  420.0f;
            if (p >= 1.0f) { p = 1.0f; phase = Phase::SHOW_LOGO; phase_start = now; }
            for (auto& pt : particles) {
                SDL_FPoint logo_screen = GetScreenOriginal(pt);
                pt.current.x = Lerp(pt.target.x, logo_screen.x, p);
                pt.current.y = Lerp(pt.target.y, logo_screen.y, p);
            }
            break;
        }
        case Phase::SHOW_LOGO: {
                                   if (now - phase_start > static_cast<Uint64>(logo_display_time * 1000.0f)) {
                                       StartShowProps();
                                       // Time's up → quit the application
                                       //running = false;           // will break main loop
                                                                  // or: SDL_Quit(); return; // but better to let main clean up
                                   }
                                   break;
                               }

        case Phase::SHOW_PROPS: {
                                    float p = (now - phase_start) / 1000.0f; // 1 second animation
                                    if (p >= 1.0f) p = 1.0f;
                                    props_anim_progress = p;

                                    for (auto& pt : particles) {
                                        pt.current.x = Lerp(pt.current.x, pt.original.x + (screen_w - props_surface->w)/2.0f, p);
                                        pt.current.y = Lerp(pt.current.y, pt.original.y + (screen_h - props_surface->h)/2.0f, p);
                                    }

                                    // optionally, end scene after animation finishes
                                    if (p >= 1.0f && now - phase_start > 3500) { // 1.5s after animation ends
                                        running = false;
                                    }
                                    break;
                                }
    }
}

void StartScreen::Render() {
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
    SDL_RenderClear(renderer);

    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    for (const auto& p : particles) {
        SDL_FRect r { p.current.x, p.current.y, 1.0f, 1.0f };
        SDL_RenderFillRect(renderer, &r);
    }

    if (phase == Phase::RUNNING) {
        float scale = 4.0f;                               // double size as requested
        float sw = char_frame_width * scale;
        float sh = char_height * scale;
        float dy = static_cast<float>(bottom_y) - sh + 46.0f; // feet on line

        SDL_FRect src { static_cast<float>(char_frame * char_frame_width), 0.0f,
                        static_cast<float>(char_frame_width), static_cast<float>(char_height) };
        SDL_FRect dst { char_x - (sw - char_frame_width) / 2.0f, dy, sw, sh };

        SDL_RenderTexture(renderer, char_texture, &src, &dst);
    }
    else
    if (phase == Phase::SHOW_PROPS) {
        if (props_anim_progress < 2.0f) {
            SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
            for (const auto& p : particles) {
                SDL_FRect r { p.current.x, p.current.y, 1.0f, 1.0f };
                SDL_RenderFillRect(renderer, &r);
            }
        } else {
            SDL_FRect dst { (screen_w - props_surface->w)/2.0f,
                (screen_h - props_surface->h)/2.0f,
                static_cast<float>(props_surface->w),
                static_cast<float>(props_surface->h) };
            SDL_SetTextureAlphaMod(props_texture, 255);
            SDL_RenderTexture(renderer, props_texture, nullptr, &dst);
        }
    }

    SDL_RenderPresent(renderer);
}

void StartScreen::UpdateScreen() {
    if (!logo_surface) return;
    SDL_GetRenderOutputSize(renderer, &screen_w, &screen_h);

    // Logo centered
    int lw = logo_surface->w;
    int lh = logo_surface->h;
    logo_dest.x = (screen_w - lw) / 2;
    logo_dest.y = (screen_h - lh) / 2 - 100;
    logo_offset.x = static_cast<float>(logo_dest.x);
    logo_offset.y = static_cast<float>(logo_dest.y);

    // Central dot
    center_dot.x = static_cast<float>(screen_w) / 2.0f;
    center_dot.y = static_cast<float>(screen_h) * 0.65f;

    // Ground bar — edge-to-edge friendly
    int margin = 0;
    int usable = screen_w - 2 * margin;
    int pixels_per_full_row = usable / ground_row_height;
    int total = static_cast<int>(particles.size());
    int full_rows = total / pixels_per_full_row;
    int rem = total % pixels_per_full_row;

    std::vector<SDL_FPoint> line_targets;
    line_targets.reserve(static_cast<size_t>(total));

    bottom_y = static_cast<float>(screen_h) * 0.68f;

    // Full rows — packed densely (as before)
    for (int r = 0; r < full_rows; ++r) {
        float y = bottom_y - static_cast<float>(r) * static_cast<float>(ground_row_height);
        float row_start_x = (screen_w - pixels_per_full_row * ground_row_height) / 2.0f;
        for (int c = 0; c < pixels_per_full_row; ++c) {
            float x = row_start_x + static_cast<float>(c) * static_cast<float>(ground_row_height);
            line_targets.push_back({x, y});
        }
    }

    // Remainder row — evenly spaced across full width
    if (rem > 0) {
        float y = bottom_y - static_cast<float>(full_rows) * static_cast<float>(ground_row_height);

        // Spread evenly with equal gaps
        float spacing = static_cast<float>(usable) / static_cast<float>(rem + 1);
        float first_x = static_cast<float>(margin) + spacing / 2.0f;

        for (int c = 0; c < rem; ++c) {
            float x = first_x + static_cast<float>(c) * spacing;
            line_targets.push_back({x, y});
        }
    }

    line_positions = std::move(line_targets);

    // Character position above top of bar
    float top_bar = bottom_y - static_cast<float>(full_rows + (rem > 0 ? 1 : 0)) * static_cast<float>(ground_row_height);
    line_y = static_cast<int>(top_bar - static_cast<float>(char_height) * 2.0f - 20.0f);
}


void StartScreen::Init() {
    logo_surface = IMG_Load("logo/logo.png");
    char_texture = IMG_LoadTexture(renderer, "logo/Run_Side-Sheet.png");

    props_surface = IMG_Load("logo/props.png");
    props_texture = SDL_CreateTextureFromSurface(renderer, props_surface);

    SDL_GetRenderOutputSize(renderer, &screen_w, &screen_h);

    // Extract black pixels (local coords)
    const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(logo_surface->format);
    Uint32* pixels = static_cast<Uint32*>(logo_surface->pixels);
    int pitch = logo_surface->pitch / (int)sizeof(Uint32);
    int w = logo_surface->w, h = logo_surface->h;

    particles.clear();
    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, NULL, &r, &g, &b, &a);
            if (r < 20 && g < 20 && b < 20 && a > 180) {
                particles.push_back({ {static_cast<float>(x), static_cast<float>(y)},
                                      {0,0}, {0,0} });
            }
        }
    }

    UpdateScreen();                     // compute dot + line positions

    // Set all particles to center dot for first phase
    for (auto& p : particles) {
        p.current.x = center_dot.x;
        p.current.y = center_dot.y;
        p.target    = center_dot;
    }

    rng.seed(std::random_device{}());
    last_time = SDL_GetTicks();
    phase_start = last_time;
    phase = Phase::DOT;

    char_speed = (static_cast<float>(screen_w) + extra_distance) / desired_cross_time;
    //char_speed = 520.0f;
    char_frame_width = 64;
    char_height = 64;
}

void StartScreen::StartSpreadToLine() {
    phase = Phase::SPREAD_TO_LINE;
    phase_start = SDL_GetTicks();

    int N = static_cast<int>(particles.size());
    std::vector<size_t> perm((size_t)N);
    std::iota(perm.begin(), perm.end(), 0);
    std::shuffle(perm.begin(), perm.end(), rng);

    for (size_t i = 0; i < (size_t)N; ++i) {
        particles[i].target = line_positions[perm[i]];
    }
}

void StartScreen::StartFadeToLogo() {
    phase = Phase::FADE_TO_LOGO;
    phase_start = SDL_GetTicks();

    // targets stay at line positions → lerp back to logo in Update()
}

SDL_FPoint StartScreen::GetScreenOriginal(const Particle& p) const {
    return { p.original.x + logo_offset.x, p.original.y + logo_offset.y };
}

float StartScreen::Lerp(float a, float b, float t) {
    return a + (b - a) * t;
}

namespace StartScreen_Event {
    void event_Loop(SDL_Renderer* renderer) {
        Audio audio;
        audio.RegisterSound("intro", "music/intro.wav");
        StartScreen* scene = new StartScreen(renderer);
        audio.Play("intro");
        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_SPACE) {
                    scene->~StartScreen();
                    scene = new StartScreen(renderer);   // restart from DOT
                }

                if (e.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED ||
                    e.type == SDL_EVENT_WINDOW_RESIZED) {
                    scene->UpdateScreen();
                }
            }

            scene->Update();
            scene->Render();
        }
    }
}