Skip to content

Picking 系统架构

Bevy 的 Picking 不是一个组件、一个系统,而是一套分层框架。理解它的三层结构,后面的所有用法都是自然推论。

三层分工

  1. 输入层bevy_inputbevy_picking::input)——把鼠标/触摸的硬件事件翻译成统一的 PointerInput,写入事件队列;
  2. 后端层(backends)——每个后端负责一种"命中检测":bevy_ui 检测 UI 节点,bevy_sprite 检测 2D 精灵,MeshPickingPlugin 检测 3D 网格。后端往 PointerHits 里写"指针碰到了哪些实体、距离多远";
  3. 事件层bevy_picking::events)——把 PointerHits 翻译成高层事件:OverOutClickDrag……这些事件以 Pointer<E> 的形式发出,支持 Observer 监听和事件冒泡。
鼠标点击


PointerInput(输入层)


PointerHits(后端层:UI / Sprite / Mesh 各写各的)


Pointer<Click>(事件层:统一格式,自动冒泡)


你的 Observer 闭包

MeshPickingPlugin:不在 DefaultPlugins 里

这是新手最常踩的坑:DefaultPlugins 包含了 UI 和 Sprite 的 picking 后端,但不包含 3D 网格的。原因是 3D 射线检测开销较大,而且很多游戏会用自己的物理引擎做射线。要用 3D picking,必须手动加:

rust
App::new()
    .add_plugins((DefaultPlugins, MeshPickingPlugin))

加了之后,所有带 Mesh3d 的实体默认都能被鼠标点到——不需要额外组件。不想被点到的实体加 Pickable::IGNORE

Pointer<E>:统一的事件容器

所有指针事件都包裹在 Pointer<E> 里:

rust
pub struct Pointer<E: Debug + Clone + Reflect> {
    pub entity: Entity,           // 事件目标实体
    pub pointer_id: PointerId,    // 哪根指针(鼠标/触摸)
    pub pointer_location: Location,
    pub event: E,                 // 具体事件数据
}

E 可以是 ClickOverOutPressReleaseDragDragStartDragEndDragDrop……每种 E 携带不同的字段。Pointer<E> 实现了 Deref<Target = E>,所以可以直接读 drag.delta,不用写 drag.event.delta

Observer 模式:.observe()

Picking 事件走 Bevy 的 Observer 机制(第 8 章介绍过)。用法是在实体上链式调用 .observe()

rust
commands
    .spawn((Mesh3d(...), MeshMaterial3d(...)))
    .observe(|click: On<Pointer<Click>>| {
        info!("clicked on {:?}", click.entity);
    });

Observer 闭包的第一个参数是 On<Pointer<E>>,可以通过 .entity 拿到目标实体,通过 .event(或 Deref)拿到事件数据。闭包还可以带额外的系统参数(QueryRes 等),跟普通系统一样。

事件冒泡

Pointer<E> 标注了 #[entity_event(propagate = PointerTraversal, auto_propagate)]——事件会自动从目标实体向父级传播,直到某个 Observer 调用 event.propagate(false) 阻止。这意味着:你在父级节点挂一个 Observer,就能接住所有子节点的事件,不需要逐个挂。

下一节开始写代码:先用 MeshPickingPlugin + Click Observer 做一个最小可用的点选 demo。