Android向けUnityアプリを拡張ビルドする方法


Unityで作成したAndroidアプリをAndroid向けに拡張した時にやったことメモ

はじめに

UnityからAndroidやiOS向けバイナリを無料で吐き出せるようになりました。
しかし、モバイルOSのネイティブ機能をフルで活かそうとすると幾つかそれぞれのプラットフォーム向けに拡張したくなる時があると思います。
その際に、それぞれのプラットフォーム向けにライブラリを作成しそのコードをJNI経由等で呼び出す方法があります。

sample.cs
// Androidの場合
AndroidJavaClass obj = new AndroidJavaClass("classへのフルパス");
obj.CallStatic("呼び出すメソッド名");

しかし、特にAndroid環境では、画面のライフサイクルに合わせた拡張がしたいという要望が高いので、何とかできないかと試してみました。

UnityPlayerNativeActivityとは

幾つか情報を調べると、Android環境下ではUnityPlayerNativeActivityと呼ばれるActivity(多分NativeActivityのサブクラス系)でUnityフレームワークが実行され更にその上でユーザーが作成したプログラムが動作するような作りになっているらしい。
Android 2.3以下だとUnityPlayerActivityがベースになるらしいが実機未確認

つまり、このベースとして実行されるUnityPlayerNativeActivityをうまくサブクラス化し、ライフサイクルに関係するメソッドをオーバーライドしてやれば好きなことができそうです。

実手順

1. Unityから基本となるバイナリの作成

まずは、Unityが吐き出すAndroidコードを捕まえないことにはどうにもならないので、UnityでAndroidバイナリを吐き出せるようにします。
また、この手順はWindows 8.1 、Unity 4.5.4環境下で試しています。

  1. File->Build Settings を開き、PlatformをAndroidに変更する

  2. Add Currentをタップして、初期起動させたい画面を選択する(ここは不明)

  3. Pleyer Settings をタップして開く
    !

  4. Android 環境を選択してることを確認し、下のOther Settingsを開く

  5. Bundle Identifierを変更する。これはAndroidの パッケージ名 と同じにする必要が有るので注意

  6. Bundle Version 及び Bundle Version Code を変更する。これはそれぞれ、 versionNameversionCode に相当する。

  7. Publishing Settingsを開いて証明書関係の設定をする ← 実際にはこの後の作業で再証明つけるからいらない気がする

  8. 設定が終わったら、Build Settingsウィンドウの Build ボタンを押してビルドする

  9. 失敗する場合は適度に直して作成する

2. 成果物の確認

上記の手順が終わり、無事APKファイルが作成されると、作業ディレクトリのルートにTempフォルダができているはず。

これは、Unity公式サンプルのShootingGameをビルドした直後のディレクトリ

このTempフォルダ内にStagingAreaというフォルダが存在し、この下に生成されたAndroid用コードが入っています。

StagingAreaの内部構造

3. 生成されたコードを拡張

2.でAndroid向けコードが生成されたので、このコードを元に拡張を行います。

幾つかネットを検索した限り、出回っている手順情報としてあったのは下の2つに大別されそうです。

  • eclipseを使用し、このStagingAreaをライブラリ化してメインのプロジェクトにインポート 参考:UnityでAndroidの機能を拡張する2つの手法とは (3/3)
  • この生成されたコードからeclipseでライブラリプロジェクトを作成し、jarファイルを生成し、それをインポート

eclipseでのインポート情報が多いのは、この出力されたディレクトリ構成がeclipseでのプロジェクトのフォルダ構成に添っているからだと思います。

Android Studioでのインポート方法

現在すでにAndroid開発はAndroid Studioに流れているので、可能であればASで動かせたほうが今後ハッピーです。

基本となるプロジェクトの生成
  1. Android Studioで New Projectを作成する。この時Package name:をUnityで設定したBundle Identifierと同じにする
  2. Minimum SDK もUnityのOther Settingsで設定したものと同じにする
  3. Blank Activityを選択し、Activityが1つ存在するプロジェクトを作成する
実行に必要なファイルのインポート
  1. app\src\mainディレクトリの下に assets ディレクトリを作成する
  2. 作成した assets ディレクトリ配下に、Unityで生成した StagingArea¥assets ディレクトリ以下を全てコピーする
  3. app\src\mainディレクトリの下に jniLibs ディレクトリを作成する
  4. 作成した jniLibs ディレクトリ配下に、Unityで生成した StagingArea¥libs ディレクトリ以下を全てコピーする
  5. app\src\main\resディレクトリの下にUnityで生成した StagingArea¥res ディレクトリ下の drawable ディレクトリ自体をコピーする
  6. app\libsディレクトリの下に、Unityをインストールしたディレクトリの Editor¥Data¥PlaybackEngines¥androidplayer¥release¥bin¥classes.jar をコピーする

    ここまで終わると、だいたいこんなディレクトリ構造ができていると思います

xmlファイルのマージ

  1. StagingArea¥res¥values¥strings.xml<string name="app_name">アプリ名</string>app\src\main\res\values\strings.xml にマージする
  2. app\src\main\AndroidManifest.xmlStagingArea\AndroidManifest.xml をマージします
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="test.test.test"
    android:installLocation="preferExternal"
    android:theme="@android:style/Theme.NoTitleBar"
    >

    <supports-screens
        android:anyDensity="true"
        android:largeScreens="true"
        android:normalScreens="true"
        android:smallScreens="true"
        android:xlargeScreens="true" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/app_icon"
        android:label="@string/app_name">
        <activity
            android:name="test.test.test.MyActivity"
            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
            android:label="@string/title_activity_my"
            android:launchMode="singleTask"
            android:screenOrientation="landscape">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="unityplayer.UnityActivity"
                android:value="true" />
            <meta-data
                android:name="unityplayer.ForwardNativeEventsToDalvik"
                android:value="false" />
        </activity>
    </application>
    <uses-feature android:glEsVersion="0x00020000" />
</manifest>

application->activityのandroid:nameが自分が作成したActivity名になっているかを注意してください。

Activityのサブクラス化

やっとやりたいことに追い付きました。
最初に自動生成されたActivityの中身を書き換えます。
注意点は、onCreateの中でsetContentViewを呼んではいけない点です。

MyActivity.java
package test.test.test;

import com.unity3d.player.UnityPlayerNativeActivity;

public class MyActivity extends UnityPlayerNativeActivity {
    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
    }
}

// あとはお好きに

ここで、com.unity3d.player.UnityPlayerNativeActivityが見つからなくて怒られる場合には、sync project with gradle filesボタンを押すと直ると思います。

4.実行

通常のAndroidアプリの実行方法で実行できる

最後に

本当であれば多分ライブラリモジュール化して取り込むのが正解な気もするんですが、起動Activityの関係とか考えると直取り込みのほうが個人レベルだと楽な気がします。
この後Unity側でアプリの変更を行っても、基本的には、

  1. Unityで更新後Androidビルド
  2. AS側の assets ディレクトリ以下を全削除
  3. Unityで生成した StagingArea¥assets ディレクトリ以下を assets ディレクトリ以下に全コピー

で済みます。

おまけ

Activityのサブクラスでゴニョゴニョできるってことはちょっと危ない橋も渡れるということで・・・

通常ビルドした時の実行画面

起動Activityをこんな感じにすると

MyActivity.java
package test.test.test;

import com.unity3d.player.UnityPlayerNativeActivity;

public class MyActivity extends UnityPlayerNativeActivity {
    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            View decor = this.getWindow().getDecorView();
            decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE);
        }
    }
}

// あとはお好きに

当然画面の座標軸系がずれるんでそこら辺は自分で要調整してになりますけど。