mod blocks; mod debug; mod fighter; use engine::*; use serde::Deserialize; use thiserror::Error; use blocks::*; use debug::*; use fighter::*; fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugins(FeathersPlugins) .add_plugins((BlocksPlugin, FighterPlugin, DebugPlugin)) .init_state::() .init_state::() .init_resource::() .init_resource::() // Check if assets were added to loading queue .add_systems( Update, loading_check .run_if(in_state(Loading(true))) .run_if(resource_changed::), ) // Wait for pending assets to be loaded .add_systems(Update, loading_wait.run_if(in_state(Loading(true)))) // Once done loading, move to the setup state .add_systems(OnEnter(Loading(false)), setup_game) // Check if the game is ready to progress .add_systems(Update, setup_wait.run_if(in_state(GameState::Setup))) // State toggles .add_systems(Update, ( ( toggle_state_visibility::, sync_state_to_ui::, ).run_if(state_changed::), ( toggle_state_visibility::, sync_state_to_ui::, ).run_if(state_changed::) )) .run(); } /// Reports if the game is loading assets #[derive(States, Clone, Eq, Debug, PartialEq, Hash, Component)] struct Loading(bool); impl Default for Loading { fn default() -> Self { Loading(true) } } impl fmt::Display for Loading { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Loading {}", self.0) } } /// Tracks what state the main game loop is in #[derive(States, Default, Clone, Eq, Debug, PartialEq, Hash, Component)] enum GameState { #[default] Boot, Setup, Run, } impl fmt::Display for GameState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { GameState::Boot => write!(f, "Game State: Boot"), GameState::Setup => write!(f, "GameState: Setup"), GameState::Run => write!(f, "GameState: Run"), } } } /// A list of all assets so we don't lose them #[derive(Default, Resource, Debug)] struct AllAssets { handles: Vec, } /// A "checklist" to know if we can progress from setup to the game #[derive(Default, Resource)] struct SetupChecklist { spawn_shape: bool, } impl SetupChecklist { fn done(&self) -> bool { self.spawn_shape } } /// Sends the game into Loading::Active if assets are added to the AllAssets list fn loading_check(mut next: ResMut>, all_assets: Res) { debug_assert!(all_assets.is_changed()); next.set(Loading(true)); } /// Waits in Loading::Active until all assets are loaded then move to Loading(false) fn loading_wait( curr: Res>, mut next: ResMut>, server: Res, all_assets: Res, ) { debug_assert!(*curr.get() == Loading(true)); if all_assets .handles .iter() .all(|h| matches!(server.get_load_state(h.id()), Some(LoadState::Loaded))) { next.set(Loading(false)); } } /// Moves the game from Boot to Setup fn setup_game(curr: Res>, mut next: ResMut>) { debug_assert!(*curr.get() == GameState::Boot); next.set(GameState::Setup); } /// Wait until all checklist items are complete fn setup_wait( curr: Res>, mut next: ResMut>, checklist: Res, ) { debug_assert!(*curr.get() == GameState::Setup); // If all checks pass, move on to the run state if checklist.done() { next.set(GameState::Run); } } /// A wrapper around a handle for assigning an arbitrary Handle to an entity #[derive(Debug, Component)] struct AssetComponent { handle: Handle, } impl AssetComponent { fn new(handle: Handle) -> Self { Self { handle } } }