ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-05-03中級

io.github.sceneview を使った Antigravity AR 開発入門——AnchorNode のエラーを乗り越えて

SceneView(io.github.sceneview)を使ってAndroidのAR機能を実装する方法を解説。AnchorNodeのエラー対処、ARCoreのインストール処理、平面検出の可視化、Antigravity AIを組み合わせたシーン生成まで、実機で詰まった箇所を中心に紹介します。

SceneViewARCore2Android27AR開発2AnchorNodeAntigravity338app-dev47

Android で AR 機能を実装しようとして io.github.sceneview を選んだ方の多くが、最初に AnchorNode 関連のエラーで詰まります。検索しても日本語の情報が少なく、Stack Overflow の回答も古いものが多い——そんな状況でこの記事にたどり着いた方の参考になれば嬉しいです。

SceneView は ARCore をより使いやすく抽象化したライブラリです。Sceneform が2021年に非推奨になって以降、Android AR 開発のデファクト的な選択肢になっています。私自身、個人開発で AR を試した際に最初の数日をライフサイクルとセッション管理のエラーに費やしたので、同じ落とし穴を避けられるよう、つまずきやすい順に並べてあります。

SceneView のセットアップ

まず、最新の SceneView を追加します。

// build.gradle.kts(Module)
dependencies {
    implementation("io.github.sceneview:arsceneview:2.3.0")
}

AndroidManifest.xml に ARCore 関連の記述を追加します。

<manifest>
    <!-- ARCore 必須 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera.ar" android:required="true" />
    
    <application>
        <meta-data
            android:name="com.google.ar.core"
            android:value="required" />
    </application>
</manifest>

requiredoptional にすると、ARCore 非対応端末にも配信できますが、その場合は実行時の可用性チェック(後述)が必須になります。AR が体験の中心なら required、付加機能なら optional という使い分けが実務的です。

レイアウトと ARSceneView

Compose を使っている場合は AndroidView でラップします。

// Compose での使い方
@Composable
fun ARScreen() {
    var arSceneView: ARSceneView? = remember { null }
    
    AndroidView(
        factory = { context ->
            ARSceneView(context).also { sceneView ->
                arSceneView = sceneView
            }
        },
        modifier = Modifier.fillMaxSize()
    )
}

従来の XML レイアウトの場合は直接配置できます。

<io.github.sceneview.ar.ARSceneView
    android:id="@+id/arSceneView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

AnchorNode エラーの対処

SceneView を使い始めて最初にぶつかる問題が、AnchorNode のライフサイクルエラーです。よく見られるのは以下のようなクラッシュです。

java.lang.IllegalStateException: AnchorNode must be attached to an ARSceneView

原因は、ARSceneView が完全に初期化される前に AnchorNode を追加しようとすること、またはセッションが有効でない状態で操作しようとすることです。

修正前(よくある間違い):

// ❌ onViewCreated で即座に AnchorNode を作ろうとしている
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val anchorNode = AnchorNode(arSceneView.engine, anchor) // クラッシュ
    arSceneView.addChild(anchorNode)
}

修正後:

// ✅ onSessionResumed コールバックを使う
arSceneView.onSessionResumed = { session ->
    // セッションが有効になってから操作する
    setupARContent()
}
 
private fun setupARContent() {
    arSceneView.onTapAr = { hitResult, _ ->
        val anchor = hitResult.createAnchor()
        val anchorNode = AnchorNode(arSceneView.engine, anchor)
        anchorNode.addChild(
            ModelNode(
                modelInstance = modelLoader.createModelInstance(modelFile),
                autoAnimate = true
            )
        )
        arSceneView.addChild(anchorNode)
    }
}

onSessionResumed コールバックで ARCore セッションが確立してから処理を開始するのが基本です。タップ時の hitResult から createAnchor() を呼ぶ流れにしておくと、「まだ平面が検出されていない座標にアンカーを作ろうとしてクラッシュする」という別系統のエラーも同時に防げます。

ARCore のインストールを確実に促す

見落としやすいのが、「ARCore 対応端末なのにアプリが落ちる」ケースです。対応していても、ARCore(Google Play Services for AR)が端末に入っていない、あるいは古いことがあります。requestInstall でインストールを促す処理を入れておくと、この種のクラッシュを丸ごと防げます。

// ARCore の導入状況を確認し、未導入ならインストールを促す
private var installRequested = false
 
private fun ensureArCore(): Boolean {
    return try {
        when (ArCoreApk.getInstance().requestInstall(requireActivity(), !installRequested)) {
            ArCoreApk.InstallStatus.INSTALL_REQUESTED -> {
                // インストール画面に遷移したので、次回 onResume を待つ
                installRequested = true
                false
            }
            ArCoreApk.InstallStatus.INSTALLED -> true
        }
    } catch (e: UnavailableUserDeclinedInstallationException) {
        // ユーザーがインストールを拒否した
        showFallbackUI()
        false
    }
}

ポイントは installRequested フラグを持っておくことです。requestInstall は一度インストール画面へ遷移すると、戻ってきた直後の onResume で再び呼ばれます。フラグなしで毎回 true(=ユーザーに必ず確認)を渡すと、無限にダイアログが出る挙動になってしまいます。

3Dモデルの読み込み

SceneView では .glb 形式のモデルをシンプルに扱えます。

// assets/ フォルダに model.glb を配置して読み込む
class ARFragment : Fragment() {
    
    private lateinit var modelLoader: ModelLoader
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        val sceneView = view.findViewById<ARSceneView>(R.id.arSceneView)
        modelLoader = ModelLoader(sceneView.engine, requireContext())
        
        sceneView.onSessionResumed = { _ ->
            setupTapToPlace(sceneView)
        }
    }
    
    private fun setupTapToPlace(sceneView: ARSceneView) {
        sceneView.onTapAr = { hitResult, _ ->
            lifecycleScope.launch {
                val modelInstance = modelLoader.loadModelInstance("models/robot.glb")
                    ?: return@launch
                
                val anchor = hitResult.createAnchor()
                val anchorNode = AnchorNode(sceneView.engine, anchor).apply {
                    addChild(ModelNode(
                        modelInstance = modelInstance,
                        scaleToUnits = 0.5f  // メートル単位でスケール指定
                    ))
                }
                sceneView.addChild(anchorNode)
            }
        }
    }
}

.glb のポリゴン数が多いと、配置した瞬間にフレームレートが落ちます。AR は実機のカメラ処理と描画を同時に走らせるため、デスクトップでの 3D 表示より余裕がありません。配置用モデルは数万ポリゴン以内に抑え、テクスチャは 1〜2K に圧縮しておくと安定します。

平面検出を可視化してユーザーを誘導する

AR で意外と多いのが「どこをタップすればモデルが置けるのか分からない」という体験上の問題です。SceneView は検出した平面を点群で可視化する機能を持っているので、配置できる場所をユーザーに示せます。

// 検出した平面をドットで可視化する
arSceneView.planeRenderer.isEnabled = true
 
arSceneView.onTapAr = { hitResult, _ ->
    placeModel(hitResult)
    // 配置が済んだら視覚的なノイズを減らすため非表示にする
    arSceneView.planeRenderer.isEnabled = false
}

最初は平面ガイドを表示し、最初のモデルを置いたら消す、という切り替えだけで体験の分かりやすさが大きく変わります。実機で試すと、ガイドがないと多くのユーザーがカメラを動かさずに固まってしまうのが分かります。

Antigravity AI との組み合わせ

Antigravity AI を使って、AR シーンに配置するオブジェクトや配置位置の提案を生成するアプローチも試してみました。

// Antigravity AI でシーン提案を生成して AR に反映する例
suspend fun generateARSceneFromPrompt(prompt: String): ARSceneConfig {
    val client = AntigravityClient(apiKey = "YOUR_API_KEY")
    
    val response = client.generateScene(
        prompt = prompt,
        outputFormat = "structured_json",
        parameters = mapOf(
            "include_position" to true,
            "include_scale" to true,
            "coordinate_system" to "ar_world"
        )
    )
    
    return ARSceneConfig.fromJson(response.content)
}
 
// 生成された設定を AR シーンに適用
fun applySceneConfig(config: ARSceneConfig, sceneView: ARSceneView) {
    config.objects.forEach { objectConfig ->
        // 各オブジェクトを指定位置に配置
        val position = Float3(
            objectConfig.position.x,
            objectConfig.position.y,
            objectConfig.position.z
        )
        // ...
    }
}

AI が提案したレイアウトをそのまま AR 空間に展開できるのは、インタラクティブな体験設計の可能性を大きく広げてくれます。ただし AI が返す座標は必ずしも検出済みの平面上に乗るとは限らないので、各座標をそのまま使う前にヒットテストで近傍の平面へスナップさせる一手間を入れると、宙に浮くオブジェクトを避けられます。

アンカーの後始末とドリフト対策

長く使われる AR 画面では、配置したアンカーが積み上がっていきます。ARCore はアンカーごとに環境を追跡し続けるため、不要なアンカーを残すとトラッキングが重くなり、位置がじわじわずれる「ドリフト」も目立ちやすくなります。使い終わったアンカーは明示的に解放するのが安全です。

// 配置済みモデルをすべて片付ける
fun clearPlacedModels(sceneView: ARSceneView) {
    sceneView.children
        .filterIsInstance<AnchorNode>()
        .forEach { node ->
            node.anchor.detach()      // ARCore セッションからアンカーを解放
            sceneView.removeChild(node)
        }
}

リセットボタンや画面遷移のタイミングで detach() を呼ぶ運用にしておくと、長時間の利用でも安定します。私が試した範囲では、アンカーを 10 個以上残したまま動かし続けると、古いオブジェクトの位置が実空間から少しずつ離れていく現象がはっきり出ました。

ライフサイクルの管理

SceneView はライフサイクルに敏感です。Fragment / Activity の onResume / onPause に対応した処理が必要です。

override fun onResume() {
    super.onResume()
    arSceneView.onResume()
}
 
override fun onPause() {
    super.onPause()
    arSceneView.onPause()
}
 
override fun onDestroy() {
    super.onDestroy()
    arSceneView.destroy()  // エンジンリソースの解放
}

destroy() の呼び忘れはメモリリークの原因になるので注意が必要です。SceneView が内部で使う Filament エンジンはネイティブリソースを大量に保持するため、GC だけでは解放されません。

ARCore 非対応デバイスへの対応

ARCore が使えないデバイスでのフォールバック処理も忘れずに実装します。

if (!ArCoreApk.getInstance().checkAvailability(requireContext()).isSupported) {
    // AR非対応の場合は代替UIを表示
    showFallbackUI()
    return
}

SceneView を使うことで ARCore の低レベルな API を直接扱う部分は大きく減りますが、デバイス対応確認はアプリレイヤーで必ず行う必要があります。

次のステップ

SceneView の基本が理解できたら、平面検出の精度を上げるための環境光推定(Light Estimation)や、複数のアンカーを使った空間マッピングに挑戦してみてください。AR 開発は実機で動かしながら確認するのが一番の近道で、エミュレーターでは再現できない挙動が多くあります。まずは手元の対応端末を 1 台用意し、タップでモデルを 1 つ置く——ここまでを最短で動かすところから始めるのがおすすめです。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

アプリ開発2026-04-02
AntigravityでARアプリを作る入門ガイド 2026 — ARKit・ARCoreで拡張現実を体験しよう
AntigravityのAIエージェントを活用してARKit(iOS)・ARCore(Android)の拡張現実アプリを効率よく開発する入門ガイド。2026年のAR市場動向からステップバイステップの実装手順まで丁寧に解説します。
アプリ開発2026-06-26
生成も配信も任せても、署名鍵だけは渡さない — AI 主導の配布パイプラインで鍵の管理と引き継ぎを設計する
AI Studio や Antigravity が生成から内部テスト配信までを肩代わりする時代でも、アプリの署名鍵だけは別格です。鍵を失えば、そのアプリは二度と更新できません。個人開発で複数アプリを長く運用してきた立場から、アップロード鍵とアプリ署名鍵の分離、鍵の保管、そして万一の引き継ぎまでを設計としてまとめました。
アプリ開発2026-06-26
埋め込みエミュレータでは緑なのに、実機で初めて崩れる — AI 生成 Compose アプリに実機差を塞ぐ検証ゲートを置く
AI Studio はテキストから Kotlin/Compose アプリを生成し、埋め込みエミュレータで動かし、USB で実機へ送るところまで一画面でつなぎました。けれどエミュレータで通った画面が、手元の実機で初めて崩れることがあります。個人開発で複数アプリを抱える立場から、生成物に実機差を塞ぐ検証ゲートを据える設計をまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →