From 51eb9e3663a30668ccfb72b18e9af0703bb74f44 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 6 Feb 2025 10:23:25 -0800 Subject: [PATCH 01/13] Example boilerplate --- Cargo.toml | 11 +++++++++++ examples/ecs/entity_disabling.rs | 1 + 2 files changed, 12 insertions(+) create mode 100644 examples/ecs/entity_disabling.rs diff --git a/Cargo.toml b/Cargo.toml index 715d1e683aa03..eafad33e58694 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2007,6 +2007,17 @@ description = "Demonstrates how to send and receive events of the same type in a category = "ECS (Entity Component System)" wasm = false +[[example]] +name = "entity_disabling" +path = "examples/ecs/entity_disabling.rs" +doc-scrape-examples = true + +[package.metadata.example.entity_disabling] +name = "Entity disabling" +description = "Demonstrates how to hide entities from the ECS without deleting them" +category = "ECS (Entity Component System)" +wasm = true + [[example]] name = "fixed_timestep" path = "examples/ecs/fixed_timestep.rs" diff --git a/examples/ecs/entity_disabling.rs b/examples/ecs/entity_disabling.rs new file mode 100644 index 0000000000000..f328e4d9d04c3 --- /dev/null +++ b/examples/ecs/entity_disabling.rs @@ -0,0 +1 @@ +fn main() {} From 8f85dca45bb56662360b89957b4eece9862fc58e Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 6 Feb 2025 11:45:06 -0800 Subject: [PATCH 02/13] Write entity disabling example --- examples/ecs/entity_disabling.rs | 154 ++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 1 deletion(-) diff --git a/examples/ecs/entity_disabling.rs b/examples/ecs/entity_disabling.rs index f328e4d9d04c3..92e13b3a0fe58 100644 --- a/examples/ecs/entity_disabling.rs +++ b/examples/ecs/entity_disabling.rs @@ -1 +1,153 @@ -fn main() {} +//! Disabling entities is a powerful feature that allows you to hide entities from the ECS without deleting them. +//! +//! This can be useful for implementing features like "sleeping" objects that are offscreen +//! or managing networked entities. +//! +//! Note that you should *not* use this feature to simply make entities invisible! +//! [`Visibility`](bevy::prelude::Visibility) should be used for that: +//! disabled entities are skipped entirely, which can lead to subtle bugs. +//! +//! # Default query filters +//! +//! Under the hood, Bevy uses a "default query filter" that skips entities with the +//! the [`Disabled`] component. +//! These filters act as a by-default exclusion list for all queries, +//! and can be bypassed by explicitly including these components in your queries. + +use bevy::ecs::entity_disabling::Disabled; +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins((DefaultPlugins, MeshPickingPlugin)) + .add_observer(disable_entities_on_click) + .add_systems( + Update, + (list_all_named_entities, reenable_entities_on_space), + ) + .add_systems(Startup, (setup_scene, display_instructions)) + .run(); +} + +fn disable_entities_on_click( + trigger: Trigger>, + ignored_query: Query<(), Or<(With, With)>>, + mut commands: Commands, +) { + let clicked_entity = trigger.target(); + // Windows and text are entities and can be clicked! + // We definitely don't want to disable the window itself, + // because that would cause the app to close! + if ignored_query.contains(clicked_entity) { + return; + } + + // Just add the `Disabled` component to the entity to disable it. + commands.entity(clicked_entity).insert(Disabled); +} + +#[derive(Component)] +struct EntityNameText; + +// The query here will not find entities with the `Disabled` component, +// because it does not explicitly include it. +fn list_all_named_entities( + query: Query<&Name>, + mut name_text_query: Query<&mut Text, With>, + mut commands: Commands, +) { + let mut text_string = String::from("Named entities found:\n"); + // Query iteration order is not guaranteed, so we sort the names + // to ensure the output is consistent. + let mut names: Vec = query.iter().cloned().collect(); + names.sort_by_key(|name| name.clone()); + + for name in names.iter() { + text_string.push_str(&format!("{:?}\n", name)); + } + + if let Ok(mut text) = name_text_query.get_single_mut() { + *text = Text::new(text_string); + } else { + commands.spawn(( + EntityNameText, + Text::new( + "Click an entity to disable it. + \nPress Space to re-enable all disabled entities.", + ), + Node { + position_type: PositionType::Absolute, + top: Val::Px(12.0), + right: Val::Px(12.0), + ..default() + }, + )); + } +} + +fn reenable_entities_on_space( + mut commands: Commands, + // This query can find disabled entities, + // because it explicitly includes the `Disabled` component. + disabled_entities: Query>, + input: Res>, +) { + if input.just_pressed(KeyCode::Space) { + for entity in disabled_entities.iter() { + // To re-enable an entity, just remove the `Disabled` component. + commands.entity(entity).remove::(); + } + } +} + +const X_EXTENT: f32 = 900.; + +fn setup_scene( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + commands.spawn(Camera2d::default()); + + let named_shapes = [ + (Name::new("Annulus"), meshes.add(Annulus::new(25.0, 50.0))), + ( + Name::new("Bestagon"), + meshes.add(RegularPolygon::new(50.0, 6)), + ), + (Name::new("Rhombus"), meshes.add(Rhombus::new(75.0, 100.0))), + ]; + let num_shapes = named_shapes.len(); + + for (i, (name, shape)) in named_shapes.into_iter().enumerate() { + // Distribute colors evenly across the rainbow. + let color = Color::hsl(360. * i as f32 / num_shapes as f32, 0.95, 0.7); + + commands.spawn(( + name, + Mesh2d(shape), + MeshMaterial2d(materials.add(color)), + Transform::from_xyz( + // Distribute shapes from -X_EXTENT/2 to +X_EXTENT/2. + -X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * X_EXTENT, + 0.0, + 0.0, + ), + )); + } +} + +fn display_instructions(mut commands: Commands) { + commands.spawn(( + Text::new( + "Click an entity to disable it. + \nPress Space to re-enable all disabled entities.", + ), + Node { + position_type: PositionType::Absolute, + top: Val::Px(12.0), + left: Val::Px(12.0), + ..default() + }, + )); +} From ce3b6ffd4f95096ee5c67a138a92d3276ef9a617 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 6 Feb 2025 11:53:05 -0800 Subject: [PATCH 03/13] Note about hierarchy and disabling --- examples/ecs/entity_disabling.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/ecs/entity_disabling.rs b/examples/ecs/entity_disabling.rs index 92e13b3a0fe58..be1f8e38de57d 100644 --- a/examples/ecs/entity_disabling.rs +++ b/examples/ecs/entity_disabling.rs @@ -43,6 +43,8 @@ fn disable_entities_on_click( } // Just add the `Disabled` component to the entity to disable it. + // Note that the `Disabled` component is *only* added to the entity, + // its children are not affected. commands.entity(clicked_entity).insert(Disabled); } From 01393bd82d47484ae15a375a7aee2a4a00e154a9 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 6 Feb 2025 13:00:30 -0800 Subject: [PATCH 04/13] cargo run -p build-templated-pages -- update examples --- examples/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/README.md b/examples/README.md index dc562b2bf9fe4..fef76c1a19fb3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -307,6 +307,7 @@ Example | Description [Custom Schedule](../examples/ecs/custom_schedule.rs) | Demonstrates how to add custom schedules [Dynamic ECS](../examples/ecs/dynamic.rs) | Dynamically create components, spawn entities with those components and query those components [ECS Guide](../examples/ecs/ecs_guide.rs) | Full guide to Bevy's ECS +[Entity disabling](../examples/ecs/entity_disabling.rs) | Demonstrates how to hide entities from the ECS without deleting them [Event](../examples/ecs/event.rs) | Illustrates event creation, activation, and reception [Fallible System Parameters](../examples/ecs/fallible_params.rs) | Systems are skipped if their parameters cannot be acquired [Fallible Systems](../examples/ecs/fallible_systems.rs) | Systems that return results to handle errors From 063b5a2bd8117cd33d9eeaab4548b59ae4dff898 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sun, 9 Feb 2025 13:43:00 -0500 Subject: [PATCH 05/13] Typo Co-authored-by: Zachary Harrold --- examples/ecs/entity_disabling.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ecs/entity_disabling.rs b/examples/ecs/entity_disabling.rs index be1f8e38de57d..27827da55ec19 100644 --- a/examples/ecs/entity_disabling.rs +++ b/examples/ecs/entity_disabling.rs @@ -1,4 +1,4 @@ -//! Disabling entities is a powerful feature that allows you to hide entities from the ECS without deleting them. +//! Disabling entities is a powerful feature that allows you to hide entities from the ECS without deleting them. //! //! This can be useful for implementing features like "sleeping" objects that are offscreen //! or managing networked entities. From b537516ff57385c3cf78aa18201b21d8d70d31c5 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sun, 9 Feb 2025 10:47:02 -0800 Subject: [PATCH 06/13] Demonstrate explicitly including DQF components --- examples/ecs/entity_disabling.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/ecs/entity_disabling.rs b/examples/ecs/entity_disabling.rs index 27827da55ec19..e8f21d85de319 100644 --- a/examples/ecs/entity_disabling.rs +++ b/examples/ecs/entity_disabling.rs @@ -13,6 +13,8 @@ //! the [`Disabled`] component. //! These filters act as a by-default exclusion list for all queries, //! and can be bypassed by explicitly including these components in your queries. +//! For example, `Query<&A, With`, `Query<(Entity, Has>)` or +//! `Query<&A, Or<(With, With)>>` will include disabled entities. use bevy::ecs::entity_disabling::Disabled; use bevy::prelude::*; From 38861ad345e2817f5057a92a77af4d72343fbcae Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sun, 9 Feb 2025 10:49:16 -0800 Subject: [PATCH 07/13] Explicit double newline in instructions --- examples/ecs/entity_disabling.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/ecs/entity_disabling.rs b/examples/ecs/entity_disabling.rs index e8f21d85de319..adf64a3957d6e 100644 --- a/examples/ecs/entity_disabling.rs +++ b/examples/ecs/entity_disabling.rs @@ -144,8 +144,7 @@ fn setup_scene( fn display_instructions(mut commands: Commands) { commands.spawn(( Text::new( - "Click an entity to disable it. - \nPress Space to re-enable all disabled entities.", + "Click an entity to disable it.\n\nPress Space to re-enable all disabled entities.", ), Node { position_type: PositionType::Absolute, From 256cbe2f262342ba635936b8ef84fd23767fd122 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sun, 9 Feb 2025 10:50:33 -0800 Subject: [PATCH 08/13] Fix initialization of name text --- examples/ecs/entity_disabling.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/ecs/entity_disabling.rs b/examples/ecs/entity_disabling.rs index adf64a3957d6e..34ca8cf5d7bfd 100644 --- a/examples/ecs/entity_disabling.rs +++ b/examples/ecs/entity_disabling.rs @@ -75,10 +75,7 @@ fn list_all_named_entities( } else { commands.spawn(( EntityNameText, - Text::new( - "Click an entity to disable it. - \nPress Space to re-enable all disabled entities.", - ), + Text::default(), Node { position_type: PositionType::Absolute, top: Val::Px(12.0), From 13104b06a1832694d4a13c0d38615f23762ac5b2 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sun, 9 Feb 2025 10:51:07 -0800 Subject: [PATCH 09/13] Native query sorting --- examples/ecs/entity_disabling.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/ecs/entity_disabling.rs b/examples/ecs/entity_disabling.rs index 34ca8cf5d7bfd..76e4e544d46ff 100644 --- a/examples/ecs/entity_disabling.rs +++ b/examples/ecs/entity_disabling.rs @@ -63,10 +63,7 @@ fn list_all_named_entities( let mut text_string = String::from("Named entities found:\n"); // Query iteration order is not guaranteed, so we sort the names // to ensure the output is consistent. - let mut names: Vec = query.iter().cloned().collect(); - names.sort_by_key(|name| name.clone()); - - for name in names.iter() { + for name in query.iter().sort::<&Name>() { text_string.push_str(&format!("{:?}\n", name)); } From 7b5621572df20bca0a39565327b22978579f04af Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sun, 9 Feb 2025 10:54:46 -0800 Subject: [PATCH 10/13] DisableOnClick marker --- examples/ecs/entity_disabling.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/ecs/entity_disabling.rs b/examples/ecs/entity_disabling.rs index 76e4e544d46ff..88ce62de9237e 100644 --- a/examples/ecs/entity_disabling.rs +++ b/examples/ecs/entity_disabling.rs @@ -31,16 +31,19 @@ fn main() { .run(); } +#[derive(Component)] +struct DisableOnClick; + fn disable_entities_on_click( trigger: Trigger>, - ignored_query: Query<(), Or<(With, With)>>, + valid_query: Query<&DisableOnClick>, mut commands: Commands, ) { let clicked_entity = trigger.target(); // Windows and text are entities and can be clicked! // We definitely don't want to disable the window itself, // because that would cause the app to close! - if ignored_query.contains(clicked_entity) { + if !valid_query.contains(clicked_entity) { return; } @@ -123,6 +126,7 @@ fn setup_scene( commands.spawn(( name, + DisableOnClick, Mesh2d(shape), MeshMaterial2d(materials.add(color)), Transform::from_xyz( From 4c4ff45f58db78f9a29c762350672acbdb797b7b Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sun, 9 Feb 2025 10:55:33 -0800 Subject: [PATCH 11/13] Avoid early return in observer --- examples/ecs/entity_disabling.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/ecs/entity_disabling.rs b/examples/ecs/entity_disabling.rs index 88ce62de9237e..666679d4df1ba 100644 --- a/examples/ecs/entity_disabling.rs +++ b/examples/ecs/entity_disabling.rs @@ -43,14 +43,12 @@ fn disable_entities_on_click( // Windows and text are entities and can be clicked! // We definitely don't want to disable the window itself, // because that would cause the app to close! - if !valid_query.contains(clicked_entity) { - return; + if valid_query.contains(clicked_entity) { + // Just add the `Disabled` component to the entity to disable it. + // Note that the `Disabled` component is *only* added to the entity, + // its children are not affected. + commands.entity(clicked_entity).insert(Disabled); } - - // Just add the `Disabled` component to the entity to disable it. - // Note that the `Disabled` component is *only* added to the entity, - // its children are not affected. - commands.entity(clicked_entity).insert(Disabled); } #[derive(Component)] From a75e66c2eac7471fe27220c716a4490fba994ca4 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sun, 9 Feb 2025 10:56:21 -0800 Subject: [PATCH 12/13] Clippy --- examples/ecs/entity_disabling.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ecs/entity_disabling.rs b/examples/ecs/entity_disabling.rs index 666679d4df1ba..d5f05bd9d7622 100644 --- a/examples/ecs/entity_disabling.rs +++ b/examples/ecs/entity_disabling.rs @@ -106,7 +106,7 @@ fn setup_scene( mut meshes: ResMut>, mut materials: ResMut>, ) { - commands.spawn(Camera2d::default()); + commands.spawn(Camera2d); let named_shapes = [ (Name::new("Annulus"), meshes.add(Annulus::new(25.0, 50.0))), From 04f80b56f584f5b3e3d01ed8e5ffdb525b920021 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sun, 9 Feb 2025 10:58:15 -0800 Subject: [PATCH 13/13] Clarification around disabling vs hiding --- examples/ecs/entity_disabling.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/ecs/entity_disabling.rs b/examples/ecs/entity_disabling.rs index d5f05bd9d7622..d8c1301324805 100644 --- a/examples/ecs/entity_disabling.rs +++ b/examples/ecs/entity_disabling.rs @@ -3,8 +3,9 @@ //! This can be useful for implementing features like "sleeping" objects that are offscreen //! or managing networked entities. //! -//! Note that you should *not* use this feature to simply make entities invisible! -//! [`Visibility`](bevy::prelude::Visibility) should be used for that: +//! While disabling entities *will* make them invisible, +//! that's not its primary purpose! +//! [`Visibility`](bevy::prelude::Visibility) should be used to hide entities; //! disabled entities are skipped entirely, which can lead to subtle bugs. //! //! # Default query filters