visionOS Reality Composer Pro の試してみた
visionOS で 3Dコンテンツを作成するときには Reality Composer Pro を使用して作成するのですが、実際にどうやって作成しているのか、実際に試してみました。
RealityComposerProとは
VisionPro で 3D空間に3Dモデルを配置するには RealityComposerProを使用して簡単に配置をすることができます。
UnityやUnrealEngineのようなゲームエンジンのように3D空間上にオブジェクト(Entity)を配置し、オブジェクトに対してコンポーネントをつけることで、機能を付与することができます。
SwiftUI上ではこのRealityComposerProで作成したシーンを読み込むことで3D空間上にオブジェクトを配置することができます。
環境構築
visionOSの開発を行うには Xcodeのベータバージョンをダウンロードして使用する必要があります。
ここから Xcode 15 の bataバージョンをダウンロードしてインストールします。
現在は beta5 があるのでそちらをインストールしています。

ダウンロードしたxidを展開して出てきたXcodeを開きvisionOSにチェックをinstallを行ってください。

ダウンロードしたXcodeを開きCreateProjectからvisionOS向けのプロジェクトを作成します。

次にプロジェクトの設定を行います。
空間上にオブジェクトを表示するので Initial Scene は Volume にします。
Immersive Space は バーチャル空間をどのように使用するかなのですが、今回はMixにして現実空間と一緒に表示されるようにします。

作成されたプロジェクトを開くとコードとプレビューの表示が一緒に表示されます。
プレビューの表示は選択しているファイルのViewが表示されている状態です。
試しに、ContentViewからImmersiveViewのファイルを選択すると表示が切り替わるのが確認できると思います。
再生し Enlarge RealityView Contentボタンを押すと 球体の大きさが変わり、Show Immersive Space ボタンを押すと空中に2つ球体が表示されるのが確認できます。

RealityComposerPro
表示されている球体は Reality Compooser Pro で設定され表示されています。
ContentView では Reality Composer Pro で作成した realityKitContentBundle にある Scene という名前のシーンを読み込んでいます。
RealityView { content in
    // Add the initial RealityKit content
    if let scene = try? await Entity(named: "Scene", in: realityKitContentBundle) {
        content.add(scene)
    }
}
続いてImmersiveViewを見てみましょう。ImmersiveViewではImmersiveという名前のシーンを読み込んでいます。
RealityView { content in
    // Add the initial RealityKit content
    if let scene = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
        content.add(scene)
    }
}
ではこの中身をReality Composer Proで確認してみましょう。
Packages/RealityKitContent/Packageを選択してOpen in Reality Composer Proを選択して Reality Composer Proを開きます。

Reality Composer Proを開くと球体が表示しているシーンが開きます。
このシーン上にEntityを配置することで空間にオブジェクトを設置していきます。
今は Immersiveのシーンが開いている状態なので球体が2つシーン上に配置されています。

Scene シーンをダブルクリックして開くと ContentView で表示している球体が1つのシーンが表示されます。

試しに Sceneにオブジェクトを追加してみましょう。
左下の+マークから Primitive Shape/Cube を選択して 立方体を追加します。
Root以下に移動させ、球体の上に配置してみましょう。


セーブしてXcodeに戻り、ContentViewを選択すると立方体が追加されているのが確認できると思います。
Reality Composer Proで編集した内容をセーブすると自動的に Xcode 上でも反映されて確認することができます。

Component
次は SceneにあるSphereを選択してコンポーネントを確認してみましょう。

TransformはかならずEntityにつくコンポーネントで位置、回転、大きさを表しています。
MaterialBindingは使用するマテリアルを設定し、SpherePrimitiveは球体モデルのメッシュを表現しています。
Collisionは当たり判定を設定していおり、InputTargetをつけることでヒットしたユーザーの入力イベントを検知できるようになります。
入力イベントは ContentView の Gesture部分で設定しています。
ContentViewではオブジェクトをクリックしても大きさが変わるようになっています。
.gesture(TapGesture().targetedToAnyEntity().onEnded { _ in
    enlarge.toggle()
})
EntityCompoonentSystem
visionOSのコンポーネントはEntityComponentSystemという仕組みで動作しています。

コンポーネントでEntityに情報を追加し、
システムの方でコンポーネントを元にどう動作するかを実装します。
それではコンポーネントとシステム作成してみましょう。
今回は並行移動させる機能を作っていきたいと思います。
Cubeを選択して 右下の AddComponentを選択し、New Componentを選択します。

コンポーネントの名前を入力してコンポーネントを作成します。

これで作成したコンポーネントがCubeにつきました。
Xcodeに戻り 作成したMoveComponentを編集していきましょう。
MoveComponentは Packages/RealityKitContent/Sources/RealityKitContent/MoveComponentに作成されています。

今回は移動をさせるために velocityをコンポーネントに追加しましょう。
SIMD は ベクトルを表現するための型で 今回は3次元ベクトルの SIMD3<Float> を使用しています。
import RealityKit
// Ensure you register this component in your app’s delegate using:
// MoveComponent.registerComponent()
public struct MoveComponent: Component, Codable {
    var velocity : SIMD3<Float> = [0.0,0.0,0.0]
    public init() {
    }
}
RealityComposerProに戻ってMoveComponent を確認してみると Velocityが追加されています。
奥に動くようにzに -0.01 を入れておきましょう.

次にこれを動作させるためにMoveComponentと同じ階層にシステムを作成します。
Xcodeに戻りRealityKitContentを選択してNewFileを選択し、名前をMoveSystemとします。

ファイルを作成したらMoveSystemの中身を実装しましょう。
import RealityKit
public struct MoveSystem: System {
    
    static let query = EntityQuery(where: .has(RealityKitContent.MoveComponent.self))
    public init(scene: Scene) {
    }
    
    public func update(context: SceneUpdateContext) {
        let entities = context.scene.performQuery(Self.query).map({ $0 })
        if entities.isEmpty {
            return
        }
        
        for entity in entities {
            let moveComponent = entity.components[RealityKitContent.MoveComponent.self]!;
            entity.transform.translation += moveComponent.velocity;
        }
    }
}
上から見ていくと query ではシーンからどのコンポーネントを取得するかのクエリを定義しています。
今回は MoveComoponentを取得して操作するためMoveComopnentを取得するクエリを定義します。
static let query = EntityQuery(where: .has(RealityKitContent.MoveComponent.self))
シーンに対してクエリを指定して対象のEntityを取得してきます。
let entities = context.scene.performQuery(Self.query).map({ $0 })
取得したEntityからMoveComponentを取得してtransform.translationにvelocityを足してあげます。
updateでは毎フレーム処理が呼ばれるためvelocity方向に位置が移動し続けます。
for entity in entities {
    let moveComponent = entity.components[RealityKitContent.MoveComponent.self]!;
    entity.transform.translation += moveComponent.velocity;
}
これでMoveSystemの実装は完了です。
次に作成したコンポーネントとシステムを使用できるように登録しましょう。
まずは RealityComposerProTestAppにinitを追加します。
コンポーネントはregisterCompoonent, システムはregisterSystemで登録を行います。
import RealityKitContent
struct RealityComposerProTestApp: App {
    init(){
        RealityKitContent.MoveComponent.registerComponent()
        RealityKitContent.MoveSystem.registerSystem()
    }
    var body: some Scene {
        ....
    }
}
これでシステムとコンポーネントが動作するようになりました!
それでは動作を確認してみましょう。

立方体が奥へ進んでいくようになりました!
コンポーネントにしているため他のオブジェクトにコンポーネントをつけることで同じように動作させることができます。
球にもコンポーネントをつけてみましょう。
今度は手前に移動させるように z に 0.01 を入れます。

ContentViewに戻って確認すると立方体と球が動いているのが確認できます。

まとめ
RealityComposerProを使用するとGUI上でシーンにオブジェクトを配置することができます。
また、ComponentとSystemを使用することでComponent単位で挙動を実装することが可能です。
これはUnityやUnrealなどのゲームエンジンに近い形でシーンを構築できるため便利でした。
今回は紹介しませんでしたが音の発生源を追加したり、ShaderGraphによってシェーダの作成も可能です。
RealityComposerPro使いこなしていきましょう!
(Unityでできるのであればそちらの方が良さそうですが・・)
書いた人

佐藤 寿樹
株式会社コナミデジタルエンタテインメントに入社し5年間ウイニングイレブンのオンライン実装に携わる。
その後、株式会社コロプラで9年間エンジニアとしてアプリ開発・運用を行い、位置情報やARを使用したARゲーム開発、OculusRiftやPSVRなどのVRゲーム開発を経験しMESONへ入社。
MESON Works
MESONの制作実績一覧もあります。ご興味ある方はぜひ見てみてください。

Discussion