From 4f95599f8a7914184e2f4e9f7cf3a4815d8805fe Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:58:53 -0300 Subject: [PATCH 1/2] feat: Rigidbodies improvement (#74) * Removes RotSprite RotSprite was too slow for realtime stuff. This also adds some new debug visualization. * Better rapier2d colliders * Better collider loading/deloading --- assets/player/jetpack.png | Bin 0 -> 199 bytes src/chunk.rs | 1 - src/chunk_manager.rs | 143 +++++++++++++++++++++----------- src/consts.rs | 1 + src/debug.rs | 92 +++++++++++++++------ src/main.rs | 2 +- src/rigidbody.rs | 166 ++++++++------------------------------ 7 files changed, 197 insertions(+), 208 deletions(-) create mode 100644 assets/player/jetpack.png diff --git a/assets/player/jetpack.png b/assets/player/jetpack.png new file mode 100644 index 0000000000000000000000000000000000000000..7652922b698eb601938ee9d4a1b91b9a69bb6452 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRM!3HF?&tBaMq)I$p9780g=Jp@tZBXEGwa=Jw zTH4y6GyUD0BQ9=x6LwtLva;dg^eId1^|!P-MJXt)G|Ue2$>cfNexYhLQ`Xvuzopr06@S$?EnA( literal 0 HcmV?d00001 diff --git a/src/chunk.rs b/src/chunk.rs index 4977571..15a238a 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -93,7 +93,6 @@ impl Chunk { &self.get_values(materials), CHUNK_LENGHT as u32, CHUNK_LENGHT as u32, - (0., 0.), ) } diff --git a/src/chunk_manager.rs b/src/chunk_manager.rs index d4644a3..503767c 100644 --- a/src/chunk_manager.rs +++ b/src/chunk_manager.rs @@ -218,80 +218,117 @@ pub fn manager_setup( pub fn add_colliders( mut commands: Commands, chunk_manager: Res, - chunks: Query<(Entity, &ChunkComponent)>, - rigidbodies: Query<(&Transform, &Rigidbody)>, + chunks: Query<(Entity, &ChunkComponent), Without>, materials: (Res>, Res), + has_collider: Res, ) { - let materials = materials.0.get(materials.1 .0.clone()).unwrap(); + puffin::profile_function!(); + + let Some(materials) = materials.0.get(materials.1 .0.clone()) else { + return; + }; - let mut rects = vec![]; + if has_collider.0.is_empty() { + return; + } + for (ent, pos) in &chunks { + for rect in has_collider.0.iter() { + if rect.contains(pos.0) { + if let Some(chunk) = chunk_manager.chunks.get(&pos.0) { + let collider = chunk.get_collider(materials); + + if let Some(collider) = collider { + commands + .entity(ent) + .insert(collider) + .insert(bevy_rapier2d::prelude::RigidBody::Fixed); + } + } + + break; + } + } + } +} + +pub fn remove_colliders( + mut commands: Commands, + chunks: Query<(Entity, &ChunkComponent), With>, + has_collider: Res, +) { + if has_collider.0.is_empty() { + return; + } + + for (ent, pos) in &chunks { + let mut contains = false; + for rect in has_collider.0.iter() { + if rect.contains(pos.0) { + contains = true; + break; + } + } + + if !contains { + //If none contains, remove collider and go to next chunk + //Remove collider + if let Some(mut entity) = commands.get_entity(ent) { + entity.remove::(); + entity.remove::(); + } + } + } +} + +pub fn update_has_collider( + mut has_collider: ResMut, + rigidbodies: Query<(&Transform, &Rigidbody)>, +) { + has_collider.0 = vec![]; for (transform, rigidbody) in &rigidbodies { - let l = std::f32::consts::SQRT_2 * (rigidbody.width as f32).max(rigidbody.height as f32); + let angle = -transform.rotation.to_euler(EulerRot::XYZ).2; + let mut center = transform.translation.xy(); + center.y *= -1.; + center += vec2(rigidbody.width as f32, rigidbody.height as f32) + .rotate(Vec2::from_angle(angle)) + / 2.; + let l = std::f32::consts::SQRT_2 * (rigidbody.width as f32).max(rigidbody.height as f32); let angle = std::f32::consts::FRAC_PI_4 + std::f32::consts::PI; - let mut top_left = transform.translation.xy(); - top_left.y *= -1.; + let mut top_left = center; top_left += vec2(angle.cos(), angle.sin()) * l / 2.; let angle = std::f32::consts::FRAC_PI_4; - let mut down_right = transform.translation.xy(); - down_right.y *= -1.; + let mut down_right = center; down_right += vec2(angle.cos(), angle.sin()) * l / 2.; /*{ //Some debug visualization let mut top_left = top_left; top_left.y *= -1.; - gizmos.circle_2d(top_left, 10., Color::RED); + gizmos.circle_2d(top_left, 5., Color::RED); let mut down_right = down_right; down_right.y *= -1.; - gizmos.circle_2d(down_right, 10., Color::BLACK); + gizmos.circle_2d(down_right, 5., Color::BLACK); }*/ + const LOADING_OFF: f32 = 1.5; + top_left -= vec2(CHUNK_LENGHT as f32, CHUNK_LENGHT as f32) * LOADING_OFF; + down_right += vec2(CHUNK_LENGHT as f32, CHUNK_LENGHT as f32) * LOADING_OFF; + top_left /= CHUNK_LENGHT as f32; down_right /= CHUNK_LENGHT as f32; - const LOADING_OFF: i32 = 2; let bounds_rect = IRect::new( - top_left.x as i32 - LOADING_OFF, - top_left.y as i32 - LOADING_OFF, - down_right.x as i32 + LOADING_OFF, - down_right.y as i32 + LOADING_OFF, + top_left.x as i32, + top_left.y as i32, + down_right.x as i32, + down_right.y as i32, ); - rects.push(bounds_rect); - } - - if !rects.is_empty() { - 'chunks: for (ent, pos) in &chunks { - for (i, rect) in rects.iter().enumerate() { - //If on bounds continue by breaking this loop - if rect.contains(pos.0) { - break; - } else if i == rects.len() - 1 { - //If none contains, remove collider and go to next chunk - //Remove collider - if let Some(mut entity) = commands.get_entity(ent) { - entity.remove::(); - entity.remove::(); - } - continue 'chunks; - } - } - - if let Some(chunk) = chunk_manager.chunks.get(&pos.0) { - let collider = chunk.get_collider(materials); - - if let Some(collider) = collider { - commands - .entity(ent) - .insert(collider) - .insert(bevy_rapier2d::prelude::RigidBody::Fixed); - } - } - } + has_collider.0.push(bounds_rect); } } @@ -670,6 +707,9 @@ fn clear_render_rect(mut dirty_rects: ResMut) { dirty_rects.render = HashMap::new(); } +#[derive(Resource, Default)] +pub struct HasCollider(pub Vec); + pub struct ChunkManagerPlugin; impl Plugin for ChunkManagerPlugin { fn build(&self, app: &mut App) { @@ -680,12 +720,19 @@ impl Plugin for ChunkManagerPlugin { ) .add_systems( Update, - (update_manager_pos, add_colliders).run_if(in_state(GameState::Game)), + ( + update_manager_pos, + add_colliders.after(update_has_collider), + remove_colliders.after(update_has_collider), + update_has_collider, + ) + .run_if(in_state(GameState::Game)), ) .add_systems( PreUpdate, clear_render_rect.run_if(in_state(GameState::Game)), ) + .init_resource::() .init_resource::() .init_resource::(); diff --git a/src/consts.rs b/src/consts.rs index f2f9b7e..4fbbf51 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -51,6 +51,7 @@ pub const _CAMERA_SPEED: f32 = 10.; pub const PLAYER_LAYER: f32 = 1.; pub const PARTICLE_LAYER: f32 = 10.; pub const AUTOMATA_LAYER: f32 = 100.; +pub const RIGIDBODY_LAYER: f32 = 1000.; //Buttons pub const NORMAL_BUTTON: Color = Color::rgb(0.15, 0.15, 0.15); diff --git a/src/debug.rs b/src/debug.rs index 837efb4..c6a88e0 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -95,7 +95,32 @@ fn prev_mpos( //Debug Render systems -pub fn render_dirty_rects(mut commands: Commands, dirty_rects: Res) { +pub fn render_dirty_rects_gizmos(mut gizmos: Gizmos, dirty_rects: Res) { + let (dirty_update, render_update) = (&dirty_rects.new, &dirty_rects.render); + + let mut i = false; + for rect in [dirty_update, render_update] { + for (chunk_pos, rect) in rect { + let size = + UVec2::new(rect.max.x - rect.min.x + 1, rect.max.y - rect.min.y + 1).as_vec2(); + let position = ivec2( + chunk_pos.x * CHUNK_LENGHT as i32 + rect.min.x as i32, + -(chunk_pos.y * CHUNK_LENGHT as i32) - rect.min.y as i32, + ) + .as_vec2() + + vec2(size.x, -(size.y)) / 2.; + + if !i { + gizmos.rect_2d_gradient(position, 0., size); + } else { + gizmos.rect_2d(position, 0., size, Color::rgba(1., 0.25, 1., 1.)); + } + } + i = true; + } +} + +pub fn _render_dirty_rects_image(mut commands: Commands, dirty_rects: Res) { let (dirty_update, render_update) = (&dirty_rects.new, &dirty_rects.render); let mut i = 0.; @@ -128,28 +153,7 @@ pub fn render_dirty_rects(mut commands: Commands, dirty_rects: Res) } } -fn render_actors(mut commands: Commands, actors: Query<&Actor>) { - for actor in actors.iter() { - commands - .spawn(SpriteBundle { - sprite: Sprite { - color: Color::rgba(0.75, 0.25, 0.25, 0.2), - custom_size: Some(Vec2::new(actor.width as f32, actor.height as f32)), - anchor: Anchor::TopLeft, - ..default() - }, - transform: Transform::from_translation(Vec3::new( - actor.pos.x as f32, - -actor.pos.y as f32, - 1., - )), - ..default() - }) - .insert(DeleteImage); - } -} - -pub fn delete_image(mut commands: Commands, debug_images: Query>) { +pub fn _delete_image(mut commands: Commands, debug_images: Query>) { for image in debug_images.iter() { commands.entity(image).despawn(); } @@ -158,6 +162,18 @@ pub fn delete_image(mut commands: Commands, debug_images: Query) { + for actor in actors.iter() { + gizmos.rect_2d( + vec2(actor.pos.x as f32, -actor.pos.y as f32) + + vec2(actor.width as f32, -(actor.height as f32)) / 2., + 0., + Vec2::new(actor.width as f32, actor.height as f32), + Color::rgba(0.75, 0.25, 0.25, 0.2), + ); + } +} + fn _camera(keys: Res>, mut camera_q: Query<&mut Transform, With>) { let x = -(keys.pressed(KeyCode::A) as u8 as f32) + keys.pressed(KeyCode::D) as u8 as f32; let y = -(keys.pressed(KeyCode::S) as u8 as f32) + keys.pressed(KeyCode::W) as u8 as f32; @@ -166,13 +182,40 @@ fn _camera(keys: Res>, mut camera_q: Query<&mut Transform, With Rect2dGradient for Gizmos<'a> { + fn rect_2d_gradient(&mut self, position: Vec2, rotation: f32, size: Vec2) { + let rotation = Mat2::from_angle(rotation); + let [tl, tr, br, bl] = rect_inner(size).map(|vec2| position + rotation * vec2); + self.linestrip_gradient_2d([ + (tl, Color::RED), + (tr, Color::GREEN), + (br, Color::BLUE), + (bl, Color::PURPLE), + (tl, Color::GREEN), + ]); + } +} + +fn rect_inner(size: Vec2) -> [Vec2; 4] { + let half_size = size / 2.; + let tl = Vec2::new(-half_size.x, half_size.y); + let tr = Vec2::new(half_size.x, half_size.y); + let bl = Vec2::new(-half_size.x, -half_size.y); + let br = Vec2::new(half_size.x, -half_size.y); + [tl, tr, br, bl] +} + pub struct DebugPlugin; impl Plugin for DebugPlugin { fn build(&self, app: &mut App) { app.add_systems( Update, ( - render_dirty_rects.after(update_actors), + render_dirty_rects_gizmos.after(update_actors), brush.after(chunk_manager_update), render_actors.after(update_actors), prev_mpos.after(brush), @@ -180,7 +223,6 @@ impl Plugin for DebugPlugin { ) .run_if(in_state(GameState::Game)), ) - .add_systems(PreUpdate, delete_image.run_if(in_state(GameState::Game))) .add_plugins(WorldInspectorPlugin::new()) .add_plugins(RapierDebugRenderPlugin::default()) //Frame on console diff --git a/src/main.rs b/src/main.rs index 849aea8..347c40d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,7 +69,7 @@ fn main() { ParticlesPlugin, MaterialsPlugin, CameraPlugin, - //RigidbodyPlugin, + RigidbodyPlugin, )) .add_plugins(( RapierPhysicsPlugin::::pixels_per_meter(6.), diff --git a/src/rigidbody.rs b/src/rigidbody.rs index 46bb3ef..0c93fa2 100644 --- a/src/rigidbody.rs +++ b/src/rigidbody.rs @@ -1,9 +1,6 @@ use crate::prelude::*; -use bevy::render::render_resource::*; -use bevy::render::*; -use geo::{Simplify, TriangulateEarcut}; +use geo::{SimplifyVwPreserve, TriangulateEarcut}; use itertools::Itertools; -use smallvec::{SmallVec, ToSmallVec}; #[derive(Component)] pub struct Rigidbody { @@ -12,20 +9,10 @@ pub struct Rigidbody { pub height: u8, // Positions to remove Object atom later pub filled: Vec, - pub texture: Handle, - pub texture_ent: Entity, - pub text_update: Option, -} - -impl Rigidbody { - pub fn texture_lenght(&self) -> u32 { - const OFF: f32 = 1.2; - ((self.width as f32 * OFF).powi(2) + (self.height as f32 * OFF).powi(2)).sqrt() as u32 - } } #[derive(Component, Default)] -pub struct RigidbodyHandle(pub Handle); +pub struct RigidbodyHandle(Handle, Vec2); #[derive(Component)] pub struct Hydrated; @@ -33,64 +20,44 @@ pub struct Hydrated; //TODO Add rigidbody file pub fn load_images(mut commands: Commands, server: Res) { let image: Handle = server.load("tree.png"); - commands.spawn(RigidbodyHandle(image.clone())); + commands.spawn(RigidbodyHandle(image.clone(), vec2(64., -64.))); + + let image: Handle = server.load("player/player_tool.png"); + commands.spawn(RigidbodyHandle(image.clone(), vec2(0., 0.))); + let image: Handle = server.load("player/jetpack.png"); + commands.spawn(RigidbodyHandle(image.clone(), vec2(-64., 0.))); } pub fn add_rigidbodies( mut commands: Commands, - mut images: ResMut>, + images: Res>, handles: Query<(Entity, &RigidbodyHandle), Without>, ) { for (ent, handle) in &handles { let image = images.get(handle.0.clone()).unwrap().clone(); - let lenght = image.width().max(image.height()) * 2; - let new_image = Image::new( - Extent3d { - height: lenght, - width: lenght, - ..Default::default() - }, - TextureDimension::D2, - vec![0; (lenght as usize).pow(2) * 4], - TextureFormat::Rgba8UnormSrgb, - ); - let texture = images.add(new_image); - - let texture_ent = commands - .spawn(SpriteBundle { - texture: texture.clone(), - sprite: Sprite { - anchor: bevy::sprite::Anchor::TopLeft, - ..Default::default() - }, - ..Default::default() - }) - .id(); - let rigidbody = Rigidbody { atoms: image_atoms(&image), height: image.height() as u8, width: image.width() as u8, filled: vec![], - texture, - text_update: None, - texture_ent, }; - let collider = get_collider( - &image_values(&image), - image.width(), - image.height(), - (rigidbody.width as f64 / -2., rigidbody.height as f64 / -2.), - ) - .unwrap(); + let collider = get_collider(&image_values(&image), image.width(), image.height()).unwrap(); commands .spawn(collider) .insert(rigidbody) .insert(bevy_rapier2d::prelude::RigidBody::Dynamic) - .insert(TransformBundle::default()); + .insert(SpriteBundle { + texture: handle.0.clone(), + sprite: Sprite { + anchor: bevy::sprite::Anchor::TopLeft, + ..Default::default() + }, + transform: Transform::from_xyz(handle.1.x, -handle.1.y, RIGIDBODY_LAYER), + ..Default::default() + }); commands.entity(ent).insert(Hydrated); } @@ -99,39 +66,26 @@ pub fn add_rigidbodies( pub fn update_rigidibodies( mut chunk_manager: ResMut, mut rigidbodies: Query<(&Transform, &mut Rigidbody)>, - mut transforms: Query<&mut Transform, Without>, materials: (Res>, Res), mut dirty_rects: ResMut, ) { + puffin::profile_function!(); + let materials = materials.0.get(materials.1 .0.clone()).unwrap(); for (transform, mut rigidbody) in &mut rigidbodies { - let mut rotation = -(transform.rotation.to_euler(EulerRot::XYZ).2 as f64).to_degrees(); - if rotation < 0. { - rotation += 360.; - } - let (width, height, rotated) = rotsprite::rotsprite( - rigidbody.atoms.as_slice(), - &Atom::default(), - rigidbody.width as usize, - rotation, - ) - .unwrap(); - - //New texture data - let mut data = SmallVec::new(); + let (width, height) = (rigidbody.width as usize, rigidbody.height as usize); + let angle = -transform.rotation.to_euler(EulerRot::XYZ).2; + let mut top_left = transform.translation.xy(); + top_left.y *= -1.; - let mut off = transform.translation.xy(); - off.y *= -1.; - let pos = vec2(width as f32 / -2., height as f32 / -2.) + off; - - //This fills the chunks with Object atoms, and also converts the atom data to color data + //This fills the chunks with Object atoms for (y, x) in (0..height).cartesian_product(0..width) { - let pos = pos + vec2(x as f32, y as f32); - let chunk_pos = global_to_chunk(pos.as_ivec2()); + let pos = top_left + vec2(x as f32, y as f32).rotate(Vec2::from_angle(angle)); + let chunk_pos = global_to_chunk(pos.round().as_ivec2()); - let rotated_atom = rotated[y * width + x]; - if materials[rotated_atom.id].is_solid() { + let rotated_atom = rigidbody.atoms[y * width + x]; + if materials[&rotated_atom].is_solid() { update_dirty_rects_3x3(&mut dirty_rects.current, chunk_pos); if let Some(atom) = chunk_manager.get_mut_atom(chunk_pos) { @@ -140,30 +94,8 @@ pub fn update_rigidibodies( rigidbody.filled.push(chunk_pos); } } - - data.extend_from_slice(&rotated_atom.color); - } else { - data.extend_from_slice(&[0, 0, 0, 0]); } } - - //Set new texture and new texture transform - let mut text_transform = transforms.get_mut(rigidbody.texture_ent).unwrap(); - text_transform.translation.x = pos.as_ivec2().x as f32; - text_transform.translation.y = -pos.as_ivec2().y as f32; - - let text_update = ExtractedTextureUpdate { - id: rigidbody.texture.id(), - data, - origin: Origin3d { x: 0, y: 0, z: 0 }, - size: Extent3d { - width: width as u32, - height: height as u32, - depth_or_array_layers: 1, - }, - }; - - rigidbody.text_update = Some(text_update); } } @@ -185,27 +117,6 @@ pub fn unfill_rigidbodies( } } -pub fn extract_images( - rigidibodies: Extract>, - mut extracted_updates: ResMut, -) { - for rigibody in &rigidibodies { - if let Some(text_update) = &rigibody.text_update { - extracted_updates.push(ExtractedTextureUpdate { - data: vec![0; (rigibody.texture_lenght().pow(2) * 4) as usize].to_smallvec(), - id: text_update.id, - origin: Origin3d::ZERO, - size: Extent3d { - width: rigibody.texture_lenght(), - height: rigibody.texture_lenght(), - depth_or_array_layers: 1, - }, - }); - extracted_updates.push(text_update.clone()); - } - } -} - pub fn image_values(image: &Image) -> Vec { let mut values = vec![]; for pixel in image.data.chunks_exact(4) { @@ -236,20 +147,13 @@ pub fn image_atoms(image: &Image) -> Vec { atoms } -pub fn get_collider( - values: &[f64], - width: u32, - height: u32, - origin: (f64, f64), -) -> Option { - let c = ContourBuilder::new(width, height, false) - .x_origin(origin.0) - .y_origin(origin.1); +pub fn get_collider(values: &[f64], width: u32, height: u32) -> Option { + let c = ContourBuilder::new(width, height, false); let res = c.contours(values, &[0.5]).unwrap(); let mut colliders = vec![]; for countour in res { - let geometry = countour.geometry().simplify(&4.0); + let geometry = countour.geometry().simplify_vw_preserve(&1.5); for polygon in geometry { let triangles = polygon.earcut_triangles(); @@ -283,9 +187,5 @@ impl Plugin for RigidbodyPlugin { ) .run_if(in_state(GameState::Game)), ); - - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_systems(ExtractSchedule, extract_images); - } } } From b280004f3435a5278d065f1ce81f9163aaad69ad Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Wed, 31 Jan 2024 03:26:50 -0300 Subject: [PATCH 2/2] feat: Rigidbody grabbing on debug (#76) * Grab rigidbodies on debug --- src/debug.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/debug.rs b/src/debug.rs index c6a88e0..d556a23 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -209,6 +209,69 @@ fn rect_inner(size: Vec2) -> [Vec2; 4] { [tl, tr, br, bl] } +pub fn grab_rigidbodies( + mut commands: Commands, + rigidbodies: Query<(&mut Transform, Entity, &Collider), With>, + mut transforms: Query<&mut Transform, (With, Without)>, + window: Query<&Window>, + camera_q: Query<(&Camera, &GlobalTransform)>, + mut grabbed: ResMut, + input: Res>, +) { + let (camera, camera_transform) = camera_q.single(); + let window = window.single(); + + if input.pressed(KeyCode::R) { + if let Some(world_position) = window + .cursor_position() + .and_then(|cursor| camera.viewport_to_world(camera_transform, cursor)) + .map(|ray| ray.origin.truncate()) + { + if let Some(grabbed_ent) = grabbed.0 { + //Update pos + let mut transform = transforms.get_mut(grabbed_ent).unwrap(); + transform.translation.x = world_position.x; + transform.translation.y = world_position.y; + } else { + //Add joint + for (transform, entity, collider) in &rigidbodies { + if collider.contains_point( + transform.translation.xy(), + transform.rotation.to_euler(EulerRot::XYZ).2, + world_position, + ) { + let anchor = (world_position - transform.translation.xy()).rotate( + Vec2::from_angle(-transform.rotation.to_euler(EulerRot::XYZ).2), + ); + let joint = RevoluteJointBuilder::new() + .local_anchor1(anchor) + .local_anchor2(Vec2::ZERO); + + let move_ent = commands + .spawn(RigidBody::KinematicPositionBased) + .insert(ImpulseJoint::new(entity, joint)) + .insert(TransformBundle::from_transform(Transform::from_xyz( + world_position.x, + world_position.y, + 0., + ))) + .id(); + + grabbed.0 = Some(move_ent); + + break; + } + } + } + } + } else if let Some(ent) = grabbed.0.take() { + commands.entity(ent).despawn_recursive() + } +} + +#[derive(Resource, Default)] +pub struct Grabbed(Option); + pub struct DebugPlugin; impl Plugin for DebugPlugin { fn build(&self, app: &mut App) { @@ -219,6 +282,7 @@ impl Plugin for DebugPlugin { brush.after(chunk_manager_update), render_actors.after(update_actors), prev_mpos.after(brush), + grab_rigidbodies, //_camera ) .run_if(in_state(GameState::Game)), @@ -227,6 +291,7 @@ impl Plugin for DebugPlugin { .add_plugins(RapierDebugRenderPlugin::default()) //Frame on console .add_plugins((LogDiagnosticsPlugin::default(), FrameTimeDiagnosticsPlugin)) + .init_resource::() .init_resource::(); } }