A Quadrilateral Cowboy clone intended to help me learn Game Dev
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}