Skip to content

Commit

Permalink
feat: loading and splash screens
Browse files Browse the repository at this point in the history
  • Loading branch information
eerii committed Jul 18, 2024
1 parent f0b7f85 commit 49daf1e
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 30 deletions.
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@ default = [ # Only in debug
release = [ # Only in release (build with --release --no-default-features --features release)
"bevy_embedded_assets",
"common",
"loading",
"tts",
]
common = ["input", "menu", "persist", "ui"]
common = ["menu", "persist"]

3d_camera = []
input = ["leafwing-input-manager"]
menu = ["navigation", "input"]
navigation = ["bevy-alt-ui-navigation-lite"]
loading = ["ui"]
menu = ["navigation", "input", "ui"]
navigation = ["bevy-alt-ui-navigation-lite", "ui"]
persist = ["bevy-persistent"]
pixel_perfect = []
resizable = []
Expand Down
19 changes: 15 additions & 4 deletions src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub struct AssetLoaderPlugin;
impl Plugin for AssetLoaderPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(LoadingData::default())
.add_systems(Startup, load_core)
.add_systems(OnEnter(GameState::Startup), load_core)
.add_systems(
OnEnter(GameState::Loading),
load_example,
Expand Down Expand Up @@ -62,7 +62,7 @@ pub struct ExampleAssets {
// Systems
// ·······

fn load_core(mut cmd: Commands, asset_server: Res<AssetServer>) {
pub(crate) fn load_core(mut cmd: Commands, asset_server: Res<AssetServer>) {
// They use the asset server directly
let assets = CoreAssets {
bevy_icon: asset_server.load(if cfg!(feature = "pixel_perfect") {
Expand Down Expand Up @@ -99,7 +99,7 @@ fn load_example(
// ·······

#[derive(Resource, Debug, Default)]
struct LoadingData {
pub(crate) struct LoadingData {
assets: Vec<UntypedHandle>,
loaded: usize,
total: usize,
Expand All @@ -118,7 +118,7 @@ impl LoadingData {
}

/// Returns the current loaded assets and the total assets registered
fn current(&mut self, asset_server: &AssetServer) -> (usize, usize) {
pub(crate) fn current(&mut self, asset_server: &AssetServer) -> (usize, usize) {
// Find assets that have already been loaded and remove them from the list
self.assets.retain(|asset| {
let Some(state) = asset_server.get_load_states(asset) else { return true };
Expand All @@ -142,10 +142,21 @@ impl LoadingData {
}

fn check_load_state(
#[cfg(feature = "loading")] curr_loading_state: Res<
State<crate::ui::loading::LoadingScreenState>,
>,
mut next_state: ResMut<NextState<GameState>>,
mut loading_data: ResMut<LoadingData>,
asset_server: Res<AssetServer>,
) {
#[cfg(feature = "loading")]
if !matches!(
curr_loading_state.get(),
crate::ui::loading::LoadingScreenState::Loading
) {
return;
}

let (loaded, total) = loading_data.current(&asset_server);
if loaded == total {
next_state.set(if cfg!(feature = "menu") { GameState::Menu } else { GameState::Play });
Expand Down
10 changes: 8 additions & 2 deletions src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
use bevy::prelude::*;

use crate::data::{GameOptions, Persistent};
use crate::{
data::{init_data, GameOptions, Persistent},
GameState,
};

/// The luminance of the background color
pub const BACKGROUND_LUMINANCE: f32 = 0.05;
Expand All @@ -18,7 +21,10 @@ pub struct CameraPlugin;

impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, init);
app.add_systems(
OnEnter(GameState::Startup),
init.after(init_data),
);
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};

#[cfg(not(feature = "persist"))]
pub use self::alt::Persistent;
use crate::GameState;

// ······
// Plugin
Expand All @@ -19,7 +20,7 @@ pub struct DataPlugin;

impl Plugin for DataPlugin {
fn build(&self, app: &mut App) {
app.add_systems(PreStartup, init);
app.add_systems(OnEnter(GameState::Startup), init_data);
}
}

Expand Down Expand Up @@ -102,7 +103,7 @@ mod alt {
// Systems
// ·······
#[cfg(feature = "persist")]
fn init(mut cmd: Commands) {
pub(crate) fn init_data(mut cmd: Commands) {
let path = std::path::Path::new(if cfg!(target_arch = "wasm32") { "local" } else { ".data" });
info!("{:?}", path);

Expand Down Expand Up @@ -132,7 +133,7 @@ fn init(mut cmd: Commands) {
}

#[cfg(not(feature = "persist"))]
fn init(mut cmd: Commands) {
pub(crate) fn init_data(mut cmd: Commands) {
cmd.insert_resource(Persistent(GameOptions::default()));
cmd.insert_resource(Persistent(SaveData::default()));
}
9 changes: 7 additions & 2 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use bevy::prelude::*;
pub use leafwing_input_manager::prelude::ActionState;
use leafwing_input_manager::prelude::*;

use crate::GameState;

// ······
// Plugin
// ······
Expand All @@ -16,12 +18,15 @@ pub struct InputPlugin;
impl Plugin for InputPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(InputManagerPlugin::<Action>::default())
.add_systems(Startup, init);
.add_systems(
OnEnter(GameState::Play),
init.run_if(run_once()),
);

#[cfg(feature = "menu")]
app.add_systems(
Update,
handle_input.run_if(in_state(crate::GameState::Play)),
handle_input.run_if(in_state(GameState::Play)),
);
}
}
Expand Down
25 changes: 23 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ use bevy::{log::LogPlugin, prelude::*, window::WindowResolution};
/// be deleted automatically when the state ends
#[derive(States, Debug, Default, Clone, Eq, PartialEq, Hash)]
pub enum GameState {
/// The game starts on the loading state
/// It stays here until all the relevant assets are ready
/// The game starts on the setup state
/// This runs before *anything*, including Startup
/// It inmediately transitions to loading, so only use it for OnEnter
#[default]
Startup,
/// After startup it transitions to loading, which handles splash screens
/// and assets. It stays here until all the relevant assets are ready
Loading,
/// The main menu of the game, everything is paused
Menu,
Expand Down Expand Up @@ -160,5 +164,22 @@ impl Plugin for GamePlugin {

#[cfg(feature = "ui")]
app.add_plugins(ui::UiPlugin);

app.add_systems(
Update,
finish_setup.run_if(in_state(GameState::Startup)),
);
}
}

// ·······
// Systems
// ·······

/// This system inmediately transitions Startup to Loading, ensuring that the
/// first only lasts for a frame and that only the OnEnter and OnExit schedules
/// are valid. This is a replacement for PreStartup and PostStartup that works
/// with the new 0.14 schedule ordering.
fn finish_setup(mut next_state: ResMut<NextState<GameState>>) {
next_state.set(GameState::Loading);
}
11 changes: 9 additions & 2 deletions src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
use bevy::prelude::*;
use sickle_ui::{prelude::*, SickleUiPlugin};

use crate::camera::FinalCamera;
use crate::{camera::FinalCamera, GameState};

#[cfg(feature = "loading")]
pub mod loading;
#[cfg(feature = "menu")]
pub mod menu;
#[cfg(feature = "navigation")]
Expand All @@ -13,6 +15,8 @@ pub mod navigation;
pub mod tts;
pub mod widgets;

const UI_GAP: Val = Val::Px(16.);

// ······
// Plugin
// ······
Expand All @@ -24,7 +28,10 @@ pub struct UiPlugin;
impl Plugin for UiPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(SickleUiPlugin)
.add_systems(PostStartup, init);
.add_systems(OnExit(GameState::Startup), init);

#[cfg(feature = "loading")]
app.add_plugins(loading::LoadingScreenPlugin);

#[cfg(feature = "menu")]
app.add_plugins(menu::MenuPlugin);
Expand Down
Loading

0 comments on commit 49daf1e

Please sign in to comment.