RealityKit/ARKit:两物体专属碰撞设置及启动碰撞异常问题
问题1:启动后圆柱体持续触发碰撞的修复
原因分析
- 重复创建实体与订阅:每次点击屏幕时,无论场景是否已放置,都会重复加载圆柱体模型、配置碰撞组件并订阅碰撞事件——这些未被添加到场景的隐形圆柱体会互相碰撞,或与AR检测到的平面触发碰撞。
- 无碰撞源过滤:碰撞事件订阅未限制触发实体,任何物体(包括AR平面)与圆柱体碰撞都会触发变色逻辑。
修复代码
修改handleTap方法,将实体创建逻辑仅放在场景首次放置的分支内,并在碰撞事件中过滤触发源为球体:
@objc func handleTap(recognizer: UITapGestureRecognizer) { guard let view = self.view, let focusEntity = self.focusEntity else { return } let tapLocation = recognizer.location(in: self.view) // 仅在场景未放置时执行实体创建逻辑 if !sceneIsPlaced { let anchor = AnchorEntity() let importModel = try! Entity.load(named: "cilinder") importModel.position = focusEntity.position importModel.scale = SIMD3(repeating: 0.5) // 配置圆柱体 let kolomMiddleModel = importModel.findEntity(named: "cilinder")!.children[0] as! (ModelEntity & HasPhysicsBody & HasCollision) let materialKolomMiddle = SimpleMaterial(color: .yellow, isMetallic: true) kolomMiddleModel.model?.materials = [materialKolomMiddle] kolomMiddleModel.generateCollisionShapes(recursive: false) kolomMiddleModel.components.set(PhysicsBodyComponent(massProperties: .default, material: .default, mode: .dynamic)) // 配置球体 let materialsBall = SimpleMaterial(color: .red, isMetallic: true) let ballModel = ModelEntity(mesh: .generateSphere(radius: 0.1), materials: [materialsBall]) as (Entity & HasPhysicsBody & HasCollision) ballModel.position = [-0.1, -1.0,-0.1] ballModel.generateCollisionShapes(recursive: true) ballModel.name = "ballModel" // 配置灯光 let directionalLight = DirectionalLight() directionalLight.light.color = .white directionalLight.light.intensity = 4000 directionalLight.light.isRealWorldProxy = true directionalLight.shadow?.maximumDistance = 1.5 directionalLight.shadow?.depthBias = 7.0 directionalLight.orientation = simd_quatf(angle: .pi/1.5, axis: [0,1,0]) let lightAnchor = AnchorEntity(world: [0, 0, 2.5]) lightAnchor.addChild(directionalLight) // 订阅碰撞事件,仅响应球体触发的碰撞 view.scene.subscribe(to: CollisionEvents.Began.self, on: kolomMiddleModel) { event in if event.entityB.name == "ballModel" { print("Collision with ball detected!") let material = SimpleMaterial(color: .red, isMetallic: true) kolomMiddleModel.model?.materials = [material] } }.store(in: &self.subscriptions) // 添加所有实体到场景 anchor.addChild(importModel) anchor.addChild(ballModel) anchor.addChild(lightAnchor) view.scene.addAnchor(anchor) sceneIsPlaced = true } else { // 场景已放置时,仅处理球体移动逻辑 if let locationEntity = view.entity(at: tapLocation) as? ModelEntity, locationEntity.name == "ballModel" { locationEntity.physicsBody = .init() locationEntity.physicsBody?.mode = .kinematic locationEntity.physicsMotion = PhysicsMotionComponent(linearVelocity: [0, 0, -0.5], angularVelocity: [1, 3, 5]) } } }
额外优化:可以通过碰撞过滤排除AR平面的碰撞,避免圆柱体与地面平面触发碰撞事件。
问题2:设置碰撞过滤实现指定物体碰撞
实现思路
使用RealityKit的CollisionFilter,通过**碰撞类别(category)和碰撞掩码(mask)**定义哪些实体之间可以触发碰撞:
- 为球体、目标圆柱体、其他圆柱体分配不同的碰撞类别位值。
- 配置实体的
CollisionComponent,仅让球体与目标圆柱体的掩码互相包含。
修复代码
- 定义碰撞类别常量:
// 在Coordinator类内定义 private let ballCategory: CollisionCategory = .init(rawValue: 1 << 0) private let targetCylinderCategory: CollisionCategory = .init(rawValue: 1 << 1) private let otherCylinderCategory: CollisionCategory = .init(rawValue: 1 << 2)
- 配置球体的碰撞过滤:
// 创建球体时添加 ballModel.collision = CollisionComponent( shapes: [.generateSphere(radius: 0.1)], filter: CollisionFilter( category: ballCategory, mask: targetCylinderCategory // 仅与目标圆柱体碰撞 ) )
- 配置目标圆柱体的碰撞过滤:
// 配置目标圆柱体时添加 kolomMiddleModel.collision = CollisionComponent( shapes: kolomMiddleModel.generateCollisionShapes(recursive: false), filter: CollisionFilter( category: targetCylinderCategory, mask: ballCategory // 仅与球体碰撞 ) )
- 后续添加其他圆柱体时,配置为不与球体碰撞:
// 示例:创建其他圆柱体 let otherCylinder = ModelEntity(mesh: .generateCylinder(height: 0.5, radius: 0.1), materials: [SimpleMaterial(color: .blue, isMetallic: true)]) as (ModelEntity & HasPhysicsBody & HasCollision) otherCylinder.generateCollisionShapes(recursive: false) otherCylinder.components.set(PhysicsBodyComponent(massProperties: .default, material: .default, mode: .dynamic)) otherCylinder.collision = CollisionComponent( shapes: otherCylinder.generateCollisionShapes(recursive: false), filter: CollisionFilter( category: otherCylinderCategory, mask: .all & ~ballCategory // 不与球体碰撞,可与其他物体碰撞 ) ) // 添加到场景 anchor.addChild(otherCylinder)
这样设置后,只有标记为targetCylinderCategory的圆柱体才会与球体触发碰撞事件,其他圆柱体不会响应球体的碰撞。
内容的提问来源于stack exchange,提问作者saro
相关产品推荐
相关产品推荐

