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  --------- Co-authored-by: Zachary Harrold <zac@harrold.com.au>
This commit is contained in:
parent
0b11b1f37e
commit
33e83330eb
11
Cargo.toml
11
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"
|
||||
|
@ -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
|
||||
|
153
examples/ecs/entity_disabling.rs
Normal file
153
examples/ecs/entity_disabling.rs
Normal file
@ -0,0 +1,153 @@
|
||||
//! 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.
|
||||
//!
|
||||
//! 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
|
||||
//!
|
||||
//! 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.
|
||||
//! For example, `Query<&A, With<Disabled>`, `Query<(Entity, Has<Disabled>>)` or
|
||||
//! `Query<&A, Or<(With<Disabled>, With<B>)>>` will include disabled entities.
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct DisableOnClick;
|
||||
|
||||
fn disable_entities_on_click(
|
||||
trigger: Trigger<Pointer<Click>>,
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
#[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<EntityNameText>>,
|
||||
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.
|
||||
for name in query.iter().sort::<&Name>() {
|
||||
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::default(),
|
||||
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<Entity, With<Disabled>>,
|
||||
input: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
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::<Disabled>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const X_EXTENT: f32 = 900.;
|
||||
|
||||
fn setup_scene(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||
) {
|
||||
commands.spawn(Camera2d);
|
||||
|
||||
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,
|
||||
DisableOnClick,
|
||||
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.\n\nPress Space to re-enable all disabled entities.",
|
||||
),
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(12.0),
|
||||
left: Val::Px(12.0),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
Loading…
Reference in New Issue
Block a user