Dark Spaces – Devlog 006

Quickly switching between scenes, darker domes, and improved material preview scene.

I needed a quick way to switch scenes to explore different ideas. I also wanted to be able to switch scenes when I’m using the app outside of development. Until now, I’ve been manually changing the scene in the immersive space.

if let scene = try? await Entity(named: "SomeSceneName", in: realityKitContentBundle) {
  content.add(scene)
}

I have some ideas for how I want the main window to look, but I’m not ready to start on that yet. For now, I just need a way to switch scenes for myself. I added an enum where I can list my scene. Each has case a string value for the path to the RCP scene, and a string description version that I can use in a picker.

enum Scenes: String, CaseIterable, Identifiable {
    case stacks = "Scenes/Stacks"
    case hallway = "Scenes/Test"
    case between = "Scenes/Between"
    case capturedSystem = "Scenes/CapturedSystem"
    case materials = "Scenes/Immersive"

    var id: String { self.rawValue }
    
    var description: String {
        switch self {
        case .stacks: return "Stacks"
        case .hallway: return "Hallway"
        case .between: return "Between"
        case .capturedSystem: return "Captured System"
        case .materials: return "Materials"
        }
    }
}

Then I modified the boilerplate ToggleImmersiveSpaceButton. I added a picker to let myself select another scene. I also added some logic that will let me move from one scene to another without having to manually exit each time. We can only have one immersive space open a time, so I use the window to control the scenes. I exit the current space, then enter it again with the new scene value. The code is sloppy, but it will be replaced soon.

struct ToggleImmersiveSpaceButton: View {

    @Environment(AppModel.self) private var appModel
    @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace
    @Environment(\.openImmersiveSpace) private var openImmersiveSpace
    @State var selectedScene = Scenes.stacks

    var body: some View {
        VStack {
            Picker("Space", selection: $selectedScene) {
                ForEach(Scenes.allCases) {
                    Text($0.description)
                        .tag($0)
                }
            }.pickerStyle(.automatic)
            Button {
                openSpaceWithScene(switchScenes: false)
            } label: {
                Text(appModel.immersiveSpaceState == .open ? "leave" : "enter")
            }
            .disabled(appModel.immersiveSpaceState == .inTransition)
            .animation(.none, value: 0)
            .fontWeight(.semibold)
        }
        .onChange(of: selectedScene) { _, newValue in
            appModel.immersiveSceneName = newValue
            openSpaceWithScene(switchScenes: true)
        }
    }

    func openSpaceWithScene(switchScenes: Bool = false) {
        Task { @MainActor in
            switch appModel.immersiveSpaceState {
            case .open:
                appModel.immersiveSpaceState = .inTransition
                await dismissImmersiveSpace()
                // Example: changing the picker value will switch scenes, but tapping the button will let us exit
                if(switchScenes) {
                    openSpace()
                }
            case .closed:
                openSpace()
            case .inTransition:
                break
            }
        }
    }

    func openSpace() {
        Task { @MainActor in
            appModel.immersiveSpaceState = .inTransition
            switch await openImmersiveSpace(id: appModel.immersiveSpaceID) {
            case .opened:
                break
            case .userCancelled, .error:
                fallthrough
            @unknown default:
                appModel.immersiveSpaceState = .closed
            }
        }
    } 
}

Now the immersive space loads the scene based on the value set in appModel.immersiveSceneName.

struct ImmersiveView: View {

    @Environment(AppModel.self) private var appModel

    var body: some View {
        RealityView { content in

            if let scene = try? await Entity(named: appModel.immersiveSceneName.rawValue, in: realityKitContentBundle) {
                content.add(scene)
            }
            
        }
        .modifier(DragGestureWithPivot046())
        .onAppear {
            appModel.immersiveSpaceState = .open
        }
    }
}

I have some other ideas for how to use the same ImmersiveView to load more than one scene. I may or may not create a space for each scene. It just depends on how much gesture and input code is shared between scenes. I may want unique input in some scenes that doesn’t make sense in others.

Add a Sky Dome

I’ve been using the SkyDome.usdz that ships with Reality Composer Pro for a few scenes, but it was too bright for my moody scenes. The materials in this use some gradient textures. I copied those and darkened them.

Default vs. Darkened Gradient

Now my scenes that use this dome are a lot darker. I also added a couple of new materials to my preview scene and added this one to my scene picker.

Material Preview scene using the darkened Sky Dome

There is a lot more to do before I have a working app structure. For now I’m going to go back to work on the Stacks scene that I started in Devlog 005.

Video Walkthrough

Questions or feedback?