初心者のためのVision Proアプリ構築チュートリアル

visionpro.png

概要

このチュートリアルでは、VisionOS で何ができるかを駆け足で紹介します。 Swift や iOS の開発経験がほとんどない方を想定し、Vision Pro 開発ならではのポイントを共有します。

以下のトピックについて紹介します。

  • 環境構築
  • VisionOS プロジェクトの作成方法
  • SwiftUI、Immersive Spaceの使用方法
  • 3Dモデルのインポート方法
  • Reality Composer Proの使用方法
  • チュートリアルクリア後のネクストアクション

このチュートリアルをクリアすると、このようなVision Pro向けのミニアプリが作成できます。

さぁ!始めましょう!!!

環境構築

2024 年 2 月の時点では、最新のXcodeでもVision Pro向けアプリの開発に対応しました!! アプリを公開する際には料金を支払う必要がありますが、開発だけなら、開発者アカウントも不要です!

スクリーンショット2024-02-1816.00.18.png

プロジェクトの作成

Xcodeを起動したら、Create New Projectを選択してください。
スクリーンショット2024-02-1816.02.52.png

その後、テンプレートの中からvisionOSのAppを選択してください。

スクリーンショット2024-02-1816.03.02.png

次のページで、プロジェクトの名前を決めます。私の場合は、MyFirstAppとしました。(⚠︎Appを名前につけるのはオススメしません。アップアップしてるのがバレます。)
このチュートリアルでは各所オプションは以下のように設定してください。

  • Initial Scene:Window
  • Immersive Space Renderer:RealityKit
  • Immersive Space:Mixed

※各オプションの意味については、記事内で解説します。

スクリーンショット2024-02-1814.23.48.png

コードを理解しよう!

プロジェクトが作成されると、以下のように表示されるはずです。

スクリーンショット2024-02-1814.26.29.png

うわ、、、めちゃくちゃコードとファイルあるじゃん、、とビビらないでください。各ファイルが何をするのものなのかを見て、色々と試してみましょう。意外とわかりやすいんです。

実際に、注意が必要なコード、ファイルは以下の4つのみです。

  • あなたの決めたプロジェクト名App.swift または、MyFirstAppApp.swift
  • ContentView.swift ※
  • ImmersiveView.swift ※
  • Pakages/RealityKitContent

※これらのコードは、この記事にコピーしています。テンプレートの内容が変更されていた場合はこのコピーを参考にしてください。

Pakages/RealityKitContentは後ほどReality Composer Proで開いて確認します。

この状態で、左上の実行ボタンを押すと、シュミレーターでビルドされます。最初に、2Dのパネルが表示されShow ImmersiveSpaceのトグルを動かすと部屋の中に2つの球体が表示されます。

スクリーンショット2024-02-1814.43.36.png

ContentView() とは?

ContentView.swift は、最初に表示される2Dのウィンドウに関して記述されたファイルです。 SwiftUIという UI フレームワークを使用した Swift ファイルです。

これまでに SwiftUI コードを読んだことがない場合、一見難しく思えるかもしれません。ですが、一度説明を受けると、実際には非常に簡単に読むことができます!

以下のコードをコピーしました。

import SwiftUI
import RealityKit
import RealityKitContent

struct ContentView: View {

    @State private var showImmersiveSpace = false
    @State private var immersiveSpaceIsShown = false

    @Environment(\.openImmersiveSpace) var openImmersiveSpace
    @Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace

    var body: some View {
        VStack {
            Model3D(named: "Scene", bundle: realityKitContentBundle)
                .padding(.bottom, 50)

            Text("Hello, world!")

            Toggle("Show ImmersiveSpace", isOn: $showImmersiveSpace)
                .font(.title)
                .frame(width: 360)
                .padding(24)
                .glassBackgroundEffect()
        }
        .padding()
        .onChange(of: showImmersiveSpace) { _, newValue in
            Task {
                if newValue {
                    switch await openImmersiveSpace(id: "ImmersiveSpace") {
                    case .opened:
                        immersiveSpaceIsShown = true
                    case .error, .userCancelled:
                        fallthrough
                    @unknown default:
                        immersiveSpaceIsShown = false
                        showImmersiveSpace = false
                    }
                } else if immersiveSpaceIsShown {
                    await dismissImmersiveSpace()
                    immersiveSpaceIsShown = false
                }
            }
        }
    }
}

このコードの意味を紐解いてみましょう。注意する必要があるのは、これらの 3 行だけです。

Model3D(named: "Scene", bundle: realityKitContentBundle)
                .padding(.bottom, 50)

Text("Hello, world!")
                .offset(z: 100)

Toggle("Show ImmersiveSpace", isOn: $showImmersiveSpace)
                .font(.title)
                .frame(width: 360)
                .padding(24)
                .glassBackgroundEffect()

これらは、このビューを定義しています。

スクリーンショット2024-02-1814.45.36.png

Model3D() とは?

Model3D(named: "Scene", bundle: realityKitContentBundle)

これは、Vision VisionOSのための新しいSwiftUIの機能です。基本的な機能としては、2DのSwiftUIビュー内に3Dモデルを埋め込むことができます。このコードでは、realityKitContentバンドルの中のSceneというid(ファイル名)を持った3Dモデルをインポートしています。

新しいText() の機能 ?

Text("Hello, world!")  これはきっと」どこかで見たことがあるでしょう!そう!テキストです!

VisionOS向けアプリ開発の新しい点の1つは、テキストなどの SwiftUI コンポーネントに奥行きを追加できることです。これにより、多くのクールなインタラクションを作成できるようになります。たとえば、ユーザーがボタンを見ると、ボタンが少し浮き上がるとか!

Z 軸 (奥行き) にオフセットを追加するだけの場合、その仕組みは以下のとおりです。

Text("Hello, world!")
                .offset(z: 100)

※Windowの平面城が0になります。

スクリーンショット2024-02-1815.03.32.png

WindowからImmersive Spaceへの移行

Toggle("Show ImmersiveSpace", isOn: $showImmersiveSpace)

最後の行は、ユーザーによって切り替が可能なトグルを作成しています。ユーザーが変数を切り替えると、 showImmersiveSpace 変数に書き込まれます。ただし、この行は変数を書き込むだけであり、実際のWindowからImmersive Spaceへの移行への移行処理は .onChange の内部で行われます。

.onChange(of: showImmersiveSpace) { _, newValue in
            Task {
                if newValue {
                    switch await openImmersiveSpace(id: "ImmersiveSpace") {
                    case .opened:
                        immersiveSpaceIsShown = true
                    case .error, .userCancelled:
                        fallthrough
                    @unknown default:
                        immersiveSpaceIsShown = false
                        showImmersiveSpace = false
                    }
                } else if immersiveSpaceIsShown {
                    await dismissImmersiveSpace()
                    immersiveSpaceIsShown = false
                }
            }
}

View.onChange 修飾子は showImmersiveSpace が変わるたびに呼び出されます。TrueかFalseかを確認し、新しVisionOSの関数である openImmersiveSpace もしくは dismissImmersiveSpace を呼び出してimmersive spaceを閉じるか開くかをの処理を行います。

一般に、.onChange を使用すると、SwiftUI ビュー内で @State としてマークされた変数の変更を監視できます。

それでは、immersive space がどのように作成されるのかを見てみましょう!

ImmersiveView() とは?

ImmersiveView は ContentViewと似たSwiftUI viewですが、1つの大きな違いがあります。それは、3DコンテンツをレンダリングするためにRealityKitというゲームエンジンを利用する点です。

以下にコードをコピーしました。一緒に見てみましょう!

import SwiftUI
import RealityKit
import RealityKitContent

struct ImmersiveView: View {
    var body: some View {
        RealityView { content in
            // Add the initial RealityKit content
            if let scene = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(scene)
            }
        }
    }
}

ここでの VisionOS の主な新機能はRealityViewです。このビューを使用すると、物理システム、シーンレンダリング、ECS、アニメーションなどを含む、SwiftUI ビュー内で RealityKit の能力を最大限に活用できます。要望があれば、RealityKit + VisionOS に関するチュートリアルを書くかも。。?

それでは、RealityView の中身を見てみましょう!

sceneをロードする方法

Entity(named: "Immersive", in: realityKitContentBundle)

この例では、 realityKitContentBundle から* Immersive*という名前のエンティティを読み込みます。エンティティとは、ゲームの中の単なるオブジェクトで、モデルやモデルのコレクションであることがあります。 (これは、Unity の GameObject や Unreal Engine の Actor と似ています。)

この例は、 ContentView() 内で使用した Model3D での呼び出しと同じ処理になります。主な違いは、 Model3D では RealityKit を使わずに動作するため、RealityKitの全機能にアクセスできない点です。

if let scene = try? await Entity(named: "Immersive", in: realityKitContentBundle) {.
ここでは、そのエンティティが実際に realityKitContentBundle の中にあるかどうかを確かめるために if let を使います。これは基本的に、モデルが正しくロードされている場合はそれを scene という変数に割り当て、そうでない場合は else でコンテンツを実行することを示しています。

また、エンティティのロードは非同期であるため、ここでは try? await も用いられています。

content.add(scene)
if let で、realityKitContentBundle からエンティティが正しくロードされた場合、この新しく作成されたシーン変数が アプリ内の content に追加されます。 contentRealityView によって提供される変数です。 content に追加されたエンティティのみがレンダリングされます。

アプリがどのように起動するか?

VisionOS アプリを起動した際、 あなたの決めたプロジェクト名App.swift がアプリの初期ビューを定義するファイルになります。もし、作成した別のViewで起動したい場合は、ContentView() の部分を他のものに変更するだけでOKです。

Body

このテンプレートでは、アプリ内のBodyに ContentView()ContentView() という2つのViewが定義されています。

これは、一度に 1 つのビューを組み込む通常の IOS 開発とは少し異なります。body内に複数のビューがある場合、最初のビューのみがユーザーに表示されます。ここで 2 つのビューを追加した理由は、起動後に ContentView() から に ImmersiveView() に移行するミニアプリだからです。なので、事前にここで両方を宣言しておいた方が簡単です。

Window View と Volume View

ここで、作成したViewの外側でラップされている WindowGroupImmersiveSpace が何を意味するのかをみてみましょう。

Vision OSには以下の3種類のViewがあります。

  • Windows: 3Dモデルを埋め込むことが可能な2Dパネル。
  • Volumes: RealityKitやUnityを使用して作成した3DコンテンツなどのSceneを表示できる領域。中に収めるシーンがボリュームよりも大きい場合は、ボリューム内に入りきった部分のみが表示されます。
  • Spaces:制限のない領域。環境全体を制御したり、完全な VR ゲームを作成したりすることもできます。

スクリーンショット2024-02-1819.07.59.pngApple公式ページより

この3 つのタイプの感覚を掴むと、今回のコード達の理解が更に容易になるはずです。
単なる 2D パネルである ContentView() は、これが 2D であることを示すために WindowGroup によってラップされています。表示される 2 つの球体を表す ImmersiveView() については、2 つの球体を表示するために無制限のスペースが必要なので、ImmersiveSpace でラップされています。

OK!!! ここまで来れば全てのコードが理解できたと思います!早速遊んでみましょう!

Reality Composer Pro

ここまで、私たちが見ているのは SwiftUI Viewだけです。多くのViewは、 realityKitContentBundle と呼ばれるバンドルから 3D Sceneをロードします。そのバンドルの中身を見てみましょう!

Packagesフォルダの配下にあるRealityKitContent/Packagesファイルを開いて、右上のOpen in Reality Composer Pro をクリックしてください。

スクリーンショット2024-02-1819.26.12.png

Reality Composer Proが起動し以下のような画面が確認できるはずです。

スクリーンショット2024-02-1819.26.29.png

Unity または Blender を使用したことがある方には、非常に見慣れた画面かもしれません。 キーボードの WSAD を使用して移動することができ、Q と E を使用して上下に移動できます。

以下のプロジェクト ブラウザを見ると、 Immersive.usda と Scene.usda という2 つの見慣れた .usda ファイルが表示されます。これらは、ContentView() でモデルをロードし、ImmersiveView() でシーンをロードするときに使用した名前です。

  • Entity(named: "Immersive", in: realityKitContentBundle)
  • Model3D(named: "Scene", bundle: realityKitContentBundle)

スクリーンショット2024-02-1819.37.21.png

それでは、遊んでみましょう。Scene.usdaをダブルクリックし、右上の + ボタンをクリックすると、使用できる 3D モデルが選択できます。それをダブルクリックもしくは、シーンにドラッグ アンド ドロップします。

スクリーンショット2024-02-1819.44.09.png

すると、左側のSceneファイルのリスト内に既存の球体モデル( GridMaterial )と同列に3Dモデルが配置されます。
スクリーンショット2024-02-1819.44.29.png

もう一方を一度削除すると、おもちゃの飛行機がルート内の唯一のモデルになります。この状態で、保存するとプロジェクト ブラウザのアイコンがおもちゃの飛行機に変わります。
スクリーンショット2024-02-1819.44.58.png

保存されたことが確認できた後、Xcode に戻ってアプリを再構築します。これでおもちゃの飛行機が見えてきます!

Immersive.usda も同様に開いて遊んでみてください。
スクリーンショット2024-02-1819.47.33.png

Immasiveファイルを指定し、3Dオブジェクトを追加。

スクリーンショット2024-02-1819.48.00.png

この時、右のパネルで各モデルの位置を調整できます。

スクリーンショット2024-02-1820.03.13.png

それぞれ配置。

スクリーンショット2024-02-1819.49.45.png

できました。こんな感じです。

スクリーンショット2024-02-1819.52.30.png

まとめ

ここまで読んでいただきありがとうございました!作るだけならサクサクできるのですが、今回のチュートリアル記事作成には結構時間がかかってしまいました。。。

日本語でも多くの方が、記事を作成してくださっていますので、是非検索してトライしてみてください。
また、さらに詳しく知りたい場合は、WWDC トークをチェックし、SwiftUI と ARKit をカバーする iOS チュートリアルをいくつか見ることをオススメします。

個人的には、VisionOS プラットフォームに非常に可能性を感じて興奮しています。 ついに電脳コイルの時代が来たか!!!!と。笑

今回のような初心者向けチュートリアルに興味がある場合は、是非私のX等々にリプやDMください!!
今後、以下の分野に関するチュートリアルを作成する予定です。

  • SwiftUI + VisionOS
  • RealityKit + VisionOS
  • ARKit + VisionOS

しかし、このチュートリアルを書くのにかかった時間を考えると…、悩ましいです。。。


【monoDuki合同会社について】

monoDuki合同会社は、鹿児島の「モノ好き」達が世の中をもっと面白くしようと、「XR事業」と「スタートアップ支援事業」の2つに注力し、世の中に対して新たな可能性を提供します。