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

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 ↗

Transcript all transcripts

Chapters

Code shown on screen · 10 snippets

Cauldron component swift · at 6:08 ↗
// 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 swift · at 6:42 ↗
// 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 swift · at 7:00 ↗
// 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)
    }
}

@_cdecl("createRealityComposerProPlugin")
public func createRealityComposerProPlugin() -> UnsafeMutableRawPointer {
    return RCPCustomComponentsPlugin().passRetained()
}
Cauldron component with vortex properties swift · at 10:49 ↗
// 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 swift · at 11:05 ↗
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 swift · at 13:25 ↗
// 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 swift · at 14:05 ↗
extension SetWaterLevelAction {
    static func subscribe() {
        Task { @MainActor 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 swift · at 14:56 ↗
// 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()
    }
}

@_cdecl("createRealityComposerProPlugin")
public func createRealityComposerProPlugin() -> UnsafeMutableRawPointer {
    return RCPCustomComponentsPlugin().passRetained()
}
Cauldron with @Scriptable macro swift · at 17:32 ↗
// Expose Cauldron to Script Graphs

import RealityKit
import RealityKitScripting
import RealityKitScriptingMacros

@Scriptable
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 swift · at 18:08 ↗
// Register scripting module

public func setup(context: any RealityComposerProContext) {
    context.registerComponent(Cauldron.self)
    context.registerSystem(CauldronSystem.self)

    context.registerAction(SetWaterLevelAction.self)
    SetWaterLevelAction.subscribe()

    Task { @MainActor in
        let config = RKS.Configuration(id: "ChaparralVillage")
            .onInitialize { _ in
            [
                Module("ChaparralVillage") {
                    Cauldron.SchemaProvider.schema
                }
            ]
        }
        try! RKS.addConfiguration(config)
    }
}