2026 Graphics & GamesSpatial Computing
WWDC26 · 22 min · Graphics & Games / Spatial Computing
Extend Reality Composer Pro 3 functionality with Xcode
Discover how Reality Composer Pro 3 empowers you to build bigger, more ambitious spatial projects. Learn about creating project-specific plugins that let you edit custom components, run custom systems, and build your own ScriptGraph nodes—giving you complete control over your spatial authoring workflow.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 10 snippets
Cauldron component
// Add a component to represent the water level
import RealityKit
public struct Cauldron: Component, Codable {
public var waterLevel: Float
enum CodingKeys: CodingKey {
case waterLevel
}
} CauldronSystem
// Add a system to control the water level
import RealityKit
public struct CauldronSystem: System {
let query = EntityComponentQuery(Cauldron.self)
public init(scene: Scene) {}
public func update(context: SceneUpdateContext) {
for (entity, cauldron) in context.entities(matching: query) {
guard let water = entity.findEntity(named: "Cauldron_Water_mesh")
else { continue }
water.setPosition(SIMD3<Float>(0, 1, 0) * cauldron.waterLevel, relativeTo: entity)
}
}
} RCPCustomComponentsPlugin
// Make sure that Reality Composer Pro 3 knows about the Cauldron and CauldronSystem
import RealityComposerPro
final class RCPCustomComponentsPlugin: RealityComposerProPlugin {
public func setup(context: any RealityComposerProContext) {
context.registerComponent(Cauldron.self)
context.registerSystem(CauldronSystem.self)
}
}
("createRealityComposerProPlugin")
public func createRealityComposerProPlugin() -> UnsafeMutableRawPointer {
return RCPCustomComponentsPlugin().passRetained()
} Cauldron component with vortex properties
// Properties to control water surface
import RealityKit
public struct Cauldron: Component, Codable {
public var waterLevel: Float
public var rotationSpeed: Float
public var minWaterLevel: Float
public var maxWaterLevel: Float
public var vortexCoeff: Float
} CauldronSystem update with ShaderGraph
public func update(context: SceneUpdateContext) {
for (entity, cauldron) in context.entities(matching: query) {
guard let water = entity.findEntity(named: "Cauldron_Water_mesh") else { continue }
water.setPosition(SIMD3<Float>(0, 1, 0) * cauldron.waterLevel, relativeTo: entity)
guard var model = water.components[ModelComponent.self] else { continue }
guard var mat = model.materials.first as? ShaderGraphMaterial else { continue }
let surface = computeSurface(cauldron: cauldron)
try? mat.setParameter(name: "Level Radius", value: .float(surface.levelRadius))
try? mat.setParameter(name: "Lowest Point",
value: .float(cauldron.waterLevel - surface.lowestPoint))
try? mat.setParameter(name: "Height Change", value: .float(surface.heightChange))
try? mat.setParameter(name: "Level Coeff", value: .float(surface.levelCoeff))
try? mat.setParameter(name: "Is Level", value: .bool(surface.isLevel))
model.materials[0] = mat
water.components.set(model)
}
} SetWaterLevelAction
// Custom action for setting the water level of the Cauldron
import RealityKit
public struct SetWaterLevelAction: EntityAction, Codable {
// Parameters for the action
public let startWaterLevel: Float
public let endWaterLevel: Float
// Required by EntityAction protocol
public var animatedValueType: (any AnimatableData.Type)? { Transform.self }
} SetWaterLevelAction subscribe
extension SetWaterLevelAction {
static func subscribe() {
Task { in
SetWaterLevelAction.subscribe(to: .updated) { event in
let normalizedTime = (event.playbackController.time - event.startTime) /
event.duration
let action = event.action
let currentLevel = action.startWaterLevel +
Float(normalizedTime) * (action.endWaterLevel - action.startWaterLevel)
guard let entity = event.targetEntity else { return }
guard var cauldron = entity.components[Cauldron.self] else { return }
cauldron.waterLevel = currentLevel
entity.components.set(cauldron)
}
}
}
} RCPCustomComponentsPlugin with action
// Make sure that Reality Composer Pro 3 knows about the SetWaterLevelAction
import RealityComposerPro
final class RCPCustomComponentsPlugin: RealityComposerProPlugin {
public func setup(context: any RealityComposerProContext) {
context.registerComponent(Cauldron.self)
context.registerSystem(CauldronSystem.self)
context.registerAction(SetWaterLevelAction.self)
SetWaterLevelAction.subscribe()
}
}
("createRealityComposerProPlugin")
public func createRealityComposerProPlugin() -> UnsafeMutableRawPointer {
return RCPCustomComponentsPlugin().passRetained()
} Cauldron with @Scriptable macro
// Expose Cauldron to Script Graphs
import RealityKit
import RealityKitScripting
import RealityKitScriptingMacros
public struct Cauldron: Component, Codable {
public var waterLevel: Float
public var rotationSpeed: Float
public var minWaterLevel: Float
public var maxWaterLevel: Float
public var vortexCoeff: Float
} Register scripting module
// Register scripting module
public func setup(context: any RealityComposerProContext) {
context.registerComponent(Cauldron.self)
context.registerSystem(CauldronSystem.self)
context.registerAction(SetWaterLevelAction.self)
SetWaterLevelAction.subscribe()
Task { in
let config = RKS.Configuration(id: "ChaparralVillage")
.onInitialize { _ in
[
Module("ChaparralVillage") {
Cauldron.SchemaProvider.schema
}
]
}
try! RKS.addConfiguration(config)
}
} Related sessions
-
24 min -
19 min -
17 min -
22 min