うえすと開発メモ

タグ:Android

Unityで作ったプロジェクトをAndroid向けにビルドする際に、
アイコンの下に表示されるアプリ名を言語別に変更(ローカライズ)する方法です。

↓のように英語の場合と日本語の場合で分けたいとき 
localize_01
※iOSの場合は、Xcodeプロジェクトができた後に、InfoPlist.stringを追加する以下の方法で出来ます。
be-style [Unity] iOSアプリ名のローカライズ
https://blog.be-style.jpn.com/article/57050228.html 

Androidの場合は、Unityプロジェクトビューのルート(フォルダ構成で言うとAssets直下)に、「Plugins/Android/res」フォルダを作って、
その配下に日本語なら「values-ja」 フォルダを作ってstring.xmlファイルを配置します。

string.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">日本語アプリ名</string>
</resources>

日本語アプリ名の箇所に好きな名前を入れる事で、端末の言語設定が日本語の場合に表示される名前を変えられます。

ちなみに、ローカライズファイルを用意していない言語の場合は、
Unityの「Edit→ProjectSettings→Player」内の「Product Name」に設定したデフォルト名が表示されます。 
localize_02

このエントリーをはてなブックマークに追加

Unity Androidのアプリから、別アプリのインストール状況を調べる方法です。
アプリ間連携等の際に、アプリがインストールされていればアプリを開き、インストールされていなければストアへ飛ばす等の挙動をさせたいときに使用できます。

実装時のバージョンはUnity4.1.2。

対象のアプリがインストールされているかの判定には、
インストールされているアプリのパッケージ名をリストで取得して比較を行います。

今回はAndroidのJavaクラスやメソッドを呼び出すAndroidJavaObjectを使用して、
AndroidのPackageManagerから情報を取ってきます。

 参考:
Unity Script Reference AndroidJavaObject
AndroidDevelopers PackageManager

以下、指定パッケージのアプリがインストールされているか確認するCheckInstalledPackageメソッド

 CheckInstalledPackage
	/// 
	/// 指定パッケージのアプリがインストールされているか確認する
	/// 
	private bool CheckInstalledPackage( string packageName )
	{
		// 結果
		bool result = false;
		
		AndroidJNI.AttachCurrentThread();
		AndroidJNI.PushLocalFrame(0);
		try
		{
			// UnityPlayerActivityを取得
			using( AndroidJavaClass jcUnityPlayer = new AndroidJavaClass( "com.unity3d.player.UnityPlayer" ) )
			using( AndroidJavaObject joCurrentActivity = jcUnityPlayer.GetStatic<AndroidJavaObject>( "currentActivity" ) )			
			// PackageManagerを取得
			using( AndroidJavaObject joPackageManager = joCurrentActivity.Call<AndroidJavaObject>( "getPackageManager" ) )
			//インストールアプリ情報リストを取得
			using( AndroidJavaObject joApplicationInfoList = joPackageManager.Call<AndroidJavaObject>( "getInstalledApplications", 0 ) )
			{
				// リストの要素数取得
				int listSize = joApplicationInfoList.Call<int>( "size" );
				// 対象のパッケージ名があるか探す
				for( int i = 0; i < listSize; i++ )
				{
					// パッケージ名を取得			
					string pName = GetPackageName( joApplicationInfoList, i );
					// パッケージ名を比較
					if( !string.IsNullOrEmpty( pName ) && packageName.Equals( pName ) )
					{
						// インストールされている
						result = true;
						break;
					}
				}
			}
		}
		catch (System.Exception ex)
		{
			Debug.Log( ex.Message );
		}
		finally
		{
			AndroidJNI.PopLocalFrame(System.IntPtr.Zero);
		}
		
		return result;
	}
	
	/// 
	/// インストールアプリ情報リストから指定インデックスのパッケージ名を取得する
	/// 
	private string GetPackageName( AndroidJavaObject joApplicationInfoList, int index )
	{
		string pName = "";
		
		AndroidJNI.AttachCurrentThread();
		AndroidJNI.PushLocalFrame(0);
		try
		{
			// アプリ情報取得
			using ( AndroidJavaObject joApplicationInfo = joApplicationInfoList.Call<AndroidJavaObject>( "get", index ) )
			{
				// パッケージ名を取得
				pName = joApplicationInfo.Get<string>( "packageName" );
			}
		}
		catch (System.Exception ex)
		{
			return string.Empty;
		}
		finally
		{
			AndroidJNI.PopLocalFrame(System.IntPtr.Zero);
		}
		return pName;
	}

ネイティブ側からのプリミティブ型以外の戻り値(ActivityやPackageManagerやList等)はAndroidJavaObject型で受け取る事が可能です。

GetPackageNameを別メソッドに切り出しているのは、アプリが大量にインストールされている端末でのJNI ERRORを発生させないためです。
参考:[Fixed]: JNI local reference table summary (512 entries)

既存のアクティビティ内のメソッドを使うだけなら、Unity側の記述だけで使えるので便利です。

※Unity Android Player以外ではビルドが通らないので、メソッド全体を
#if UNITY_ANDROID
#endif

で括る。 また、エディタで実行するとエラーになるので、実機で確認しましょう。

このエントリーをはてなブックマークに追加

westhillapp01://urlschemetest」のような独自のURLを使って、
Unity Androidアプリを起動する方法の実装メモです。

ブラウザや他のアプリからUnity Androidアプリを起動
custom_url_scheme_04
iOS版はこちら

実装時のバージョンはUnity4.0.1。

実装手段として、Androidのプラグインを作成してプロジェクトに組み込みます。
Unityが使用しているUnityPlayerActivityを継承する方法もあるのですが、
同時に使用しているプラグイン等との衝突を避けるために、
独自のActivityを作成して、カスタムURLスキームからの起動時には
独自のActivityを立ちあげる→UnityPlayerActivityへ遷移という実装にしました。

参考:Building Plugins for Android
   (「Extending the UnityPlayerActivity Java Code」以下)

①まずは、独自のActivityを含むjarライブラリを作成します。
eclipseで新規Androidプロジェクトを作って、
右クリック→Properties→AndroidでIsLibraryにチェック。
custom_url_scheme_02
②Unity Adnroidで使用しているクラスを参照するため、
以下パス内にある classes.jar ファイルを「libs」フォルダ内に配置します。
Winの場合:Unityのインストールパス\Editor\Data\PlaybackEngines\androidplayer\bin
Macの場合:Unityのインストールパス/Unity.app/Contents/PlaybackEngines/AndroidPlayer/bin


③独自URL経由で起動するActivityを作成します。
IntentReceiveActivity.java
public class IntentReceiveActivity extends Activity {

	/* アクティビティ保持用 */
	private static Activity mThisActivity;

	/* アクティビティ遷移ハンドラ */
	private static Handler mMoveActivityHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			// UnityPlayerProxyActivityへ遷移
			if (mThisActivity != null) {
				Intent i = new Intent(mThisActivity.getApplication(), UnityPlayerProxyActivity.class);
				mThisActivity.startActivity(i);
				mThisActivity.finish();
				mThisActivity = null;
			}
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		Context context = getApplicationContext();
		
		// SharedPreferences取得。UnityのPlayerPrefsでは、
		// バンドル名のSharedPreferencesを使用しているので合わせる
		SharedPreferences packagePrefs = context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
		SharedPreferences.Editor editor = packagePrefs.edit();
		
		// 指定のスキーム経由で起動されたフラグをSharedPreferencesに保存
		editor.putInt(getIntent().getData().getScheme(), 1);
		editor.commit();

		// アクティビティを保持
		mThisActivity = this;

		// アクティビティ遷移
		mMoveActivityHandler.sendEmptyMessageDelayed(0, 10);
	}

	@Override
	public void onBackPressed() {
		// バックキーで何もしない
		// super.onBackPressed();
	}
}
ここでは起動時のURLスキーム名をキーにしてSharedPreferencesにintのフラグを保存し、
Unityで使用している開始時のアクティビティUnityPlayerProxyActivityへ遷移するようにしています。

④↑で作成したAndroidプロジェクトをビルドして、「bin/」配下の プロジェクト名.jarファイル 
Unityプロジェクトビューのルート(フォルダ構成で言うとAssets直下)に「Plugins/Android」フォルダを作成して配置します。


⑤「Plugins/Android」フォルダに、 AndroidManifest.xml を作成します。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="https://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:icon="@drawable/app_icon"
        android:label="@string/app_name"
        android:debuggable="true">
        
        <activity android:name="com.unity3d.player.UnityPlayerProxyActivity"
                  android:label="@string/app_name"
                  android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.unity3d.player.UnityPlayerActivity"
                  android:label="@string/app_name"
                  android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">
        </activity>
        <activity android:name="com.unity3d.player.UnityPlayerNativeActivity"
                  android:label="@string/app_name"
                  android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">
            <meta-data android:name="android.app.lib_name" android:value="unity" />
            <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />
        </activity>
         
        <!-- カスタムURLスキームからの起動用Activity -->
        <activity android:name="【ライブラリのパッケージ名】.IntentReceiveActivity">
            <intent-filter>
                <data android:scheme="【URLスキーム名】" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <action android:name="android.intent.action.VIEW" />
            </intent-filter>
        </activity>
         
    </application>
</manifest>
大事なのは<!-- カスタムURLスキームからの起動用Activity -->のIntentReceiveActivity定義部分です。
カスタムURLスキームリンク要求時に、指定のActivityでアプリを立ち上げる設定をしています。

【ライブラリのパッケージ名】の部分に①で作成したライブラリのパッケージ名を入れ、
【URLスキーム名】の部分を使用したい独自スキーム名にします。
※スキーム名は、URLの":"以前の部分になります。↑の例でいうと westhillapp01

※同時に使用しているプラグイン等でAndroidManifestが既に作られている場合には、
<!-- カスタムURLスキームからの起動用Activity -->部分のみ追記すれば問題ないです。


⑥Unity側で、カスタムURLスキームからの起動か否かを
以下のようにPlayerPrefsを使用して②で保存したフラグから判別することが出来ます。
bool launchFromCustomUrlScheme = PlayerPrefs.GetInt("【URLスキーム名】", 0) == 1 ? true: false;
PlayerPrefs.DeleteKey("【URLスキーム名】"); 

このエントリーをはてなブックマークに追加

以前とりあげたUnityでのAndroidアプリ内課金プラグインであるprime31のAndroid In App Billing Pluginですが、
AndroidのIn App Billingがversion3になって色々変わっていて、
プラグインの方も大きく変更されているので、新バージョンでの実装メモになります。

prime31
https://www.prime31.com/

今回も管理なしアイテムと、ユーザアカウント毎に管理のアイテムで試しました。

導入時のバージョンはUnity4.0.1、
プラグインのバージョン(公式サイトでのリリース日)は2013-01-21です。

--------------------------------------------------------
※注意:ここで紹介しているバージョンは古いので
実装の際は公式で最新のドキュメントを確認することをおすすめします。
--------------------------------------------------------

※前準備として事前に、デベロッパーコンソールにてアプリと課金アイテムを登録し、
 アイテムのみ公開状態にしておく。(アプリが非公開なら、公開されることはない)
 テストアカウントの追加を行ない、端末のメインアカウントをテストアカウントにする。

※旧バージョンから移行する場合には、最初にPlugins配下の
 InAppBillingAndroid フォルダを削除する必要があります。

以下、実装の簡単な流れ

①Android In App Billing Pluginをインポート(※1)した後、
 最初のシーンに GoogleIABEventListener を配置。
 DontDestroyOnLoadで死なないようにしておく。

②まずは GoogleIAB.init( publicKey ) で初期化します。
 引数にはデベロッパーコンソールで確認できる、自身のパブリックキーを入れます。
 アプリ内購入が可能である場合には 
 GoogleIABEventListenerbillingSupportedEvent() が非同期で呼ばれ、
 アプリ内購入不可の場合は
 billingNotSupportedEvent( string error ) が呼ばれます。

③詳細なログ出力を行うかどうかを
 GoogleIAB.enableLogging( AndroidDetailLogging ) で設定し、
 署名の認証チェックを自動で行うかどうかを
 GoogleIAB.setAutoVerifySignatures( AndroidAutoVerifySignatures ) で設定します。

④ GoogleIAB.purchaseProduct( productId ) で購入リクエストを送信する。
 購入したいアイテムのプロダクトIDを引数に入れる。
 これでマーケットアプリが立ち上がり、アイテムの購入画面が表示される。
IAB01
⑤購入が正常に終わると、GoogleIABEventListener 
 purchaseSucceededEvent( GooglePurchase purchase ) 
 が呼ばれます。ここで対応するアイテム等をゴニョゴニョする。
 引数の GooglePurchase にはプロダクトIDやオーダーID等、
 購入レシートと同じ情報が含まれています。

⑥In App Billing version3では、消費タイプのアイテムは購入後に消費リクエストを投げないと
 追加購入ができなくなるので、
 GoogleIAB.consumeProduct( googlePurchase.productId ) で消費リクエストを送ります。

⑦正常に通れば、GoogleIABEventListener 
 consumePurchaseSucceededEvent( GooglePurchase purchase ) が呼ばれます。

⑧リストアの実装は GoogleIAB.queryInventory( productIdList.ToArray() )
 の引数にプロダクトIDリストを入れて呼ぶことで、
 queryInventorySucceededEvent( List<GooglePurchase> purchases, List<GoogleSkuInfo> skus )
 が呼ばれ、purchases に購入済みアイテム一覧が入っているので、この情報で復旧できます。

⑨アプリケーションの終了時、OnApplicationQuit()等の中で、
 GoogleIAB.unbindService()でBillingServiceを終了する。

⑩その他のGoogleIABEventListener 内イベント
purchaseCompleteAwaitingVerificationEvent( string purchaseData, string signature )
  → 購入時のレシート通知イベント。GooglePurchaseと同じ情報ですが、 
    こちらはJSON形式になっています。(※version2のレシートとは形式が異なります)
purchaseFailedEvent( string error )
   購入エラー。キャンセルでも呼ばれます。
queryInventoryFailedEvent( string error )
  → 購入済アイテムの取得失敗。
consumePurchaseFailedEvent( string error )
  → 消費の失敗。

※1 他のAssetや独自でAndroidManifestをカスタムしている場合には、
 (Plugins→Android配下にAndroidManifest.xmlファイルがある場合)
 上書きされてしまうので、AndroidManifestだけはインポートしないでおき、
 元のAndroidManifestとprime31のAndroidManifestを手動でマージする必要があります。

このエントリーをはてなブックマークに追加

--------------------------------------------------------
※注意:In App Billing ver2のプラグインの記事になります。
最新のver3に対応しているプラグインの記事はこちらになります。
--------------------------------------------------------

UnityでのAndroidアプリ内課金では割とポピュラーな、prime31のAndroid In App Billing Plugin($65)を使用した、アプリ内課金の実装メモです。

iOS版はこちら

prime31
https://www.prime31.com/unity/

こちらもわかりやすいテストシーンが付属してあるので、そのまんまなのですが、メモ。
管理なしアイテムと、ユーザアカウント毎に管理のアイテムで試しました。

導入時のバージョンはUnity3.5.6、プラグインのバージョンは1.2.8です。

--------------------------------------------------------
※注意:ここで紹介しているバージョンは古いので
実装の際は公式で最新のドキュメントを確認することをおすすめします。
--------------------------------------------------------

※前準備として事前に、デベロッパーコンソールにてアプリと課金アイテムを登録し、
 アイテムのみ公開状態にしておく。(アプリが非公開なら、公開されることはない)
 テストアカウントの追加を行ない、端末のメインアカウントをテストアカウントにする。

以下、実装の簡単な流れ

①Android In App Billing Pluginをインポート(※1)した後、
 最初のシーンにIABAndroidManagerプレハブとIABAndroidEventListenerを配置。
 DontDestroyOnLoadで死なないようにしておく。

②まずはIABAndroid.init(publicKey)でBillingServiceを起動します。
 引数にはデベロッパーコンソールで確認できる、自身のパブリックキーを入れます。
 IABAndroidEventListenerbillingSupportedEvent (bool isSupported)
 が非同期で呼ばれ、isSupporedには問題がなければtrueが入ってきます。

IABAndroid.purchaseProduct(productId)で購入リクエストを送信する。
 購入したいアイテムのプロダクトIDを引数に入れる。
 これでマーケットアプリが立ち上がり、アイテムの購入画面が表示される。
IAB01
④購入が正常に終わると、IABAndroidEventListener
 purchaseSucceededEvent (string productId, string developerPayload)
 が呼ばれるので、ここで対応するアイテム等をゴニョゴニョする。

⑤リストアの実装はIABAndroid.restoreTransactions()でリクエスト。
 IABAndroidEventListenertransactionsRestoredEvent()が呼ばれた後に、
 purchaseSignatureVerifiedEvent (Hashtable payload)(※2)と
 購入済みアイテムのpurchaseSucceededEvent (string productId, string developerPayload)が順次呼ばれる。
 purchaseSignatureVerifiedEventにはアイテムのステータスがJSON形式で入ってくる。
 参照の仕方はテストシーンを確認するとわかると思います。

⑥アプリケーションの終了時OnApplicationQuit()等の中で、
 IABAndroid.stopBillingService()でBillingServiceを終了する。

⑦その他のIABAndroidEventListener内イベント
purchaseFailedEvent (string productId, string developerPayload)
   購入エラー
purchaseCancelledEvent (string productId, string developerPayload)
  → 購入キャンセル
purchaseRefundedEvent (string productId, string developerPayload)
  → 購入払い戻し?
confirmationFailedEvent (string productId, string developerPayload)
  → 認証の失敗。googleCheckoutから手動で払い戻しをした時にも呼ばれる。
transactionRestoreFailedEvent (string error)
  → リストアエラー
purchaseSignatureVerificationFailedEvent (Hashtable payload)
  → prime31のドキュメントに載っていない。未使用?

 それぞれエラー対応や、使っているものがあれば対応する。


※1 他のAssetや独自でAndroidManifestをカスタムしている場合には、
 (Plugins→Android配下にAndroidManifest.xmlファイルがある場合)
 上書きされてしまうので、AndroidManifestだけはインポートしないでおき、
 元のAndroidManifestとprime31のAndroidManifestを手動でマージする必要があります。

※2 purchaseSignatureVerifiedEvent (Hashtable payload)は、リストア時以外にも、購入済みアイテムのステータスが変更された後などにも呼ばれたりします。(払い戻し時等)

このエントリーをはてなブックマークに追加

このページのトップヘ