RealityKit & Reality Composer Pro

Feb 4 2026 · Swift 6, visionOS 26, Xcode 26

Lesson 04: Using Reality Composer Pro to Create AR Experiences

Demo

Episode complete

Play next episode

Next
Transcript

Start with the app you’ve been building or use the QHoops app in the Starter folder.

In order to shoot the ball, or Quaffle, through the hoop, you’ll need to add velocity it. In the Vision Pro Simulator, you’re limited to dragging and tapping. You can add the needed linear velocity with a tap to the ball. First, your player will need to drag the ball to the ideal position, then tap it.

In Xcode, open ImmersiveView.swift and create a computed variable called tapGesture to apply the force. Below the dragGesture computed variable, add the following code:

var tapGesture: some Gesture {
  TapGesture()
    .targetedToAnyEntity()
    .onEnded { value in
      // do nothing
      value.entity.components[PhysicsBodyComponent.self]?.mode = .dynamic
      value.entity.components[PhysicsMotionComponent.self]?.linearVelocity = [0, 7, -5]
    }
}

Here you’re adding linearVelocity on the y and z axes. You’ll add more on the y axis to get the lift and fight the effect of gravity, since the ball weighs 1 kilogram by default.

At the bottom of the RealityView add another .gesture modifier:

.gesture(tapGesture)

Build and run. When in the immersive view, drag the ball to the center. Then, tap it to take your shot. Feel free to adjust the values, but these values seem to work.

Now, you need to add a goal judge to check if the ball went through the hoop. Open up the Reality Composer Pro project from Xcode and drag another cylinder into the scene.

With the cylinder still selected in the scene, rename it Goal in the hierarchy.

Set the x, y and z scale to 4.462, 0.5, 4.462. Set the x, y and z rotation to 90, 0, 0. Set the x, y and z position to 0, 245.5, -504. Double-click the Goal in the hierarchy to focus on the goal. It should be inside the hoop.

Now, add a collision component to the goal. Ideally, the shape of the collision target should match the shape roughly, but the default box should be OK for now. Save, Command - S to save your work.

In order to detect if the ball makes contact with the goal cylinder, you’ll need to make a few state variables. At the top of the ImmersiveView.swift file’s struct, add an goal entity and an EventSubscription as optionals. Also, add a Boolean for goalCelebration.

@State private var goalEntity: Entity?
@State private var goalScored: EventSubscription?
@State private var goalCelebration: Bool = false

Below the floor code, assign the goal entity to the variable. This will look for the goal among the content entities.

goalEntity = content.entities.first?.findEntity(named: "Goal")

To detect a collision with the goal, add the code below. You don’t need to specify the ball, since it will be the only entity moving in the scene. If the ball collides with the goal, you’ll celebrate.

goalScored = content.subscribe(to: CollisionEvents.Began.self, on: goalEntity) { collisionEvent in
  print("Goal detected \(collisionEvent.entityA.name) and \(collisionEvent.entityB.name)")
  goalCelebration = true
}

Build and Run to try hitting the Goal.

Since you can detect a goal now, you can hide the goal entity by zeroing out its opacity.

goalEntity?.components.set(OpacityComponent(opacity: 0.0))

Now, to celebrate the goal, you can add some flair with a particle emitter and some celebratory cheering.

Go back to Reality Composer Pro and tap the + at the bottom of the hierarchy pane. Choose Particle Emitter. Rename it ConfettiEmitter. In the Inspector, click the Particles tab and set the particle Birth Rate to 500. Uncheck the End Color. Set the Opacity over Life to Constant. Under Properties set Size to 2cm, Size Over Life Power to 10, and Life Span to 20. Set the Orientation Direction to -1. Scroll down to Force Fields and set the Noise Strength to 0.03.

From the lesson’s Resources folder, drag the grey-square-512@2x.png into the Project Browser. Set the particle image to the gray square. Press the Play button at the top to start it. Let it loop while you design it.

Click on the Emitter tab and set the Birth Direction to Constant and set the y value of Emit Direction to -1. Set the Speed to 0.08. Set the Spawning Occasion to 0.08. Feel free to come back and experiment with some more settings.

Set the x, y and z scale to 9,9,9.

Then uncheck the loop so that the confetti only falls once. You can press Burst under the Particle tab to test a batch or particles.

Finally, set the x, y and z position to 0, 270 and -150 to put it in the right place. Save the file.

Back in Xcode, open up ImmersiveView.swift and add a state variable for the confetti:

@State private var confetti: Entity?

Add a reference in the RealityView content to the confetti emitter:

confetti = content.entities.first?.findEntity(named: "ConfettiEmitter")

As soon as you run the app, the confetti will fire, so set the opacity to 0.

confetti?.components.set(OpacityComponent(opacity: 0.0))

You might recall from the Introduction to visionOS lesson that you need to use the update closure to make changes based on events. Add the following update closure to the RealityView:

} update: { content in
  if let _ = content.entities.first {
    if goalCelebration == true {
      confetti?.components.set(OpacityComponent(opacity: 1.0))
    }
  }
}

We’re not done just yet! Spatial computing is not just about visuals but also audio. Now to add some cheering to celebrate the goal.

Go back to Reality Composer Pro and from the lesson’s Resources folder, drag cheering.m4a into the Project Browser. Then, drag it into to hierarchy. Save the project file.

Click the + at the bottom of the hierarchy and choose AudioChannel Audio. In the Inspector, set the Resource to cheering_mp4. Save the file.

Back in Xcode, open ImmersiveView.swift and add a couple of state variables. One for the cheering entity and one for the audio resource.

@State private var cheering: Entity?
@State private var audio: AudioFileResource?

At the bottom of the RealityView content, add a reference to the entity and the audio in the realityKitContentBundle. This will prepare the assets and preload the audio.

cheering = content.entities.first?.findEntity(named: "ChannelAudio")
audio = try? await AudioFileResource(named: "/Root/cheering_m4a", from: "Immersive.usda", in: realityKitContentBundle)

Finally, add the audio to the update closure inside the if-statement:

if let audioPlaybackControl = cheering?.prepareAudio(audio!) {
  audioPlaybackControl.play()
}

Build and run. Then try to score a goal. You should see the confetti come down and the celebratory cheer!

Congratulations on completing the course — and for scoring a goal.

See forum comments
Cinema mode Download course materials from Github
Previous: Instruction Next: Conclusion