use bevy::{gltf::GltfNode, prelude::*}; use bevy_common_assets::ron::RonAssetPlugin; use crate::game::GameState; use std::{fs, path}; pub fn plugin(app: &mut App) { app.add_plugins(RonAssetPlugin::::new(&["level.ron"])) .add_systems(OnEnter(GameState::Loading), load_levels) .add_systems(OnEnter(GameState::Main), list_levels) .add_systems(OnEnter(GameState::Main), draw_menu) .add_systems(Update, level_buttons.run_if(in_state(GameState::Main))) .add_systems(OnExit(GameState::Main), unload_menu) .add_systems(OnEnter(LoadingSceneDataState), load_level_scene) .add_systems(Update, load_level.run_if(in_state(LoadingLevelState))) .add_computed_state::() .add_computed_state::() .add_computed_state::() .init_resource::(); } #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct LoadingSceneDataState; impl ComputedStates for LoadingSceneDataState { type SourceStates = GameState; fn compute(sources: Self::SourceStates) -> Option { match sources { GameState::LoadingSceneData(_) => Some(LoadingSceneDataState), _ => None } } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct LoadingLevelState; impl ComputedStates for LoadingLevelState { type SourceStates = GameState; fn compute(sources: Self::SourceStates) -> Option { match sources { GameState::LoadingLevel(_) => Some(LoadingLevelState), _ => None } } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct LevelState; impl ComputedStates for LevelState { type SourceStates = GameState; fn compute(sources: Self::SourceStates) -> Option { match sources { GameState::Level(_) => Some(LevelState), _ => None } } } #[derive(serde::Deserialize, bevy::asset::Asset, bevy::reflect::TypePath, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct Level { mesh_path: String, pub name: String } #[derive(Resource)] pub struct Levels { pub levels: Vec> } #[derive(Component)] struct LevelData(pub Level); #[derive(Component)] struct MainMenu; #[derive(Component)] struct LevelComp; fn load_levels(mut commands: Commands, asset_server: Res, mut next_state: ResMut>) { commands.insert_resource(Levels{ levels: fs::read_dir(path::Path::new("assets").join("levels")).unwrap().map(|entry| -> Option> { let entry = entry.unwrap(); if entry.path().is_dir() { None } else if entry.path().to_str().unwrap().ends_with(".level.ron") { Some(asset_server.load(entry.path().to_str().unwrap().replace("assets/", ""))) } else { None } }).filter(|val| -> bool { matches!(val, Some(_)) }).map(|val| -> Handle { val.unwrap() }).collect::>>() }); next_state.set(GameState::Main); } fn list_levels(levels: Res, level_data: Res>) { println!("Levels: {}", levels.levels.iter().map(|val| { level_data.get(val).unwrap().name.clone() }).collect::>().join(", ")); } fn draw_menu(mut commands: Commands, levels: Res, level_data: Res>, asset_server: Res) { commands.spawn((Camera::default(), Camera2d, MainMenu)); commands.spawn(( Node{ width: percent(100), height: percent(100), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() }, MainMenu )).with_children(move |parent| { for level in &levels.levels { let level_handle = level_data.get(level).unwrap(); let a_s = asset_server.clone(); parent.spawn(( Button, Node{ width: px(150), height: px(65), border: UiRect::all(px(5)), justify_content: JustifyContent::Center, align_items: AlignItems::Center, border_radius: BorderRadius::MAX, ..default() }, BorderColor::all(Color::WHITE), BackgroundColor(Color::BLACK), LevelData(level_handle.clone()) )).with_children(move |parent| { parent.spawn(( Text::new(level_handle.name.clone()), TextFont{ font: a_s.load("fonts/NotoSans.ttf"), font_size: 24.0, ..default() }, TextColor(Color::srgb(0.9, 0.9, 0.9)), TextShadow::default() )); }); } }); } fn level_buttons(interaction_query: Query<(&Interaction, &Button, &LevelData), Changed>, mut next_state: ResMut>) { for (interaction, _button, level_data) in &interaction_query { match *interaction { Interaction::Pressed => { next_state.set(GameState::LoadingSceneData(level_data.0.clone())) }, _ => () } } } fn unload_menu(mut commands: Commands, menu_query: Query<(Entity, &MainMenu)>) { println!("Unloading main menu stuff"); for (entity, _main_menu) in &menu_query { commands.entity(entity).despawn(); } } #[derive(Resource, Default)] struct LevelSceneData(pub Handle); fn load_level_scene(state: Res>, asset_server: Res, mut next_state: ResMut>, mut scene_data: ResMut) { let level_data = match state.clone() { GameState::LoadingSceneData(val) => { val.to_owned() }, _ => panic!("This shouldn't fire, but panic if it does") }; println!("Loading {}", &level_data.name); scene_data.0 = asset_server.load(level_data.mesh_path.clone()); next_state.set(GameState::LoadingLevel(level_data)); } fn load_level(mut commands: Commands, state: Res>, scene_data: Res, gltfs: Res>, gltf_nodes: Res>, mut next_state: ResMut>) { let level_data = match state.clone() { GameState::LoadingLevel(val) => { val.to_owned() }, _ => panic!("This shouldn't fire, but panic if it does") }; if let Some(scene_node) = gltfs.get(&scene_data.0) { commands.spawn(( LevelComp, SceneRoot(scene_node.scenes[0].clone()) )); if let Some(player_start_node) = gltf_nodes.get(scene_node.named_nodes.get("PlayerStart").unwrap()) { commands.spawn(( Camera3d::default(), Camera::default(), player_start_node.transform.clone() )); } else { panic!("Could not get player start node"); } next_state.set(GameState::Level(level_data.clone())); } }