CocosCreator 射线投射:3D物体精准选中技术解析

CocosCreator 射线投射:3D物体精准选中技术解析

一、射线投射的核心原理与CocosCreator实现基础

射线投射(Raycasting)是3D图形学中用于检测空间中物体与虚拟射线相交的核心技术。在CocosCreator中,这一技术通过物理引擎(如Built-in Physics或Cannon.js)的数学计算实现,其本质是构造一条从起点出发、沿特定方向延伸的无限长直线,检测该直线与场景中碰撞体的交点。

1.1 数学模型解析

射线投射的数学基础可表示为参数方程:
R(t) = O + t * D
其中:

  • O 为射线起点(通常为摄像机近裁剪面坐标)
  • D 为归一化方向向量(由屏幕点击位置转换而来)
  • t 为距离参数,当t>0且最小时的交点即为最近碰撞点

CocosCreator的物理引擎通过空间分区算法(如BVH或八叉树)加速射线与碰撞体的相交测试,确保在复杂场景中仍能保持高效。

1.2 CocosCreator中的基础实现路径

  1. 获取屏幕点击坐标:通过touchmouse事件获取归一化屏幕坐标(范围[-1,1])
  2. 转换为世界空间射线
    1. // 示例:从屏幕坐标生成射线
    2. const touchPos = event.getLocation();
    3. const ray = this.camera.screenPointToRay(touchPos.x, touchPos.y);
  3. 执行物理查询
    1. const results = this.physicsSystem.raycast(
    2. ray.origin,
    3. ray.direction,
    4. 1000, // 最大检测距离
    5. PhysicsSystem.RaycastClosest // 检测模式
    6. );

二、完整实现方案与代码详解

2.1 基础选中检测实现

  1. import { _decorator, Component, Node, PhysicsSystem, Ray, Vec3 } from 'cc';
  2. @ccclass('RaycastSelector')
  3. export class RaycastSelector extends Component {
  4. @property(Node)
  5. cameraNode: Node | null = null;
  6. private cameraComp = null;
  7. private physicsSystem = null;
  8. onLoad() {
  9. this.cameraComp = this.cameraNode?.getComponent(Camera);
  10. this.physicsSystem = director.getPhysicsSystem();
  11. }
  12. onTouchStart(event: Touch) {
  13. const touchPos = event.getLocation();
  14. const ray = this.cameraComp.screenPointToRay(
  15. touchPos.x / canvas.width * 2 - 1,
  16. -(touchPos.y / canvas.height * 2 - 1) // 注意Y轴坐标系转换
  17. );
  18. const result = this.physicsSystem.raycast(
  19. ray.origin,
  20. ray.direction,
  21. 1000,
  22. PhysicsSystem.RaycastClosest
  23. );
  24. if (result) {
  25. const hitNode = result.collider.node;
  26. console.log(`选中物体: ${hitNode.name},距离: ${result.distance}`);
  27. // 触发选中效果(如高亮)
  28. this.highlightSelected(hitNode);
  29. }
  30. }
  31. private highlightSelected(node: Node) {
  32. // 实现选中高亮逻辑(如修改材质或添加特效)
  33. }
  34. }

2.2 多物体检测与优先级处理

当场景中存在重叠物体时,可通过PhysicsSystem.RaycastAll获取所有碰撞结果,并按距离排序:

  1. const allResults = this.physicsSystem.raycast(
  2. ray.origin,
  3. ray.direction,
  4. 1000,
  5. PhysicsSystem.RaycastAll
  6. );
  7. allResults.sort((a, b) => a.distance - b.distance);
  8. const topHit = allResults[0]; // 最近物体

2.3 性能优化策略

  1. 层级过滤:通过setRaycastLayers限制检测的物理层
    1. this.physicsSystem.setRaycastLayers(1 << Layer.Selectable); // 仅检测特定层
  2. 距离裁剪:合理设置最大检测距离
  3. 静态物体合并:对静态场景使用合并网格减少碰撞体数量
  4. 结果缓存:对频繁检测的物体缓存碰撞结果

三、高级应用场景与解决方案

3.1 非凸碰撞体检测

对于复杂形状物体,需确保碰撞体设置为Mesh Collider并启用凸包分解:

  1. // 在3D模型导入设置中勾选
  2. // "Convex"选项或通过代码动态设置
  3. const collider = node.addComponent(MeshCollider);
  4. collider.convex = true;

3.2 透明物体穿透处理

通过材质属性判断是否允许射线穿透:

  1. // 在着色器中添加_RaycastEnable属性
  2. // 检测时检查材质属性
  3. const renderer = hitNode.getComponent(MeshRenderer);
  4. if (renderer.material.getProperty('_RaycastEnable')) {
  5. // 允许穿透的逻辑
  6. }

3.3 多摄像机场景处理

当存在多个摄像机时,需明确指定射线生成的摄像机:

  1. // 在多摄像机场景中,确保使用正确的摄像机实例
  2. const mainCamera = find('MainCamera')?.getComponent(Camera);
  3. const ray = mainCamera.screenPointToRay(...);

四、常见问题与调试技巧

4.1 射线未命中问题排查

  1. 检查摄像机投影矩阵:确认正交/透视投影设置正确
  2. 验证碰撞体状态:确保目标物体有碰撞体组件且未禁用
  3. 调试可视化:使用辅助线绘制射线方向
    1. // 调试时绘制射线(需自定义DebugDraw系统)
    2. DebugDraw.drawLine(ray.origin, ray.origin.add(ray.direction.multiplyScalar(1000)));

4.2 性能瓶颈优化

  1. 使用Profiler分析:监控Physics.raycast调用耗时
  2. 空间分区优化:对密集物体使用网格或八叉树分区
  3. 批处理检测:对固定位置的物体预计算空间索引

五、最佳实践建议

  1. 分层检测策略:将交互层与静态背景层分离
  2. 结果池化:复用RaycastResult对象避免频繁内存分配
  3. 异步检测:对复杂场景使用WebWorker进行离屏检测
  4. 输入缓冲:对快速连续点击进行去抖动处理

通过系统掌握射线投射技术,开发者可在CocosCreator中实现从简单物体选中到复杂交互系统的全链条开发。建议结合实际项目需求,逐步从基础实现向性能优化和高级功能演进,最终构建出稳定高效的3D交互系统。