1

So I have the default scene for an ImmersiveView that has two plain spheres. I am moving one of them with my game controller.

import SwiftUI
import RealityKit
import RealityKitContent
import GameController

struct ImmersiveView: View {
    
    @State var sphereX: Float = 0.5
    @State var sphereY: Float = 1.5
    @State var sphereZ: Float = -1.5
    
    var body: some View {
        RealityView { content in
            if let scene = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(scene)
            }
            
            Task.detached {
                for await _ in NotificationCenter.default.notifications(named: .GCControllerDidConnect) {
                    Task { @MainActor in
                        for controller in GCController.controllers() {
                            controller.extendedGamepad?.valueChangedHandler = { pad, _ in
                                Task { @MainActor in
                                    let speed: Float = 0.1
                                    sphereX += pad.leftThumbstick.xAxis.value * speed
                                    sphereY += pad.leftThumbstick.yAxis.value * speed
                                    sphereZ -= pad.rightThumbstick.yAxis.value * speed
                                }
                            }
                        }
                    }
                }
            }
        } update: { content in
            guard let scene = content.entities.first,
                  let movableSphere = scene.findEntity(named: "Sphere_Right") else {
                return
            }
            movableSphere.position.x = sphereX
            movableSphere.position.y = sphereY
            movableSphere.position.z = sphereZ
        }
    }
}

How can I detect that the movable sphere collides with the other sphere?

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
swiftyboi
  • 2,965
  • 4
  • 25
  • 52

2 Answers2

1

Detecting collisions in RealityView for visionOS

Let's assume that Reality Composer Pro scene contains a cube that sits above a sphere primitive. Both models must have the Physics Body component (first is dynamic and second is static) and Collision component. Try the following code if you're planning to detect a collision between 3D objects.

import SwiftUI
import RealityKit
import RealityKitContent

struct ContentView : View {
    
    @State private var subs: [EventSubscription] = []

    var body: some View {
        VStack {
            RealityView { content in
                if let scene = try? await Entity(named: "Scene", in: realityKitContentBundle) {
                    content.add(scene)
                }
            } update: { content in
                if let cube = content.entities.first?.findEntity(named: "Cube") as? ModelEntity {
                    let event = content.subscribe(to: CollisionEvents.Began.self, on: cube) { ce in

                        print("Collision between \(ce.entityA.name) and \(ce.entityB.name) is occured")
                    }
                    DispatchQueue.main.async {
                        subs.append(event)
                    }
                }
            }
        }
    }
}
#Preview {
    ContentView()
}

enter image description here


Or create a scene programmatically from scratch:

struct ContentView : View {
    
    @State private var subs: [EventSubscription] = []

    var body: some View {
        VStack {
            RealityView { content in                    
                let ball = ModelEntity(mesh: .generateSphere(radius: 0.15))
                ball.generateCollisionShapes(recursive: false)
                ball.name = "Sphere"
                content.add(ball)
                
                let cube = ModelEntity(mesh: .generateBox(size: 0.25))
                cube.generateCollisionShapes(recursive: false)
                cube.components[PhysicsBodyComponent.self] = .init()
                cube.position = [0.259, 2.0, 0.0]
                cube.name = "Cube"
                content.add(cube)

                let event = content.subscribe(to: CollisionEvents.Began.self, on: cube) { ce in
                    print("Collision between \(ce.entityA.name) and \(ce.entityB.name) is occured")
                }
                Task {
                    subs.append(event)
                }
            } 
        }
    }
}
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • That you, this is very helpful. But I am curious as to why a dispatch to the main thread is needed here. – swiftyboi Jul 09 '23 at 17:37
  • You'll receive the `"Modifying state during view update, this will cause undefined behavior"` message unless you asynchronously handle the addition to the array. – Andy Jazz Jul 09 '23 at 19:14
  • I noticed, in the "Build spatial experiences with RealityKit" video, they subscribed to an event in the `make` closure, rather than the `update` closure. That may be a better spot, so you aren't resubscribing more than you need to https://developer.apple.com/wwdc23/10080 – Mikaela Caron Jul 18 '23 at 20:33
  • 1
    Hello! Every time the `update` closure would run (when state changes), the event is subscribed to again, and added to the array agin. – Mikaela Caron Jul 19 '23 at 16:45
  • Totally agree with you, Mikaela. – Andy Jazz Jul 20 '23 at 06:19
0

In RealityView make: you can subscribe to collision events associated with an Entity.

let subscribe = content.subscribe(to: CollisionEvents.Began.self, on: sphere) { event in
                        print("collision started \(event.entityA.id), \(event.entityB.id))")
                    }

The Sphere's will need a Physics body component, which you can to the scene with Reality Composer Pro or via code: sphere.physicsBody = PhysicsBodyComponent().

Note: you need to keep a reference to the subscription in order to receive the events.

@State var subscriptions: [EventSubscription] = []
...
subscriptions.append(subscribe)
svarrall
  • 8,545
  • 2
  • 27
  • 32