Lab 020 – Exploring Collision Triggers

Starting with simple triggers to perform actions in a scene.

I haven’t had an opportunity to explore collisions and physics in RealityKit. So I used this lab to learn the basics of trigger (non-physics based) collisions.

Example 1: Unfiltered collision. Anything can collide with anything. This event will fire twice. Once for each entity in the collision. We don’t need to call burst on entityB, since both entities will be entityA in “their” version of the event.

collisionBeganUnfiltered = content.subscribe(to: CollisionEvents.Began.self)  
{ collisionEvent in
  collisionEvent.entityA.components[ParticleEmitterComponent.self]?.burst()
}

Example 2: Only trigger collisions on the subject. The red and green sphere can trigger a collision on the subject but not with each other.

 collisionBeganSubject = content.subscribe(to: CollisionEvents.Began.self, on: subject)  { collisionEvent in
  if let subject {
     bounceEntity(subject)
  }
}

Example 3: Building on example 2, the red and green spheres can both change the color of the subject. I’m sure there is a better way to do this with groups, filters, or masks. I just haven’t gotten there yet.

collisionBeganSubjectColor = content
    .subscribe(to: CollisionEvents.Began.self, on: subject)  { collisionEvent in
        print("Collision Subject Color Change \(collisionEvent.entityA.name) and \(collisionEvent.entityB.name)")
        if let subject {
            if(collisionEvent.entityB.name == "StepSphereRed") {
                swapColorEntity(subject, color: .stepRed)
            } else if (collisionEvent.entityB.name == "StepSphereGreen") {
                swapColorEntity(subject, color: .stepGreen)
            }
        }
    }

Example 4: Reset the subject color on collision end.

collisionEndedSubject = content
    .subscribe(to: CollisionEvents.Ended.self, on: subject)  { collisionEvent in
        print("Collision Subject Revert \(collisionEvent.entityA.name) and \(collisionEvent.entityB.name)")
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in
            if let subject {
                swapColorEntity(subject, color: .stepBlue)

            }
        }
    }

Video demo

Full Lab code

struct Lab020: View {

    @State var subject: Entity?
    @State var collisionBeganUnfiltered: EventSubscription?
    @State var collisionBeganSubject: EventSubscription?
    @State var collisionBeganSubjectColor: EventSubscription?
    @State var collisionEndedSubject: EventSubscription?

    var body: some View {
        RealityView { content in
            if let scene = try? await Entity(named: "Lab020Scene", in: realityKitContentBundle) {
                content.add(scene)
                subject = scene.findEntity(named: "StepSphereBlue")

                // Example 1: This unfiltered event will fire twice. Once for each entity in the collision
                // Each will be entityA in their own version of the event, so we don't need the burst for entityB
                collisionBeganUnfiltered = content.subscribe(to: CollisionEvents.Began.self)  { collisionEvent in
                    print("Collision unfiltered \(collisionEvent.entityA.name) and \(collisionEvent.entityB.name)")
                    collisionEvent.entityA.components[ParticleEmitterComponent.self]?.burst()

                }

                // Example 2: Only trigger collisions on the subject.
                // Both the red and green spheres can collide with the subect to perform the bounce action
                collisionBeganSubject = content
                    .subscribe(to: CollisionEvents.Began.self, on: subject)  { collisionEvent in
                        print("Collision Subject Bounce \(collisionEvent.entityA.name) and \(collisionEvent.entityB.name)")
                        if let subject {
                            bounceEntity(subject)
                        }
                    }

                // Example 3: Have the red and green spheres change the color of the subject
                // I'm sure there is a better way to do this with filters or masks...
                collisionBeganSubjectColor = content
                    .subscribe(to: CollisionEvents.Began.self, on: subject)  { collisionEvent in
                        print("Collision Subject Color Change \(collisionEvent.entityA.name) and \(collisionEvent.entityB.name)")
                        if let subject {
                            if(collisionEvent.entityB.name == "StepSphereRed") {
                                swapColorEntity(subject, color: .stepRed)
                            } else if (collisionEvent.entityB.name == "StepSphereGreen") {
                                swapColorEntity(subject, color: .stepGreen)
                            }
                        }
                    }

                // Example 4: Reset the subject after a short timer
                collisionEndedSubject = content
                    .subscribe(to: CollisionEvents.Ended.self, on: subject)  { collisionEvent in
                        print("Collision Subject Revert \(collisionEvent.entityA.name) and \(collisionEvent.entityB.name)")
                        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in
                            if let subject {
                                swapColorEntity(subject, color: .stepBlue)

                            }
                        }
                    }

            }

        }
        .modifier(DragGestureImproved())
    }

    func swapColorEntity(_ entity: Entity, color: UIColor) {
        if var mat = entity.components[ModelComponent.self]?.materials.first as? PhysicallyBasedMaterial {
            mat.baseColor = .init(tint: color)
            entity.components[ModelComponent.self]?.materials[0] = mat
        }
    }


    func bounceEntity(_ entity: Entity) {
        let transform = Transform(
            scale: SIMD3<Float>(repeating: entity.scale.x),
            rotation: simd_quatf(angle: 0, axis: SIMD3<Float>(0, 0, 0)),
            translation: SIMD3<Float>(entity.position.x, entity.position.y + 0.05, entity.position.z)
        )

        let transform2 = Transform(
            scale: SIMD3<Float>(repeating: entity.scale.x),
            rotation: simd_quatf(angle: 0, axis: SIMD3<Float>(0, 0, 0)),
            translation: SIMD3<Float>(entity.position.x, 1.0, entity.position.z)
        )

        entity.move(
            to: transform,
            relativeTo: entity.parent!,
            duration: 0.5,
            timingFunction: .easeIn
        )

        Timer.scheduledTimer(withTimeInterval: 0.45, repeats: false) { _ in
            entity.move(
                to: transform2,
                relativeTo: entity.parent!,
                duration: 0.3,
                timingFunction: .easeOut
            )
        }
    }

}

Support our work so we can continue to bring you new examples and articles.

Download the Xcode project with this and many more labs from Step Into Vision.

Questions or feedback?

2 Comments