6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AndroidアプリにUnity as a Libraryでリッチな3Dを簡単に組み込む

Posted at

本記事は、 Android Advent Calendar 2021 17日目の記事です。
PureなAndroidというよりUnity組み込むという飛び道具的な話

Unity as a Libraryとは

image.png

拡張現実(AR)、3D/2D リアルタイムレンダリング、2D ミニゲームなど、Unity で開発した機能を、ネイティブモバイルアプリケーションに直接挿入。

Unity as a Library はゲームエンジンであるUnityで開発した機能を、モジュールとしてAndroidアプリケーションへ組み込むことが出来る仕組みである。 従来の3Dグラフィックのように、自分で1からOpenGLESなどのグラフィックライブラリを使って描画することは無い。そのため、簡単に3D表現をアプリに組み込むことができる。

なぜ組み込みができるかというと、UnityからAndroid向けに吐き出されるアプリケーションが次のような構成になっているからです。

もともと Unity は昔から、エンジン部分をライブラリとしてアプリケーション本体から切り離すような構成をしていました。具体的に Android では、エンジンのエントリーは libmain.so 、ビューとして SurfaceView (本体はVulkanあるいはGLSL)、ラッパーとしての UnityPlayer(extends FrameLayout)があり、これを使うためのアプリケーションとして MainActivity がある、という構成です。

from https://tech.mirrativ.stream/entry/2020/10/20/100000
【Unity】MirrativのEmbedding Unityを更新した話: 実践 Unity as a Library

Android側から見ると簡単に3D描画できるSurfaceView(GLSurcefaceView)として開発ができる。

Unityで作ったシーンをAndroidアプリに組み込む

以下の環境で作業をしていきます。

  • MacBook Pro (13-inch, M1, 2020) MacOS Big Sur 11.5.1(20G80)
  • Unity 2021.2.0b9
  • Android Studio Arctic Fox 2020.3.1 Patch 2

Rosetta 2を使わないarm64対応のラインナップです。

Unityのシーンを用意する

Sampleアプリケーションとして、Cubeを原点(position X:0 Y:0 Z:0)に置いたシーンを用意しました。加えて、Unityを象徴する青と灰色のSkyBoxは残しておきます。
image.png

UnityプロジェクトのExport

File > Build Settings を開き、PlatformAndroid を選択されていることを確認します(UnityマークがAndroidの欄にない場合は、 Switch Platform を押す)

image.png

IL2CPPの設定

PlayerSettings を押して、 Setting for Android > Other Settings を開き Configuration > Scripting BackendMono から IL2CPP に変えておきます。

image.png

変えると Configuration > Target ArchitectureARM64 にチェックできるので、チェックを入れます。

image.png

Build Settingsにもどり、Export Projectにチェックを入れてExportを押すと、UnityからGradleプロジェクトが書き出されます。(ちょっと時間かかる)
image.png

UnityEditorでやる作業はここまでです。

unityLibraryをインポート

サンプルアプリのプロジェクトとして UaaLSample というプロジェクトを用意した。プロジェクト直下に先程 ExportされたunityLibraryフォルダをコピーする

image.png

Android Studioを開き以下の修正を加える。

:unityLibrary という名前のモジュールをAndroid Studioが認識できるにように、settings.gradle に追加する。

settings.gradle
include ':app', ':unityLibrary'

:appで利用できるようにdependenciesに追加。Unity関連のクラスファイルを使えるように jar ファイルを追加する。

build.gradle(app)
dependencies {
    implementation project(':unityLibrary')
    implementation fileTree(dir: project(':unityLibrary').getProjectDir().toString() + ('\\libs'), include: ['*.jar'])
    ...
}

:unityLibrary 側の AndroidManifest.xml から以下の項目を削除する。昔はホーム画面にUnityだけ起動するアイコンが追加されたが、今は違うみたいでビルドが通らない。

AndroidManifest.xml
<intent-filter>
  <action android:name="android.intent.action.MAIN" />
  <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

NDKを使ってUnity側のソースもAndroid Studio側でビルドされるため、NDKの場所を明記する。

loacl.properties
ndk.dir=/Users/XXXXXXXXXXXX/Library/Android/android-ndk-r21d

UnityPlayerをViewに挿入する

上半分にUnityで描画(SurfaceViewを子に持つFrameView)されるViewを置いた画面を用意した。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    tools:ignore="HardcodedText">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/unity"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/unity">

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Button"/>

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Button"/>

        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Button"/>
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

基本的には、UnityPlayerをインスタンス化してaddViewするだけである。
しかし、それだけではUnity側が動かないみたいなので、requestFocusonWindowFocusChangedonResumeは呼び出す必要がある。

MainActivity.kt

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import com.unity3d.player.UnityPlayer

class MainActivity : AppCompatActivity() {

    var unityPlayer: UnityPlayer? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        unityPlayer = UnityPlayer(this)
        window.clearFlags(1024)

        findViewById<ConstraintLayout>(R.id.unity)?.addView(
            unityPlayer, ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
        )

        unityPlayer?.requestFocus()
    }

    // Notify Unity of the focus change.
    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        unityPlayer?.windowFocusChanged(hasFocus)
    }

    override fun onResume() {
        super.onResume()
        unityPlayer?.resume()
    }
}

アプリが起動し、MainActivityのViewが表示されたところで、Unityが描画を開始し、スプラッシュ画面を表示した後に、先程作成したシーンが表示された。
output.gif

最後に

手軽に3Dグラフィックをアプリに実装するには、OpenGLESを素で書くより断然簡単!

※ ただ、プロジェクトがかさばったり、ビルドプロセスが少し増えたり、端末のリソースをUnityのMainThreadに割いたりするので、多少のデメリットもある。
※ Android アドベントカレンダーなのに単語としてUnityの出現頻度が多いなぁという気持ちになった。
※ サンプルのアプリの3Dが全然リッチじゃないので更新したい。

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?