A Quadrilateral Cowboy clone intended to help me learn Game Dev
at rust-bevy 215 lines 7.1 kB view raw
1pub fn plugin(app: &mut App) { 2 app 3 .add_systems(OnEnter(GameState::Test), run_test) 4 .add_systems(OnEnter(GameState::TestLoading), load_resources) 5 .add_systems(Update, check_input.run_if( 6 in_state(GameState::Test) 7 )); 8} 9 10#[derive(Component)] 11struct Terminal; 12 13#[derive(Component, Default)] 14struct CurrentInput(String); 15 16enum ConsoleEntry { 17 Command(String), 18 Error(String), 19 Output(String) 20} 21 22#[derive(Component, Default)] 23struct ConsoleHistory(Vec<ConsoleEntry>); 24 25#[derive(Component)] 26struct TerminalText; 27 28fn load_resources(mut commands: Commands, asset_server: Res<AssetServer>, mut next_state: ResMut<NextState<GameState>>) { 29 let assets = GameAssets{ 30 terminal_primitive0: asset_server.load("models/terminal.glb#Mesh0/Primitive0"), 31 terminal_primitive1: asset_server.load("models/terminal.glb#Mesh0/Primitive1"), 32 terminal_chassis_mat: asset_server.load("models/terminal.glb#Material0") 33 }; 34 commands.insert_resource(assets); 35 next_state.set(GameState::Test); 36} 37 38fn run_test(mut commands: Commands, game_assets: Res<GameAssets>, mut meshes: ResMut<Assets<Mesh>>, mut images: ResMut<Assets<Image>>, mut materials: ResMut<Assets<StandardMaterial>>, asset_server: Res<AssetServer>) { 39 if let Some(mesh) = meshes.get_mut(&game_assets.terminal_primitive1) { 40 // A simple Quad (4 vertices) UV mapping 41 let uvs = vec![ 42 [1.0, 1.0], [0.0, 1.0], // Top Left, Top Right 43 [1.0, 0.0], [0.0, 0.0] // Bottom Left, Bottom Right 44 ]; 45 46 // This overwrites the existing UV_0 attribute 47 mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); 48 } 49 50 let size = Extent3d{ 51 width: 512, 52 height: 512, 53 ..default() 54 }; 55 56 let mut image = Image::new_fill( 57 size, 58 TextureDimension::D2, 59 &[0, 0, 0, 0], 60 TextureFormat::Bgra8UnormSrgb, 61 RenderAssetUsages::default() 62 ); 63 64 image.texture_descriptor.usage = 65 TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT; 66 67 let image_handle = images.add(image); 68 69 commands.spawn(DirectionalLight::default()); 70 71 let texture_camera = commands.spawn(( 72 Camera2d, 73 Camera{ 74 order: -1, 75 ..default() 76 }, 77 RenderTarget::Image(image_handle.clone().into()), 78 )).id(); 79 80 commands.spawn(( 81 Node{ 82 width: percent(100), 83 height: percent(100), 84 align_items: AlignItems::Start, 85 ..default() 86 }, 87 BackgroundColor(bevy::color::palettes::css::BLACK.into()), 88 UiTargetCamera(texture_camera) 89 )).with_children(|parent| { 90 parent.spawn(( 91 Text::new(""), 92 TextFont{ 93 font_size: 14.0, 94 font: asset_server.load("fonts/NotoMono.ttf"), 95 ..default() 96 }, 97 TextColor::WHITE, 98 TerminalText 99 )); 100 }); 101 102 let material_handle = materials.add(StandardMaterial{ 103 base_color_texture: Some(image_handle), 104 reflectance: 0.02, 105 unlit: false, 106 uv_transform: Affine2::from_scale_angle_translation( 107 Vec2::new(1.0, 1.0), // Scale (e.g., 2.0 zooms in) 108 0.0, // Rotation in radians 109 Vec2::new(0.0, 0.0) // Offset (X, Y) 110 ), 111 ..default() 112 }); 113 114 // terminal mesh 115 commands.spawn(( 116 Node::default(), 117 Transform::from_translation(Vec3::ZERO).with_rotation(Quat::from_rotation_y(-std::f32::consts::FRAC_PI_2)), 118 CurrentInput::default(), 119 Terminal, 120 ConsoleHistory::default() 121 )).with_children(|parent| { 122 parent.spawn(( 123 Mesh3d(game_assets.terminal_primitive0.clone()), 124 MeshMaterial3d(game_assets.terminal_chassis_mat.clone()) 125 )); 126 127 parent.spawn(( 128 Mesh3d(game_assets.terminal_primitive1.clone()), 129 MeshMaterial3d(material_handle) 130 )); 131 }); 132 133 // light 134 commands.spawn(( 135 PointLight{ 136 shadows_enabled: true, 137 ..default() 138 }, 139 Transform::from_xyz(4.0, 8.0, 4.0).looking_at(Vec3::ZERO, Vec3::Y) 140 )); 141 142 // camera 143 commands.spawn(( 144 Camera3d::default(), 145 Transform::from_xyz(0.5, -1.0, 3.5).looking_at(Vec3::new(0.0, -1.0, 0.0), Vec3::Y) 146 )); 147 148 commands.spawn(terminal::TerminalSystem); 149 150 commands.spawn(terminal::TerminalObject::new(String::from("door1"), terminal::TObjectType::Door)); 151} 152 153fn check_input(mut keyboard_input_reader: MessageReader<KeyboardInput>, mut query: Query<(&mut CurrentInput, &Terminal, &mut ConsoleHistory)>, mut terminal_query: Query<(&mut Text, &TerminalText)>, mut terminal_system: Query<&mut terminal::TerminalSystem>, mut commands: Commands) { 154 let mut current_input = query.single_mut().unwrap(); 155 let mut current_terminal_text = terminal_query.single_mut().unwrap(); 156 let mut terminal = terminal_system.single_mut().unwrap(); 157 158 for keyboard_input in keyboard_input_reader.read() { 159 if !keyboard_input.state.is_pressed() { 160 continue; 161 } 162 163 current_terminal_text.0.0.clear(); 164 165 match (&keyboard_input.logical_key, &keyboard_input.text) { 166 (Key::Enter, _) => { 167 if current_input.0.0.is_empty() { 168 continue; 169 } 170 171 current_input.2.0.push(ConsoleEntry::Command(current_input.0.0.to_owned())); 172 let nodes = terminal.parse(current_input.0.0.clone()); 173 commands.trigger(terminal::TerminalEvent::new(nodes)); 174 current_input.0.0.clear(); 175 }, 176 (Key::Backspace, _) => { 177 current_input.0.0.pop(); 178 }, 179 (_, Some(inserted_text)) => { 180 if inserted_text.chars().all(is_printable_char) { 181 current_input.0.0.push_str(inserted_text); 182 } 183 }, 184 _ => continue, 185 } 186 187 for entry in current_input.2.0.iter() { 188 match entry { 189 ConsoleEntry::Command(cmd) => { 190 current_terminal_text.0.0 += cmd; 191 }, 192 ConsoleEntry::Output(output) => { 193 current_terminal_text.0.0 += output; 194 // TODO: Need to figure out how to change color of text line-by-line 195 }, 196 ConsoleEntry::Error(error) => { 197 current_terminal_text.0.0 += error; 198 // TODO: Need to figure out how to change color of text line-by-line 199 } 200 } 201 202 current_terminal_text.0.0 += "\n"; 203 } 204 205 current_terminal_text.0.0 += &current_input.0.0; 206 } 207} 208 209fn is_printable_char(chr: char) -> bool { 210 let is_in_private_use_area = ('\u{e000}'..='\u{f8ff}').contains(&chr) 211 || ('\u{f0000}'..='\u{ffffd}').contains(&chr) 212 || ('\u{100000}'..='\u{10fffd}').contains(&chr); 213 214 !is_in_private_use_area && !chr.is_ascii_control() 215}