💬

[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空間に配置する際は、以下の要素が重要です:

  1. 一意のID: attachments.entity(for: "GameOver") でAttachmentを取得する際、"GameMenu"という一意のIDが必要です。このIDは、SwiftUIビュー側で定義する際にも同じものを使用する必要があります。

  2. 重複チェック: findEntity(named:) を使用して、同じ名前のEntityが既に存在するかチェックします。これにより、Attachmentの重複を防ぎます。

  3. 位置指定: position プロパティを使用して3D空間での配置位置を指定します。座標系は以下の通りです:

    • x軸: 左右の位置(0が中心)
    • y軸: 上下の位置(値が大きいほど上)
    • z軸: 前後の位置(負の値でユーザーに近づく)

まとめ

VisionOSのAttachmentsを使用することで、3D空間内に状況に応じたUIを適切に配置することができます。

この機能を活用することで、よりインタラクティブで没入感のある空間アプリケーションを開発することが可能になります。

参考リンク

GitHubで編集を提案

Discussion