A Quadrilateral Cowboy clone intended to help me learn Game Dev
at rust-bevy 219 lines 7.3 kB view raw
1use bevy::{gltf::GltfNode, prelude::*}; 2use bevy_common_assets::ron::RonAssetPlugin; 3use crate::game::GameState; 4use std::{fs, path}; 5 6pub fn plugin(app: &mut App) { 7 app.add_plugins(RonAssetPlugin::<Level>::new(&["level.ron"])) 8 .add_systems(OnEnter(GameState::Loading), load_levels) 9 .add_systems(OnEnter(GameState::Main), list_levels) 10 .add_systems(OnEnter(GameState::Main), draw_menu) 11 .add_systems(Update, level_buttons.run_if(in_state(GameState::Main))) 12 .add_systems(OnExit(GameState::Main), unload_menu) 13 .add_systems(OnEnter(LoadingSceneDataState), load_level_scene) 14 .add_systems(Update, load_level.run_if(in_state(LoadingLevelState))) 15 .add_computed_state::<LoadingLevelState>() 16 .add_computed_state::<LevelState>() 17 .add_computed_state::<LoadingSceneDataState>() 18 .init_resource::<LevelSceneData>(); 19} 20 21#[derive(Debug, Clone, PartialEq, Eq, Hash)] 22struct LoadingSceneDataState; 23 24impl ComputedStates for LoadingSceneDataState { 25 type SourceStates = GameState; 26 27 fn compute(sources: Self::SourceStates) -> Option<Self> { 28 match sources { 29 GameState::LoadingSceneData(_) => Some(LoadingSceneDataState), 30 _ => None 31 } 32 } 33} 34 35#[derive(Debug, Clone, PartialEq, Eq, Hash)] 36struct LoadingLevelState; 37 38impl ComputedStates for LoadingLevelState { 39 type SourceStates = GameState; 40 41 fn compute(sources: Self::SourceStates) -> Option<Self> { 42 match sources { 43 GameState::LoadingLevel(_) => Some(LoadingLevelState), 44 _ => None 45 } 46 } 47} 48 49#[derive(Debug, Clone, PartialEq, Eq, Hash)] 50struct LevelState; 51 52impl ComputedStates for LevelState { 53 type SourceStates = GameState; 54 55 fn compute(sources: Self::SourceStates) -> Option<Self> { 56 match sources { 57 GameState::Level(_) => Some(LevelState), 58 _ => None 59 } 60 } 61} 62 63#[derive(serde::Deserialize, bevy::asset::Asset, bevy::reflect::TypePath, Clone, Debug, PartialEq, Eq, Hash, Default)] 64pub struct Level { 65 mesh_path: String, 66 pub name: String 67} 68 69#[derive(Resource)] 70pub struct Levels { 71 pub levels: Vec<Handle<Level>> 72} 73 74#[derive(Component)] 75struct LevelData(pub Level); 76 77#[derive(Component)] 78struct MainMenu; 79 80#[derive(Component)] 81struct LevelComp; 82 83fn load_levels(mut commands: Commands, asset_server: Res<AssetServer>, mut next_state: ResMut<NextState<GameState>>) { 84 commands.insert_resource(Levels{ 85 levels: fs::read_dir(path::Path::new("assets").join("levels")).unwrap().map(|entry| -> Option<Handle<Level>> { 86 let entry = entry.unwrap(); 87 88 if entry.path().is_dir() { 89 None 90 } else if entry.path().to_str().unwrap().ends_with(".level.ron") { 91 Some(asset_server.load(entry.path().to_str().unwrap().replace("assets/", ""))) 92 } else { 93 None 94 } 95 }).filter(|val| -> bool { 96 matches!(val, Some(_)) 97 }).map(|val| -> Handle<Level> { 98 val.unwrap() 99 }).collect::<Vec<Handle<Level>>>() 100 }); 101 102 next_state.set(GameState::Main); 103} 104 105fn list_levels(levels: Res<Levels>, level_data: Res<Assets<Level>>) { 106 println!("Levels: {}", levels.levels.iter().map(|val| { 107 level_data.get(val).unwrap().name.clone() 108 }).collect::<Vec<String>>().join(", ")); 109} 110 111fn draw_menu(mut commands: Commands, levels: Res<Levels>, level_data: Res<Assets<Level>>, asset_server: Res<AssetServer>) { 112 commands.spawn((Camera::default(), Camera2d, MainMenu)); 113 commands.spawn(( 114 Node{ 115 width: percent(100), 116 height: percent(100), 117 align_items: AlignItems::Center, 118 justify_content: JustifyContent::Center, 119 ..default() 120 }, 121 MainMenu 122 )).with_children(move |parent| { 123 for level in &levels.levels { 124 let level_handle = level_data.get(level).unwrap(); 125 let a_s = asset_server.clone(); 126 127 parent.spawn(( 128 Button, 129 Node{ 130 width: px(150), 131 height: px(65), 132 border: UiRect::all(px(5)), 133 justify_content: JustifyContent::Center, 134 align_items: AlignItems::Center, 135 border_radius: BorderRadius::MAX, 136 ..default() 137 }, 138 BorderColor::all(Color::WHITE), 139 BackgroundColor(Color::BLACK), 140 LevelData(level_handle.clone()) 141 )).with_children(move |parent| { 142 parent.spawn(( 143 Text::new(level_handle.name.clone()), 144 TextFont{ 145 font: a_s.load("fonts/NotoSans.ttf"), 146 font_size: 24.0, 147 ..default() 148 }, 149 TextColor(Color::srgb(0.9, 0.9, 0.9)), 150 TextShadow::default() 151 )); 152 }); 153 } 154 }); 155} 156 157fn level_buttons(interaction_query: Query<(&Interaction, &Button, &LevelData), Changed<Interaction>>, mut next_state: ResMut<NextState<GameState>>) { 158 for (interaction, _button, level_data) in &interaction_query { 159 match *interaction { 160 Interaction::Pressed => { 161 next_state.set(GameState::LoadingSceneData(level_data.0.clone())) 162 }, 163 _ => () 164 } 165 } 166} 167 168fn unload_menu(mut commands: Commands, menu_query: Query<(Entity, &MainMenu)>) { 169 println!("Unloading main menu stuff"); 170 171 for (entity, _main_menu) in &menu_query { 172 commands.entity(entity).despawn(); 173 } 174} 175 176#[derive(Resource, Default)] 177struct LevelSceneData(pub Handle<Gltf>); 178 179fn load_level_scene(state: Res<State<GameState>>, asset_server: Res<AssetServer>, mut next_state: ResMut<NextState<GameState>>, mut scene_data: ResMut<LevelSceneData>) { 180 let level_data = match state.clone() { 181 GameState::LoadingSceneData(val) => { 182 val.to_owned() 183 }, 184 _ => panic!("This shouldn't fire, but panic if it does") 185 }; 186 187 println!("Loading {}", &level_data.name); 188 scene_data.0 = asset_server.load(level_data.mesh_path.clone()); 189 next_state.set(GameState::LoadingLevel(level_data)); 190} 191 192fn load_level(mut commands: Commands, state: Res<State<GameState>>, scene_data: Res<LevelSceneData>, gltfs: Res<Assets<Gltf>>, gltf_nodes: Res<Assets<GltfNode>>, mut next_state: ResMut<NextState<GameState>>) { 193 let level_data = match state.clone() { 194 GameState::LoadingLevel(val) => { 195 val.to_owned() 196 }, 197 _ => panic!("This shouldn't fire, but panic if it does") 198 }; 199 200 if let Some(scene_node) = gltfs.get(&scene_data.0) { 201 202 commands.spawn(( 203 LevelComp, 204 SceneRoot(scene_node.scenes[0].clone()) 205 )); 206 207 if let Some(player_start_node) = gltf_nodes.get(scene_node.named_nodes.get("PlayerStart").unwrap()) { 208 commands.spawn(( 209 Camera3d::default(), 210 Camera::default(), 211 player_start_node.transform.clone() 212 )); 213 } else { 214 panic!("Could not get player start node"); 215 } 216 217 next_state.set(GameState::Level(level_data.clone())); 218 } 219}