Skip to content

Commit f04406c

Browse files
chompaaalice-i-cecile
authored andcommitted
Unify picking backends (#17348)
# Objective Currently, our picking backends are inconsistent: - Mesh picking and sprite picking both have configurable opt in/out behavior. UI picking does not. - Sprite picking uses `SpritePickingCamera` and `Pickable` for control, but mesh picking uses `RayCastPickable`. - `MeshPickingPlugin` is not a part of `DefaultPlugins`. `SpritePickingPlugin` and `UiPickingPlugin` are. ## Solution - Add configurable opt in/out behavior to UI picking (defaults to opt out). - Replace `RayCastPickable` with `MeshPickingCamera` and `Pickable`. - Remove `SpritePickingPlugin` and `UiPickingPlugin` from `DefaultPlugins`. ## Testing Ran some examples. ## Migration Guide `UiPickingPlugin` and `SpritePickingPlugin` are no longer included in `DefaultPlugins`. They must be explicitly added. `RayCastPickable` has been replaced in favor of the `MeshPickingCamera` and `Pickable` components. You should add them to cameras and entities, respectively, if you have `MeshPickingSettings::require_markers` set to `true`. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent c9f37ef commit f04406c

File tree

13 files changed

+107
-65
lines changed

13 files changed

+107
-65
lines changed

crates/bevy_picking/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ pub mod prelude {
179179
#[doc(hidden)]
180180
pub use crate::mesh_picking::{
181181
ray_cast::{MeshRayCast, MeshRayCastSettings, RayCastBackfaces, RayCastVisibility},
182-
MeshPickingPlugin, MeshPickingSettings, RayCastPickable,
182+
MeshPickingCamera, MeshPickingPlugin, MeshPickingSettings,
183183
};
184184
#[doc(hidden)]
185185
pub use crate::{

crates/bevy_picking/src/mesh_picking/mod.rs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
//! by adding [`Pickable::IGNORE`].
55
//!
66
//! To make mesh picking entirely opt-in, set [`MeshPickingSettings::require_markers`]
7-
//! to `true` and add a [`RayCastPickable`] component to the desired camera and target entities.
7+
//! to `true` and add [`MeshPickingCamera`] and [`Pickable`] components to the desired camera and
8+
//! target entities.
89
//!
910
//! To manually perform mesh ray casts independent of picking, use the [`MeshRayCast`] system parameter.
1011
//!
@@ -26,12 +27,19 @@ use bevy_reflect::prelude::*;
2627
use bevy_render::{prelude::*, view::RenderLayers};
2728
use ray_cast::{MeshRayCast, MeshRayCastSettings, RayCastVisibility, SimplifiedMesh};
2829

30+
/// An optional component that marks cameras that should be used in the [`MeshPickingPlugin`].
31+
///
32+
/// Only needed if [`MeshPickingSettings::require_markers`] is set to `true`, and ignored otherwise.
33+
#[derive(Debug, Clone, Default, Component, Reflect)]
34+
#[reflect(Debug, Default, Component)]
35+
pub struct MeshPickingCamera;
36+
2937
/// Runtime settings for the [`MeshPickingPlugin`].
3038
#[derive(Resource, Reflect)]
3139
#[reflect(Resource, Default)]
3240
pub struct MeshPickingSettings {
33-
/// When set to `true` ray casting will only happen between cameras and entities marked with
34-
/// [`RayCastPickable`]. `false` by default.
41+
/// When set to `true` ray casting will only consider cameras marked with
42+
/// [`MeshPickingCamera`] and entities marked with [`Pickable`]. `false` by default.
3543
///
3644
/// This setting is provided to give you fine-grained control over which cameras and entities
3745
/// should be used by the mesh picking backend at runtime.
@@ -54,20 +62,13 @@ impl Default for MeshPickingSettings {
5462
}
5563
}
5664

57-
/// An optional component that marks cameras and target entities that should be used in the [`MeshPickingPlugin`].
58-
/// Only needed if [`MeshPickingSettings::require_markers`] is set to `true`, and ignored otherwise.
59-
#[derive(Debug, Clone, Default, Component, Reflect)]
60-
#[reflect(Component, Default, Clone)]
61-
pub struct RayCastPickable;
62-
6365
/// Adds the mesh picking backend to your app.
6466
#[derive(Clone, Default)]
6567
pub struct MeshPickingPlugin;
6668

6769
impl Plugin for MeshPickingPlugin {
6870
fn build(&self, app: &mut App) {
6971
app.init_resource::<MeshPickingSettings>()
70-
.register_type::<RayCastPickable>()
7172
.register_type::<MeshPickingSettings>()
7273
.register_type::<SimplifiedMesh>()
7374
.add_systems(PreUpdate, update_hits.in_set(PickSet::Backend));
@@ -78,18 +79,18 @@ impl Plugin for MeshPickingPlugin {
7879
pub fn update_hits(
7980
backend_settings: Res<MeshPickingSettings>,
8081
ray_map: Res<RayMap>,
81-
picking_cameras: Query<(&Camera, Option<&RayCastPickable>, Option<&RenderLayers>)>,
82+
picking_cameras: Query<(&Camera, Has<MeshPickingCamera>, Option<&RenderLayers>)>,
8283
pickables: Query<&Pickable>,
83-
marked_targets: Query<&RayCastPickable>,
84+
marked_targets: Query<&Pickable>,
8485
layers: Query<&RenderLayers>,
8586
mut ray_cast: MeshRayCast,
8687
mut output: EventWriter<PointerHits>,
8788
) {
8889
for (&ray_id, &ray) in ray_map.map().iter() {
89-
let Ok((camera, cam_pickable, cam_layers)) = picking_cameras.get(ray_id.camera) else {
90+
let Ok((camera, cam_can_pick, cam_layers)) = picking_cameras.get(ray_id.camera) else {
9091
continue;
9192
};
92-
if backend_settings.require_markers && cam_pickable.is_none() {
93+
if backend_settings.require_markers && !cam_can_pick {
9394
continue;
9495
}
9596

crates/bevy_sprite/src/lib.rs

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ mod texture_slice;
2121
///
2222
/// This includes the most common types in this crate, re-exported for your convenience.
2323
pub mod prelude {
24+
#[cfg(feature = "bevy_sprite_picking_backend")]
25+
#[doc(hidden)]
26+
pub use crate::picking_backend::{
27+
SpritePickingCamera, SpritePickingMode, SpritePickingPlugin, SpritePickingSettings,
28+
};
2429
#[doc(hidden)]
2530
pub use crate::{
2631
sprite::{Sprite, SpriteImageMode},
@@ -52,28 +57,8 @@ use bevy_render::{
5257
};
5358

5459
/// Adds support for 2D sprite rendering.
55-
pub struct SpritePlugin {
56-
/// Whether to add the sprite picking backend to the app.
57-
#[cfg(feature = "bevy_sprite_picking_backend")]
58-
pub add_picking: bool,
59-
}
60-
61-
#[expect(
62-
clippy::allow_attributes,
63-
reason = "clippy::derivable_impls is not always linted"
64-
)]
65-
#[allow(
66-
clippy::derivable_impls,
67-
reason = "Known false positive with clippy: <https://github.com/rust-lang/rust-clippy/issues/13160>"
68-
)]
69-
impl Default for SpritePlugin {
70-
fn default() -> Self {
71-
Self {
72-
#[cfg(feature = "bevy_sprite_picking_backend")]
73-
add_picking: true,
74-
}
75-
}
76-
}
60+
#[derive(Default)]
61+
pub struct SpritePlugin;
7762

7863
pub const SPRITE_SHADER_HANDLE: Handle<Shader> =
7964
weak_handle!("ed996613-54c0-49bd-81be-1c2d1a0d03c2");
@@ -125,9 +110,7 @@ impl Plugin for SpritePlugin {
125110
);
126111

127112
#[cfg(feature = "bevy_sprite_picking_backend")]
128-
if self.add_picking {
129-
app.add_plugins(SpritePickingPlugin);
130-
}
113+
app.add_plugins(SpritePickingPlugin);
131114

132115
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
133116
render_app

crates/bevy_sprite/src/picking_backend.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ use bevy_render::prelude::*;
2020
use bevy_transform::prelude::*;
2121
use bevy_window::PrimaryWindow;
2222

23-
/// A component that marks cameras that should be used in the [`SpritePickingPlugin`].
23+
/// An optional component that marks cameras that should be used in the [`SpritePickingPlugin`].
24+
///
25+
/// Only needed if [`SpritePickingSettings::require_markers`] is set to `true`, and ignored
26+
/// otherwise.
2427
#[derive(Debug, Clone, Default, Component, Reflect)]
2528
#[reflect(Debug, Default, Component, Clone)]
2629
pub struct SpritePickingCamera;
@@ -62,6 +65,7 @@ impl Default for SpritePickingSettings {
6265
}
6366
}
6467

68+
/// Enables the sprite picking backend, allowing you to click on, hover over and drag sprites.
6569
#[derive(Clone)]
6670
pub struct SpritePickingPlugin;
6771

crates/bevy_ui/src/lib.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub use focus::*;
3535
pub use geometry::*;
3636
pub use layout::*;
3737
pub use measurement::*;
38+
use prelude::UiPickingPlugin;
3839
pub use render::*;
3940
pub use ui_material::*;
4041
pub use ui_node::*;
@@ -45,6 +46,9 @@ use widget::{ImageNode, ImageNodeSize};
4546
///
4647
/// This includes the most common types in this crate, re-exported for your convenience.
4748
pub mod prelude {
49+
#[cfg(feature = "bevy_ui_picking_backend")]
50+
#[doc(hidden)]
51+
pub use crate::picking_backend::{UiPickingCamera, UiPickingPlugin, UiPickingSettings};
4852
#[doc(hidden)]
4953
#[cfg(feature = "bevy_ui_debug")]
5054
pub use crate::render::UiDebugOptions;
@@ -79,17 +83,12 @@ pub struct UiPlugin {
7983
/// If set to false, the UI's rendering systems won't be added to the `RenderApp` and no UI elements will be drawn.
8084
/// The layout and interaction components will still be updated as normal.
8185
pub enable_rendering: bool,
82-
/// Whether to add the UI picking backend to the app.
83-
#[cfg(feature = "bevy_ui_picking_backend")]
84-
pub add_picking: bool,
8586
}
8687

8788
impl Default for UiPlugin {
8889
fn default() -> Self {
8990
Self {
9091
enable_rendering: true,
91-
#[cfg(feature = "bevy_ui_picking_backend")]
92-
add_picking: true,
9392
}
9493
}
9594
}
@@ -181,6 +180,7 @@ impl Plugin for UiPlugin {
181180
)
182181
.chain(),
183182
)
183+
.add_plugins(UiPickingPlugin)
184184
.add_systems(
185185
PreUpdate,
186186
ui_focus_system.in_set(UiSystem::Focus).after(InputSystem),
@@ -219,11 +219,6 @@ impl Plugin for UiPlugin {
219219
);
220220
build_text_interop(app);
221221

222-
#[cfg(feature = "bevy_ui_picking_backend")]
223-
if self.add_picking {
224-
app.add_plugins(picking_backend::UiPickingPlugin);
225-
}
226-
227222
if !self.enable_rendering {
228223
return;
229224
}

crates/bevy_ui/src/picking_backend.rs

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,59 @@ use bevy_app::prelude::*;
2929
use bevy_ecs::{prelude::*, query::QueryData};
3030
use bevy_math::{Rect, Vec2};
3131
use bevy_platform_support::collections::HashMap;
32+
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
3233
use bevy_render::prelude::*;
3334
use bevy_transform::prelude::*;
3435
use bevy_window::PrimaryWindow;
3536

3637
use bevy_picking::backend::prelude::*;
3738

39+
/// An optional component that marks cameras that should be used in the [`UiPickingPlugin`].
40+
///
41+
/// Only needed if [`UiPickingSettings::require_markers`] is set to `true`, and ignored
42+
/// otherwise.
43+
#[derive(Debug, Clone, Default, Component, Reflect)]
44+
#[reflect(Debug, Default, Component)]
45+
pub struct UiPickingCamera;
46+
47+
/// Runtime settings for the [`UiPickingPlugin`].
48+
#[derive(Resource, Reflect)]
49+
#[reflect(Resource, Default)]
50+
pub struct UiPickingSettings {
51+
/// When set to `true` UI picking will only consider cameras marked with
52+
/// [`UiPickingCamera`] and entities marked with [`Pickable`]. `false` by default.
53+
///
54+
/// This setting is provided to give you fine-grained control over which cameras and entities
55+
/// should be used by the UI picking backend at runtime.
56+
pub require_markers: bool,
57+
}
58+
59+
#[expect(
60+
clippy::allow_attributes,
61+
reason = "clippy::derivable_impls is not always linted"
62+
)]
63+
#[allow(
64+
clippy::derivable_impls,
65+
reason = "Known false positive with clippy: <https://github.com/rust-lang/rust-clippy/issues/13160>"
66+
)]
67+
impl Default for UiPickingSettings {
68+
fn default() -> Self {
69+
Self {
70+
require_markers: false,
71+
}
72+
}
73+
}
74+
3875
/// A plugin that adds picking support for UI nodes.
76+
///
77+
/// This is included by default in [`UiPlugin`](crate::UiPlugin).
3978
#[derive(Clone)]
4079
pub struct UiPickingPlugin;
4180
impl Plugin for UiPickingPlugin {
4281
fn build(&self, app: &mut App) {
43-
app.add_systems(PreUpdate, ui_picking.in_set(PickSet::Backend));
82+
app.init_resource::<UiPickingSettings>()
83+
.register_type::<(UiPickingCamera, UiPickingSettings)>()
84+
.add_systems(PreUpdate, ui_picking.in_set(PickSet::Backend));
4485
}
4586
}
4687

@@ -63,8 +104,14 @@ pub struct NodeQuery {
63104
/// we need for determining picking.
64105
pub fn ui_picking(
65106
pointers: Query<(&PointerId, &PointerLocation)>,
66-
camera_query: Query<(Entity, &Camera, Has<IsDefaultUiCamera>)>,
107+
camera_query: Query<(
108+
Entity,
109+
&Camera,
110+
Has<IsDefaultUiCamera>,
111+
Has<UiPickingCamera>,
112+
)>,
67113
primary_window: Query<Entity, With<PrimaryWindow>>,
114+
settings: Res<UiPickingSettings>,
68115
ui_stack: Res<UiStack>,
69116
node_query: Query<NodeQuery>,
70117
mut output: EventWriter<PointerHits>,
@@ -81,7 +128,8 @@ pub fn ui_picking(
81128
// cameras. We want to ensure we return all cameras with a matching target.
82129
for camera in camera_query
83130
.iter()
84-
.map(|(entity, camera, _)| {
131+
.filter(|(_, _, _, cam_can_pick)| !settings.require_markers || *cam_can_pick)
132+
.map(|(entity, camera, _, _)| {
85133
(
86134
entity,
87135
camera.target.normalize(primary_window.single().ok()),
@@ -91,7 +139,7 @@ pub fn ui_picking(
91139
.filter(|(_entity, target)| target == &pointer_location.target)
92140
.map(|(cam_entity, _target)| cam_entity)
93141
{
94-
let Ok((_, camera_data, _)) = camera_query.get(camera) else {
142+
let Ok((_, camera_data, _, _)) = camera_query.get(camera) else {
95143
continue;
96144
};
97145
let mut pointer_pos =
@@ -122,6 +170,10 @@ pub fn ui_picking(
122170
continue;
123171
};
124172

173+
if settings.require_markers && node.pickable.is_none() {
174+
continue;
175+
}
176+
125177
// Nodes that are not rendered should not be interactable
126178
if node
127179
.inherited_visibility
@@ -208,7 +260,7 @@ pub fn ui_picking(
208260

209261
let order = camera_query
210262
.get(*camera)
211-
.map(|(_, cam, _)| cam.order)
263+
.map(|(_, cam, _, _)| cam.order)
212264
.unwrap_or_default() as f32
213265
+ 0.5; // bevy ui can run on any camera, it's a special case
214266

examples/picking/debug_picking.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ fn main() {
1010
filter: "bevy_dev_tools=trace".into(), // Show picking logs trace level and up
1111
..default()
1212
}))
13-
// Unlike UiPickingPlugin, MeshPickingPlugin is not a default plugin
14-
.add_plugins((MeshPickingPlugin, DebugPickingPlugin))
13+
.add_plugins((MeshPickingPlugin, DebugPickingPlugin, UiPickingPlugin))
1514
.add_systems(Startup, setup_scene)
1615
.insert_resource(DebugPickingMode::Normal)
1716
// A system that cycles the debugging state when you press F3:

examples/picking/mesh_picking.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
//!
1717
//! By default, the mesh picking plugin will raycast against all entities, which is especially
1818
//! useful for debugging. If you want mesh picking to be opt-in, you can set
19-
//! [`MeshPickingSettings::require_markers`] to `true` and add a [`RayCastPickable`] component to
20-
//! the desired camera and target entities.
19+
//! [`MeshPickingSettings::require_markers`] to `true` and add a [`Pickable`] component to the
20+
//! desired camera and target entities.
2121
2222
use std::f32::consts::PI;
2323

examples/picking/simple_picking.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ use bevy::prelude::*;
44

55
fn main() {
66
App::new()
7-
// Unlike UiPickingPlugin, MeshPickingPlugin is not a default plugin
8-
.add_plugins((DefaultPlugins, MeshPickingPlugin))
7+
.add_plugins((DefaultPlugins, MeshPickingPlugin, UiPickingPlugin))
98
.add_systems(Startup, setup_scene)
109
.run();
1110
}

examples/picking/sprite_picking.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use std::fmt::Debug;
66

77
fn main() {
88
App::new()
9-
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
9+
.add_plugins((
10+
DefaultPlugins.set(ImagePlugin::default_nearest()),
11+
SpritePickingPlugin,
12+
))
1013
.add_systems(Startup, (setup, setup_atlas))
1114
.add_systems(Update, (move_sprite, animate_sprite))
1215
.run();

0 commit comments

Comments
 (0)