Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Splash screen allows exiting early (with escape key) #257

Merged
merged 12 commits into from
Aug 11, 2024
139 changes: 103 additions & 36 deletions src/screens/splash.rs
benfrankel marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! A splash screen that plays briefly at startup.

use std::collections::VecDeque;

use bevy::{
input::common_conditions::input_just_pressed,
prelude::*,
render::texture::{ImageLoaderSettings, ImageSampler},
};
Expand All @@ -11,6 +14,8 @@ use crate::{theme::prelude::*, AppSet};
pub(super) fn plugin(app: &mut App) {
// Spawn splash screen.
app.insert_resource(ClearColor(SPLASH_BACKGROUND_COLOR));
app.init_resource::<SplashScreenImageList>();

app.add_systems(OnEnter(Screen::Splash), spawn_splash);

// Animate splash screen.
Expand All @@ -35,48 +40,84 @@ pub(super) fn plugin(app: &mut App) {
)
.run_if(in_state(Screen::Splash)),
);

// exit the splash screen early if the player hits escape
app.add_systems(
Update,
exit_splash_screen
.run_if(input_just_pressed(KeyCode::Escape).and_then(in_state(Screen::Splash))),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally think that this reads better when written as two separate run_if because of the line break, but I'm not blocking on that.

Copy link
Collaborator

@benfrankel benfrankel Aug 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO that's confusing because you have to know whether that yields an and or an or behavior. Would be neat if run conditions could use && somehow to improve readability.

);
}

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(mut commands: Commands, asset_server: Res<AssetServer>) {
commands
.ui_root()
.insert((
Name::new("Splash screen"),
#[derive(Component)]
benfrankel marked this conversation as resolved.
Show resolved Hide resolved
struct SplashScreenContainer;
will-hart marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Resource)]
benfrankel marked this conversation as resolved.
Show resolved Hide resolved
struct SplashScreenImageList(VecDeque<&'static str>);
will-hart marked this conversation as resolved.
Show resolved Hide resolved

impl Default for SplashScreenImageList {
fn default() -> Self {
// Use paths here rather than an ImageHandle as the loading screen hasn't
// run yet. To show your own splash images, replace or add here
Self(VecDeque::from_iter(["images/splash.png"]))
}
}
benfrankel marked this conversation as resolved.
Show resolved Hide resolved

fn exit_splash_screen(mut next_screen: ResMut<NextState<Screen>>) {
next_screen.set(Screen::Loading);
}

fn splash_image_bundle(asset_server: &AssetServer, path: &'static str) -> impl Bundle {
(
Name::new("Splash image"),
ImageBundle {
style: Style {
margin: UiRect::all(Val::Auto),
width: Val::Percent(70.0),
..default()
},
image: UiImage::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).
path,
|settings: &mut ImageLoaderSettings| {
// Make an exception for the splash image in case
// `ImagePlugin::default_nearest()` is used for pixel art.
settings.sampler = ImageSampler::linear();
},
)),
background_color: BackgroundColor(Color::srgba(0., 0., 0., 0.)),
..default()
},
UiImageFadeInOut {
total_duration: SPLASH_DURATION_SECS,
fade_duration: SPLASH_FADE_DURATION_SECS,
t: 0.0,
},
)
}

fn spawn_splash(
mut commands: Commands,
splash_images: Res<SplashScreenImageList>,
mut next_screen: ResMut<NextState<Screen>>,
) {
if splash_images.0.is_empty() {
will-hart marked this conversation as resolved.
Show resolved Hide resolved
// if there are no splash images, go directly to the loading screen
next_screen.set(Screen::Loading);
} else {
// spawn the UI root but wait for the first timer tick to show an image
commands.ui_root().insert((
Name::new("Splash screen container"),
BackgroundColor(SPLASH_BACKGROUND_COLOR),
StateScoped(Screen::Splash),
))
.with_children(|children| {
children.spawn((
Name::new("Splash image"),
ImageBundle {
style: Style {
margin: UiRect::all(Val::Auto),
width: Val::Percent(70.0),
..default()
},
image: UiImage::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();
},
)),
..default()
},
UiImageFadeInOut {
total_duration: SPLASH_DURATION_SECS,
fade_duration: SPLASH_FADE_DURATION_SECS,
t: 0.0,
},
));
});
SplashScreenContainer,
));
}
}

#[derive(Component, Reflect)]
Expand Down Expand Up @@ -135,8 +176,34 @@ fn tick_splash_timer(time: Res<Time>, mut timer: ResMut<SplashTimer>) {
timer.0.tick(time.delta());
}

fn check_splash_timer(timer: ResMut<SplashTimer>, mut next_screen: ResMut<NextState<Screen>>) {
if timer.0.just_finished() {
fn check_splash_timer(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut timer: ResMut<SplashTimer>,
mut next_screen: ResMut<NextState<Screen>>,
mut splash_screens: ResMut<SplashScreenImageList>,
containers: Query<Entity, With<SplashScreenContainer>>,
) {
if !timer.0.just_finished() {
return;
}

// try to get the next splash screen. If there isn't one, we move on to the next screen
if let Some(next_splash_image) = splash_screens.0.pop_front() {
benfrankel marked this conversation as resolved.
Show resolved Hide resolved
// despawn other splash images
if let Ok(container) = containers.get_single() {
will-hart marked this conversation as resolved.
Show resolved Hide resolved
commands
.entity(container)
.despawn_descendants()
.with_children(|parent| {
parent.spawn(splash_image_bundle(&asset_server, next_splash_image));
});
}

// reset the timer
timer.0.reset();
} else {
// no more splash screens, exit
next_screen.set(Screen::Loading);
}
}