跳到主要内容

射线检测

射线检测查询两个 3D 点之间的直线是否与碰撞形状相交,以及交点位置。方法位于刚体系统 app.systems.rigidbody,各开发方式用法相同,只需应用和两个点。

通过射线拾取​

常见用途是拾取:用户点击或触摸屏幕选中实体。下面从相机穿过指针位置发射射线,返回最近命中刚体:

射线检测

射线从相机出发,结束于指针映射到远裁剪平面的位置,因此穿过指针下的对象:

function pick(screenX, screenY) {
// From the camera position...
const from = cameraEntity.getPosition();

// ...to the pointer position projected onto the far clip plane
const to = cameraEntity.camera.screenToWorld(screenX, screenY, cameraEntity.camera.farClip);

// Return the closest hit, or null
const result = app.systems.rigidbody.raycastFirst(from, to);
if (result) {
console.log(`You selected ${result.entity.name}`);
}
}

app.mouse.on(pc.EVENT_MOUSEDOWN, (event) => pick(event.x, event.y));
app.touch?.on(pc.EVENT_TOUCHSTART, (event) => pick(event.touches[0].x, event.touches[0].y));

编辑器中可放到相机实体脚本,以 this.app 和 this.entity 代替 app、cameraEntity;物理实体拾取提供完整脚本。React 使用 useApp(),Web Components 通过 whenReady('pc-app') 访问。相关模式见移动刚体。

射线结果​

刚体系统提供两个方法:

每个命中为 RaycastResult:

属性说明
entity碰撞形状被命中的实体
point世界空间命中位置
normal命中表面的世界空间法线
hitFraction命中位置沿射线的比例,start 为 0,end 为 1

射线同时命中刚体和触发体积,可通过以下筛选跳过触发器。

筛选射线命中​

两个方法均接受选项对象:

选项说明
filterTags仅匹配指定标签。格式为将 Tags#has 参数放入数组:['enemy'] 要求该标签,['enemy', 'boss'] 要求同时具有两者,[['red', 'blue']] 要求任一
filterCallback接收各命中实体,返回 true 保留
filterCollisionGroup射线的碰撞组,与各刚体掩码比较
filterCollisionMask射线的碰撞掩码,与各刚体组比较
sort仅 raycastAll,按由近到远排序
// Find every enemy the shot passes through, nearest first
const hits = app.systems.rigidbody.raycastAll(from, to, {
filterTags: ['enemy'],
sort: true
});
for (const hit of hits) {
hit.entity.script.health.damage(10 * (1 - hit.hitFraction));
}

// Pick anything except the player's own body
const result = app.systems.rigidbody.raycastFirst(from, to, {
filterCallback: (entity) => entity !== player
});

标签或回调筛选需要沿射线取得全部命中,因此 raycastFirst 使用它们时工作量与 raycastAll 相同。组和掩码在物理引擎内部筛选,开销更低。按标签射线检测展示完整项目。

探测环境​

实体也可通过射线探测周围。例如判断角色是否站在地面,从角色位置向下发射短射线,检查是否命中自身以外的对象:

const from = player.getPosition();
const to = new pc.Vec3(from.x, from.y - 1.1, from.z);
const hit = app.systems.rigidbody.raycastFirst(from, to, {
filterCallback: (entity) => entity !== player
});
const onGround = hit !== null;

另请参阅​