Bevy Picking Wrong Entities: No Collider Bug
Have you ever encountered an issue where your Bevy game's picking system selects the wrong entities, specifically those without colliders? This can be a frustrating problem, leading to unexpected behavior and a poor user experience. In this article, we'll dive deep into this issue, exploring its causes, providing a minimal reproduction case, and discussing potential solutions. Let's get started and unravel this picking puzzle!
Understanding the Problem: Why Picking Goes Wrong
When implementing a picking system in Bevy, the goal is to accurately identify the entity that the user is interacting with, typically by clicking or hovering the mouse cursor. This usually involves raycasting from the camera into the scene and detecting collisions with entities that have colliders. However, there are instances where the picking system might select entities that lack colliders, such as the primary window, leading to unexpected results. This misidentification can stem from various factors, including:
- Incorrect Querying: The picking logic might be querying entities in a way that doesn't properly filter out those without colliders. This can happen if the query is too broad and includes entities that are not intended to be pickable.
- Z-fighting: When two or more objects occupy the same space and same depth, the depth buffer may become inaccurate, leading to picking the wrong object.
- Layering Issues: If entities are not properly layered or ordered in the scene, the picking system might select the wrong entity due to overlapping or incorrect depth sorting.
- Camera and Projection: If the camera's projection or transform is not correctly set up, it can lead to inaccurate raycasting and picking results.
It's important to note that accurately implementing entity picking is vital for any interactive 3D application. Without it, users may experience frustration as their intended selections fail or unexpected objects are highlighted. Therefore, understanding the root causes of such issues and addressing them effectively is key to a polished and intuitive user experience.
Minimal Reproduction Case: Demonstrating the Issue
To better illustrate this problem, let's examine a minimal reproduction case written in Rust using the Bevy game engine and the avian3d and bevy_picking crates. This code snippet demonstrates how the picking system can incorrectly select the primary window instead of entities with colliders:
use avian3d::prelude::*;
use bevy::{picking::pointer::PointerInteraction, prelude::*};
fn main() {
App::new()
.add_plugins((
DefaultPlugins,
PhysicsPlugins::default(),
PhysicsPickingPlugin,
))
.add_systems(Startup, setup)
.add_systems(Update, update)
.run();
}
fn setup(mut commands: Commands) {
commands.spawn((Camera3d::default(), Transform::IDENTITY));
}
fn update(mut commands: Commands, pointers: Query<&PointerInteraction>) {
let pointer = pointers.single().unwrap();
let Some((hit_e, hit)) = pointer.get_nearest_hit() else {
println!("No hit");
return;
};
println!("Hit: target={hit_e:?} data={hit:?}");
commands.entity(*hit_e).log_components();
}
This code sets up a basic Bevy application with the necessary plugins for physics and picking. The update system retrieves the PointerInteraction resource and attempts to get the nearest hit entity. However, in this case, it often selects the primary window (entity 0v0) instead of any actual game objects with colliders.
Here are the dependency versions used in this example:
[dependencies]
avian3d = { version = "0.4.1" }
bevy = { version = "0.17.3" }
This issue has also been observed with the latest version from git, tested at commit 567b9f511fa45874fccda1c0c01d3e6bb0e689c0:
[patch.crates-io]
avian3d = { git = "https://github.com/avianphysics/avian", branch = "main" }
When running this program, you might see output similar to this:
Hit: target=0v0 data=HitData { camera: 0v0, depth: 0.0, position: Some(Vec3(693.3281, 425.65625, 0.0)), normal: None }
2025-12-04T02:27:33.489247Z INFO bevy_ecs::system::commands::entity_command: Entity 0v0: [DebugName { name: "bevy_window::window::Window" }, DebugName { name: "bevy_window::window::CursorOptions" }, DebugName { name: "bevy_window::window::PrimaryWindow" }, DebugName { name: "bevy_window::raw_handle::RawHandleWrapperHolder" }, DebugName { name: "bevy_winit::system::CachedWindow" }, DebugName { name: "bevy_winit::system::WinitWindowPressedKeys" }, DebugName { name: "bevy_winit::system::CachedCursorOptions" }, DebugName { name: "bevy_window::raw_handle::RawHandleWrapper" }, DebugName { name: "bevy_picking::hover::PickingInteraction" }]
As you can see, the query incorrectly identifies the primary window as the hit entity, which doesn't have a collider component. This highlights the core of the issue we're addressing.
Analyzing the Cause: Why the Window is Picked
The reason the primary window is being picked in this scenario boils down to how the picking system and queries interact within Bevy. The PointerInteraction resource provides information about the pointer's interactions with the scene, including potential hits. However, the get_nearest_hit() method, as used in the example, might not inherently filter out entities based on the presence of a collider.
In essence, the raycasting might be intersecting with the window surface, and since the window entity is part of the scene, it becomes a potential hit candidate. Without an explicit filter for entities with colliders, the window entity, being the closest in some cases, gets selected.
This behavior underscores the importance of precise query construction when dealing with picking. A query that indiscriminately fetches entities can lead to unintended selections, especially when dealing with entities like the primary window that might not be relevant for picking interactions.
Potential Solutions: Filtering Entities with Colliders
To address this issue, we need to modify the picking logic to explicitly filter for entities that have a collider component. This ensures that only entities intended for interaction are considered as potential hits. There are several ways to achieve this in Bevy.
1. Modifying the Query
The most straightforward approach is to modify the query in the update system to include a requirement for the Collider component. This can be done by adding &Collider to the query:
fn update(
mut commands: Commands,
pointers: Query<&PointerInteraction>,
query: Query<&Collider>
) {
let pointer = pointers.single().unwrap();
let Some((hit_e, hit)) = pointer.get_nearest_hit() else {
println!("No hit");
return;
};
// Rest of the logic
}
By adding the second query, you can filter only those entities that have colliders. However, this alone will not solve the issue. You need to correlate the hit_e with the entities that have colliders. This can be achieved by iterating over the entities with colliders and checking if their entity ID matches hit_e.
2. Using a Component to Mark Pickable Entities
Another approach is to introduce a marker component, such as Pickable, and add it to entities that should be pickable. Then, the query can filter for entities with both the Collider and Pickable components. This approach provides more flexibility and control over which entities are considered for picking.
#[derive(Component)]
struct Pickable;
// Add Pickable component to entities that should be pickable
commands.spawn(( /* ... */, Collider::/* ... */, Pickable));
// Modify the query
fn update(
mut commands: Commands,
pointers: Query<&PointerInteraction>,
query: Query<&Collider, With<Pickable>>
) {
// ...
}
3. Custom Raycasting Logic
For more advanced scenarios, you might need to implement custom raycasting logic that provides finer-grained control over the picking process. This involves manually creating a ray from the camera's position and direction, and then iterating over the scene's entities to check for intersections with their colliders. While this approach requires more code, it offers the most flexibility and allows for optimizations tailored to your specific needs.
Conclusion: Ensuring Accurate Picking in Bevy
The issue of picking entities without colliders can be a stumbling block in Bevy game development. However, by understanding the causes and implementing appropriate filtering techniques, you can ensure accurate and reliable picking behavior. Whether it's modifying queries, using marker components, or implementing custom raycasting, the key is to explicitly control which entities are considered for picking interactions.
By addressing this issue effectively, you'll create a more polished and intuitive user experience in your Bevy games. Remember, accurate picking is essential for any interactive application, and taking the time to implement it correctly will pay dividends in the long run.
For more information on Bevy and its picking capabilities, refer to the Bevy documentation, a valuable resource for any Bevy developer. Happy coding!