vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【Unity5】【Android】【Mac】プラグインで端末のディレクトリにアクセスする 2

前回の続き。

Githubにあげました。
masanori840816/SetMaterialsFromGallery · GitHub

※2015/05/06編集
iOS用のプラグインを追加したためプロジェクト名を変更しました。

Unityプロジェクトの作成

  1. Unityでプロジェクトを作成して、Build SettingsでPlatformをAndroidに変更しておきます。
  2. Player Settings > Other Settings > Identification > PlayerSettings.bundleIdentifierを「jp.plugincontroller」に変更します。
    今回の場合にネイティブプラグインと揃える必要があるかはわかりませんが、とりあえず揃えています。
  3. Minimum API LevelはAPI Level 15にしました。今後の実装内容によっては変更するかもしれません。

プラグインファイルを設置する

Assets > Plugins > Androidに前回作成したJarファイルを置きます。

プラグインを呼びだすスクリプトを作成する

CtrlPlugins.cs

using UnityEngine;
using System.Collections;

public class CtrlPlugins : MonoBehaviour
{
    readonly string PLUGIN_CLASS_PATH = "jp.plugincontroller.PluginConnector";
    public string GetText()
    {
        string strGotText = "";
        // DCIMディレクトリのパスを取得.
        using(AndroidJavaClass clsPlugin = new AndroidJavaClass(PLUGIN_CLASS_PATH))
        {
            strGotText = clsPlugin.CallStatic("GetDcimPath");
        }
        return strGotText;
    }
    public void ShowToast()
    {
        // トーストを表示する.
        using(AndroidJavaClass clsPlugin = new AndroidJavaClass(PLUGIN_CLASS_PATH))
        {
            clsPlugin.CallStatic("ShowToast");
        }
    }
}
  • AndroidJavaClassを使ってプラグイン側のクラスを使用できます(クラスのフルパスを渡します)。
  • 呼び出される側のメソッドをstaticにしており、「CallStatic(呼び出したいメソッド名)」でメソッドを呼ぶことができます。
  • 戻り値がある場合は「CallStatic」のように指定し、ない場合は<>を省略します。
  • プラグイン呼び出しの処理は結構重いようで、using(){}を使って必要なとき以外は開放されるようにしていますが、断続的に呼ぶ場合などは生成・開放のタイミングを変更したほうが良いかと思います。

戻るボタンを押してアプリを終了したり

  • Androidの戻るボタンが押されたことを知るには「Input.GetKey(KeyCode.Escape)」を使用します。
    が、これだとWindowsMacなどでescキーを押した時と区別がつかないため、プラットフォームも確認しておきます。

  • 単純に「Input.GetKey(KeyCode.Escape)」のtrue/falseだけを見てしまうと、一度に複数回処理が実行されてしまうため、もう一つフラグを追加しています。

CtrlMain.cs

using UnityEngine;
using System.Collections;
using System.IO;

public class CtrlMain : MonoBehaviour
{
    public GameObject _gmoPlugins;
    CtrlPlugins _ctrPlugins;

    string _strDcimPath = "";
    bool _isEscKeyPushed = false;
    bool _isSecondCheckStarted = false;
    float _fltDeltaTime = 0f;
    string[] _strFileNames;

    void Start ()
    {
        _ctrPlugins = _gmoPlugins.GetComponent();
        // DCIMディレクトリのパスを取得する.
        _strDcimPath = _ctrPlugins.GetText();
        // DCIMディレクトリ内にあるファイルを取得.
        _strFileNames = Directory.GetDirectories(_strDcimPath);
    }
    void Update()
    {
        if (Application.platform == RuntimePlatform.Android)
        {
            if(_isEscKeyPushed)
            {
                if(_isSecondCheckStarted)
                {
                    if(Input.GetKey(KeyCode.Escape))
                    {
                        // 戻るボタンを一定時間以内に二回押下したらアプリケーション終了.
                  Application.Quit();
                    }
                }
                else
                {
                    if(! Input.GetKey(KeyCode.Escape))
                    {
                        // 最初の戻るボタン押下後、戻るボタンから一度指を離したら2回目のチェック開始.
                        _isSecondCheckStarted = true;
                    }
                }
                // 前フレームからの経過時間を加算する.
                _fltDeltaTime += Time.deltaTime;
                // トーストを表示して一定時間が経過したらフラグをリセット.
                if(_fltDeltaTime >= 3f)
                {
                    _fltDeltaTime = 0f;
                    _isEscKeyPushed = false;
                }
            }
            else
            {
                if(Input.GetKey(KeyCode.Escape))
                {
                    // トーストを表示.
                    _ctrPlugins.ShowToast();
                    _isEscKeyPushed = true;
                    _isSecondCheckStarted = false;
                }
            }
        }
    }
    void OnGUI()
    {
        GUI.skin.label.fontSize = 40;
        GUI.Label(new Rect(20f, 20f, 800f, 100f), _strDcimPath);
        GUI.Label(new Rect(20f, 130f, 800f, 100f), "Dir " + Directory.Exists(_strDcimPath + "/Camera"));
        GUI.Label(new Rect(20f, 250f, 800f, 100f), "File " + File.Exists(_strDcimPath + "/Camera/IMG_20150327_174416.jpg"));
        float fltY = 360f;
        for(int i = _strFileNames.Length - 1; i >= 0; i--)
        {
            GUI.Label(new Rect(20f, (fltY + (i * 120)), 800f, 100f), _strFileNames[i]);
        }
    }
}

ファイルアクセスの権限付与

さて、上記のコードを最初Android4.1の端末で動作させて、問題なく動くことを確認していました。

ふと、5.1の入ったNexus7で動作させたところ、DCIMディレクトリへのアクセスは問題がないのに、その下にあるCameraなどのディレクトリやファイルにアクセスできず、File.Existsの結果がFalseになりました。

実は4.4からファイルのアクセス権が変更され、DCIMディレクトリなどにアクセスするのはデフォルトで可能なものの、それ以外のディレクトリにアクセスするためには権限の付与が必要となったそうです。

AndroidManifest.xml

ファイルのアクセス権限をアプリに付与するためには、AndroidManifest.xmlにuses-permissionを追加します。

が、プロジェクトを作成しただけだとAndroidManifest.xmlは作成されません。

どうやら以下のファイルをデフォルトで見ているようです。

/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/AndroidManifest.xml

これをプロジェクトに合わせて変更するには、プラグイン(Jar)と同じ、Assets > Plugins > Android に、AndroidManifest.xmlを複製して、それを編集します。
プラグインのプロジェクト内のAndroidManifest.xmlなどを持ってきても、ビルド時にエラーになってしまいます。

AndroidManifest.xml

< ?xml version="1.0" encoding="utf-8"? >
< manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.unity3d.player"
    android:installLocation="preferExternal"
    android:versionCode="1"
    android:versionName="1.0" >
    < supports-screens
        android:smallScreens="true"
        android:normalScreens="true"
        android:largeScreens="true"
        android:xlargeScreens="true"
        android:anyDensity="true"/ >

    < application
        android:theme="@android:style/Theme.NoTitleBar"
        android:icon="@drawable/app_icon"
        android:label="@string/app_name"
        android:debuggable="true" >
        < activity android:name="com.unity3d.player.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 >
    < uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" / >
    < uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" / >
< /manifest >

これでAndroid4.4以上の端末でもファイル・ディレクトリにアクセスでき、File.Existsの結果もTrueになります。

参考

【Unity】戻るボタン

【Unity】時間のカウント

【Unity】UnityでManifestファイル変更

C#】ファイル・ディレクトリの存在確認

Android】ファイルへのアクセス権限