use bevy::{
image::{ImageLoaderSettings, ImageSampler},
input::common_conditions::input_just_pressed,
prelude::*,
};
#[cfg(feature = "hot_patch")]
use bevy_simple_subsecond_system::hot;
use crate::{PostPhysicsAppSystems, screens::Screen, theme::prelude::*};
pub(super) fn plugin(app: &mut App) {
app.insert_resource(ClearColor(SPLASH_BACKGROUND_COLOR));
app.add_systems(OnEnter(Screen::Splash), spawn_splash_screen);
app.add_systems(
Update,
(
tick_fade_in_out.in_set(PostPhysicsAppSystems::TickTimers),
apply_fade_in_out.in_set(PostPhysicsAppSystems::Update),
)
.run_if(in_state(Screen::Splash)),
);
app.register_type::<SplashTimer>();
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(PostPhysicsAppSystems::TickTimers),
check_splash_timer.in_set(PostPhysicsAppSystems::Update),
)
.run_if(in_state(Screen::Splash)),
);
app.add_systems(
Update,
enter_title_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;
#[cfg_attr(feature = "hot_patch", hot)]
fn spawn_splash_screen(mut commands: Commands, asset_server: Res<AssetServer>) {
commands
.spawn((
widget::ui_root("Splash Screen"),
StateScoped(Screen::Splash),
children![(
Name::new("Splash image"),
Node {
margin: UiRect::all(Val::Auto),
width: Val::Percent(70.0),
..default()
},
ImageNode::new(asset_server.load_with_settings(
#[cfg(feature = "dev")]
"images/splash.png",
#[cfg(not(feature = "dev"))]
"images/splash.ktx2",
|settings: &mut ImageLoaderSettings| {
settings.sampler = ImageSampler::linear();
},
)),
ImageNodeFadeInOut {
total_duration: SPLASH_DURATION_SECS,
fade_duration: SPLASH_FADE_DURATION_SECS,
t: 0.0,
},
)],
))
.insert(BackgroundColor(SPLASH_BACKGROUND_COLOR));
}
#[derive(Component, Reflect)]
#[reflect(Component)]
struct ImageNodeFadeInOut {
total_duration: f32,
fade_duration: f32,
t: f32,
}
impl ImageNodeFadeInOut {
fn alpha(&self) -> f32 {
let t = (self.t / self.total_duration).clamp(0.0, 1.0);
let fade = self.fade_duration / self.total_duration;
((1.0 - (2.0 * t - 1.0).abs()) / fade).min(1.0)
}
}
fn tick_fade_in_out(time: Res<Time>, mut animation_query: Query<&mut ImageNodeFadeInOut>) {
for mut anim in &mut animation_query {
anim.t += time.delta_secs();
}
}
fn apply_fade_in_out(mut animation_query: Query<(&ImageNodeFadeInOut, &mut ImageNode)>) {
for (anim, mut image) in &mut animation_query {
image.color.set_alpha(anim.alpha())
}
}
#[derive(Resource, Debug, Clone, PartialEq, Reflect)]
#[reflect(Resource)]
struct SplashTimer(Timer);
impl Default for SplashTimer {
fn default() -> Self {
Self(Timer::from_seconds(SPLASH_DURATION_SECS, TimerMode::Once))
}
}
#[cfg_attr(feature = "hot_patch", hot)]
fn insert_splash_timer(mut commands: Commands) {
commands.init_resource::<SplashTimer>();
}
#[cfg_attr(feature = "hot_patch", hot)]
fn remove_splash_timer(mut commands: Commands) {
commands.remove_resource::<SplashTimer>();
}
#[cfg_attr(feature = "hot_patch", hot)]
fn tick_splash_timer(time: Res<Time>, mut timer: ResMut<SplashTimer>) {
timer.0.tick(time.delta());
}
#[cfg_attr(feature = "hot_patch", hot)]
fn check_splash_timer(timer: ResMut<SplashTimer>, mut next_screen: ResMut<NextState<Screen>>) {
if timer.0.just_finished() {
next_screen.set(Screen::Title);
}
}
#[cfg_attr(feature = "hot_patch", hot)]
fn enter_title_screen(mut next_screen: ResMut<NextState<Screen>>) {
next_screen.set(Screen::Title);
}