entity_disabling/
entity_disabling.rs

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
20use bevy::ecs::entity_disabling::Disabled;
21use bevy::prelude::*;
22
23fn 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)]
36struct DisableOnClick;
37
38fn 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)]
56struct EntityNameText;
57
58// The query here will not find entities with the `Disabled` component,
59// because it does not explicitly include it.
60fn 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.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
88fn 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
103const X_EXTENT: f32 = 900.;
104
105fn 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
141fn 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}