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 ↗Chapters
Code shown on screen · 12 snippets
Loading an entity
RealityView { content in
do {
let entity = try await Entity(named: "DioramaAssembled", in: realityKitContentBundle)
content.add(entity)
} catch {
// Handle error
}
} Adding a component
let component = MyComponent()
entity.components.set(component) Attachments data flow
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
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
static let markersQuery = EntityQuery(where: .has(PointOfInterestComponent.self))
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
final class AttachmentsProvider {
var attachments: [ObjectIdentifier: AnyView] = [:]
var sortedTagViewPairs: [(tag: ObjectIdentifier, view: AnyView)] { ... }
}
...
var attachmentsProvider = AttachmentsProvider()
RealityView { _, _ in
} update: { _, _ in
} attachments: {
ForEach(attachmentsProvider.sortedTagViewPairs, id: \.tag) { pair in
pair.view
}
} Design-time and Run-time components
// 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
static let markersQuery = EntityQuery(where: .has(PointOfInterestComponent.self))
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
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
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
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
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)
})
}
} Related sessions
-
28 min -
21 min -
21 min -
20 min -
31 min -
25 min