Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2023 Spatial Computing

WWDC23 · 34 min · Spatial Computing

Work with Reality Composer Pro content in Xcode

Learn how to bring content from Reality Composer Pro to life in Xcode. We’ll show you how to load 3D scenes into Xcode, integrate your content with your code, and add interactivity to your app. We’ll also share best practices and tips for using these tools together in your development workflow. To get the most out of this session, we recommend first watching “Meet Reality Composer Pro” and “Explore materials in Reality Composer Pro" to learn more about creating 3D scenes.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 12 snippets

Loading an entity swift · at 3:12 ↗
RealityView { content in
    do {
        let entity = try await Entity(named: "DioramaAssembled", in: realityKitContentBundle)
        content.add(entity)
    } catch {
        // Handle error
    }
}
Adding a component swift · at 6:39 ↗
let component = MyComponent()
entity.components.set(component)
Attachments data flow swift · at 12:21 ↗
RealityView { _, _ in
    // load entities from your Reality Composer Pro package bundle
} update: { content, attachments in
           
   if let attachmentEntity = attachments.entity(for: "🐠") {
     		content.add(attachmentEntity)
 	 }
           
} attachments: {
    Button { ... }
       .background(.green)
       .tag("🐠")
}
Adding attachments swift · at 15:48 ↗
let myEntity = Entity()

RealityView { content, _ in
    if let entity = try? await Entity(named: "MyScene", in: realityKitContentBundle) {
        content.add(entity)
    }
} update: { content, attachments in

    if let attachmentEntity = attachments.entity(for: "🐠") {
        content.add(attachmentEntity)
    }

    content.add(myEntity)

} attachments: {
    Button { ... }
       .background(.green)
       .tag("🐠")
}
Adding point of interest attachment entities swift · at 20:43 ↗
static let markersQuery = EntityQuery(where: .has(PointOfInterestComponent.self))
@State var attachmentsProvider = AttachmentsProvider()

rootEntity.scene?.performQuery(Self.markersQuery).forEach { entity in
  guard let pointOfInterest = entity.components[PointOfInterestComponent.self] else { return }
  
  let attachmentTag: ObjectIdentifier = entity.id

  let view = LearnMoreView(name: pointOfInterest.name, description: pointOfInterest.description)
                           .tag(attachmentTag)

   attachmentsProvider.attachments[attachmentTag] = AnyView(view)
}
AttachmentsProvider swift · at 21:40 ↗
@Observable final class AttachmentsProvider {
    var attachments: [ObjectIdentifier: AnyView] = [:]
    var sortedTagViewPairs: [(tag: ObjectIdentifier, view: AnyView)] { ... }
}

...

@State var attachmentsProvider = AttachmentsProvider()

RealityView { _, _ in

} update: { _, _ in

} attachments: {
    ForEach(attachmentsProvider.sortedTagViewPairs, id: \.tag) { pair in
        pair.view
    }
}
Design-time and Run-time components swift · at 22:31 ↗
// Design-time component
public struct PointOfInterestComponent: Component, Codable {
    public var region: Region = .yosemite
    public var name: String = "Ribbon Beach"
    public var description: String?
}

// Run-time component
public struct PointOfInterestRuntimeComponent: Component {
    public let attachmentTag: ObjectIdentifier
}
Adding a run-time component for each design-time component swift · at 25:38 ↗
static let markersQuery = EntityQuery(where: .has(PointOfInterestComponent.self))
@State var attachmentsProvider = AttachmentsProvider()

rootEntity.scene?.performQuery(Self.markersQuery).forEach { entity in
  guard let pointOfInterest = entity.components[PointOfInterestComponent.self] else { return }
  
  let attachmentTag: ObjectIdentifier = entity.id

  let view = LearnMoreView(name: pointOfInterest.name, description: pointOfInterest.description)
                           .tag(attachmentTag)

   attachmentsProvider.attachments[attachmentTag] = AnyView(view)
   let runtimeComponent = PointOfInterestRuntimeComponent(attachmentTag: attachmentTag)
   entity.components.set(runtimeComponent)
}
Adding and positioning the attachment entities swift · at 26:19 ↗
static let runtimeQuery = EntityQuery(where: .has(PointOfInterestRuntimeComponent.self))

RealityView { _, _ in

} update: { content, attachments in x
   
    rootEntity.scene?.performQuery(Self.runtimeQuery).forEach { entity in
        guard let component = entity.components[PointOfInterestRuntimeComponent.self],
              let attachmentEntity = attachments.entity(for: component.attachmentTag) else { 
            return 
        }        
        content.add(attachmentEntity)
        attachmentEntity.setPosition([0, 0.5, 0], relativeTo: entity)
    }
} attachments: {
    ForEach(attachmentsProvider.sortedTagViewPairs, id: \.tag) { pair in
        pair.view
    }
}
Audio Playback swift · at 28:55 ↗
func playOceanSound() {
        
    guard let entity = entity.findEntity(named: "OceanEmitter"),
        let resource = try? AudioFileResource(named: "/Root/Resources/Ocean_Sounds_wav",
                                   from: "DioramaAssembled.usda",
                                   in: RealityContent.realityContentBundle) else { return }
        
    let audioPlaybackController = entity.prepareAudio(resource)
    audioPlaybackController.play()
}
Terrain material transition using the slider swift · at 31:02 ↗
@State private var sliderValue: Float = 0.0

Slider(value: $sliderValue, in: (0.0)...(1.0))
    .onChange(of: sliderValue) { _, _ in
        guard let terrain = rootEntity.findEntity(named: "DioramaTerrain"),
                var modelComponent = terrain.components[ModelComponent.self],
                var shaderGraphMaterial = modelComponent.materials.first 
                    as? ShaderGraphMaterial else { return }
        do {
            try shaderGraphMaterial.setParameter(name: "Progress", value: .float(sliderValue))
            modelComponent.materials = [shaderGraphMaterial]
            terrain.components.set(modelComponent)
        } catch { }
    }
}
Audio transition using the slider swift · at 31:57 ↗
@State private var sliderValue: Float = 0.0
static let audioQuery = EntityQuery(where: .has(RegionSpecificComponent.self) 
                                    && .has(AmbientAudioComponent.self))

Slider(value: $sliderValue, in: (0.0)...(1.0))
    .onChange(of: sliderValue) { _, _ in
        // ... Change the terrain material property ...
                                
        rootEntity?.scene?.performQuery(Self.audioQuery).forEach({ audioEmitter in
            guard var audioComponent = audioEmitter.components[AmbientAudioComponent.self],
                  let regionComponent = audioEmitter.components[RegionSpecificComponent.self]
            else { return }

            let gain = regionComponent.region.gain(forSliderValue: sliderValue)
            audioComponent.gain = gain
            audioEmitter.components.set(audioComponent)
        })
    }
}