【Unity】AndroidでNativeのGUIやら何やら追加したい - Androidその2 Advent Calendar 2016
はじめに
この記事はAndroidその2 Advent Calendar 2016の16日目の記事です。
UnityでAndroid用のアプリを作る場合に、Unity側で用意されている機能だけでは足りないなどの理由で、ネイティブのプラグインを作成したい場合があります。
今回はこの辺りの記事で書いたようなClassのファイルだけでなく、Layoutや画像などを追加する方法を調べてみました。
jarに画像を追加する
この記事と同じように、jarファイルをまず作成してプラグインとして利用する方法を試してみました。
例えば プロジェクトのルートフォルダ/app/src/main/assets に「images」というディレクトリを作成して、その中に入っているJPGファイルをjarファイルに追加したい場合は プロジェクトのルートフォルダ/app/src/build.gradle を下記のように作成します。
build.gradle
apply plugin: 'com.android.library' android { compileSdkVersion 25 buildToolsVersion "25.0.0" defaultConfig { minSdkVersion 19 targetSdkVersion 25 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main { resources{ srcDir "src/main/assets/images" resources.includes = ['**.jpg', '**.JPG', '**.png', '**.PNG'] } } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:25.0.1' compile files('libs/classes.jar') } task clearJar(type: Delete) { delete 'release/' + 'androidplugin01.jar' } task exportJar(type: Copy) { from('build/intermediates/bundles/release/') into('release/') include('classes.jar') rename('classes.jar', 'androidplugin01.jar') } exportJar.dependsOn(clearJar, build)
ビルド後に作成されるandroidplugin01.jarの容量が、画像分増えていれば成功で、
あとは「AssetManager assetManager = currentActivity.getResources().getAssets();」などでアクセスできます。
ただし、layout.xmlだのあれこれ追加したいと思い始めると、やっぱりこれは面倒な気がします…。
(これはこれで使い方によっては便利なのだとは思うのですが)
UnityでAndroidProjectをエクスポートする
Unity5から(多分)Androidのビルドとして、apkファイルを直接出力するだけでなく、AndroidProjectをエクスポートすることができるようになりました。
方法はBuild Settingsで2つの設定をするだけでできます。
- 「Build System」を「Gradle(New)」に変更する
- 「Export Project」にチェックを入れて「Export」ボタンを押す
するとフォルダ指定を求められるので、適当にフォルダを作成して、それを指定します。
なおUnityProjectと同じフォルダにすることはできません(UnityProject内のフォルダとかならOK)。
ネイティブの関数を呼ぶ
さて、出力されたAndroidProjectを開いてみると、 プロジェクトのルートフォルダ/src/main/java/jp/masanori/plugintest (jp/masanori/plugintestはパッケージ名)に、UnityPlayerActivity.javaがあります。
これはWindowsだと下記の場所にあるファイルと同じ内容です。
C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Source\com\unity3d\player UnityPlayerActivity.java
プラグインの関数を呼ぶときと同じく、このUnityPlayerActivityをUnity側から呼んでやればアクセスできそうです。
Unity
ということでまずはUnity側。
PluginCtrl.cs
using UnityEngine; namespace Assets.Scripts { public class PluginCtrl : MonoBehaviour { private readonly string PluginClassPath = "jp.masanori.plugintest.UnityPlayerActivity"; public void RequestFilePathList() { using (var androidJavaClass = new AndroidJavaClass(PluginClassPath)) { androidJavaClass.CallStatic("requestFilePathList"); } } } }
このような関数を、例えばボタンが押されたなどのタイミングで呼ぶことにします。
MainCtrl.cs
using UnityEngine; using UnityEngine.UI; namespace Assets.Scripts { public class MainCtrl : MonoBehaviour { public GameObject PluginCtrlObject; public Button ImageButton; private PluginCtrl pluginCtrl; private void Start () { pluginCtrl = PluginCtrlObject.GetComponent(); ImageButton.onClick.AddListener(OnImageButtonClicked); } private void OnImageButtonClicked() { pluginCtrl.RequestFilePathList(); } } }
AndroidManifest.xml
< ?xml version="1.0" encoding="utf-8"? > < manifest xmlns:android="http://schemas.android.com/apk/res/android" package="jp.masanori.plugintest" xmlns:tools="http://schemas.android.com/tools" android:installLocation="preferExternal" android:versionCode="1" android:versionName="1.0" > < permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/ > < permission android:name="android.permission.READ_EXTERNAL_STORAGE" / > < supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true"/ > < application android:theme="@style/UnityThemeSelector" android:icon="@drawable/app_icon" android:label="@string/app_name" android:debuggable="true" > < activity android:name=".UnityPlayerActivity" android:label="@string/app_name" > < intent-filter > < action android:name="android.intent.action.MAIN" / > < category android:name="android.intent.category.LAUNCHER" / > < /intent-filter> < meta-data android:name="unityplayer.UnityActivity" android:value="true" / > < /activity > < /application > < /manifest >
C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Apk にあるAndroidManifest.xmlをベースにしていますが、UnityProject側に入れる必要は無いかもしれません。
Android
AndroidProjectとして出力したあと、UnityPlayerActivity.javaを編集します。
UnityPlayerActivity.java
package jp.masanori.plugintest; import com.unity3d.player.UnityPlayer; import android.app.Activity; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.os.Bundle; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; public class UnityPlayerActivity extends Activity { protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code private static void requestFilePathList(){ // TODO: 画像ファイルへのアクセス. } // Setup activity layout @Override protected void onCreate (Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); getWindow().setFormat(PixelFormat.RGBX_8888); // <--- This makes xperia play happy mUnityPlayer = new UnityPlayer(this); setContentView(mUnityPlayer); mUnityPlayer.requestFocus(); } ~省略~
これでUnity側でボタンが押されたときに「requestFilePathList()」が呼ばれるようになるので、あとは通常のAndroidアプリと同じようにassetsフォルダの画像にアクセスすればOKです。
もし画像をUnity側から操作したい場合は、Filesフォルダなどに画像をコピーする必要があるかもしれません。
DataBindingを使う
エクスポート後のProjectは通常のAndroidアプリのProjectとほぼ同じです。
なので、DataBindingを使うこともできます。
まずはLayoutを作成します。
native_button_layout.xml
< ?xml version="1.0" encoding="utf-8"? > < layout xmlns:android="http://schemas.android.com/apk/res/android" > < RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" > < Button android:text="@string/native_button_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentStart="true" android:layout_marginStart="65dp" android:layout_marginTop="82dp" android:id="@+id/native_button" / > < /RelativeLayout > < /layout >
DataBindingを有効にします。
build.gradle
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.2' } } allprojects { repositories { flatDir { dirs 'libs' } } } apply plugin: 'com.android.application' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } android { compileSdkVersion 25 buildToolsVersion '25.0.0' defaultConfig { targetSdkVersion 25 } lintOptions { abortOnError false } signingConfigs { release { storeFile file('C:/Users/masanori/.android/debug.keystore') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } } buildTypes { debug { jniDebuggable true } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt' signingConfig signingConfigs.release } } dataBinding { enabled = true } }
UnityのViewに追加します。
~省略~ import jp.masanori.plugintest.databinding.NativeButtonLayoutBinding; public class UnityPlayerActivity extends Activity { protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code private static void requestFilePathList(){ // TODO: 画像ファイルへのアクセス. } // Setup activity layout @Override protected void onCreate (Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); getWindow().setFormat(PixelFormat.RGBX_8888); // <--- This makes xperia play happy // DataBindingを取得してmUnityPlayerに追加. NativeButtonLayoutBinding binding = DataBindingUtil.setContentView(this, R.layout.native_button_layout); mUnityPlayer.addView(binding.getRoot()); mUnityPlayer = new UnityPlayer(this); setContentView(mUnityPlayer); mUnityPlayer.requestFocus(); }
ポイントとしてはDataBindingはmUnityPlayerに追加する、ということでしょうか。
「setContentView(mUnityPlayer);」の後にDataBindingの処理を行うと、Unity側の画面が消えてネイティブ側の画面のみが表示され、悲しい気持ちになります。
また、下記はプラグイン作成時の注意ではありますが、今回の場合も気をつけた方が良いかもしれません。
おわりに
iOSのネイティブプラグインは以前からUnityのビルド後に追加する形であったため、Androidでも同じようにできる、というのは便利ですね。
ただ多くの場合jarファイルのように一つにファイルがまとまることがないため、複数プロジェクトで使いまわしする場合などは不便かも?
そもそもUnity側で解決できるならそちらでやったほうが…。
とまぁ微妙なところもありますが、適材適所、便利に問題に対応していくのが良いかと思います。