Download src/ui.rs from EHS-Studio/hieroxel: direct link, hf CLI and curl.
- Browser
- Download file 9.2 kB
-
https://huggingface.co/EHS-Studio/hieroxel/resolve/main/src/ui.rs
- Command line
-
hf download hf://EHS-Studio/hieroxel/src/ui.rs
-
curl -L -o ui.rs https://huggingface.co/EHS-Studio/hieroxel/resolve/main/src/ui.rs
9.2 kB
| //! HUD: crosshair, hotbar, debug overlay, controls help. | |
| use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; | |
| use bevy::prelude::*; | |
| use bevy::window::{PresentMode, PrimaryWindow}; | |
| use crate::player::Player; | |
| use crate::render::{DayCycle, VoxelMaterials}; | |
| use crate::world::block; | |
| use crate::world::living::Living; | |
| use crate::world::VoxelWorld; | |
| pub struct HudPlugin; | |
| impl Plugin for HudPlugin { | |
| fn build(&self, app: &mut App) { | |
| app.add_systems(PostStartup, spawn_hud) | |
| .add_systems(Update, (update_hotbar, update_debug, toggle_panels)); | |
| } | |
| } | |
| struct HotbarSlot(usize); | |
| struct HotbarIcon(usize); | |
| struct SelectedName; | |
| struct DebugText; | |
| struct HelpText; | |
| const HELP: &str = "Click to play | WASD move | Space jump | Shift sprint | F fly (Space/Ctrl up/down)\n\ | |
| LMB break | RMB place | MMB pick | 1-9 / wheel slot | Q/E change block\n\ | |
| T fast-forward time | +/- view distance | V vsync | G pause living world | C cave culling\n\ | |
| F3 debug | F2 screenshot | F11 fullscreen | H hide help | Esc release mouse"; | |
| fn text_font(size: f32) -> TextFont { | |
| TextFont { | |
| font_size: size.into(), | |
| ..default() | |
| } | |
| } | |
| fn shadow() -> TextShadow { | |
| TextShadow { | |
| offset: Vec2::splat(1.5), | |
| color: Color::srgba(0.0, 0.0, 0.0, 0.75), | |
| } | |
| } | |
| fn spawn_hud(mut commands: Commands, mats: Res<VoxelMaterials>, player: Res<Player>) { | |
| // Crosshair. | |
| commands | |
| .spawn(Node { | |
| position_type: PositionType::Absolute, | |
| width: Val::Percent(100.0), | |
| height: Val::Percent(100.0), | |
| justify_content: JustifyContent::Center, | |
| align_items: AlignItems::Center, | |
| ..default() | |
| }) | |
| .with_children(|root| { | |
| root.spawn(( | |
| Node { | |
| width: Val::Px(2.0), | |
| height: Val::Px(18.0), | |
| position_type: PositionType::Absolute, | |
| ..default() | |
| }, | |
| BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.85)), | |
| )); | |
| root.spawn(( | |
| Node { | |
| width: Val::Px(18.0), | |
| height: Val::Px(2.0), | |
| position_type: PositionType::Absolute, | |
| ..default() | |
| }, | |
| BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.85)), | |
| )); | |
| }); | |
| // Hotbar. | |
| commands | |
| .spawn(Node { | |
| position_type: PositionType::Absolute, | |
| width: Val::Percent(100.0), | |
| bottom: Val::Px(14.0), | |
| flex_direction: FlexDirection::Column, | |
| align_items: AlignItems::Center, | |
| row_gap: Val::Px(6.0), | |
| ..default() | |
| }) | |
| .with_children(|root| { | |
| root.spawn(( | |
| SelectedName, | |
| Text::new(""), | |
| text_font(16.0), | |
| TextColor(Color::WHITE), | |
| shadow(), | |
| )); | |
| root.spawn(Node { | |
| flex_direction: FlexDirection::Row, | |
| column_gap: Val::Px(4.0), | |
| padding: UiRect::all(Val::Px(4.0)), | |
| ..default() | |
| }) | |
| .insert(BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.35))) | |
| .with_children(|bar| { | |
| for i in 0..9 { | |
| bar.spawn(( | |
| HotbarSlot(i), | |
| Node { | |
| width: Val::Px(48.0), | |
| height: Val::Px(48.0), | |
| border: UiRect::all(Val::Px(3.0)), | |
| justify_content: JustifyContent::Center, | |
| align_items: AlignItems::Center, | |
| ..default() | |
| }, | |
| BorderColor::all(Color::srgba(0.0, 0.0, 0.0, 0.5)), | |
| BackgroundColor(Color::srgba(0.1, 0.1, 0.12, 0.6)), | |
| )) | |
| .with_children(|slot| { | |
| slot.spawn(( | |
| HotbarIcon(i), | |
| ImageNode::new(mats.icons[player.hotbar[i] as usize].clone()), | |
| Node { | |
| width: Val::Px(36.0), | |
| height: Val::Px(36.0), | |
| ..default() | |
| }, | |
| )); | |
| }); | |
| } | |
| }); | |
| }); | |
| commands.spawn(( | |
| DebugText, | |
| Text::new(""), | |
| text_font(15.0), | |
| TextColor(Color::WHITE), | |
| shadow(), | |
| Node { | |
| position_type: PositionType::Absolute, | |
| left: Val::Px(10.0), | |
| top: Val::Px(8.0), | |
| ..default() | |
| }, | |
| )); | |
| commands.spawn(( | |
| HelpText, | |
| Text::new(HELP), | |
| text_font(13.0), | |
| TextColor(Color::srgba(1.0, 1.0, 1.0, 0.85)), | |
| shadow(), | |
| Node { | |
| position_type: PositionType::Absolute, | |
| right: Val::Px(10.0), | |
| top: Val::Px(8.0), | |
| ..default() | |
| }, | |
| )); | |
| } | |
| fn update_hotbar( | |
| player: Res<Player>, | |
| mats: Res<VoxelMaterials>, | |
| mut slots: Query<(&HotbarSlot, &mut BorderColor)>, | |
| mut icons: Query<(&HotbarIcon, &mut ImageNode)>, | |
| mut name: Query<&mut Text, With<SelectedName>>, | |
| ) { | |
| if !player.is_changed() { | |
| return; | |
| } | |
| for (slot, mut border) in &mut slots { | |
| *border = if slot.0 == player.slot { | |
| BorderColor::all(Color::srgb(1.0, 0.92, 0.55)) | |
| } else { | |
| BorderColor::all(Color::srgba(0.0, 0.0, 0.0, 0.5)) | |
| }; | |
| } | |
| for (icon, mut image) in &mut icons { | |
| let handle = &mats.icons[player.hotbar[icon.0] as usize]; | |
| if image.image != *handle { | |
| image.image = handle.clone(); | |
| } | |
| } | |
| if let Ok(mut text) = name.single_mut() { | |
| text.0 = block::info(player.selected()).name.to_string(); | |
| } | |
| } | |
| fn update_debug( | |
| diagnostics: Res<DiagnosticsStore>, | |
| world: Res<VoxelWorld>, | |
| player: Res<Player>, | |
| cycle: Res<DayCycle>, | |
| living: Res<Living>, | |
| windows: Query<&Window, With<PrimaryWindow>>, | |
| mut text: Query<&mut Text, With<DebugText>>, | |
| time: Res<Time>, | |
| mut timer: Local<f32>, | |
| ) { | |
| *timer -= time.delta_secs(); | |
| if *timer > 0.0 { | |
| return; | |
| } | |
| *timer = 0.25; | |
| let Ok(mut text) = text.single_mut() else { return }; | |
| let fps = diagnostics | |
| .get(&FrameTimeDiagnosticsPlugin::FPS) | |
| .and_then(|d| d.smoothed()) | |
| .unwrap_or(0.0); | |
| let ms = diagnostics | |
| .get(&FrameTimeDiagnosticsPlugin::FRAME_TIME) | |
| .and_then(|d| d.smoothed()) | |
| .unwrap_or(0.0); | |
| let vsync = windows | |
| .single() | |
| .map(|w| matches!(w.present_mode, PresentMode::AutoVsync | PresentMode::Fifo)) | |
| .unwrap_or(false); | |
| let p = player.feet; | |
| let s = world.generator.surface(p.x.floor() as i32, p.z.floor() as i32); | |
| let facing = { | |
| let a = (-player.yaw).rem_euclid(std::f32::consts::TAU); | |
| ["N", "NE", "E", "SE", "S", "SW", "W", "NW"][((a / std::f32::consts::FRAC_PI_4) + 0.5) as usize % 8] | |
| }; | |
| let mesh_avg = if world.stats.mesh_jobs > 0 { | |
| world.stats.mesh_micros as f64 / world.stats.mesh_jobs as f64 / 1000.0 | |
| } else { | |
| 0.0 | |
| }; | |
| let target = player | |
| .target | |
| .and_then(|(hit, _)| world.get(hit).map(|b| format!("{} at {} {} {}", block::info(b).name, hit.x, hit.y, hit.z))) | |
| .unwrap_or_else(|| "-".into()); | |
| text.0 = format!( | |
| "Hieroxel {fps:.0} fps ({ms:.2} ms) vsync {}\n\ | |
| XYZ {:.1} {:.1} {:.1} facing {facing} {} {}\n\ | |
| Looking at: {target}\n\ | |
| View {} chunks ({} blocks) columns {} meshed chunks {} ({} cave-culled) triangles {:.2}M\n\ | |
| Workers {} pending {} mesh avg {mesh_avg:.2} ms\n\ | |
| Time {} living world {} ({} changes)", | |
| if vsync { "on" } else { "off" }, | |
| p.x, | |
| p.y, | |
| p.z, | |
| s.biome.name(), | |
| if player.flying { "flying" } else { "" }, | |
| world.render_distance, | |
| world.render_distance * 32, | |
| world.columns.len(), | |
| world.stats.meshed_chunks, | |
| world.culled_chunks, | |
| world.stats.triangles as f64 / 1e6, | |
| world.workers, | |
| world.pending_work(), | |
| cycle.clock(), | |
| if living.enabled { "on" } else { "paused" }, | |
| living.growth_events, | |
| ); | |
| } | |
| fn toggle_panels( | |
| keys: Res<ButtonInput<KeyCode>>, | |
| mut debug: Query<&mut Visibility, (With<DebugText>, Without<HelpText>)>, | |
| mut help: Query<&mut Visibility, (With<HelpText>, Without<DebugText>)>, | |
| ) { | |
| let flip = |v: &mut Visibility| { | |
| *v = if *v == Visibility::Hidden { Visibility::Inherited } else { Visibility::Hidden }; | |
| }; | |
| if keys.just_pressed(KeyCode::F3) { | |
| debug.iter_mut().for_each(|mut v| flip(&mut v)); | |
| } | |
| if keys.just_pressed(KeyCode::KeyH) { | |
| help.iter_mut().for_each(|mut v| flip(&mut v)); | |
| } | |
| } | |