[VisionOS]Attachmentsの使い方
VisionOSアプリケーション開発において、Attachmentsは3D空間内にUIを配置する重要な機能です。この記事では、ゲームアプリを例にAttachmentsの使い方を解説します。
Attachmentsとは
Attachmentsは、RealityKitの3D空間内に SwiftUI のビューを配置するための機能です。
例えば、ゲーム画面のUI、メニュー、ステータス表示などを3D空間内の特定の位置に配置することができます。
Apple TVアプリでは字幕の表示に利用されています。
実装方法
RealityView の実装
ImmersiveView
というImmersive Spacesを表示するViewの中に表示する例を考えます。
struct ImmersiveView: View {
@State private var gameStatus: GameStatus = .notStarted
var body: some View {
RealityView { content, attachments in
// 初期セットアップ
let anchor = AnchorEntity(.world)
content.add(anchor)
} update: { content, attachments in
// Attachments の更新
update(attachments, gameStatus: gameStatus)
} attachments: {
attachmentContents
}
}
}
RealityView は3つのクロージャーを持ちます:
- 初期化クロージャー:3Dコンテンツの初期セットアップを行います
- update クロージャー:状態が変化したときの更新処理を行います
- attachments クロージャー:表示する Attachment を指定します
AttachmentContent の定義
extension ImmersiveView {
@AttachmentContentBuilder
var attachmentContents: some AttachmentContent {
gameMenuAttachment
gameClearAttachment
gameOverAttachment
}
var gameOverAttachment: some AttachmentContent {
Attachment(id: "GameOver") {
Text("Game Over 👾")
.font(.system(size: 200, weight: .bold))
.foregroundStyle(.red)
}
}
}
@AttachmentContentBuilder
を使用して、表示したい Attachment を定義します。
この例では、ゲームの状態に応じて表示する Attachment を定義しています。
ゲームの状態管理
RealityViewのupdate関数でAttachmentsを更新するために状態管理をAppModelに持たせた例を示します。
ゲームの状態はGameStatus
という列挙型で管理します:
enum GameStatus: String {
case notStarted // ゲーム開始前の状態
case playing // ゲームプレイ中の状態
case gameOver // ゲームオーバー状態
case cleared // ゲームクリア状態
}
このGameStatus
に応じて、画面に表示するAttachmentsを切り替えることで、ゲームの進行状況に合わせた適切なUIを提供します。
Attachments の更新ロジック
例えば、ゲームが終了しその結果がGameOver
だった場合、下記のように更新が走ります。
それぞれで呼ばれるメソッドも参考にしてください
extension ImmersiveViewModel {
func updateAttachments(_ attachments: RealityViewAttachments, gameStatus: GameStatus) {
switch status {
case .notStarted:
remove(.gameClear, .gameOver)
attach(.gameMenu, from: attachments, y: 1, z: -1)
case .cleared:
remove(.gameOver)
attach(.gameClear, from: attachments, y: 1.2, z: -1)
attach(.gameMenu, from: attachments, y: 0.8, z: -1)
case .gameOver:
remove(.gameClear)
attach(.gameOver, from: attachments, y: 1.2, z: -1)
attach(.gameMenu, from: attachments, y: 0.8, z: -1)
case .playing:
remove(.gameMenu, .gameClear, .gameOver)
}
}
/// 指定されたIDのAttachmentをシーンから削除
func remove(_ ids: AttachmentID...) {
for id in ids {
root.findEntity(named: id.rawValue)?.removeFromParent()
}
}
/// 指定されたIDのAttachmentを3D空間の指定位置に配置
/// - Parameters:
/// - id: AttachmentのID
/// - attachments: RealityViewAttachments
/// - x: X座標(左右位置、デフォルト: 0)
/// - y: Y座標(上下位置、デフォルト: 1)
/// - z: Z座標(前後位置、デフォルト: -0.8)
func attach(
_ id: AttachmentID,
from attachments: RealityViewAttachments,
x: Float = 0, y: Float = 1, z: Float = -0.8
) {
guard root.findEntity(named: id.rawValue) == nil,
let entity = attachments.entity(for: id.rawValue) else { return }
entity.name = id.rawValue
entity.position = [x, y, z]
entity.components.set(BillboardComponent()) // Entityが常にユーザーの方向を向くように設定
root.addChild(entity)
}
}
ゲームの状態(GameStatus)に応じて、表示するAttachmentsを切り替えています:
- ゲーム開始前:メニューのみを表示
- プレイ中:全てのUIを非表示
- クリア時:クリア表示とメニューを上下に配置
- ゲームオーバー時:ゲームオーバー表示とメニューを上下に配置
BillboardComponentはラベルやボタンなど常にユーザーへ正対させたい時に便利なComponentです。
Attachments の登録と配置
Attachmentを3D空間に配置する際は、以下の要素が重要です:
-
一意のID:
attachments.entity(for: "GameOver")
でAttachmentを取得する際、"GameMenu"という一意のIDが必要です。このIDは、SwiftUIビュー側で定義する際にも同じものを使用する必要があります。 -
重複チェック:
findEntity(named:)
を使用して、同じ名前のEntityが既に存在するかチェックします。これにより、Attachmentの重複を防ぎます。 -
位置指定:
position
プロパティを使用して3D空間での配置位置を指定します。座標系は以下の通りです:- x軸: 左右の位置(0が中心)
- y軸: 上下の位置(値が大きいほど上)
- z軸: 前後の位置(負の値でユーザーに近づく)
まとめ
VisionOSのAttachmentsを使用することで、3D空間内に状況に応じたUIを適切に配置することができます。
この機能を活用することで、よりインタラクティブで没入感のある空間アプリケーションを開発することが可能になります。
Discussion