Skip to content

Commit 33e8333

Browse files
Add entity disabling example (#17710)
# Objective The entity disabling / default query filter work added in #17514 and #13120 is neat, but we don't teach users how it works! We should fix that before 0.16. ## Solution Write a simple example to teach the basics of entity disabling! ## Testing `cargo run --example entity_disabling` ## Showcase ![image](https://github.com/user-attachments/assets/9edcc5e1-2bdf-40c5-89b7-5b61c817977a) --------- Co-authored-by: Zachary Harrold <[email protected]>
1 parent 0b11b1f commit 33e8333

File tree

3 files changed

+165
-0
lines changed

3 files changed

+165
-0
lines changed

Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -2007,6 +2007,17 @@ description = "Demonstrates how to send and receive events of the same type in a
20072007
category = "ECS (Entity Component System)"
20082008
wasm = false
20092009

2010+
[[example]]
2011+
name = "entity_disabling"
2012+
path = "examples/ecs/entity_disabling.rs"
2013+
doc-scrape-examples = true
2014+
2015+
[package.metadata.example.entity_disabling]
2016+
name = "Entity disabling"
2017+
description = "Demonstrates how to hide entities from the ECS without deleting them"
2018+
category = "ECS (Entity Component System)"
2019+
wasm = true
2020+
20102021
[[example]]
20112022
name = "fixed_timestep"
20122023
path = "examples/ecs/fixed_timestep.rs"

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ Example | Description
307307
[Custom Schedule](../examples/ecs/custom_schedule.rs) | Demonstrates how to add custom schedules
308308
[Dynamic ECS](../examples/ecs/dynamic.rs) | Dynamically create components, spawn entities with those components and query those components
309309
[ECS Guide](../examples/ecs/ecs_guide.rs) | Full guide to Bevy's ECS
310+
[Entity disabling](../examples/ecs/entity_disabling.rs) | Demonstrates how to hide entities from the ECS without deleting them
310311
[Event](../examples/ecs/event.rs) | Illustrates event creation, activation, and reception
311312
[Fallible System Parameters](../examples/ecs/fallible_params.rs) | Systems are skipped if their parameters cannot be acquired
312313
[Fallible Systems](../examples/ecs/fallible_systems.rs) | Systems that return results to handle errors

examples/ecs/entity_disabling.rs

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
//! Disabling entities is a powerful feature that allows you to hide entities from the ECS without deleting them.
2+
//!
3+
//! This can be useful for implementing features like "sleeping" objects that are offscreen
4+
//! or managing networked entities.
5+
//!
6+
//! While disabling entities *will* make them invisible,
7+
//! that's not its primary purpose!
8+
//! [`Visibility`](bevy::prelude::Visibility) should be used to hide entities;
9+
//! disabled entities are skipped entirely, which can lead to subtle bugs.
10+
//!
11+
//! # Default query filters
12+
//!
13+
//! Under the hood, Bevy uses a "default query filter" that skips entities with the
14+
//! the [`Disabled`] component.
15+
//! These filters act as a by-default exclusion list for all queries,
16+
//! and can be bypassed by explicitly including these components in your queries.
17+
//! For example, `Query<&A, With<Disabled>`, `Query<(Entity, Has<Disabled>>)` or
18+
//! `Query<&A, Or<(With<Disabled>, With<B>)>>` will include disabled entities.
19+
20+
use bevy::ecs::entity_disabling::Disabled;
21+
use bevy::prelude::*;
22+
23+
fn main() {
24+
App::new()
25+
.add_plugins((DefaultPlugins, MeshPickingPlugin))
26+
.add_observer(disable_entities_on_click)
27+
.add_systems(
28+
Update,
29+
(list_all_named_entities, reenable_entities_on_space),
30+
)
31+
.add_systems(Startup, (setup_scene, display_instructions))
32+
.run();
33+
}
34+
35+
#[derive(Component)]
36+
struct DisableOnClick;
37+
38+
fn disable_entities_on_click(
39+
trigger: Trigger<Pointer<Click>>,
40+
valid_query: Query<&DisableOnClick>,
41+
mut commands: Commands,
42+
) {
43+
let clicked_entity = trigger.target();
44+
// Windows and text are entities and can be clicked!
45+
// We definitely don't want to disable the window itself,
46+
// because that would cause the app to close!
47+
if valid_query.contains(clicked_entity) {
48+
// Just add the `Disabled` component to the entity to disable it.
49+
// Note that the `Disabled` component is *only* added to the entity,
50+
// its children are not affected.
51+
commands.entity(clicked_entity).insert(Disabled);
52+
}
53+
}
54+
55+
#[derive(Component)]
56+
struct EntityNameText;
57+
58+
// The query here will not find entities with the `Disabled` component,
59+
// because it does not explicitly include it.
60+
fn list_all_named_entities(
61+
query: Query<&Name>,
62+
mut name_text_query: Query<&mut Text, With<EntityNameText>>,
63+
mut commands: Commands,
64+
) {
65+
let mut text_string = String::from("Named entities found:\n");
66+
// Query iteration order is not guaranteed, so we sort the names
67+
// to ensure the output is consistent.
68+
for name in query.iter().sort::<&Name>() {
69+
text_string.push_str(&format!("{:?}\n", name));
70+
}
71+
72+
if let Ok(mut text) = name_text_query.get_single_mut() {
73+
*text = Text::new(text_string);
74+
} else {
75+
commands.spawn((
76+
EntityNameText,
77+
Text::default(),
78+
Node {
79+
position_type: PositionType::Absolute,
80+
top: Val::Px(12.0),
81+
right: Val::Px(12.0),
82+
..default()
83+
},
84+
));
85+
}
86+
}
87+
88+
fn reenable_entities_on_space(
89+
mut commands: Commands,
90+
// This query can find disabled entities,
91+
// because it explicitly includes the `Disabled` component.
92+
disabled_entities: Query<Entity, With<Disabled>>,
93+
input: Res<ButtonInput<KeyCode>>,
94+
) {
95+
if input.just_pressed(KeyCode::Space) {
96+
for entity in disabled_entities.iter() {
97+
// To re-enable an entity, just remove the `Disabled` component.
98+
commands.entity(entity).remove::<Disabled>();
99+
}
100+
}
101+
}
102+
103+
const X_EXTENT: f32 = 900.;
104+
105+
fn setup_scene(
106+
mut commands: Commands,
107+
mut meshes: ResMut<Assets<Mesh>>,
108+
mut materials: ResMut<Assets<ColorMaterial>>,
109+
) {
110+
commands.spawn(Camera2d);
111+
112+
let named_shapes = [
113+
(Name::new("Annulus"), meshes.add(Annulus::new(25.0, 50.0))),
114+
(
115+
Name::new("Bestagon"),
116+
meshes.add(RegularPolygon::new(50.0, 6)),
117+
),
118+
(Name::new("Rhombus"), meshes.add(Rhombus::new(75.0, 100.0))),
119+
];
120+
let num_shapes = named_shapes.len();
121+
122+
for (i, (name, shape)) in named_shapes.into_iter().enumerate() {
123+
// Distribute colors evenly across the rainbow.
124+
let color = Color::hsl(360. * i as f32 / num_shapes as f32, 0.95, 0.7);
125+
126+
commands.spawn((
127+
name,
128+
DisableOnClick,
129+
Mesh2d(shape),
130+
MeshMaterial2d(materials.add(color)),
131+
Transform::from_xyz(
132+
// Distribute shapes from -X_EXTENT/2 to +X_EXTENT/2.
133+
-X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * X_EXTENT,
134+
0.0,
135+
0.0,
136+
),
137+
));
138+
}
139+
}
140+
141+
fn display_instructions(mut commands: Commands) {
142+
commands.spawn((
143+
Text::new(
144+
"Click an entity to disable it.\n\nPress Space to re-enable all disabled entities.",
145+
),
146+
Node {
147+
position_type: PositionType::Absolute,
148+
top: Val::Px(12.0),
149+
left: Val::Px(12.0),
150+
..default()
151+
},
152+
));
153+
}

0 commit comments

Comments
 (0)