当前位置:   article > 正文

【iOS ARKit】PhysicsMotionComponent

【iOS ARKit】PhysicsMotionComponent

      使用 Physics BodyComponent 组件,通过设置物理参数、物理材质、施加作用力,能完全模拟物体在真实世界中的行为,这种方式的优点是遵循物理学规律、控制精确,但缺点是不直观。使用 PhysicsMotion Component组件则可以通过直接设置速度进行物理模拟,但需要明白的是,对物体施加力与设置物体速度是两种完全不同且不相容的操作,无法混合使用。

     下面我们使用 PhysicsMotionComponent组件进行演示。在代上节码中,我们手工构建了模拟环境,这是件枯燥且容易出错的工作,而且很难构建复杂的场景,利用 Reality Composer 工具则可以快速地构建场最模型,本示例我们先使用 Reality Composer 构建基本的场景,然后通过设置速度的方式进行物理模拟。

     利用 Reality Composer 工具设置好各实体的大小、物理材质、碰撞属性和位置关系,然后在 Xcode 中导入 Reality 场景,具体代码如下。

  1. //
  2. // PhysicsMotionView.swift
  3. // ARKitDeamo
  4. //
  5. // Created by zhaoquan du on 2024/3/14.
  6. //
  7. import SwiftUI
  8. import RealityKit
  9. import ARKit
  10. struct PhysicsMotionView: View {
  11. var body: some View {
  12. PhysicsMotionViewContainer().navigationTitle("物理模拟2").edgesIgnoringSafeArea(.all)
  13. }
  14. }
  15. struct PhysicsMotionViewContainer:UIViewRepresentable {
  16. func makeCoordinator() -> Coordinator {
  17. Coordinator()
  18. }
  19. func makeUIView(context: Context) -> some ARView {
  20. let arView = ARView(frame: .zero)
  21. let config = ARWorldTrackingConfiguration()
  22. config.planeDetection = .horizontal
  23. context.coordinator.arView = arView
  24. context.coordinator.loadModel()
  25. arView.session.delegate = context.coordinator
  26. arView.session.run(config)
  27. return arView
  28. }
  29. func updateUIView(_ uiView: UIViewType, context: Context) {
  30. }
  31. class Coordinator: NSObject, ARSessionDelegate{
  32. var sphereEntity : ModelEntity!
  33. var arView:ARView? = nil
  34. let gameController = GameController()
  35. @MainActor func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
  36. guard let anchor = anchors.first as? ARPlaneAnchor,
  37. let arView = arView else{
  38. return
  39. }
  40. let planeAnchor = AnchorEntity(anchor:anchor)
  41. planeAnchor.addChild(gameController.gameAnchor)
  42. arView.scene.anchors.append(planeAnchor)
  43. gameController.gameAnchor.backWall?.visit { entity in
  44. entity.components[ModelComponent.self] = nil
  45. }
  46. gameController.gameAnchor.frontWall?.visit { entity in
  47. entity.components[ModelComponent.self] = nil
  48. }
  49. gameController.Ball13?.physicsBody?.massProperties.centerOfMass = ([0.001,0,0.001],simd_quatf(angle: 0, axis: [0,1,0]))
  50. gameController.Ball4?.physicsBody?.material = PhysicsMaterialResource.generate(friction: 0.3, restitution: 0.3)
  51. gameController.Ball6?.physicsBody?.mode = .kinematic
  52. //gameController.Ball6?.collision?.shapes.removeAll()
  53. arView.session.delegate = nil
  54. arView.session.run(ARWorldTrackingConfiguration())
  55. }
  56. @MainActor func loadModel(){
  57. gameController.gameAnchor = try! Ball.loadBallGame()
  58. if let ball = gameController.gameAnchor.motherBall as? Entity & HasCollision {
  59. let gestureRecognizers = arView?.installGestures(.translation, for: ball)
  60. if let gestureRecognizer = gestureRecognizers?.first as? EntityTranslationGestureRecognizer {
  61. gameController.gestureRecognizer = gestureRecognizer
  62. gestureRecognizer.removeTarget(nil, action: nil)
  63. gestureRecognizer.addTarget(self, action: #selector(self.handleTranslation))
  64. }
  65. }
  66. }
  67. @objc
  68. func handleTranslation(_ recognizer: EntityTranslationGestureRecognizer) {
  69. guard let ball = gameController.motherBall else { return }
  70. let settings = gameController.settings
  71. if recognizer.state == .ended || recognizer.state == .cancelled {
  72. gameController.gestureStartLocation = nil
  73. ball.physicsBody?.mode = .dynamic
  74. return
  75. }
  76. guard let gestureCurrentLocation = recognizer.translation(in: nil) else { return }
  77. guard let gestureStartLocation = gameController.gestureStartLocation else {
  78. gameController.gestureStartLocation = gestureCurrentLocation
  79. return
  80. }
  81. let delta = gestureStartLocation - gestureCurrentLocation
  82. let distance = ((delta.x * delta.x) + (delta.y * delta.y) + (delta.z * delta.z)).squareRoot()
  83. if distance > settings.ballPlayDistanceThreshold {
  84. gameController.gestureStartLocation = nil
  85. ball.physicsBody?.mode = .dynamic
  86. return
  87. }
  88. ball.physicsBody?.mode = .kinematic
  89. let realVelocity = recognizer.velocity(in: nil)
  90. let ballParentVelocity = ball.parent!.convert(direction: realVelocity, from: nil)
  91. var clampedX = ballParentVelocity.x
  92. var clampedZ = ballParentVelocity.z
  93. // 夹断
  94. if clampedX > settings.ballVelocityMaxX {
  95. clampedX = settings.ballVelocityMaxX
  96. } else if clampedX < settings.ballVelocityMinX {
  97. clampedX = settings.ballVelocityMinX
  98. }
  99. // 夹断
  100. if clampedZ > settings.ballVelocityMaxZ {
  101. clampedZ = settings.ballVelocityMaxZ
  102. } else if clampedZ < settings.ballVelocityMinZ {
  103. clampedZ = settings.ballVelocityMinZ
  104. }
  105. let clampedVelocity: SIMD3<Float> = [clampedX, 0.0, clampedZ]
  106. ball.physicsMotion?.linearVelocity = clampedVelocity
  107. }
  108. }
  109. }
  110. extension Entity {
  111. func visit(using block: (Entity) -> Void) {
  112. block(self)
  113. for child in children {
  114. child.visit(using: block)
  115. }
  116. }
  117. }
  118. #Preview {
  119. PhysicsMotionView()
  120. }

       在代码中,实现的功能如下:

    (1)加载模拟场景并进行相应的处理。

    (2) 通过设置物体速度,对物体运动进行物理模拟。

      在功能1中,我们首先使用 loadModel()方法加载 Reality 场景,然后通过 session(- session: ARSesion,didAdd anchors: [ARAnchor])方法对平面检测情况进行监视,当ARKit检测到符合要求的水平平面后,将加载的场景挂载到 ARAnchor 下显示,对不需要显示的四周围栏进行了隐藏处理,然后设置了各球体的物理参数、物理材质并重启了 ARSession(为更好组织代码,方便场景管理,我们使用了 GameController类,具体可以参看本节源码)。

     在功能2中为方便控制,我们使用了 RealityKit 中的平移手势EntityTranslationGesture Recognizer,通过计算使用者手指在屏幕上滑动的速度生成物体速度,并将其作为母球的速度(为防止速度过大,我们使用了 GameSettings 结构体并定义了几个边界值,具体可以参github源码),通过直接赋予母球速度值就可以观察母球与场景中其他球体在物理引擎作用下的运动效果。

      编译后测试,使用平移手势操作母球,当母球与场景中的其他球体发生碰撞时,会产生相应的物理效果。通过本例可以看到,在 Xcode中也可以修改 Reality Composer 工具中设定的各球体的物理属性,如代码清单中第15 行到第17所示,读者也可以修改不同属性看一看它们如何影响物体的行为,取消碰撞体,看一看还能不能发生撞。

具体代码地址:GitHub - duzhaoquan/ARkitDemo

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/268268
推荐阅读
相关标签
  

闽ICP备14008679号