资讯动态

如何用 Bevy 的 Picking 事件实现 3D 模型的点击拾取与拖拽交互?

发布时间:2026/9/12 23:50:19 来源:尧图企业网站定制
如何用 Bevy 的 Picking 事件实现 3D 模型的点击拾取与拖拽交互【免费下载链接】bevyA refreshingly simple>项目地址: https://gitcode.com/GitHub_Trending/be/bevy要在 Bevy 的 3D 场景中让模型可被指针点击、悬停高亮并跟随拖拽旋转需要用到bevy_picking提供的指针事件体系以及负责 3D 网格命中检测的MeshPickingPlugin。本文以仓库中的 picking 示例为蓝本走一遍从插件注册、实体构建、事件观察者绑定到验证命中效果与限制可拾取范围的完整路径。注意MeshPickingPlugin不是默认插件必须手动注册另外 examples/README.md 明确提示git 主分支的开发版 API 与 crates.io 上的发布版本存在不兼容差异本文代码对应仓库主分支版本。注册插件并搭建最小 3D 场景3D 拾取的最小依赖是DefaultPlugins之外的MeshPickingPlugin一个Mesh3d实体以及一个能看到它的Camera3d。simple_picking 示例的注册方式是fn main() { App::new() .add_plugins((DefaultPlugins, MeshPickingPlugin)) .add_systems(Startup, setup_scene) .run(); }该示例的setup_scene生成了一个 3D 场景的基本结构// 地面圆盘旋转 -90 度使其水平 commands.spawn(( Mesh3d(meshes.add(Circle::new(4.0))), MeshMaterial3d(materials.add(Color::WHITE)), Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)), )); // 点光源 commands.spawn(( PointLight { shadow_maps_enabled: true, ..default() }, Transform::from_xyz(4.0, 8.0, 4.0), )); // 相机 commands.spawn(( Camera3d::default(), Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), ));关于后端的选择边界mesh_picking 示例的注释说明MeshPickingPlugin基于射线检测适合作为起点和调试用途游戏项目中也可以换成物理引擎的 3D 拾取后端或拾取着色器。bevy_ui、bevy_sprite等自带后端可以与 mesh 后端同时启用UI 或 sprite 会正确遮挡或穿透其下的 3D 网格。用 Observer 绑定点击与拖拽事件bevy_picking的事件设计是把处理逻辑用观察者observer直接挂到实体上事件从目标实体出发沿实体层级向上冒泡详见 bevy_picking 的 crate 文档。simple_picking 示例展示了点击生成对象、拖拽旋转的完整闭环fn on_click_spawn_cube( _click: OnPointerClick, mut commands: Commands, mut meshes: ResMutAssetsMesh, mut materials: ResMutAssetsStandardMaterial, mut num: Localusize, ) { commands .spawn(( Mesh3d(meshes.add(Cuboid::new(0.5, 0.5, 0.5))), MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))), Transform::from_xyz(0.0, 0.25 0.55 * *num as f32, 0.0), )) // 加了 MeshPickingPlugin 之后可以为网格实体挂指针事件观察者 .observe(on_drag_rotate); *num 1; } fn on_drag_rotate(drag: OnPointerDrag, mut transforms: Querymut Transform) { if let Ok(mut transform) transforms.get_mut(drag.entity) { transform.rotate_y(drag.delta.x * 0.02); transform.rotate_x(drag.delta.y * 0.02); } }两个关键点拖拽中修改的是drag.entity被拖拽的目标实体自身的Transform而不是观察者所在的实体drag.delta的单位是屏幕像素而非世界坐标events.rs 的文档对此有明确说明This is stored in screen pixels, not world coordinates并建议用Camera的方法做屏幕空间到世界空间的转换。示例中的0.02就是像素增量到旋转角的经验缩放系数属于示例代码自身的取值。悬停与按压的视觉反馈mesh_picking 示例演示了如何用四个事件切换材质实现悬停变色、按压变色的反馈commands .spawn(( Mesh3d(shape), MeshMaterial3d(white_matl.clone()), Transform::from_xyz(...).with_rotation(Quat::from_rotation_x(-PI / 4.)), Shape, )) .observe(update_material_on::PointerOver(hover_matl.clone())) .observe(update_material_on::PointerOut(white_matl.clone())) .observe(update_material_on::PointerPress(pressed_matl.clone())) .observe(update_material_on::PointerRelease(hover_matl.clone())) .observe(rotate_on_drag);其中观察者闭包通过event.event_target()拿到事件目标实体再查询其MeshMaterial3d并替换材质句柄。该示例的拖拽旋转观察者为fn rotate_on_drag(drag: OnPointerDrag, mut transforms: Querymut Transform) { let mut transform transforms.get_mut(drag.entity).unwrap(); transform.rotate_y(drag.delta.x * 0.02); transform.rotate_x(drag.delta.y * 0.02); }事件全集定义在 events.rs按用途分三组悬停与移动PointerOver、PointerEnter、PointerMove、PointerLeave、PointerOut点击与按压PointerPress、PointerRelease、PointerClick拖拽与放置PointerDragStart、PointerDrag、PointerDragEnd、PointerDragEnter、PointerDragOver、PointerDragDrop、PointerDragLeave每个事件都携带entity目标实体、pointer指针信息和hitHitData命中数据字段。关于HitDatamesh_picking 模块文档指出两点实现细节position是世界空间坐标normal指向面外侧但对缩放过的网格不保证已归一化。观察者中还可以调用event.propagate(false)阻止事件继续沿层级向上冒泡这在拖拽场景中用于让被拖拽物自己消费事件、避免祖先实体重复响应。控制哪些实体和相机参与拾取mesh_picking 模块文档说明了默认行为与两种收窄方式默认所有使用RenderAssetUsages::MAIN_WORLD的网格都可拾取方便调试。对个别实体排除加Pickable::IGNORE组件即可mesh_picking 示例用它禁用了地面平面commands.spawn(( Mesh3d(meshes.add(Plane3d::default().mesh().size(50.0, 50.0).subdivisions(10))), MeshMaterial3d(ground_matl.clone()), Pickable::IGNORE, // 让地面不参与拾取 ));整体改为 opt-in把MeshPickingSettings::require_markers设为true之后只有带MeshPickingCamera组件的相机和带Pickable组件的实体会被射线检测考虑。MeshPickingSettings还有一个ray_cast_visibility字段默认RayCastVisibility::VisibleInView只拾取可见且在相机视野内的实体设为RayCastVisibility::Any则隐藏实体也能被射线命中。Pickable组件本身定义于 lib.rs有两个字段should_block_lower决定该实体是否阻止其下方的实体被拾取is_hoverable决定它自身是否可被悬停Pickable::IGNORE是两者的关闭快捷值。可选分支拖拽到目标区域的 Drop 交互如果交互目标不是旋转被拖物而是拖到某处放下dragdrop_picking 示例给出了一套PointerDrag*事件的用法。它在 UI 按钮上开始拖拽在 2D 网格构成的DropArea上响应进入、悬停、放下与离开// 可拖拽的 UI 按钮上开始/结束拖拽时改变自身颜色并阻止事件冒泡 .observe(|mut event: OnPointerDragStart, mut button_color: Singlemut BackgroundColor, WithDraggableButton| { button_color.0 Color::srgb(1.0, 0.5, 0.0); event.propagate(false); }) // 放置区上被拖物进入时生成半透明的幽灵预览 fn on_drag_enter(mut event: OnPointerDragEnter, button: SingleEntity, WithDraggableButton, ...) { if event.dragged *button { let Some(position) event.hit.position else { return; }; // 在 hit.position 处生成 GhostPreview 实体 } }要点是观察者里先比较event.dragged *button确认被拖动的实体确实是这个按钮再取event.hit.position世界空间坐标来定位预览或最终放置物PointerDragDrop时用event.dropped做同样的比对PointerDragLeave时销毁预览实体。运行与验证在仓库根目录直接运行官方示例即可逐条验证本文路径examples/README.md 给出通用命令格式如cargo run --example hello_world必要时可加--features x11或--features wayland强制窗口合成器# 点击生成 拖拽旋转3D 网格 UI 按钮混用 cargo run --example simple_picking # 悬停/按压变色 拖拽旋转含命中点可视化 cargo run --example mesh_picking # UI 拖拽放到 3D 放置区 cargo run --example dragdrop_pickingmesh_picking 示例还内置了一个无需交互即可观察拾取是否生效的系统——用 gizmo 在命中点画球和箭头fn draw_mesh_intersections(pointers: QueryPointerInteraction, mut gizmos: Gizmos) { for (point, normal) in pointers .iter() .filter_map(|interaction| interaction.get_nearest_hit()) .filter_map(|(_entity, hit)| hit.position.zip(hit.normal)) { gizmos.sphere(point, 0.05, RED_500); gizmos.arrow(point, point normal.normalize() * 0.5, PINK_100); } }PointerInteraction组件导入路径bevy::picking::pointer::PointerInteraction记录每个指针的悬停状态get_nearest_hit()返回最近命中实体及其HitData。把这个系统抄进自己的应用就能直观确认指针确实命中了预期实体、且命中点/法线符合预期指针悬停网格时命中点出现红球与箭头即拾取链路工作正常。限制与适用边界MeshPickingPlugin的射线检测面向简单网格场景示例注释明确建议需要更复杂交互时改用MeshRayCast系统参数做手动射线检测或接入具备射线检测能力的物理引擎。缩放网格的命中法线不保证归一化见上文HitData说明画指示器前需要自己normalize()mesh_picking 示例正是这样做的。PointerDrag/PointerMove的delta是屏幕像素增量直接乘进世界变换如旋转角、平移距离时效果会随窗口分辨率和相机距离变化示例中的0.02系数只是示例取值按自己的交互手感调整。示例代码与 crates.io 发布版 API 可能不兼容对照 examples/README.md 的提示选择匹配的文档版本。【免费下载链接】bevyA refreshingly simple>项目地址: https://gitcode.com/GitHub_Trending/be/bevy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价