#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(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(x), static_cast(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(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(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((frame_ticks / 80ULL) % 6ULL); // End when character is fully off-screen right if (char_x > static_cast(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(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(bottom_y) - sh + 46.0f; // feet on line SDL_FRect src { static_cast(char_frame * char_frame_width), 0.0f, static_cast(char_frame_width), static_cast(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(props_surface->w), static_cast(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(logo_dest.x); logo_offset.y = static_cast(logo_dest.y); // Central dot center_dot.x = static_cast(screen_w) / 2.0f; center_dot.y = static_cast(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(particles.size()); int full_rows = total / pixels_per_full_row; int rem = total % pixels_per_full_row; std::vector line_targets; line_targets.reserve(static_cast(total)); bottom_y = static_cast(screen_h) * 0.68f; // Full rows — packed densely (as before) for (int r = 0; r < full_rows; ++r) { float y = bottom_y - static_cast(r) * static_cast(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(c) * static_cast(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(full_rows) * static_cast(ground_row_height); // Spread evenly with equal gaps float spacing = static_cast(usable) / static_cast(rem + 1); float first_x = static_cast(margin) + spacing / 2.0f; for (int c = 0; c < rem; ++c) { float x = first_x + static_cast(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(full_rows + (rem > 0 ? 1 : 0)) * static_cast(ground_row_height); line_y = static_cast(top_bar - static_cast(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(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(x), static_cast(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(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(particles.size()); std::vector 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(); } } }