Initial commit

This commit is contained in:
Elias Stepanik 2025-03-23 22:20:22 +01:00
commit fbee0a415a
21 changed files with 866 additions and 0 deletions

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
Cargo.lock
combine.sh
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/

17
Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "bevy-project-template"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy = { version = "0.15.1", features = ["jpeg", "trace_tracy", "trace_tracy_memory"] }
bevy_egui = "0.31.1"
bevy_asset = "0.15.0"
bevy-inspector-egui = "0.28.0"
bevy_reflect = "0.15.0"
bevy_render = "0.15.0"
bevy_window = "0.15.0"
egui_dock = "0.14.0"
bytemuck = "1.13"
bevy_mod_debugdump = "0.12.1"
log = "0.4.25"

Binary file not shown.

80
src/app.rs Normal file
View File

@ -0,0 +1,80 @@
use bevy::prelude::*;
use bevy_egui::EguiSet;
use bevy_render::extract_resource::ExtractResourcePlugin;
use crate::helper::debug_gizmos::debug_gizmos;
use crate::helper::egui_dock::{reset_camera_viewport, set_camera_viewport, set_gizmo_mode, show_ui_system, UiState};
pub struct AppPlugin;
#[derive(Resource, Debug)]
pub struct InspectorVisible(pub bool);
impl Default for InspectorVisible {
fn default() -> Self {
InspectorVisible(false)
}
}
impl Plugin for AppPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(UiState::new());
app.insert_resource(InspectorVisible(true));
app.add_plugins(crate::plugins::camera::camera_plugin::CameraPlugin);
app.add_plugins(crate::plugins::ui::ui_plugin::UiPlugin);
app.add_plugins(crate::plugins::environment::environment_plugin::EnvironmentPlugin);
app.add_systems(Update, (debug_gizmos, toggle_ui_system));
app.add_systems(
PostUpdate,
show_ui_system
.before(EguiSet::ProcessOutput)
.before(bevy_egui::systems::end_pass_system)
.before(TransformSystem::TransformPropagate)
.run_if(should_display_inspector),
);
app.add_systems(PostUpdate, (set_camera_viewport.after(show_ui_system).run_if(should_display_inspector), reset_camera_viewport.run_if(should_not_display_inspector).after(set_camera_viewport)));
app.add_systems(Update, set_gizmo_mode);
app.register_type::<Option<Handle<Image>>>();
app.register_type::<AlphaMode>();
}
}
fn toggle_ui_system(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut inspector_visible: ResMut<InspectorVisible>,
){
// =======================
// 6) Hide Inspector
// =======================
if keyboard_input.just_pressed(KeyCode::F1) {
inspector_visible.0 = !inspector_visible.0
}
}
fn should_display_inspector(inspector_visible: Res<InspectorVisible>) -> bool {
inspector_visible.0
}
fn should_not_display_inspector(inspector_visible: Res<InspectorVisible>) -> bool {
!inspector_visible.0
}

View File

@ -0,0 +1,21 @@
use bevy::color::palettes::css::{BLUE, GREEN, RED};
use bevy::prelude::*;
pub fn debug_gizmos(mut gizmos: Gizmos) {
/* // Draw a line
gizmos.line(
Vec3::ZERO,
Vec3::new(1.0, 1.0, 1.0),
RED,
);
// Draw a sphere
gizmos.sphere(Vec3::new(2.0, 2.0, 2.0), 0.5, BLUE);
// Draw a wireframe cube
gizmos.rect(Isometry3d::IDENTITY, Vec2::ONE, GREEN);*/
}

343
src/helper/egui_dock.rs Normal file
View File

@ -0,0 +1,343 @@
use bevy::prelude::*;
use bevy_asset::{ReflectAsset, UntypedAssetId};
use bevy_egui::{egui, EguiContext};
use bevy_inspector_egui::bevy_inspector::hierarchy::{hierarchy_ui, SelectedEntities};
use bevy_inspector_egui::bevy_inspector::{
self, ui_for_entities_shared_components, ui_for_entity_with_children,
};
use std::any::TypeId;
use bevy_reflect::TypeRegistry;
use bevy_render::camera::{CameraProjection, Viewport};
use bevy_window::{PrimaryWindow, Window};
use egui_dock::{DockArea, DockState, NodeIndex, Style};
#[cfg(egui_dock_gizmo)]
use transform_gizmo_egui::GizmoMode;
/// Placeholder type if gizmo is disabled.
#[cfg(not(egui_dock_gizmo))]
#[derive(Clone, Copy)]
pub struct GizmoMode;
#[derive(Component)]
pub struct MainCamera;
pub fn show_ui_system(world: &mut World) {
let Ok(egui_context) = world
.query_filtered::<&mut EguiContext, With<PrimaryWindow>>()
.get_single(world)
else {
return;
};
let mut egui_context = egui_context.clone();
world.resource_scope::<UiState, _>(|world, mut ui_state| {
ui_state.ui(world, egui_context.get_mut())
});
}
// make camera only render to view not obpub structed by UI
pub fn set_camera_viewport(
ui_state: Res<UiState>,
primary_window: Query<&mut Window, With<PrimaryWindow>>,
egui_settings: Query<&bevy_egui::EguiSettings>,
mut cameras: Query<&mut Camera, With<MainCamera>>,
) {
let mut cam = cameras.single_mut();
let Ok(window) = primary_window.get_single() else {
return;
};
let scale_factor = window.scale_factor() * egui_settings.single().scale_factor;
let viewport_pos = ui_state.viewport_rect.left_top().to_vec2() * scale_factor;
let viewport_size = ui_state.viewport_rect.size() * scale_factor;
let physical_position = UVec2::new(viewport_pos.x as u32, viewport_pos.y as u32);
let physical_size = UVec2::new(viewport_size.x as u32, viewport_size.y as u32);
// The desired viewport rectangle at its offset in "physical pixel space"
let rect = physical_position + physical_size;
let window_size = window.physical_size();
// wgpu will panic if trying to set a viewport rect which has coordinates extending
// past the size of the render target, i.e. the physical window in our case.
// Typically this shouldn't happen- but during init and resizing etc. edge cases might occur.
// Simply do nothing in those cases.
if rect.x <= window_size.x && rect.y <= window_size.y {
cam.viewport = Some(Viewport {
physical_position,
physical_size,
depth: 0.0..1.0,
});
}
}
pub fn reset_camera_viewport(mut cameras: Query<&mut Camera, With<MainCamera>>) {
if let Ok(mut cam) = cameras.get_single_mut() {
cam.viewport = None; // Reset the viewport to its default state
}
}
pub fn set_gizmo_mode(input: Res<ButtonInput<KeyCode>>, mut ui_state: ResMut<UiState>) {
#[cfg(egui_dock_gizmo)]
let keybinds = [
(KeyCode::KeyR, GizmoMode::Rotate),
(KeyCode::KeyT, GizmoMode::Translate),
(KeyCode::KeyS, GizmoMode::Scale),
];
#[cfg(not(egui_dock_gizmo))]
let keybinds = [];
for (key, mode) in keybinds {
if input.just_pressed(key) {
ui_state.gizmo_mode = mode;
}
}
}
#[derive(Eq, PartialEq)]
pub enum InspectorSelection {
Entities,
Resource(TypeId, String),
Asset(TypeId, String, UntypedAssetId),
}
#[derive(Resource)]
pub struct UiState {
state: DockState<EguiWindow>,
viewport_rect: egui::Rect,
selected_entities: SelectedEntities,
selection: InspectorSelection,
gizmo_mode: GizmoMode,
}
impl UiState {
pub fn new() -> Self {
let mut state = DockState::new(vec![EguiWindow::GameView]);
let tree = state.main_surface_mut();
let [game, _inspector] =
tree.split_right(NodeIndex::root(), 0.75, vec![EguiWindow::Inspector]);
let [game, _hierarchy] = tree.split_left(game, 0.2, vec![EguiWindow::Hierarchy]);
let [_game, _bottom] =
tree.split_below(game, 0.8, vec![EguiWindow::Resources, EguiWindow::Assets]);
Self {
state,
selected_entities: SelectedEntities::default(),
selection: InspectorSelection::Entities,
viewport_rect: egui::Rect::NOTHING,
#[cfg(egui_dock_gizmo)]
gizmo_mode: GizmoMode::Translate,
#[cfg(not(egui_dock_gizmo))]
gizmo_mode: GizmoMode,
}
}
pub fn ui(&mut self, world: &mut World, ctx: &mut egui::Context) {
let mut tab_viewer = TabViewer {
world,
viewport_rect: &mut self.viewport_rect,
selected_entities: &mut self.selected_entities,
selection: &mut self.selection,
gizmo_mode: self.gizmo_mode,
};
DockArea::new(&mut self.state)
.style(Style::from_egui(ctx.style().as_ref()))
.show(ctx, &mut tab_viewer);
}
}
#[derive(Debug)]
pub enum EguiWindow {
GameView,
Hierarchy,
Resources,
Assets,
Inspector,
}
pub struct TabViewer<'a> {
world: &'a mut World,
selected_entities: &'a mut SelectedEntities,
selection: &'a mut InspectorSelection,
viewport_rect: &'a mut egui::Rect,
gizmo_mode: GizmoMode,
}
impl egui_dock::TabViewer for TabViewer<'_> {
type Tab = EguiWindow;
fn ui(&mut self, ui: &mut egui_dock::egui::Ui, window: &mut Self::Tab) {
let type_registry = self.world.resource::<AppTypeRegistry>().0.clone();
let type_registry = type_registry.read();
match window {
EguiWindow::GameView => {
*self.viewport_rect = ui.clip_rect();
draw_gizmo(ui, self.world, self.selected_entities, self.gizmo_mode);
}
EguiWindow::Hierarchy => {
let selected = hierarchy_ui(self.world, ui, self.selected_entities);
if selected {
*self.selection = InspectorSelection::Entities;
}
}
EguiWindow::Resources => select_resource(ui, &type_registry, self.selection),
EguiWindow::Assets => select_asset(ui, &type_registry, self.world, self.selection),
EguiWindow::Inspector => match *self.selection {
InspectorSelection::Entities => match self.selected_entities.as_slice() {
&[entity] => ui_for_entity_with_children(self.world, entity, ui),
entities => ui_for_entities_shared_components(self.world, entities, ui),
},
InspectorSelection::Resource(type_id, ref name) => {
ui.label(name);
bevy_inspector::by_type_id::ui_for_resource(
self.world,
type_id,
ui,
name,
&type_registry,
)
}
InspectorSelection::Asset(type_id, ref name, handle) => {
ui.label(name);
bevy_inspector::by_type_id::ui_for_asset(
self.world,
type_id,
handle,
ui,
&type_registry,
);
}
},
}
}
fn title(&mut self, window: &mut Self::Tab) -> egui_dock::egui::WidgetText {
format!("{window:?}").into()
}
fn clear_background(&self, window: &Self::Tab) -> bool {
!matches!(window, EguiWindow::GameView)
}
}
#[allow(unused)]
pub fn draw_gizmo(
ui: &mut egui::Ui,
world: &mut World,
selected_entities: &SelectedEntities,
gizmo_mode: GizmoMode,
) {
let (cam_transform, projection) = world
.query_filtered::<(&GlobalTransform, &Projection), With<MainCamera>>()
.single(world);
let view_matrix = Mat4::from(cam_transform.affine().inverse());
let projection_matrix = projection.get_clip_from_view();
if selected_entities.len() != 1 {
#[allow(clippy::needless_return)]
return;
}
/*for selected in selected_entities.iter() {
let Some(transform) = world.get::<Transform>(selected) else {
continue;
};
let model_matrix = transform.compute_matrix();
let mut gizmo = Gizmo::new(GizmoConfig {
view_matrix: view_matrix.into(),
projection_matrix: projection_matrix.into(),
orientation: GizmoOrientation::Local,
modes: EnumSet::from(gizmo_mode),
..Default::default()
});
let Some([result]) = gizmo
.interact(ui, model_matrix.into())
.map(|(_, res)| res.as_slice())
else {
continue;
};
let mut transform = world.get_mut::<Transform>(selected).unwrap();
transform = Transform {
translation: Vec3::from(<[f64; 3]>::from(result.translation)),
rotation: Quat::from_array(<[f64; 4]>::from(result.rotation)),
scale: Vec3::from(<[f64; 3]>::from(result.scale)),
};
}*/
}
pub fn select_resource(
ui: &mut egui::Ui,
type_registry: &TypeRegistry,
selection: &mut InspectorSelection,
) {
let mut resources: Vec<_> = type_registry
.iter()
.filter(|registration| registration.data::<ReflectResource>().is_some())
.map(|registration| {
(
registration.type_info().type_path_table().short_path(),
registration.type_id(),
)
})
.collect();
resources.sort_by(|(name_a, _), (name_b, _)| name_a.cmp(name_b));
for (resource_name, type_id) in resources {
let selected = match *selection {
InspectorSelection::Resource(selected, _) => selected == type_id,
_ => false,
};
if ui.selectable_label(selected, resource_name).clicked() {
*selection = InspectorSelection::Resource(type_id, resource_name.to_string());
}
debug!("{}", resource_name);
}
}
pub fn select_asset(
ui: &mut egui::Ui,
type_registry: &TypeRegistry,
world: &World,
selection: &mut InspectorSelection,
) {
let mut assets: Vec<_> = type_registry
.iter()
.filter_map(|registration| {
let reflect_asset = registration.data::<ReflectAsset>()?;
Some((
registration.type_info().type_path_table().short_path(),
registration.type_id(),
reflect_asset,
))
})
.collect();
assets.sort_by(|(name_a, ..), (name_b, ..)| name_a.cmp(name_b));
for (asset_name, asset_type_id, reflect_asset) in assets {
let handles: Vec<_> = reflect_asset.ids(world).collect();
ui.collapsing(format!("{asset_name} ({})", handles.len()), |ui| {
for handle in handles {
let selected = match *selection {
InspectorSelection::Asset(_, _, selected_id) => selected_id == handle,
_ => false,
};
if ui
.selectable_label(selected, format!("{:?}", handle))
.clicked()
{
*selection =
InspectorSelection::Asset(asset_type_id, asset_name.to_string(), handle);
}
}
});
}
}

2
src/helper/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod egui_dock;
pub mod debug_gizmos;

88
src/main.rs Normal file
View File

@ -0,0 +1,88 @@
mod plugins;
mod app;
mod helper;
use bevy::DefaultPlugins;
use bevy::gizmos::{AppGizmoBuilder, GizmoPlugin};
use bevy::log::info;
use bevy::prelude::{default, App, GizmoConfigGroup, PluginGroup, Reflect, Res, Resource};
use bevy::render::RenderPlugin;
use bevy::render::settings::{Backends, RenderCreation, WgpuSettings};
use bevy_egui::{EguiPlugin};
use bevy_inspector_egui::DefaultInspectorConfigPlugin;
use bevy_window::{PresentMode, Window, WindowPlugin};
use crate::app::AppPlugin;
const TITLE: &str = "Project Template";
const RESOLUTION: (f32,f32) = (1920f32, 1080f32);
const RESIZABLE: bool = true;
const DECORATIONS: bool = true;
const TRANSPARENT: bool = true;
const PRESENT_MODE: PresentMode = PresentMode::AutoVsync;
fn main() {
let mut app = App::new();
register_platform_plugins(&mut app);
app.add_plugins(AppPlugin);
app.add_plugins(EguiPlugin);
app.add_plugins(DefaultInspectorConfigPlugin);
/*app.add_plugins(GizmoPlugin);*/
app.run();
}
#[derive(Resource)]
pub struct InspectorVisible(bool);
fn register_platform_plugins(app: &mut App) {
#[cfg(target_os = "windows")]
{
// Register Windows-specific plugins
info!("Adding Windows-specific plugins");
app.add_plugins(DefaultPlugins
.set(RenderPlugin {
render_creation: RenderCreation::Automatic(WgpuSettings {
backends: Some(Backends::VULKAN),
..default()
}),
..default()
})
.set(WindowPlugin {
primary_window: Some(Window {
title: TITLE.to_string(), // Window title
resolution: RESOLUTION.into(), // Initial resolution (width x height)
resizable: RESIZABLE, // Allow resizing
decorations: DECORATIONS, // Enable window decorations
transparent: TRANSPARENT, // Opaque background
present_mode: PRESENT_MODE, // VSync mode
..default()
}),
..default()
})
);
}
#[cfg(target_os = "macos")]
{
info!("Adding macOS-specific plugins");
app.add_plugins(DefaultPlugins)
.set(WindowPlugin {
primary_window: Some(Window {
title: crate::TITLE.to_string(), // Window title
resolution: crate::RESOLUTION.into(), // Initial resolution (width x height)
resizable: crate::RESIZABLE, // Allow resizing
decorations: crate::DECORATIONS, // Enable window decorations
transparent: crate::TRANSPARENT, // Opaque background
present_mode: crate::PRESENT_MODE, // VSync mode
..default()
}),
..default()
});
}
}
fn should_display_inspector(inspector_visible: Res<InspectorVisible>) -> bool {
inspector_visible.0
}

View File

@ -0,0 +1,12 @@
use bevy::a11y::AccessibilitySystem::Update;
use bevy::app::{App, Plugin, PreUpdate, Startup};
pub struct CameraPlugin;
impl Plugin for CameraPlugin {
fn build(&self, _app: &mut App) {
_app.add_systems(Startup, (crate::plugins::camera::systems::camera_system::setup));
_app.add_systems(PreUpdate, (crate::plugins::camera::systems::camera_system::camera_controller_system));
}
}

View File

@ -0,0 +1,2 @@
pub mod camera_plugin;
pub mod systems;

View File

@ -0,0 +1,167 @@
use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::math::{Vec3};
use bevy::prelude::*;
use bevy_render::camera::{Exposure, PhysicalCameraParameters, Projection};
use bevy_window::CursorGrabMode;
use crate::helper::egui_dock::MainCamera;
#[derive(Component)]
pub struct CameraController {
pub yaw: f32,
pub pitch: f32,
pub speed: f32,
pub sensitivity: f32,
}
impl Default for CameraController {
fn default() -> Self {
Self {
yaw: 0.0,
pitch: 0.0,
speed: 10.0,
sensitivity: 0.1,
}
}
}
pub fn setup(mut commands: Commands,){
commands.spawn((
Transform::from_xyz(0.0, 0.0, 10.0), // initial f32
GlobalTransform::default(),
Camera3d::default(),
Projection::from(PerspectiveProjection{
near: 0.0001,
..default()
}),
MainCamera,
CameraController::default(),
Exposure::from_physical_camera(PhysicalCameraParameters {
aperture_f_stops: 1.0,
shutter_speed_s: 1.0 / 125.0,
sensitivity_iso: 100.0,
sensor_height: 0.01866,
}),
));
}
/// Example system to control a camera using double-precision for position.
pub fn camera_controller_system(
time: Res<Time>,
keyboard_input: Res<ButtonInput<KeyCode>>,/*
mouse_button_input: Res<ButtonInput<MouseButton>>,*/
mut mouse_motion_events: EventReader<MouseMotion>,
mut mouse_wheel_events: EventReader<MouseWheel>,
mut windows: Query<&mut Window>,
mut query: Query<(&mut Transform, &mut CameraController)>,
mut app_exit_events: EventWriter<AppExit>,
) {
let mut window = windows.single_mut();
let (mut transform, mut controller) = query.single_mut();
// ====================
// 1) Handle Mouse Look
// ====================
if !window.cursor_options.visible {
for event in mouse_motion_events.read() {
// Adjust yaw/pitch in f32
controller.yaw -= event.delta.x * controller.sensitivity;
controller.pitch += event.delta.y * controller.sensitivity;
controller.pitch = controller.pitch.clamp(-89.9, 89.9);
// Convert degrees to radians (f32)
let yaw_radians = controller.yaw.to_radians();
let pitch_radians = controller.pitch.to_radians();
// Build a double-precision quaternion from those angles
let rot_yaw = Quat::from_axis_angle(Vec3::Y, yaw_radians);
let rot_pitch = Quat::from_axis_angle(Vec3::X, -pitch_radians);
transform.rotation = rot_yaw * rot_pitch;
}
}
// ====================
// 2) Adjust Movement Speed with Mouse Wheel
// ====================
for event in mouse_wheel_events.read() {
let base_factor = 1.1_f32;
let factor = base_factor.powf(event.y);
controller.speed *= factor;
if controller.speed < 0.01 {
controller.speed = 0.01;
}
}
// ====================
// 3) Handle Keyboard Movement (WASD, Space, Shift)
// ====================
let mut direction = Vec3::ZERO;
// Forward/Back
if keyboard_input.pressed(KeyCode::KeyW) {
direction += transform.forward().as_vec3();
}
if keyboard_input.pressed(KeyCode::KeyS) {
direction -= transform.forward().as_vec3();
}
// Left/Right
if keyboard_input.pressed(KeyCode::KeyA) {
direction -= transform.right().as_vec3();
}
if keyboard_input.pressed(KeyCode::KeyD) {
direction += transform.right().as_vec3();
}
// Up/Down
if keyboard_input.pressed(KeyCode::Space) {
direction += transform.up().as_vec3();
}
if keyboard_input.pressed(KeyCode::ShiftLeft) || keyboard_input.pressed(KeyCode::ShiftRight) {
direction -= transform.up().as_vec3();
}
// Normalize direction if needed
if direction.length_squared() > 0.0 {
direction = direction.normalize();
}
// Apply movement in double-precision
let delta_seconds = time.delta_secs_f64();
let distance = controller.speed as f64 * delta_seconds;
transform.translation += direction * distance as f32;
// =========================
// 4) Lock/Unlock Mouse (L)
// =========================
if keyboard_input.just_pressed(KeyCode::KeyL) {
// Toggle between locked and unlocked
if window.cursor_options.grab_mode == CursorGrabMode::None {
// Lock
window.cursor_options.visible = false;
window.cursor_options.grab_mode = CursorGrabMode::Locked;
} else {
// Unlock
window.cursor_options.visible = true;
window.cursor_options.grab_mode = CursorGrabMode::None;
}
}
// =======================
// 7) Exit on Escape
// =======================
if keyboard_input.pressed(KeyCode::Escape) {
app_exit_events.send(Default::default());
}
}

View File

@ -0,0 +1 @@
pub mod camera_system;

View File

@ -0,0 +1,15 @@
use bevy::app::{App, Plugin, Startup};
use bevy::color::palettes::basic::{GREEN, YELLOW};
use bevy::color::palettes::css::RED;
use bevy::prelude::*;
pub struct EnvironmentPlugin;
impl Plugin for EnvironmentPlugin {
fn build(&self, app: &mut App) {
}
}

View File

@ -0,0 +1,2 @@
pub mod environment_plugin;
pub mod systems;

View File

@ -0,0 +1,11 @@
use bevy::color::palettes::basic::*;
use bevy::color::palettes::css::{BEIGE, MIDNIGHT_BLUE, ORANGE, ORANGE_RED, SEA_GREEN};
use bevy::math::*;
use bevy::pbr::{CascadeShadowConfigBuilder, NotShadowCaster};
use bevy::prelude::*;
pub fn setup(mut commands: Commands,) {
// Insert the octree into the ECS
}

View File

@ -0,0 +1 @@
pub mod environment_system;

3
src/plugins/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod camera;
pub mod ui;
pub mod environment;

2
src/plugins/ui/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod systems;
pub mod ui_plugin;

View File

@ -0,0 +1 @@
pub mod ui_system;

View File

@ -0,0 +1,64 @@
use bevy::asset::AssetServer;
use bevy::prelude::*;
use crate::plugins::camera::systems::camera_system::CameraController;
#[derive(Component)]
pub struct SpeedDisplay;
/// Spawns a UI Text entity to display speed/positions.
pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Use the new UI API, or the old UI Node-based system.
// This example uses an older approach to Node/Style, but can be adapted to `TextBundle`.
// If you're on Bevy 0.11+, you can also do `TextBundle::from_section(...)`.
commands.spawn((
// The text to display:
Text::new("Speed: 0.0"),
// The font, loaded from an asset file
TextFont {
font: asset_server.load("fonts/minecraft_font.ttf"),
font_size: 25.0,
..default()
},
// The text layout style
TextLayout::new_with_justify(JustifyText::Left),
// Style for positioning the UI node
Node {
position_type: PositionType::Relative,
bottom: Val::Px(9.0),
right: Val::Px(9.0),
..default()
},
// Our marker so we can query this entity
SpeedDisplay,
));
}
/// System that updates the UI text each frame with
/// - speed
/// - camera f32 position
/// - camera global f64 position
/// - current chunk coordinate
pub fn update(
// Query the camera controller so we can see its speed
query_camera_controller: Query<&CameraController>,
// We also query for the camera's f32 `Transform` and the double `DoubleTransform`
camera_query: Query<(&Transform, &Camera)>,
// The UI text entity
mut query_text: Query<&mut Text, With<SpeedDisplay>>,
) {
let camera_controller = query_camera_controller.single();
let (transform, _camera) = camera_query.single();
let mut text = query_text.single_mut();
// Format the string to show speed, positions, and chunk coords
text.0 = format!(
"\n Speed: {:.3}\n Position(f32): ({:.2},{:.2},{:.2})",
camera_controller.speed,
transform.translation.x,
transform.translation.y,
transform.translation.z,
);
}

View File

@ -0,0 +1,15 @@
use bevy::app::{App, FixedUpdate, Plugin, PreUpdate, Startup};
use bevy::prelude::IntoSystemConfigs;
use crate::plugins::ui::systems::ui_system::*;
pub struct UiPlugin;
impl Plugin for UiPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup);
app.add_systems(FixedUpdate, update);
}
}