Android XR の基礎を学ぶ: パート 2 - オービターと空間環境

1. 始める前に

学習内容

  • Android XR によって実現される独自のユーザー エクスペリエンス。
  • Jetpack Compose XR ライブラリを使用して Android XR ヘッドセット向けにアプリを最適化する方法。
  • Jetpack Compose XR ライブラリの UI 要素を使用する方法。
  • Android XR 用アプリの作成に関する情報の入手場所。

対象外:

必要なもの

作成するアプリの概要

この Codelab では、フローティング UI 要素を追加し、アプリの使用中にユーザーを取り囲む仮想環境をカスタマイズすることで、既存の XR 機能を使用したアプリをさらに最適化します。

スターター コード

最終結果

2. 設定する

コードを取得する

  1. この Codelab のコードは、xr-codelabs GitHub リポジトリ内の xr-fundamentals ディレクトリにあります。次のコマンドを実行して、リポジトリのクローンを作成してください。
git clone https://github.com/android/xr-codelabs.git
  1. または、リポジトリを ZIP ファイルとしてダウンロードすることもできます。

プロジェクトを開く

  • Android Studio を起動し、xr-fundamentals/part1 ディレクトリをインポートします。xr-fundamentals/part2 ディレクトリにはソリューション コードが含まれています。不明な点がある場合や、プロジェクト全体を確認したいときは、いつでも参照できます。

コードを理解する

  • Android Studio でプロジェクトを開き、初期状態のコードを確認します。
  • 最初の Codelab を受講していない場合や、Android XR Emulator をまだ使用していない場合は、Android XR Emulator でアプリを実行するの手順に沿ってアプリを実行してください。

3. XR のコンセプトを学ぶ: オービター

オービターは、フルスペース モードで使用できるフローティング UI 要素です。通常は、空間パネル内のコンテンツや、オービットが固定されている他のエンティティを操作するために使用されます。コンテンツのコントロールにオービターを使用することで、コンテンツにより多くのスペースができ、メインのコンテンツを表示したまま、オービター内の機能にユーザーがすばやくアクセスできるようになります。Orbiter を使用すると、既存の UI コンポーネント(ナビゲーション バーなど)を統合したり、新しい UI コンポーネントを作成したりできます。

また、Orbiter API を使用すると、ホームスペース モードまたは XR 以外のデバイスで実行する場合は通常どおりオービターのコンテンツをレンダリングし、フルスペース モードで実行する場合は、オービターに自動的に分割されます。

ホームスペース モードでは、このナビゲーション レールはメインアプリ パネル内にレンダリングされます。

フルスペース モードでは、ナビゲーション レールがプライマリ パネルに接続されたオービターとして分割されます。

この時点で、アプリの上部アプリバーには、ホームスペース モードとフルスペース モードを切り替えるボタンが含まれています。このボタンは、フルスペース モードで実行するときにオービター内に収めることができるコントロールの好例です。コントロールを移動してメインパネルの周りを回るようにすると、コントロールが目立つようになり、クリックするとアプリのコンテンツがそのパネルに閉じられることも視覚的に示されます。

現状

実装内容

オービター設計の考慮事項について詳しくは、空間 UI をご覧ください。

4. オービターを追加する

スペース モードの切り替えボタンをラップする

スペースモードの切り替えボタンをオービターに変換するには、ToggleSpaceModeButton コンポーザブルを Orbiter コンポーザブルでラップします。

ui/component/XRFundamentalsTopAppBar .kt

import androidx.compose.foundation.shape.CornerSize
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
import androidx.xr.compose.spatial.EdgeOffset
import androidx.xr.compose.spatial.Orbiter
import androidx.xr.compose.spatial.OrbiterEdge
import androidx.xr.compose.subspace.layout.SpatialRoundedCornerShape

... 

Orbiter(
    position = OrbiterEdge.Top,
    alignment = Alignment.End,
    offset = EdgeOffset.inner(16.dp),
    shape = SpatialRoundedCornerShape(
        CornerSize(percent = 100)
    )
) {
    ToggleSpaceModeButton()
}

アプリを実行します。ホームスペース モードで実行すると、何も変更されていないことがわかります。ただし、切り替えボタンをクリックしてアプリがフルスペース モードになると、ボタンはアプリバーの上部ではなく、プライマリ空間パネルの右上端に移動します。

オービターは、UI ツリー内の最も近い親空間エンティティであるプライマリ空間パネルに固定されます。プライマリ空間パネルに対するオービタの正確な位置は、positionalignmentoffset パラメータによって決まります。これらのパラメータを変更して、サポートされている動作の範囲を確認してみてください。

ホームスペース モード

フルスペース モード

5. XR のコンセプトを学ぶ: 空間環境

Orbiter を使用して UI 要素の 3D 空間内の位置をカスタマイズすると、XR デバイスでのユーザー エクスペリエンスを向上させることができます。アプリの使用時にユーザーが置かれる空間環境をカスタマイズすることで、エクスペリエンスをさらに向上させることができます。

空間環境では、奥行き、テクスチャ、3D ジオメトリ アセットを組み込んで、視覚的に没入感のある豊かなエクスペリエンスを実現できます。これは、球状のスカイボックス画像(EXR 形式)を使用して遠くのパノラマの背景を作成するか、ジオメトリ アセット(glTF 形式)を使用して、スカイボックスにブレンドできる前景と中景の要素を作成することで実現できます。たとえば、動画ストリーミング アプリでは、投影スクリーンと車のあるドライブイン映画館の glTF を使用して夜間スカイボックスを使用できます。ユーザーの空間環境を設定するためのアセットを作成する場合は、適度なファイルサイズを維持しながら、アセットで高品質の解像度を実現してください。詳細については、環境アセットを最適化するをご覧ください。

また、空間環境の不透明度を制御することもできます。これにより、現実世界の動画ストリーミングをバーチャル環境に透過的に重ね合わせることができるため、ユーザーは自分の位置を把握しやすくなります。

岩場のある空間環境に立つ人物。中央に大きな UI パネルがあります。

次のステップでは、アプリにジオメトリ アセットを追加し、ユーザーが環境を選択できるようにメニューを作成します。

空間環境の設計と実装の詳細については、空間環境アプリに空間環境を追加するをご覧ください。

6. ユーザーが空間環境を変更できるようにする

アプリが空間環境を制御する仕組み

始める前に、アプリが空間環境を制御する仕組みを理解しておきましょう。

パネル内のコンテンツとは異なり、アプリは環境を直接制御しません。代わりに、SceneCore セッションを操作して、システムで使用する環境の設定を指定できます。この設定は SpatialEnvironmentPreference で表されます。これは、スカイボックス EXR 画像やジオメトリ glTF で構成されます。アプリが設定を提供する際の動作は、設定時にアプリが備えている機能によって異なります。アプリに環境を変更する機能がある場合は、システムがすぐにその機能を使用します。機能がない場合、アプリがその機能を取得したときに設定が適用されます。

たとえば、アプリは通常、ホームスペース モードで実行中に環境を変更する機能を持っていませんが、フルスペース モードで実行している場合は通常、環境を変更できます。そのため、ホームスペース モードでユーザーが環境設定を設定できるようにした場合、通常、その設定はアプリがフルスペース モードで実行されるまで有効になりません。

XR SceneCore ライブラリへの依存関係を追加する

空間環境の変更を開始するには、XR SceneCore ライブラリの依存関係を追加します。このライブラリは、環境アセットの読み込みと環境設定の設定に使用します。アセットを読み込む API の中には ListenableFuture データ型を使用するものがあるため、kotlinx-coroutines-guava アーティファクトにも依存関係を追加する必要があります。

libs.version.toml

[versions]
...
xrSceneCore = "1.0.0-alpha04"
kotlinxCoroutinesGuava = "1.10.2"

[libraries]
...
androidx-xr-scenecore = { group = "androidx.xr.scenecore", name = "scenecore", version.ref = "xrSceneCore"}
jetbrains-kotlinx-coroutines-guava = {group = "org.jetbrains.kotlinx", name="kotlinx-coroutines-guava", version.ref = "kotlinxCoroutinesGuava"}

app/build.gradle.kts

dependencies {
    ...
    implementation(libs.androidx.xr.scenecore)
    implementation(libs.jetbrains.kotlinx.coroutines.guava)
    ...
}

プロジェクトに環境アセットを追加する

固有の環境設定を指定するには、スカイボックスやジオメトリ アセットが必要です。この Codelab では、green_hills_ktx2_mipmap.glb ジオメトリ アセットのみを使用します。このアセットは、解答コードを含む part2 フォルダまたは GitHub にあります。

  1. Android Studio の [Project] ウィンドウでアプリ モジュールを右クリックします。次に、[新規] 新規 > [フォルダ] > [アセット フォルダ] を選択し、[完了] をクリックしてフォルダを作成します。
  2. 作成した app/src/main/assets フォルダに GLB ファイルを追加します。

環境オプションをモデル化する

UI コードとシステム API の間のやり取りを簡素化するには、Kotlin データクラスを作成して、各環境オプションをモデル化します。

  1. プロジェクト ウィンドウで com.example.android.xrfundamentals パッケージを右クリックし、[新規] > [パッケージ] を選択します。パッケージ名として「com.example.android.xrfundamentals.environment」と入力します。
  2. パッケージを右クリックして、[New] > [Kotlin Class/File] を選択します。名前として「EnvironmentOption」と入力し、[データクラス] タイプをクリックします。
  3. 作成したファイルに次のコードを追加します。

EnvironmentOption.kt

data class EnvironmentOption(val name: String, val skyboxPath: String?, val geometryPath: String?)

val DEFAULT_ENVIRONMENT = EnvironmentOption("Default", null, null)

val ENVIRONMENT_OPTIONS = listOf(
    DEFAULT_ENVIRONMENT,
    EnvironmentOption("Green Hills", null, "green_hills_ktx2_mipmap.glb")
)

読み込みアセットを作成して SpatialEnvironmentPreference を返すヘルパーを追加

次に、データクラスにヘルパー メソッドを追加して、EnvironmentOption を対応する SpatialEnvrionmentPreference に簡単に変換できるようにします。

EnvironmentOption.kt

import androidx.xr.runtime.Session
import androidx.xr.scenecore.ExrImage
import androidx.xr.scenecore.GltfModel
import androidx.xr.scenecore.SpatialEnvironment
import kotlinx.coroutines.guava.await

...

data class EnvironmentOption(val name: String, val skyboxPath: String?, val geometryPath: String?) {
    suspend fun toSpatialEnvironmentPreference(session: Session): SpatialEnvironmentPreference? {
        if (skyboxPath == null && geometryPath == null) {
            return null
        } else {
            val skybox = skyboxPath?.let {
                ExrImage.create(session, it).await()
            }

            val geometry = geometryPath?.let {
                GltfModel.create(session, it).await()
            }

            return SpatialEnvironmentPreference(skybox, geometry)
        }
    }
}

注意すべき点がいくつかあります。

  • スカイボックスとジオメトリの両方が null の場合、デフォルトのシステム環境設定を使用する必要があることを示す null が返されます。詳しくは、setSpatialEnvironmentPreference をご覧ください。
  • skybox リソースと geometry リソースは非同期的に作成されます。これらのアセットは非常に大きく、メモリに読み込むのに時間がかかることがあるためです。本番環境のアプリでは、環境を頻繁に切り替える場合は、これらのアセットをメモリにキャッシュ保存することを検討してください。

環境選択 UI を実装する

環境選択 UI を実装するには、クリックされたときに環境オプションを順に表示する 2 つ目のオービターを追加します。

オービターを追加する

  1. プロジェクト ウィンドウで app モジュールを右クリックし、[New] > [Vector Asset] を選択します。[クリップアート] フィールドをクリックし、landscape アセット([塗りつぶし] アイコン ファミリーから)を検索して選択し、[OK]、[次へ] の順にクリックしてアセットを作成します。
  2. com.example.android.xrfundamentals.ui.component パッケージを右クリックして、[New] > [Kotlin Class/File] を選択します。名前として「EnvironmentSelectionOrbiter」と入力し、[ファイル] タイプをクリックします。
  3. 作成したファイル内に、次のように EnvironmentSelectionOrbiter コンポーザブルの実装を追加します。

EnvironmentSelectionOrbiter.kt

import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.xr.compose.spatial.EdgeOffset
import androidx.xr.compose.spatial.Orbiter
import androidx.xr.compose.spatial.OrbiterEdge
import androidx.xr.compose.subspace.layout.SpatialRoundedCornerShape
import com.example.android.xrfundamentals.R

@Composable
fun EnvironmentSelectionOrbiter(
    modifier: Modifier = Modifier,
    onClick: () -> Unit = {},
) {
    Orbiter(
        position = OrbiterEdge.Top,
        alignment = Alignment.Start,
        offset = EdgeOffset.inner(16.dp),
        shape = SpatialRoundedCornerShape(
            CornerSize(100)
        )
    ) {
        FilledTonalIconButton(
            modifier = modifier,
            onClick = onClick,
        ) {
            Icon(painterResource(R.drawable.baseline_landscape_24), "Show environment selection dialog")
        }
    }
}
  1. 最後に、プライマリ空間パネル内に EnvironmentSelectionOrbiter を追加します。

XRFundamentalsApp.kt

import androidx.xr.compose.platform.LocalSpatialCapabilities
import com.example.android.xrfundamentals.ui.component.EnvironmentSelectionOrbiter

...

SpatialPanel(...) {

    // Only show the environment selection orbiter if the app is actually able to
    // change the environment
    if (LocalSpatialCapabilities.current.isAppEnvironmentEnabled) {
        EnvironmentSelectionOrbiter(
            onClick = { TODO() }
        )
    }
    ...
}

オービターをクリックしたときに環境を変更する

すべてを機能させるには、最後の手順として、EnvironmentSelectionOrbiter クリック ハンドラで setSpatialEnvironmentPreference を呼び出す必要があります。

  1. 現在の環境オプションを追跡する変数を設定します(Subspace の外部に設定して、ホームスペース モードとフルスペース モードを切り替えたときに状態が維持されるようにします)。また、現在の XR セッションの変数と、toSpatialEnvironmentPreference ヘルパーを呼び出すコルーチン スコープを作成します。

XRFundamentalsApp.kt

import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.xr.compose.platform.LocalSession

... 

var currentEnvironmentOptionIndex by remember { mutableStateOf(0) }

Subspace {
    val session = checkNotNull(LocalSession.current)
    val scope = rememberCoroutineScope()
    ...
}
  1. onClick コールバックを実装して、環境オプションを順に表示します。

XRFundamentalsApp.kt

EnvironmentSelectionOrbiter(
    onClick = {
        scope.launch {
            currentEnvironmentOptionIndex =
                (currentEnvironmentOptionIndex + 1) % ENVIRONMENT_OPTIONS.size
            session.scene.spatialEnvironment.setSpatialEnvironmentPreference(
                ENVIRONMENT_OPTIONS[currentEnvironmentOptionIndex].toSpatialEnvironmentPreference(
                    session
                )
            )
        }
    }
)

アプリをもう一度実行して、Green Hills 環境とデフォルト環境を切り替えられることを確認します。

b0e9571ef5f5597b.gif

7. 完了

引き続き XR の活用方法を学ぶには、次のリソースや演習をご覧ください。

参考資料

課題

  • 追加の環境アセットを検索または作成し、オプションとして追加してみましょう。
  • 環境コントローラと UI を変更して、ユーザーが setPassthroughOpacityPreference API を使用してパススルー設定を設定できるようにしてみましょう。パススルーの制御は、環境アセットの変更とは異なる機能によって制限されます。

リファレンス ドキュメント