Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ecs/fallible_params.rs
6592 views
1
//! This example demonstrates how fallible parameters can prevent their systems
2
//! from running if their acquiry conditions aren't met.
3
//!
4
//! Fallible system parameters include:
5
//! - [`Res<R>`], [`ResMut<R>`] - Resource has to exist, and the [`World::default_error_handler`] will be called if it doesn't.
6
//! - [`Single<D, F>`] - There must be exactly one matching entity, but the system will be silently skipped otherwise.
7
//! - [`Option<Single<D, F>>`] - There must be zero or one matching entity. The system will be silently skipped if there are more.
8
//! - [`Populated<D, F>`] - There must be at least one matching entity, but the system will be silently skipped otherwise.
9
//!
10
//! Other system parameters, such as [`Query`], will never fail validation: returning a query with no matching entities is valid.
11
//!
12
//! The result of failed system parameter validation is determined by the [`SystemParamValidationError`] returned
13
//! by [`SystemParam::validate_param`] for each system parameter.
14
//! Each system will pass if all of its parameters are valid, or else return [`SystemParamValidationError`] for the first failing parameter.
15
//!
16
//! To learn more about setting the fallback behavior for [`SystemParamValidationError`] failures,
17
//! please see the `error_handling.rs` example.
18
//!
19
//! [`SystemParamValidationError`]: bevy::ecs::system::SystemParamValidationError
20
//! [`SystemParam::validate_param`]: bevy::ecs::system::SystemParam::validate_param
21
22
use bevy::ecs::error::warn;
23
use bevy::prelude::*;
24
use rand::Rng;
25
26
fn main() {
27
println!();
28
println!("Press 'A' to add enemy ships and 'R' to remove them.");
29
println!("Player ship will wait for enemy ships and track one if it exists,");
30
println!("but will stop tracking if there are more than one.");
31
println!();
32
33
App::new()
34
// By default, if a parameter fail to be fetched,
35
// `World::get_default_error_handler` will be used to handle the error,
36
// which by default is set to panic.
37
.set_error_handler(warn)
38
.add_plugins(DefaultPlugins)
39
.add_systems(Startup, setup)
40
.add_systems(Update, (user_input, move_targets, track_targets).chain())
41
// This system will always fail validation, because we never create an entity with both `Player` and `Enemy` components.
42
.add_systems(Update, do_nothing_fail_validation)
43
.run();
44
}
45
46
/// Enemy component stores data for movement in a circle.
47
#[derive(Component, Default)]
48
struct Enemy {
49
origin: Vec2,
50
radius: f32,
51
rotation: f32,
52
rotation_speed: f32,
53
}
54
55
/// Player component stores data for going after enemies.
56
#[derive(Component, Default)]
57
struct Player {
58
speed: f32,
59
rotation_speed: f32,
60
min_follow_radius: f32,
61
}
62
63
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
64
// Spawn 2D camera.
65
commands.spawn(Camera2d);
66
67
// Spawn player.
68
let texture = asset_server.load("textures/simplespace/ship_C.png");
69
commands.spawn((
70
Player {
71
speed: 100.0,
72
rotation_speed: 2.0,
73
min_follow_radius: 50.0,
74
},
75
Sprite {
76
image: texture,
77
color: bevy::color::palettes::tailwind::BLUE_800.into(),
78
..Default::default()
79
},
80
Transform::from_translation(Vec3::ZERO),
81
));
82
}
83
84
/// System that reads user input.
85
/// If user presses 'A' we spawn a new random enemy.
86
/// If user presses 'R' we remove a random enemy (if any exist).
87
fn user_input(
88
mut commands: Commands,
89
enemies: Query<Entity, With<Enemy>>,
90
keyboard_input: Res<ButtonInput<KeyCode>>,
91
asset_server: Res<AssetServer>,
92
) {
93
let mut rng = rand::rng();
94
if keyboard_input.just_pressed(KeyCode::KeyA) {
95
let texture = asset_server.load("textures/simplespace/enemy_A.png");
96
commands.spawn((
97
Enemy {
98
origin: Vec2::new(
99
rng.random_range(-200.0..200.0),
100
rng.random_range(-200.0..200.0),
101
),
102
radius: rng.random_range(50.0..150.0),
103
rotation: rng.random_range(0.0..std::f32::consts::TAU),
104
rotation_speed: rng.random_range(0.5..1.5),
105
},
106
Sprite {
107
image: texture,
108
color: bevy::color::palettes::tailwind::RED_800.into(),
109
..default()
110
},
111
Transform::from_translation(Vec3::ZERO),
112
));
113
}
114
115
if keyboard_input.just_pressed(KeyCode::KeyR)
116
&& let Some(entity) = enemies.iter().next()
117
{
118
commands.entity(entity).despawn();
119
}
120
}
121
122
// System that moves the enemies in a circle.
123
// Only runs if there are enemies, due to the `Populated` parameter.
124
fn move_targets(mut enemies: Populated<(&mut Transform, &mut Enemy)>, time: Res<Time>) {
125
for (mut transform, mut target) in &mut *enemies {
126
target.rotation += target.rotation_speed * time.delta_secs();
127
transform.rotation = Quat::from_rotation_z(target.rotation);
128
let offset = transform.right() * target.radius;
129
transform.translation = target.origin.extend(0.0) + offset;
130
}
131
}
132
133
/// System that moves the player, causing them to track a single enemy.
134
/// If there is exactly one, player will track it.
135
/// Otherwise, the player will search for enemies.
136
fn track_targets(
137
// `Single` ensures the system runs ONLY when exactly one matching entity exists.
138
mut player: Single<(&mut Transform, &Player)>,
139
// `Option<Single>` never prevents the system from running, but will be `None` if there is not exactly one matching entity.
140
enemy: Option<Single<&Transform, (With<Enemy>, Without<Player>)>>,
141
time: Res<Time>,
142
) {
143
let (player_transform, player) = &mut *player;
144
if let Some(enemy_transform) = enemy {
145
// Enemy found, rotate and move towards it.
146
let delta = enemy_transform.translation - player_transform.translation;
147
let distance = delta.length();
148
let front = delta / distance;
149
let up = Vec3::Z;
150
let side = front.cross(up);
151
player_transform.rotation = Quat::from_mat3(&Mat3::from_cols(side, front, up));
152
let max_step = distance - player.min_follow_radius;
153
if 0.0 < max_step {
154
let velocity = (player.speed * time.delta_secs()).min(max_step);
155
player_transform.translation += front * velocity;
156
}
157
} else {
158
// 0 or multiple enemies found, keep searching.
159
player_transform.rotate_axis(Dir3::Z, player.rotation_speed * time.delta_secs());
160
}
161
}
162
163
/// This system always fails param validation, because we never
164
/// create an entity with both [`Player`] and [`Enemy`] components.
165
fn do_nothing_fail_validation(_: Single<(), (With<Player>, With<Enemy>)>) {}
166
167