diff --git a/Cargo.lock b/Cargo.lock index 2be1a6f..9ea99f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -372,6 +372,15 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "bevy-scene-hook" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "089da39d9031fb7d3988d44f2e7e27c8075e66167d228127ab01670eb2c400d6" +dependencies = [ + "bevy", +] + [[package]] name = "bevy-tnua" version = "0.15.0" @@ -3939,6 +3948,7 @@ dependencies = [ "bevy", "bevy-alt-ui-navigation-lite", "bevy-inspector-egui", + "bevy-scene-hook", "bevy-tnua", "bevy-tnua-physics-integration-layer", "bevy-tnua-rapier3d", diff --git a/Cargo.toml b/Cargo.toml index d06b27a..4799a8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ rand = "0.8.5" serde = "*" ron = "0.8.0" bevy_mod_outline = "0.7.0" +bevy-scene-hook = "10.0.0" [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = { version = "*", features = ["console", "Window", "Storage"] } diff --git a/assets/models/tower.glb b/assets/models/tower.glb new file mode 100644 index 0000000..18a840b Binary files /dev/null and b/assets/models/tower.glb differ diff --git a/src/loading.rs b/src/loading.rs index 74cbe91..ed17f32 100644 --- a/src/loading.rs +++ b/src/loading.rs @@ -22,7 +22,7 @@ pub struct Models { pub tile4: Handle, #[asset(path = "models/itemspawner.glb#Scene0")] pub item_spawner: Handle, - #[asset(path = "models/towerbase.glb#Scene0")] + #[asset(path = "models/tower.glb#Scene0")] pub tower_base: Handle, #[asset(path = "models/towerheadsm.glb#Scene0")] pub tower_head: Handle, diff --git a/src/main.rs b/src/main.rs index 6fce757..0b580f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use bevy_dolly::prelude::*; #[cfg(feature = "inspector")] use bevy_inspector_egui::quick::WorldInspectorPlugin; use bevy_rapier3d::prelude::*; +use bevy_scene_hook::HookPlugin; use bevy_tnua::prelude::*; use bevy_tnua_rapier3d::{TnuaRapier3dIOBundle, TnuaRapier3dPlugin, TnuaRapier3dSensorShape}; use bevy_two_entities::tuple::{TupleQueryExt, TupleQueryMutExt}; @@ -25,7 +26,7 @@ use map::{ map_to_world, Floor, Item, ItemSpawner, Lava, MapPlugin, MovingFloor, PlacedTower, TilePos, START_TILE, }; -use outline::{InnerMeshOutline, OutlinePlugin}; +use outline::OutlinePlugin; use save::SavePlugin; use settings::{MusicSetting, SfxSetting}; use starfield::StarfieldPlugin; @@ -193,7 +194,8 @@ fn main() { keyboard_navigation: true, ..default() }) - .add_plugins(DefaultNavigationPlugins); + .add_plugins(DefaultNavigationPlugins) + .add_plugins(HookPlugin); app.init_resource::() .register_type::() @@ -214,7 +216,6 @@ fn main() { { app.add_plugins(WorldInspectorPlugin::new()); app.add_plugins(RapierDebugRenderPlugin::default()); - app.register_type::(); app.register_type::(); app.register_type::(); } @@ -383,7 +384,6 @@ fn cursor( } fn item_probe( - mut commands: Commands, mut collision_events: EventReader, probe_query: Query<&Parent, With>, item_query: Query>, @@ -400,23 +400,16 @@ fn item_probe( if let Ok(mut selected_item) = selected_item_query.get_mut(probe_entity.get()) { selected_item.0 = Some(item_entity); } - - commands.entity(item_entity).insert(InnerMeshOutline { - width: 3., - color: Color::hsla(160., 0.9, 0.5, 1.0), - }); } CollisionEvent::Stopped(e1, e2, _) => { let queries = (&probe_query, &item_query); - let Some((_, item_entity)) = queries.get_both(*e1, *e2) else { + if !queries.both(*e1, *e2) { continue; }; for mut selected_item in selected_item_query.iter_mut() { selected_item.0 = None; } - - commands.entity(item_entity).remove::(); } } } diff --git a/src/map.rs b/src/map.rs index 493585e..df21e58 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,6 +1,7 @@ use std::{f32::consts::FRAC_PI_2, fmt::Display, time::Duration}; use bevy::{prelude::*, utils::HashSet}; +use bevy_mod_outline::{AsyncSceneInheritOutline, OutlineBundle, OutlineVolume}; use bevy_rapier3d::prelude::*; use bevy_tnua::TnuaPipelineStages; use rand::{seq::SliceRandom, thread_rng, Rng}; @@ -214,6 +215,15 @@ fn spawn_map( TilePos(pos), Collider::cuboid(TILE_SIZE.x / 2., TILE_SIZE.y / 2., TILE_SIZE.x / 2.), ActiveEvents::COLLISION_EVENTS, + OutlineBundle { + outline: OutlineVolume { + width: 3.0, + colour: Color::hsla(160., 0.9, 0.5, 1.0), + visible: true, + }, + ..default() + }, + AsyncSceneInheritOutline, )); if *col_val == 3 { @@ -361,6 +371,15 @@ fn item_spawner( }, Collider::ball(0.6), Sensor, + OutlineBundle { + outline: OutlineVolume { + width: 3.0, + colour: Color::hsla(160., 0.9, 0.5, 1.0), + visible: true, + }, + ..default() + }, + AsyncSceneInheritOutline, )) .id(); diff --git a/src/outline.rs b/src/outline.rs index fd6dbac..cba4a36 100644 --- a/src/outline.rs +++ b/src/outline.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use bevy_mod_outline::{ - AutoGenerateOutlineNormalsPlugin, OutlineBundle, OutlinePlugin as ActualOutlinePlugin, - OutlineVolume, + AsyncSceneInheritOutlinePlugin, AutoGenerateOutlineNormalsPlugin, + OutlinePlugin as ActualOutlinePlugin, OutlineVolume, }; use crate::{ @@ -15,77 +15,38 @@ impl Plugin for OutlinePlugin { fn build(&self, app: &mut App) { app.add_plugins(ActualOutlinePlugin) .add_plugins(AutoGenerateOutlineNormalsPlugin) - .add_systems(Update, update_inner) - .add_systems(Update, remove_inner) + .add_plugins(AsyncSceneInheritOutlinePlugin) .add_systems(Update, update); } } -#[derive(Component)] -pub struct InnerMeshOutline { - pub color: Color, - pub width: f32, -} - -fn update_inner( - mut commands: Commands, - query: Query<(Entity, &InnerMeshOutline), Changed>, - children_query: Query<&Children>, - mesh_query: Query>>, -) { - for (entity, inner_mesh_outline) in &query { - for descendant in children_query.iter_descendants(entity) { - if let Ok(mesh_entity) = mesh_query.get(descendant) { - // TODO we may be able to fix the "separate outline for tower head and body" - // problem by only adding the OutlineBundle to the toplevel mesh and then adding - // InheritOutlineBundle to every descendant of that mesh. - commands.entity(mesh_entity).insert(OutlineBundle { - outline: OutlineVolume { - width: inner_mesh_outline.width, - colour: inner_mesh_outline.color, - visible: true, - }, - ..default() - }); - } - } - } -} - -fn remove_inner( - mut commands: Commands, - mut removed: RemovedComponents, - children_query: Query<&Children>, - mesh_query: Query>>, -) { - for entity in removed.read() { - for descendant in children_query.iter_descendants(entity) { - if let Ok(mesh_entity) = mesh_query.get(descendant) { - commands.entity(mesh_entity).remove::(); - } - } - } -} - fn update( - mut commands: Commands, - player_query: Query< + selection_changed_query: Query< (&SelectedTile, &SelectedItem), - Or<(Changed, Changed)>, + Or<(Changed, Changed)>, >, - outlines_query: Query>, + grabbed_item_changed_query: Query<(), Changed>, + grabbed_item_removed: RemovedComponents, grabbed_item_query: Query<&Item, With>, invalid_tile_query: Query<(), Or<(With, With, With)>>, placed_tower_query: Query<&PlacedTower>, + mut outline_query: Query<&mut OutlineVolume>, ) { - let Ok((tile, item)) = player_query.get_single() else { + if selection_changed_query.is_empty() + && grabbed_item_changed_query.is_empty() + && grabbed_item_removed.is_empty() + { return; - }; + } - for entity in &outlines_query { - commands.entity(entity).remove::(); + for mut outline in &mut outline_query { + outline.visible = false; } + let Ok((tile, item)) = selection_changed_query.get_single() else { + return; + }; + let grabbed_item = grabbed_item_query.get_single(); match grabbed_item { @@ -94,10 +55,9 @@ fn update( if let Some(entity) = tile.0 { if let Ok(placed_tower) = placed_tower_query.get(entity) { - commands.entity(placed_tower.0).insert(InnerMeshOutline { - width: 3., - color: Color::hsla(160., 0.9, 0.5, 1.0), - }); + if let Ok(mut outline) = outline_query.get_mut(placed_tower.0) { + outline.visible = true; + } } } } @@ -106,21 +66,21 @@ fn update( if let Some(entity) = tile.0 { if invalid_tile_query.get(entity).is_err() { - commands.entity(entity).insert(InnerMeshOutline { - width: 3., - color: Color::hsla(160., 0.9, 0.5, 1.0), - }); + if let Ok(mut outline) = outline_query.get_mut(entity) { + outline.visible = true; + } } } } _ => { - // Outline items that can be grabbed + // No item is grabbed. + // Check selected item to see if it is + // grabbable, and if so outline it. if let Some(entity) = item.0 { - commands.entity(entity).insert(InnerMeshOutline { - width: 3., - color: Color::hsla(160., 0.9, 0.5, 1.0), - }); + if let Ok(mut outline) = outline_query.get_mut(entity) { + outline.visible = true; + } } } } diff --git a/src/tower.rs b/src/tower.rs index bca0590..ae6e104 100644 --- a/src/tower.rs +++ b/src/tower.rs @@ -1,7 +1,9 @@ use bevy::audio::Volume; use bevy::math::Vec3Swizzles; use bevy::{prelude::*, utils::HashSet}; +use bevy_mod_outline::{AsyncSceneInheritOutline, OutlineBundle, OutlineVolume}; use bevy_rapier3d::prelude::*; +use bevy_scene_hook::{HookedSceneBundle, SceneHook}; use bevy_two_entities::tuple::TupleQueryExt; use crate::enemy::{HitPoints, PathIndex}; @@ -88,12 +90,13 @@ impl Plugin for TowerPlugin { } fn movement( - mut tower_query: Query<(&Target, &Transform, &Children), (Without, Without)>, + mut tower_query: Query<(Entity, &Target, &Transform), (Without, Without)>, mut tower_head_query: Query<&mut Transform, With>, target_query: Query<&Transform, (With, Without)>, time: Res