Skip to content

Commit 38f6da5

Browse files
committed
Add generic systems example (#2636)
# Objective My attempt at fixing #2142. My very first attempt at contributing to Bevy so more than open to any feedback. I borrowed heavily from the [Bevy Cheatbook page](https://bevy-cheatbook.github.io/patterns/generic-systems.html?highlight=generic#generic-systems). ## Solution Fairly straightforward example using a clean up system to delete entities that are coupled with app state after exiting that state. Co-authored-by: B-Janson <[email protected]>
1 parent 56b0e88 commit 38f6da5

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,10 @@ path = "examples/ecs/event.rs"
323323
name = "fixed_timestep"
324324
path = "examples/ecs/fixed_timestep.rs"
325325

326+
[[example]]
327+
name = "generic_system"
328+
path = "examples/ecs/generic_system.rs"
329+
326330
[[example]]
327331
name = "hierarchy"
328332
path = "examples/ecs/hierarchy.rs"

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ Example | File | Description
168168
`component_change_detection` | [`ecs/component_change_detection.rs`](./ecs/component_change_detection.rs) | Change detection on components
169169
`event` | [`ecs/event.rs`](./ecs/event.rs) | Illustrates event creation, activation, and reception
170170
`fixed_timestep` | [`ecs/fixed_timestep.rs`](./ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick
171+
`generic_system` | [`ecs/generic_system.rs`](./ecs/generic_system.rs) | Shows how to create systems that can be reused with different types
171172
`hierarchy` | [`ecs/hierarchy.rs`](./ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities
172173
`iter_combinations` | [`ecs/iter_combinations.rs`](./ecs/iter_combinations.rs) | Shows how to iterate over combinations of query results.
173174
`parallel_query` | [`ecs/parallel_query.rs`](./ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator`

examples/ecs/generic_system.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use bevy::{ecs::component::Component, prelude::*};
2+
3+
/// Generic types allow us to reuse logic across many related systems,
4+
/// allowing us to specialize our function's behavior based on which type (or types) are passed in.
5+
///
6+
/// This is commonly useful for working on related components or resources,
7+
/// where we want to have unique types for querying purposes but want them all to work the same way.
8+
/// This is particularly powerful when combined with user-defined traits to add more functionality to these related types.
9+
/// Remember to insert a specialized copy of the system into the schedule for each type that you want to operate on!
10+
///
11+
/// For more advice on working with generic types in Rust, check out <https://doc.rust-lang.org/book/ch10-01-syntax.html>
12+
/// or <https://doc.rust-lang.org/rust-by-example/generics.html>
13+
14+
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
15+
enum AppState {
16+
MainMenu,
17+
InGame,
18+
}
19+
20+
#[derive(Component)]
21+
struct TextToPrint(String);
22+
23+
#[derive(Component)]
24+
struct PrinterTick(bevy::prelude::Timer);
25+
26+
#[derive(Component)]
27+
struct MenuClose;
28+
29+
#[derive(Component)]
30+
struct LevelUnload;
31+
32+
fn main() {
33+
App::new()
34+
.add_plugins(DefaultPlugins)
35+
.add_state(AppState::MainMenu)
36+
.add_startup_system(setup_system)
37+
.add_system(print_text_system)
38+
.add_system_set(
39+
SystemSet::on_update(AppState::MainMenu).with_system(transition_to_in_game_system),
40+
)
41+
// add the cleanup systems
42+
.add_system_set(
43+
// Pass in the types your system should operate on using the ::<T> (turbofish) syntax
44+
SystemSet::on_exit(AppState::MainMenu).with_system(cleanup_system::<MenuClose>),
45+
)
46+
.add_system_set(
47+
SystemSet::on_exit(AppState::InGame).with_system(cleanup_system::<LevelUnload>),
48+
)
49+
.run();
50+
}
51+
52+
fn setup_system(mut commands: Commands) {
53+
commands
54+
.spawn()
55+
.insert(PrinterTick(bevy::prelude::Timer::from_seconds(1.0, true)))
56+
.insert(TextToPrint(
57+
"I will print until you press space.".to_string(),
58+
))
59+
.insert(MenuClose);
60+
61+
commands
62+
.spawn()
63+
.insert(PrinterTick(bevy::prelude::Timer::from_seconds(1.0, true)))
64+
.insert(TextToPrint("I will always print".to_string()))
65+
.insert(LevelUnload);
66+
}
67+
68+
fn print_text_system(time: Res<Time>, mut query: Query<(&mut PrinterTick, &TextToPrint)>) {
69+
for (mut timer, text) in query.iter_mut() {
70+
if timer.0.tick(time.delta()).just_finished() {
71+
info!("{}", text.0);
72+
}
73+
}
74+
}
75+
76+
fn transition_to_in_game_system(
77+
mut state: ResMut<State<AppState>>,
78+
keyboard_input: Res<Input<KeyCode>>,
79+
) {
80+
if keyboard_input.pressed(KeyCode::Space) {
81+
state.set(AppState::InGame).unwrap();
82+
}
83+
}
84+
85+
// Type arguments on functions come after the function name, but before ordinary arguments.
86+
// Here, the `Component` trait is a trait bound on T, our generic type
87+
fn cleanup_system<T: Component>(mut commands: Commands, query: Query<Entity, With<T>>) {
88+
for e in query.iter() {
89+
commands.entity(e).despawn_recursive();
90+
}
91+
}

0 commit comments

Comments
 (0)