//! A splash screen that plays bdelta_secsriefly at startup. use bevy::{ image::{ImageLoaderSettings, ImageSampler}, input::common_conditions::input_just_pressed, prelude::*, }; use crate::{screens::Screen, theme::prelude::*, AppSet}; pub(super) fn plugin(app: &mut App) { // Spawn splash screen. app.insert_resource(ClearColor(SPLASH_BACKGROUND_COLOR)); app.add_systems(OnEnter(Screen::Splash), spawn_splash_screen); // Animate splash screen. app.add_systems( Update, ( tick_fade_in_out.in_set(AppSet::TickTimers), apply_fade_in_out.in_set(AppSet::Update), ) .run_if(in_state(Screen::Splash)), ); // Add splash timer. app.register_type::(); app.add_systems(OnEnter(Screen::Splash), insert_splash_timer); app.add_systems(OnExit(Screen::Splash), remove_splash_timer); app.add_systems( Update, ( tick_splash_timer.in_set(AppSet::TickTimers), check_splash_timer.in_set(AppSet::Update), ) .run_if(in_state(Screen::Splash)), ); // Exit the splash screen early if the player hits escape. app.add_systems( Update, continue_to_loading_screen .run_if(input_just_pressed(KeyCode::Escape).and(in_state(Screen::Splash))), ); } const SPLASH_BACKGROUND_COLOR: Color = Color::srgb(0.157, 0.157, 0.157); const SPLASH_DURATION_SECS: f32 = 1.8; const SPLASH_FADE_DURATION_SECS: f32 = 0.6; fn spawn_splash_screen(mut commands: Commands, asset_server: Res) { commands .ui_root() .insert(( Name::new("Splash screen"), BackgroundColor(SPLASH_BACKGROUND_COLOR), StateScoped(Screen::Splash), )) .with_children(|children| { children.spawn(( Name::new("Splash image"), Node { margin: UiRect::all(Val::Auto), width: Val::Percent(70.0), ..default() }, ImageNode::new(asset_server.load_with_settings( // This should be an embedded asset for instant loading, but that is // currently [broken on Windows Wasm builds](https://github.com/bevyengine/bevy/issues/14246). "images/splash.png", |settings: &mut ImageLoaderSettings| { // Make an exception for the splash image in case // `ImagePlugin::default_nearest()` is used for pixel art. settings.sampler = ImageSampler::linear(); }, )), UiImageFadeInOut { total_duration: SPLASH_DURATION_SECS, fade_duration: SPLASH_FADE_DURATION_SECS, t: 0.0, }, )); }); } #[derive(Component, Reflect)] #[reflect(Component)] struct UiImageFadeInOut { /// Total duration in seconds. total_duration: f32, /// Fade duration in seconds. fade_duration: f32, /// Current progress in seconds, between 0 and [`Self::total_duration`]. t: f32, } impl UiImageFadeInOut { fn alpha(&self) -> f32 { // Normalize by duration. let t = (self.t / self.total_duration).clamp(0.0, 1.0); let fade = self.fade_duration / self.total_duration; // Regular trapezoid-shaped graph, flat at the top with alpha = 1.0. ((1.0 - (2.0 * t - 1.0).abs()) / fade).min(1.0) } } fn tick_fade_in_out(time: Res