見出し画像

visionOSでImmersiveSpaceにオブジェクトを表示してみる

前回の記事では3DオブジェクトをWindowやVolumeで表示したが、今回はより没入感のあるImmersiveSpaceにオブジェクトを表示してみる。

Immersive Spaceを表示する

テンプレートからプロジェクトを作成して、App構造体にImmersiveSpaceを追加しておく。今回は完全な没入型にしたかったので、FullImmersionStyleを指定。

@main
struct ImmersiveSpaceSampleApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        
        ImmersiveSpace(id: "ImmersiveSpace") {
            ImmersiveView()
        }
        .immersionStyle(selection: .constant(.full), in: .full)
    }
}

ContentViewには@Environmentでimmersive spaceをopen/dismissできる環境変数を用意して、ToggleのonChangeで呼ぶことで、immersive spaceを切り替えることができる。

struct ContentView: View {
    @State var showImmersiveSpace = false

    @Environment(\\.openImmersiveSpace) var openImmersiveSpace
    @Environment(\\.dismissImmersiveSpace) var dismissImmersiveSpace
    
    var body: some View {
        Toggle("Show ImmersiveSpace", isOn: $showImmersiveSpace)
            .toggleStyle(.button)
            .onChange(of: showImmersiveSpace) { _, newValue in
                Task {
                    if newValue {
                        await openImmersiveSpace(id: "ImmersiveSpace")
                    } else {
                        await dismissImmersiveSpace()
                    }
                }
            }
    }
}

補足

openImmersiveSpaceはOpenImmersiveSpaceActionのインスタンスを取得して、構造体を関数のように呼び出せるcallAsFunction(id:)を実行することで、idで指定したimmersive spaceを表示する。なお、この処理は非同期的なのでawaitを付けてTask内で実行する必要がある。

Skyboxテクスチャとモデルを用意する

今回はHello Worldのサンプルコードからアセットをお借りして、宇宙空間(Starfield)を背景として、月のオブジェクトを表示したいので、WorldAssetsパッケージをローカルから追加しておく。

WorldAssets / Moon

ImmersiveSpaceにオブジェクトを表示する

ImmersiveSpaceではWindowやVolumeも表示できるが、今回はRealityKitのコンテンツをSwiftUIで表示できるRealityViewを使う。
まずはStarfieldのテクスチャリソースを読み込んでマテリアルを作成する。Skyboxとなる球体のEntityも作成して、マテリアルを付与する。そしてRealityViewのcontentに追加することでSkyboxを表示できる。 ※entity.scaleをマイナスにしているのは、テクスチャを内側にするため
同様にMoonのモデルを読み込んで、scaleとpositionを設定して、RealityViewのcontentに追加して、全体をZStackで囲めば、宇宙空間に浮かぶ月を表示できる。

struct ImmersiveView: View {
    var body: some View {
        ZStack {
            RealityView { content in
                guard let resource = try? TextureResource.load(named: "Starfield") else {
                    fatalError("Unable to load texture.")
                }
                
                var material = UnlitMaterial()
                material.color = .init(texture: .init(resource))
                
                let entity = Entity()
                entity.components.set(ModelComponent(
                    mesh: .generateSphere(radius: 1000),
                    materials: [material]
                ))
                
                entity.scale *= SIMD3(repeating: -1)
                content.add(entity)
            }
            
            RealityView { content in
                guard let model = try? await ModelEntity(named: "Moon", in: worldAssetsBundle) else {
                    fatalError("Unable to load model.")
                }

                model.scale = SIMD3(repeating: 1)
                model.position = SIMD3(x: 0, y: 0, z: 0)
                content.add(model)
            }
        }
    }
}
Show ImmersiveSpace
Moon in Starfield

参考


この記事が気に入ったらサポートをしてみませんか?