You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 11:15:37