visionOSアプリで衝突検知を試す
概要
MESONで実施中のApple Vision Pro 1ヶ月記事投稿チャレンジ の2/13の記事となります。
2/12の記事 : VisionProを着けて歩いてみたら、フリーwifiのログイン画面にぶつかった。そして考えた未来の情報表示について。
visionOSアプリで、3Dオブジェクトの衝突検知を試してみました。
衝突検知とは、二つ以上のオブジェクトが互いにぶつかったかを検出するプロセスです。
Collision Mode(衝突検知モード)について
visionOSでは、2種類の衝突検知モードが用意されています。
- default : 物理的な反応を伴う衝突を検知する
- trigger : 衝突を検知するが、物理的な反応はない
defaultモードでの衝突検知
1. Reality Composer Pro を開く
XcodeでvisionOS向けのプロジェクトを作成した後、Reality Composer Proを使って3Dオブジェクトを追加して行きます。
Xcodeの下記のメニューからReality Composer Proを起動することができます。
2. 衝突検知用のシーンを作成する
Reality Composer Proで、衝突検知用のシーンを作成します。
ここでは、Collisionという名前のシーンを作成しました。
3. 衝突検知用のオブジェクトを追加する
デフォルトで用意されているCubeオブジェクトとSphereオブジェクトを追加します。
3-1. Cubeの設定
Cubeオブジェクトを選択し、Add Componentボタンから、Collision
コンポーネントとPhisics Body
コンポーネントを追加します。
3-2. Sphereの設定
Shpereオブジェクトも同様に、Collision
コンポーネントとPhisics Body
コンポーネントを追加します。
また、Cubeにぶつける仕組みのために、Phisics Motion
コンポーネントとInput Target
コンポーネントも追加します。
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. ビルドして、シュミレーターで再生を確認する
triggerモードでの衝突検知
上記、defaultモードでの衝突検知で作成したプロジェクトを修正する形で進めて行きます。
1. Reality Composer Proで、衝突検知用のシーンを作成する
ここでは、Triggerという名前のシーンを作成し、Sphereオブジェクトを追加しました。
2. Xcodeで、ImmersiveView.swiftを編集する
ここでは、Sphereオブジェクトの衝突を検知するTriggerVolume
を作成し、Sphereオブジェクトが衝突するとSphereのマテリアルを赤色に変更する処理となっています。
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. ビルドして、シュミレーターで再生を確認する
書いた人
高野 剛
Unixシステムのインフラ構築・運用を経験後、ECサイトを中心としたWebアプリ開発、プロジェクトマネージメントに従事する。
ミニオン好きが高じて、USJに通い続ける中、XRアトラクションに魅了される。
自分が感動したことを他の人にも体験してもらいたいという思いから、転職を決意し、XRの学校での1年間の学びを経てMESONへ入社。
MESON Works
MESONの制作実績一覧もあります。ご興味ある方はぜひ見てみてください。
Discussion