Lab 108 – Cycle
In this lab, we cycle through the scenes created in Labs 105-107.
This lab is part of a series of experiments for an upcoming project we’re considering. Today we take the scenes from labs 105-107 and load them all. Then we hide 2 of the 3 scenes at any given time. A simple task timer cycles between these. We also show the portal content of each lab on the currently active screen.
Video Demo
A video shows movement around an immersive space as three scenes cycle into existence.
Lab Code
struct Lab108: View {
@State private var activeSceneIndex = 0
private let sceneNames = ["Vapor", "Mist", "Droplet"]
var body: some View {
RealityView { content in
guard let vapor = try? await Entity(named: "Vapor", in: realityKitContentBundle) else { return }
vapor.name = "Vapor"
await configurePortal(on: vapor, contentName: "Mist")
content.add(vapor)
guard let mist = try? await Entity(named: "Mist", in: realityKitContentBundle) else { return }
mist.name = "Mist"
mist.isEnabled = false
await configurePortal(on: mist, contentName: "Droplet")
content.add(mist)
guard let droplet = try? await Entity(named: "Droplet", in: realityKitContentBundle) else { return }
droplet.name = "Droplet"
droplet.isEnabled = false
await configurePortal(on: droplet, contentName: "DropletInner", allowsCrossing: true)
content.add(droplet)
} update: { content in
for (index, sceneName) in sceneNames.enumerated() {
content.entities.first { $0.name == sceneName }?.isEnabled = index == activeSceneIndex
}
}
.task {
while !Task.isCancelled {
try? await Task.sleep(for: .seconds(3))
activeSceneIndex = (activeSceneIndex + 1) % sceneNames.count
}
}
}
private func configurePortal(
on rootEntity: Entity,
contentName: String,
allowsCrossing: Bool = false
) async {
let portalContentRoot = Entity()
portalContentRoot.components.set(WorldComponent())
rootEntity.addChild(portalContentRoot)
if let screenSurface = rootEntity.findEntity(named: "ScreenSurface") {
var portalMaterial = PortalMaterial()
portalMaterial.faceCulling = .none
if var modelComponent = screenSurface.components[ModelComponent.self] {
modelComponent.materials = modelComponent.materials.map { _ in portalMaterial }
screenSurface.components.set(modelComponent)
screenSurface.components.set(PortalComponent(
target: portalContentRoot,
clippingMode: .plane(.negativeY),
crossingMode: allowsCrossing ? .plane(.negativeY) : .disabled
))
}
}
guard let portalScene = try? await Entity(named: contentName, in: realityKitContentBundle) else { return }
if allowsCrossing {
portalScene.components.set(PortalCrossingComponent())
} else {
portalScene.scale = .init(repeating: 0.5)
portalScene.position = [0, 4, -9]
portalScene.orientation = simd_quatf(angle: -6, axis: [1, 0, 0])
}
portalContentRoot.addChild(portalScene)
}
}
Support our work so we can continue to bring you new examples and articles.
Download the Xcode project with this and many more labs from Step Into Vision.

Follow Step Into Vision