💥

visionOSアプリで衝突検知を試す

2024/02/13に公開

概要

MESONで実施中のApple Vision Pro 1ヶ月記事投稿チャレンジ の2/13の記事となります。
2/12の記事 : VisionProを着けて歩いてみたら、フリーwifiのログイン画面にぶつかった。そして考えた未来の情報表示について。


visionOSアプリで、3Dオブジェクトの衝突検知を試してみました。
衝突検知とは、二つ以上のオブジェクトが互いにぶつかったかを検出するプロセスです。

https://developer.apple.com/documentation/realitykit/collision-detection

Collision Mode(衝突検知モード)について

visionOSでは、2種類の衝突検知モードが用意されています。

  • default : 物理的な反応を伴う衝突を検知する
  • trigger : 衝突を検知するが、物理的な反応はない

https://developer.apple.com/documentation/realitykit/collisioncomponent/mode-swift.enum

defaultモードでの衝突検知

1. Reality Composer Pro を開く

XcodeでvisionOS向けのプロジェクトを作成した後、Reality Composer Proを使って3Dオブジェクトを追加して行きます。
Xcodeの下記のメニューからReality Composer Proを起動することができます。
xcode01.png

2. 衝突検知用のシーンを作成する

Reality Composer Proで、衝突検知用のシーンを作成します。
ここでは、Collisionという名前のシーンを作成しました。
reality_composer01.png

3. 衝突検知用のオブジェクトを追加する

デフォルトで用意されているCubeオブジェクトとSphereオブジェクトを追加します。

3-1. Cubeの設定

Cubeオブジェクトを選択し、Add Componentボタンから、CollisionコンポーネントとPhisics Bodyコンポーネントを追加します。
reality_composer04.png

https://developer.apple.com/documentation/realitykit/collisioncomponent/
https://developer.apple.com/documentation/realitykit/physicsbodycomponent

3-2. Sphereの設定

Shpereオブジェクトも同様に、CollisionコンポーネントとPhisics Bodyコンポーネントを追加します。
また、Cubeにぶつける仕組みのために、Phisics MotionコンポーネントとInput Targetコンポーネントも追加します。
reality_composer05.png

https://developer.apple.com/documentation/realitykit/physicsmotioncomponent/
https://developer.apple.com/documentation/realitykit/inputtargetcomponent/

4. Xcodeでプログラムを編集する

4-1. ContentView.swift

単純に、openImmersiveSpaceを呼び出すだけのプログラムになります。

import SwiftUI
import RealityKit
import RealityKitContent

struct ContentView: View {
    @State private var isLoadingImmersiveSpace = true
    @Environment(\.openImmersiveSpace) var openImmersiveSpace

    var body: some View {
        VStack {
            if isLoadingImmersiveSpace {
                Text("Loading immersive space...")
                    .padding()
                    .glassBackgroundEffect()
            }
        }
        .onAppear {
            Task {
                await openImmersiveSpace(id: "ImmersiveSpace")
                isLoadingImmersiveSpace = false
            }
        }
    }
}

#Preview {
    ContentView()
}

4-2. ImmersiveView.swift

Sphereはタップした際に、設定した速度で射出されると同時に重力を有効化する処理となっています。
CubeはSphereと衝突した際に、重力を有効化する処理となっています。

import SwiftUI
import RealityKit
import RealityKitContent

struct ImmersiveView: View {
    @State var cube: ModelEntity? = nil
    @State var sphere: ModelEntity? = nil
    
    var body: some View {
        RealityView { content in
            // Add the initial RealityKit content
            // Collisionシーンの読み込み
            if let scene = try? await Entity(named: "Collision", in: realityKitContentBundle) {
                content.add(scene)
                
                // 「Cube」「Sphere」のEntityを取得して、変数に保持
                if let cube = scene.findEntity(named: "Cube") as? ModelEntity,
                   let sphere = scene.findEntity(named: "Sphere") as? ModelEntity
                {
                    self.cube = cube
                    self.sphere = sphere
                }
                
                // 衝突イベントを購読(「on」で、ソースオブジェクトを指定することで、主体がcubeになるようにしている
                _ = content.subscribe(to: CollisionEvents.Began.self, on: cube)
                { event in
                    handleCollision(event)
                }
            }
        }.onTapGesture {
            setupPhysicsForSphere()
        }
    }
    
    /**
     Sphere をタップした際の処理
     */
    private func setupPhysicsForSphere()
    {
        // 速度指定
        sphere?.physicsMotion?.linearVelocity = [0, 2, -5]
        // 重力を有効化
        sphere?.physicsBody?.isAffectedByGravity = true
    }
    
    /**
     衝突イベントが発生した際の処理
     eventの主体をcubeにすることで、entityAはcubeとなる
     */
    private func handleCollision(_ event: CollisionEvents.Began)
    {
        let model = event.entityA as! ModelEntity
        print(event.entityA)
        
        // 重力を有効化
        model.physicsBody?.isAffectedByGravity = true
    }
}

#Preview {
    ImmersiveView()
        .previewLayout(.sizeThatFits)
}

5. ビルドして、シュミレーターで再生を確認する

collision.gif

triggerモードでの衝突検知

上記、defaultモードでの衝突検知で作成したプロジェクトを修正する形で進めて行きます。

1. Reality Composer Proで、衝突検知用のシーンを作成する

ここでは、Triggerという名前のシーンを作成し、Sphereオブジェクトを追加しました。
reality_composer06.png

2. Xcodeで、ImmersiveView.swiftを編集する

ここでは、Sphereオブジェクトの衝突を検知するTriggerVolumeを作成し、Sphereオブジェクトが衝突するとSphereのマテリアルを赤色に変更する処理となっています。

https://developer.apple.com/documentation/realitykit/triggervolume

import SwiftUI
import RealityKit
import RealityKitContent

struct ImmersiveView: View {
    @State var sphere: ModelEntity? = nil
    
    var body: some View {
        RealityView { content in
            // Add the initial RealityKit content
            // Triggerシーンの読み込み
            if let scene = try? await Entity(named: "Trigger", in: realityKitContentBundle) {
                content.add(scene)
                
                // TriggerVolumeの作成、配置、コンテンツへの追加
                let triggerArea = TriggerVolume(shape: .generateBox(size:[1,1,1]))
                triggerArea.position = [0, 1.5, -2]
                content.add(triggerArea)
                
                if let sphere = scene.findEntity(named: "Sphere") as? ModelEntity
                {
                    self.sphere = sphere
                }
                
                // 衝突イベントを購読
                _ = content.subscribe(to: CollisionEvents.Began.self, on: sphere)
                { event in
                    handleCollision(event)
                }
            }
        }.onTapGesture {
            setupPhysicsForSphere()
        }
    }
    
    /**
     Sphere をタップした際の処理
     */
    private func setupPhysicsForSphere()
    {
        // 速度指定
        sphere?.physicsMotion?.linearVelocity = [0, 2, -5]
        // 重力を有効化
        sphere?.physicsBody?.isAffectedByGravity = true
    }
    
    /**
     衝突イベントが発生した際の処理
     マテリアルを変更し、赤色に変化させる
     */
    private func handleCollision(_ event: CollisionEvents.Began)
    {
        let model = event.entityA as! ModelEntity
        let redMaterial = SimpleMaterial(color: .red, isMetallic: false)
        sphere?.model?.materials = [redMaterial]
    }
}

#Preview {
    ImmersiveView()
        .previewLayout(.sizeThatFits)
}

3. ビルドして、シュミレーターで再生を確認する

trigger.gif

書いた人

ボブ

高野 剛

Unixシステムのインフラ構築・運用を経験後、ECサイトを中心としたWebアプリ開発、プロジェクトマネージメントに従事する。
ミニオン好きが高じて、USJに通い続ける中、XRアトラクションに魅了される。
自分が感動したことを他の人にも体験してもらいたいという思いから、転職を決意し、XRの学校での1年間の学びを経てMESONへ入社。

X

MESON Works

MESONの制作実績一覧もあります。ご興味ある方はぜひ見てみてください。

MESON Works

Discussion