如何用 Bevy 的 Picking 事件实现 3D 模型的点击拾取与拖拽交互?
如何用 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),仅供参考

相关新闻

Jaeger Service Performance Monitoring(SPM)实战指南:从 RED 指标采集到查询 API 全解析

Jaeger Service Performance Monitoring(SPM)实战指南:从 RED 指标采集到查询 API 全解析

Jaeger Service Performance Monitoring(SPM)实战指南:从 RED 指标采集到查询 API 全解析 【免费下载链接】jaeger CNCF Jaeger, a Distributed Tracing Platform 项目地址: https://gitcode.com/GitHub_Trending/ja/jaeger Service P…

2026/10/27 6:48:06 阅读更多 →
开源免费、轻量高效的mdput:能否成为Typora的可靠平替

开源免费、轻量高效的mdput:能否成为Typora的可靠平替

1. 为什么 Typora 用户都在找平替1.1 从 Typora 收费说起Typora 大概是 Markdown 编辑器里知名度最高的那个。它把“所见即所得”做到了极致——左边不用开预览窗口,输入#后面跟个空格,标题样式立刻呈现,打字体验几乎和 Word 一样流畅。2018 …

2026/10/26 10:15:06 阅读更多 →
TDengine 零代码接入 SparkplugB:基于 taosExplorer 的 IIoT 数据同步任务配置指南

TDengine 零代码接入 SparkplugB:基于 taosExplorer 的 IIoT 数据同步任务配置指南

TDengine 零代码接入 SparkplugB:基于 taosExplorer 的 IIoT 数据同步任务配置指南 【免费下载链接】TDengine High-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios 项目地址: https://gitcode.com/GitHub_Trending/t…

2026/10/26 10:15:04 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/26 12:00:21 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/27 4:06:49 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/26 4:52:12 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/26 12:00:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/27 6:20:47 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/27 4:06:50 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 12:00:19 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/25 13:21:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 8:34:37 阅读更多 →