如何让EntityTranslationGestureRecognizer识别被遮挡选中实体的触摸拖拽?
在RealityKit中拖拽被遮挡的选中实体解决方案
问题根源
你猜的没错:EntityTranslationGestureRecognizer内部会执行HitTest,并且默认只对第一个被命中的实体响应手势。当选中的实体被其他实体遮挡时,触摸事件会被前方的实体拦截,导致目标实体接收不到手势信号。
解决方案
1. 临时修改遮挡实体的碰撞掩码(最简单方案)
当用户选中某个实体后,临时将场景中其他所有实体的碰撞过滤规则调整为不响应手势HitTest,这样手势就只会识别到选中的实体。拖拽结束后再恢复原有配置。
示例代码:
// 保存其他实体的原始碰撞过滤规则 var originalCollisionFilters: [Entity: CollisionFilter?] = [:] func selectEntity(_ selectedEntity: Entity) { // 遍历场景中所有实体(根据你的场景层级调整遍历范围) for entity in arView.scene.anchors.flatMap({ $0.children }) { guard entity != selectedEntity else { continue } // 保存原始过滤规则 originalCollisionFilters[entity] = entity.collision?.filter // 临时设置为不响应任何碰撞测试 entity.collision?.filter = .none } // 绑定平移手势到选中实体 let translationGesture = EntityTranslationGestureRecognizer() translationGesture.entity = selectedEntity arView.addGestureRecognizer(translationGesture) // 监听手势结束事件,恢复碰撞规则 translationGesture.addTarget(self, action: #selector(onTranslationEnded(_:))) } @objc func onTranslationEnded(_ gesture: EntityTranslationGestureRecognizer) { // 恢复所有实体的原始碰撞过滤规则 for (entity, filter) in originalCollisionFilters { entity.collision?.filter = filter } originalCollisionFilters.removeAll() arView.removeGestureRecognizer(gesture) }
2. 自定义手势结合RealityKit坐标转换(更灵活)
如果不想修改其他实体的碰撞配置,可以直接用UIPanGestureRecognizer自定义拖拽逻辑,核心是将屏幕触摸位移转换为实体在锚定平面上的位置更新,和EntityTranslationGestureRecognizer的逻辑对齐。
示例代码:
var selectedEntity: Entity? var initialTouchLocation: CGPoint? var initialEntityPosition: SIMD3<Float>? func setupPanGesture() { let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) arView.addGestureRecognizer(panGesture) } @objc func handlePan(_ gesture: UIPanGestureRecognizer) { guard let selectedEntity = selectedEntity else { return } switch gesture.state { case .began: initialTouchLocation = gesture.location(in: arView) // 获取实体初始位置 initialEntityPosition = selectedEntity.position case .changed: guard let initialTouch = initialTouchLocation, let initialPos = initialEntityPosition else { return } let currentTouch = gesture.location(in: arView) // 计算屏幕位移对应的世界平面位移 guard let anchorPlane = arView.scene.anchors.first else { return } let initialWorldPos = arView.unprojectPoint(initialTouch, ontoPlane: anchorPlane.transform) let currentWorldPos = arView.unprojectPoint(currentTouch, ontoPlane: anchorPlane.transform) let positionDelta = currentWorldPos - initialWorldPos // 更新实体位置 selectedEntity.position = initialPos + positionDelta case .ended, .cancelled: initialTouchLocation = nil initialEntityPosition = nil default: break } }
注意事项
- 如果场景中有大量实体,第一种方法的遍历操作要注意性能,可以只遍历当前选中实体的同级或父级下的实体,缩小范围。
- 第二种方法中,
unprojectPoint(_:ontoPlane:)方法需要指定锚定平面,确保拖拽方向和默认手势一致;如果是自由空间拖拽,可以用其他坐标转换方式。
内容的提问来源于stack exchange,提问作者JBKKNOWL
相关产品推荐
相关产品推荐

