Unityで2Dゲーム系アプリを作るときのメモ

iOSで60FPS出す方法

Is it possible to get above 30 FPS on an iOS device? – Unity Answers

Androidだとデフォルトで60FPSが出ているんですが、iOSで試すとFPSが30しかでません。
これだと、アニメーションの速度などに差異が出てしまうので、同じにしたいもんです。

Application.targetFrameRate = 60;

これだけでOK!

UnityアプリをiOSで書きだしてビルドするとdSYMのところですんごい時間かかる

  • XCodeのプロジェクト設定ページからBuild Settingsタブを選択
  • dSYMでBuild Options項目を検索
  • DWARF with dSYM fileからDWARFに変更
  • via: UnityアプリをiOSに書き出す時間をdSYMを無効化して早くする – The jonki

    これで解決!
    1分以上かかっていたビルドが数秒になります。

    ただ、XCode上から設定するので、これ以降はUnityはappendでビルドしないといけないかもです。

    prime31のEtceteraTwoの在処が分からない

    LocalNotificationのスケジューリングをしようとすると、EtceteraプラグインだけではダメでTwoのほうが必要になります。
    AndroidのほうはなぜかEtceteraだけでいけるんですが、iOSは2個必要みたい。

    このtwoプラグインはAssetStoreにありません。
    なので、以下のURLから購入することができます。
    prime[31] – the best Unity native code plugins

    ただ、LocalNotificationをただ通知したいだけなら、iOS・Android含めたコードは、

    #if UNITY_IPHONE
            LocalNotification l = new LocalNotification();
            l.applicationIconBadgeNumber = 1;
            l.fireDate = System.DateTime.Now.AddSeconds(10);
            l.alertBody = "hello world";
            NotificationServices.ScheduleLocalNotification(l);
    #elif UNITY_ANDROID
            EtceteraAndroid.scheduleNotification( 10, "Notiifcation Title", "The subtitle of the notification", "Ticker text gets ticked", "my-other-special-data" );
    #endif
    

    こんな感じでもいいかなーと思います。
    LocalNotificationのためだけにtwoで$65払うのもちょっとね。

    PlayerPrefs

    ちょいと値を保存したいとき用に使える。

    Unity – Scripting API: PlayerPrefs

    複数のシーンをマージしてロードする

    テラシュールウェア [Unity3D]ロードの待ち時間を短くする(その4/Application.LoadLevelAdditive)
    このあたりを読むとApplication.LoadLevelAdditiveはかなりオーバーヘッドがあるようで、あまりシーンの分割をしすぎるのは危険のよう。

    開発する人数が多いとシーンの分割はしたくなるが、ある程度最小限のほうがよさそう。

    今回はUnity3D – Unity3Dで、体感ロード時間を減らす – Qiitaを参考にApplication.LoadLevelAdditiveAsyncを採用してみた。

    ただし、非同期で読み込むので、読み込みが完了したかが分からなかったので、以下のようにあるオブジェクトがFindできたらLoadしたとみなす処理を書いた。

    Application.LoadLevelAdditiveAsync ("Game");
    
    StartCoroutine (ObjectChecker (0.1f, () => {
        Debug.Log ("OnLoad---------------------------------");
    }));
    
    IEnumerator ObjectChecker (float secs, Action callback)
    {
        while (GameObject.Find(ConstantsUI.GAME_CAMERA) == null) {
            yield return new WaitForSeconds (secs);
        }
    
        callback ();
    }
    

    このあたりもっと良い方法がないかなー

    NGUIについて

    2Dのゲームアプリを作るため、NGUIを使ってみました。
    はじめの数日はハマることが多いですが、手に馴染んでくるとこれなしでは2Dアプリは作れないという印象。

    NGUIのレンダリングコントロール

    少しやっかいだったのが、レンダリングの順番をどうコントロールするか。

    UIPanelのRenderQでStartAtにしてここでコントロールした。
    NGUIはどうもRenderQueueが3000番から始まるようで、ここを知っていればレンダリングのコントロールは用意です。

    NGUIのコントロールとUnityの3Dコントロール、たとえばパーティクルとかを一緒に使う場合は、以下のようなコードをパーティクルにつけて、3001とかになるようにすればパーティクルが手前にレンダリングされます。

    ■参考リンク

    Webデザイナーが覚えるUnity その2 NGUIの基礎知識 | GREE Creators Blog
    【Unity】NGUIを使ってボタンを実装する。 | albatrus.com
    ゲームは初心者にやさしく: NGUI 描画順について
    NGUIではないオブジェクトをNGUIの手前に描画する方法 – 太郎Work
    ウダサンコウボウ: [Unity]NGUIのDepth設定
    ウダサンコウボウ: [Unity]NGUIのDepth設定その2(複数Atlas)

    NGUIのアニメーションをプログラムから指定する

    TweenPosition tp = TweenPosition.Begin (gameObject, 0.5f, new Vector3 (0, 0, 0));
    tp.tweenGroup = 0;
    tp.from = new Vector3 (transform.localPosition.x, transform.localPosition.y, transform.localPosition.z);
    tp.method = UITweener.Method.EaseInOut;
    tp.style = UITweener.Style.Once;
    tp.AddOnFinished (new EventDelegate (OnShowOverlayEnd));
    tp.Play (true);
    

    ■参考リンク
    開発メモ 「NGUIのまとめ(スクリプトから動的動作)」 – シヴァのブログ
    NGUI TweenPostion.. – Unity Answers
    NGUI: Next-Gen UI kit: TweenPosition Class Reference
    【Unity】NGUIのTweenを利用してみる | albatrus.com

    NGUIのイベントたち

  • void OnHover (bool isOver) – マウスポインタが重なったときに呼ばれます。タップは×。
  • void OnPress (bool isDown) – マウスによりクリック(押下時と離脱時の2回)が発生したときによばれます。タップも○。
  • void OnClick() — 上と同じですが、離脱時の1回だけ呼ばれるパターン。
  • void OnDoubleClick () — ダブルクリックされたとき。
  • void OnSelect (bool selected) –選択メニューで便利?OnClickと同じだけど、こっちは他のものが選択されるまでは2度目の選択時には呼ばれません。
  • void OnDrag (Vector2 delta) – OnPress(true) と OnPress(false)の間に起きた移動を取得。タップ○。
  • void OnDrop (GameObject drag) – ドロップ時(OnPress(false)のタイミング)にドロップされた(下にある)オブジェクト側で呼ばれる。引数には、ドロップした(上にある)オブジェクトが渡される。
  • void OnTooltip (bool show) – Sent after the mouse hovers over a collider without moving for longer thantooltipDelay, and when the tooltip should be hidden. Not sent on touch-based devices.
  • void OnScroll (float delta) マウスのホイールが動いた時によばれる
  • void OnKey (KeyCode key) キーボードが押されたときによばれる
  • via: NGUIでイベントを取得する | Unity3Dのプラグインによる開発

    こちらのサイトにはほんと助けられました。
    どうゆうイベントがいつ呼ばれるのかまったく分かりませんでした。

    動画をネイティブのプレイヤーでフルスクリーンで再生する

    Handheld.PlayFullScreenMovie ("main_movie.mp4", Color.black, FullScreenMovieControlMode.Full);
    

    たったのこれだけ、ただUnity上で実行するのではなくあくまでもネイティブのプレイヤーを表示するので、
    使いどころとしては、何かチュートリアル的な動画などを再生するときかなー

    Unity – Scripting API: Handheld.PlayFullScreenMovie
    Handheld.PlayFullScreenMovie on Android | Unity Community

    Unity4入門   最新開発環境による簡単3Dゲーム製作
    浅野 祐一 荒川 巧也 森 信虎
    ソフトバンククリエイティブ
    売り上げランキング: 1,332

    UnityでFacebookSDKを使う際の注意点

    デフォルトのAndroidManifestでは横向きで動かない

    FacebookSettingでRegenerate Android ManifestでAndroidManifestをジェネレートできますが、このままでは横向きで動きません。

    <activity android:name="com.facebook.LoginActivity" android:screenOrientation="landscape" android:configChanges="keyboardHidden|orientation" android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
    

    このようにandroid:screenOrientation=”landscape”とlandscapeとして指定する必要があります。

    Facebook側に登録するHashがkeystoreの状況によって変わってくる

    Facebookと連携するアプリを作る場合、FacebookSettingのDebug Android Key Hashの値をサイト側に登録しますが、
    この値が、アプリをPlayストアに公開できる状態にしているかどうかで変わってしまいます。
    アプリに公開できるというのは、自分でkeystoreを作っているかどうかです。

    UnityのFacebookSettingで見えているHashをいくらサイトに登録しても動きません
    実際に必要なのは、以下のようにコマンドラインから実行したときの値を登録します。

    本番用

    こっちの値をサイトに登録します。

    keytool -exportcert -alias hoge -keystore user.keystore | openssl sha1 -binary | openssl base64

    デバッグ用

    こっちの値はFacebookSettingに表示されている値。
    keystoreを自分で作っていない場合は、ここの値でOK。

    keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64

    Android Facebook SDK: Key hash does not match any stored key hashes when uploading google play – Stack Overflow
    android – How to generate Key Hash for facebook SDK In Mac – Stack Overflow
    Unity Android using Facebook SDK, missing Debug Android KeyHash – Stack Overflow
    このあたりハマっている人多いよー

    FB.Login (“email,publish_actions”, LoginCallback)の第一引数には気をつけよう

    Facebookログイン Version2.0(アプリのパーミッション仕様変更)

    ここをちゃんと指定しないと動かなかったです。
    とくに空っぽではダメで、何かしらのパーミッションは自分で指定する必要があります。

    FB.Login ("email,publish_actions", LoginCallback);
    
    Facebook Perfect GuideBook 2014年改訂版
    森嶋 良子 鈴木 麻里子 田口 和裕
    ソーテック社
    売り上げランキング: 11,234

    AndroidのAndroid In App Billingで「アイテムが見つかりませんでした。」の解決方法

    現象について

    UnityでAndroid In App Billing Pluginを使ってアプリ内課金をためす方法
    で、UnityからAndroidの課金のやり方を解説したのですが、ある時から、

    「購入しようとしたアイテムが見つかりませんでした。」

    というエラーが出て、いっこうに課金テストすることができませんでした。

    UnityのPluginが出していたエラーは以下のような感じ。

    You have not queried your inventory yet so the plugin does not have the required information to protect you from coding errors.
    CANNOT fetch sku type due to either inventory not being queried or it returned no valid skus.

    FileBasedKeyValueStore.delete: Attempt to delete ‘lkajsdflkasdlkfjaldkfjaldjflkd’ failed!

    BasicNetwork.performRequest: Unexpected response code 500 for https://android.clients.google.com/fdfe/preparePurchase

    ググると時間がたつと使えるようになるよ。といった記事もあり、待ってみましたがいっこうにダメ!

    さらに調べてみると、公式にアナウンスがあったよう。

    テスト課金、ライセンシング、APK 拡張ファイルのダウンロード 以前はこれらの機能を APK がドラフトの状態でもご利用いただけましたが、最近の変更により、今後は APK が公開されている状態である必要があります。アルファ版もしくはベータ版 APK として公開することでテストが可能で、一般公開する必要はありません。詳細については、Android Developers サイトをご確認ください。

    via
    Android デベロッパー ヘルプ

    つまり、課金の仕組み変わって、ドラフト状態だと課金のテストができないよーというもの。

    Testing In-app Billing | Android Developers
    にもDraft Apps are No Longer Supportedという文章がありました。

    これはハマるよー!わからないよー!

    解決策

    Google Play アルファ版機能を利用してアプリをテストする -でじうぃき
    「購入しようとしたアイテムが見つかりませんでした。」アプリ内課金の実機テストが出来なくなった時の対処法 | 日本VTR実験室

    こちらの記事を読んでもらえれば解決します。

    • Google Playでアルファにapkをアップロードする
    • アルファのテスターのリストを管理をクリックし、テストユーザーを含むGoogleグループを追加する
    • アプリを公開する、キャプチャなどの画像・国・規約に同意などなど
    • 公開したあと(この公開はアルファ公開なので一般にはDLできない)再度テスターのリストを管理をクリックするとURLが書いてあるので、Android端末からアクセスする
    • テストユーザーに含めたGoogleグループに所属しているユーザーならアプリのDLが出来るようになっている
    • アプリがDL出来るようになるまで少し時間が掛かります
    • またすでにUnityから同アプリを入れている場合はアンインストールしておきましょう
    • DLアプリを起動し、課金してみるとやっと念願の課金画面がでてきます

    と、かなり面倒になってしまいました。
    とくに面倒なのが、アプリを修正するたびに課金の確認をする場合はapkのアップロードが必要なところですね。

    この仕様変更は、どうも調べてると5月のどっかでされたようで、確かにその時期からなんかうまくいかないなーと思っていました。

    UnityでImage Effectを使って画面にノイズ入れたり歪ませたりしてみる

    イメージエフェクト リファレンス / Image Effect Reference
    これは楽しい!

    Unity Pro限定ですが、カメラにアタッチするだけでカメラから見えている景色を加工することができます。
    ゲームで敵に遭遇したときのエフェクトとかをこれで作るとかなりそれっぽいかもしれません。

    以下使えるエフェクト一覧。

    via: Unity – Unity Manual

    個人的にはヴォーテックスが楽しい。
    画面を渦上に歪ませられます。

    カメラにVortex Effect Scriptをアタッチした状態で、以下のようにしてみると雰囲気がつかめるかと。

    サンプルプログラム

    using UnityEngine;
    using System.Collections;
    
    public class CameraController : MonoBehaviour
    {
        private VortexEffect ve;
    
        // Use this for initialization
        void Start ()
        {
            ve = gameObject.GetComponent<VortexEffect> ();
            Invoke("VortexAnimation", 5f);
        }
    
        // Update is called once per frame
        void Update ()
        {
    
        }
    
        private void VortexAnimation ()
        {
            iTween.ValueTo (gameObject, iTween.Hash ("from", 0, "to", 1000, "time", 1.5f, "onupdate", "StartGameUpdateHandler", "oncomplete", "CompleteHandler"));
        }
    
        private void StartGameUpdateHandler (float value)
        {
            ve.angle = value;
        }
    
        private void CompleteHandler ()
        {
            iTween.ValueTo (gameObject, iTween.Hash ("from", 1000, "to", 0, "time", 1.5f, "onupdate", "StartGameUpdateHandler", "oncomplete", "VortexAnimation"));
        }
    }
    

    このサンプルでは、初めの5秒はノイズだけでそれ以降はヴォーテックスエフェクトでグルグルします。
    あと、エフェクトをtweenさせたいので、iTween.ValueToを使ってアニメーションさせています。

    サンプルデモ

    Unity Web Player | NoiseSample

    見てわかるUnityゲーム制作超入門 (Game Developer Books)
    掌田 津耶乃
    秀和システム
    売り上げランキング: 19,721

    Unityでgree/unity-webviewを使ってLandscapeでWebViewを表示するときの注意点

    UnityでWebViewを表示しようと思ってググるとgreeのこのプラグインがよく使われているので、試してみました。
    gree/unity-webview · GitHub

    ざっくりとサンプルを書くとこんな感じです。

    using UnityEngine;
    using System.Collections;
    
    public class WebviewSample : MonoBehaviour {
      public string url;
      WebViewObject webViewObject;
    
      // Use this for initialization
      void Start () {
        webViewObject = (new GameObject("WebViewObject")).AddComponent<WebViewObject>();
        webViewObject.Init((msg) => {
          Debug.Log(msg);
        });
        webViewObject.LoadURL("http://google.com/");
        webViewObject.SetMargins(0, 0, 0, 100);
        webViewObject.SetVisibility(true);
      }
    
      // Update is called once per frame
      void Update () {
    
      }
    }
    

    あとは、AndroidManifest.xmlに以下を追加し、変更点を加える。

    インターネットを使えるようにする。

    <uses-permission android:name=”android.permission.INTERNET”/>
    

    これをしないとWebView内のタッチがDOM側に通知されない。

    <meta-data android:name=”unityplayer.ForwardNativeEventsToDalvik” android:value=”true” />
    

    iOSでlandscape時に横幅いっぱいにWebViewが表示されない

    この問題を修正したプルリクがあり、あれちゃんと対応されているなーと思っていたんですが、
    Merge pull request #13 from endavid/fix/iOSLandscape · 3343201 · gree/unity-webview
    どうもunity-webview.unitypackageがこのコードを含んだ状態になっていないようで、これをいくらimportしてもうまくいかなくてハマりました。

    以下のふたつのファイルをimport後に上書きするとこの問題は解消されます。

    1. unity-webview/plugins/WebViewObject.cs at master · gree/unity-webview
    2. unity-webview/plugins/iOS/WebView.mm at master · gree/unity-webview

    参考リンク

    unity-webviewを使ってサイトを表示してみました!! 〜i-tai.net〜
    Unity上でWebViewを表示する | 代打、俺
    Unity で WebView を表示してみた。 | Lonely Mobiler

    Unity4ゲームコーディング 本当にゲームが作れるスクリプトの書き方
    浅野 祐一 荒川 巧也
    SBクリエイティブ
    売り上げランキング: 9,521

    UnityでiOSのPushNotification(APNS)を実装する方法

    UnityでAndroidのPushNotification(GCM)を実装する方法 | ひささん日記のとき同様、Apple Push Notifications (PNS for iOS) – Unity | Games2win Developersこちらのサイトのとおりやってみました。

    iOS版の場合、NotificationServicesを使って自前で書く方法がちょいちょいググると出てくるんですが、この方法はremoteNotificationCountが1以上の場合かどうかで判断していて、ポーリングしないとダメみたいであんまりやりたくありませんでした。
    たとえOnFixedUpdate内でやったとしても常にPushNotificationがあるかどうかのために回すのはあんまりイケていないです。
    このあたりの議論は、Unity 3.5 and Push Notificationsここに載っています。

    その点、prime[31] – Unity plugin documentationはイベントハンドラを登録しておくことができるので、ポーリングする必要がありません。
    今回はこのAssetの使い方がメインになります。

    事前にPushNotification用の.p12(証明書)を作成する

    このあたりはググるとすぐに出てくるので割愛。
    .pemファイルを作る方法が多いのですが、キーチェーンアクセスから.p12ファイルを書き出してそれを使っても動くので、今回は.p12ファイルを使っています。
    (どうゆう環境でPushNotificationするかによるのかもしれません)

    PushNotification用のunitypackageをimportする

    Asset Storeから prime31 Etcetera Plugin などで検索し、購入後にimportしておきます。

    demoのシーンを使ってPushNotificationを試してみる

    demoにシーンがあるので、そちらを表示しておく。

    EtceteraEventListener.cs

    StringBuilderを使うので、以下を追加。

    using System.Text;
    

    アプリ起動時にPushNotification用のdeviceTokenを取得するAPIを実行します。(詳細は別クラスで)
    ここのremoteRegistrationSucceededの中でdeviceTokenが取れるので、PushNotificationを配信するサーバーに登録しましょう。

    private string regURL = null;
    
    void remoteRegistrationSucceeded( string deviceToken )
    {
      Debug.Log( "remoteRegistrationSucceeded with deviceToken: " + deviceToken );
    
      //string apns_env = "production";
      string apns_env = "develop";
    
      StringBuilder urlString = new StringBuilder ();
    
      urlString.AppendFormat ("&appName={0}", WWW.EscapeURL ("app_name")); // provided by G2W
      urlString.AppendFormat ("&app_id={0}", WWW.EscapeURL ("PNS_ID")); // provided by G2W
      urlString.AppendFormat ("&appversion={0}", WWW.EscapeURL ("APP_Version")); // provided by G2W
      urlString.AppendFormat ("&deviceuid={0}", WWW.EscapeURL ("00"));
      urlString.AppendFormat ("&devicetoken={0}", WWW.EscapeURL (deviceToken));
      urlString.AppendFormat ("&devicename={0}", WWW.EscapeURL (SystemInfo.deviceName));
      urlString.AppendFormat ("&devicemodel={0}", WWW.EscapeURL (SystemInfo.deviceModel));
      urlString.AppendFormat ("&deviceversion={0}", WWW.EscapeURL (SystemInfo.graphicsDeviceVersion));
      urlString.AppendFormat ("&apns_env={0}", WWW.EscapeURL (apns_env));
    
      Debug.Log ("http://" + ("hoge.com/" + urlString));
      regURL = urlString.ToString ();
    
      StartCoroutine ("RegDeviceForPush");
    }
    
    IEnumerator RegDeviceForPush ()
    {
      WWW w = new WWW ("http://" + ("hoge.com/" + regURL), UTF8Encoding.UTF8.GetBytes (regURL));
      yield return w;
    
      Debug.Log ("EEEEE: " + w.error);
    }
    

    アプリがフォアグラウンドでPushNotificationを受けた場合。

    void remoteNotificationReceived( IDictionary notification )
    {
      Debug.Log( "remoteNotificationReceived" );
      Prime31.Utils.logObject( notification );
    }
    

    アプリがバックグラウンドでPushNotificationを受け、さらにその通知自体をタップした場合。

    void remoteNotificationReceivedAtLaunch( IDictionary notification )
    {
      Debug.Log( "remoteNotificationReceivedAtLaunch" );
      Prime31.Utils.logObject( notification );
    }
    

    EtceteraGUIManagerTwo.cs

    アプリ起動時にEtceteraBinding.registerForRemoteNotifcationsを呼んで、deviceTokenを登録します。

    if( GUILayout.Button( "Register for Push" ) )
    {
      EtceteraBinding.registerForRemoteNotifcations( P31RemoteNotificationType.Alert | P31RemoteNotificationType.Badge | P31RemoteNotificationType.Sound );
    }
    

    タイミングの話し

    PushNotificationするときに必ずこの話しが出てきちゃう(ぼくだけかな?)んですが、ネイティブの場合は、

    アプリ側でPushが来るパターンは以下の3つ

    1. フォアグラウンドでプッシュ通知を受け取ったとき
    2. アプリのプロセスがバックグラウンドで生きているときにプッシュ通知を受け取り、ユーザーがアクションボタンをタップしたとき
    3. アプリのプロセスがバックグラウンドで生きていないときにプッシュ通知を受け取り、ユーザーがアクションボタンをタップしたとき

    ただ、2の項目は、アプリ内にバッジのようなものを表示する場合は、 ここではやらずapplicationDidBecomeActiveでやるようにしている。
    これの理由は、バックグラウンドにいてアクションボタンではなく、ホーム画面からアプリアイコンをタップされた場合の処理が必要なので、
    あえて、applicationDidBecomeActiveでやっているのです。
    ネイティブの実装例は、PushNotification-iOSandAndroid/iOS/AppDelegate.m at master · hisasann/PushNotification-iOSandAndroidこちらをご覧ください。

    問題はこの2のapplicationDidBecomeActiveでやるような処理をUnityでどうやるかですね。

    ここでNotificationServicesを使ってもできそうなんですが、
    せっかくなので、Etceteraを使ってみましょう。

    if( EtceteraBinding.getBadgeCount() > 0)
    {
      // 何か処理をゴニョゴニョ
    
      // バッジの数値をクリア
      EtceteraBinding.setBadgeCount( -1 );
    }
    

    アプリケーションがOnResumeしたタイミングでこんな感じでPushNotificationが来ていたかどうかを確認できます。

    参考リンク

    prime[31] – Unity plugin documentation
    Unity and IOS push notifications – Google Docs

    詳細! Objective-C iPhoneアプリ開発 入門ノート Xcode5+iOS7対応
    大重 美幸
    ソーテック社
    売り上げランキング: 2,016

    UnityでAndroidのPushNotification(GCM)を実装する方法

    ググってもあんまり有力な記事がなく、AssetStoreを探してみても良さそうなのがなくまだまだUnityでのPushNotificationはこれからなのかなーという印象を受けました。

    過去にネイティブでやったときのコードはgithubにまとめてあるので、そちらをご覧ください。
    PushNotification-iOSandAndroid/Android at master · hisasann/PushNotification-iOSandAndroid

    そんな中、試してみようかと感じた記事がこちら。
    Android PNS for Unity Games | Games2win Developers
    iOS版の記事もあり、そちらはPrime31のAssetを使っていて、良さそう!と思いました。

    Asset Store – Android Etcetera PluginこれのiOS版にはPushNotificationのコードが入っていますが、Android版にはない様子。
    でも↑の記事の中でつかつているunitypackageの中に少しPrime31のコードもあったので、そちらを使うで十分そう。

    事前にSender Id、PushNotification用のIdを作成する

    よくやる作業なので、ここでの説明は割愛。

    この作業はGoogle Developers Consoleで行います。
    ProductIdというのができていたのですが、そちらではPushNotificationがうまく動かず、Project Numberを使うといけました。

    PushNotification用のunitypackageをimportする

    GoogleCloudMessaging-Modified.unitypackage – Google Drive
    こちらからDLし、Unityにインポートしておきます。

    AndroidManifest.xmlを作成する

    ↑のunitypackageを入れると、prime[31]のエディター拡張がインストールされるので、それを使うのが便利です。
    メニューからprime[31] → Generate AndroidManifest.xml Fileこれを実行すると、

    Assets/Plugins/Android

    の下に、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:theme="@android:style/Theme.NoTitleBar"
        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.prime31.UnityPlayerNativeActivity" android:screenOrientation="portrait"
                      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" />
                <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />
            </activity>
    
            <receiver
                android:name="com.prime31.GCMBroadcastReceiver"
                android:permission="com.google.android.c2dm.permission.SEND">
                <intent-filter>
                    <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
                    <action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
                    <category android:name="PACKAGE_NAME"/>
                </intent-filter>
            </receiver>
    
        <meta-data android:name="com.prime31.GoogleCloudMessagingPlugin" android:value="UnityPlayerActivityProxy"/>
      </application>
    
      <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
      <uses-permission android:name="android.permission.INTERNET"/>
      <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
      <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
      <permission android:name="PACKAGE_NAME.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
      <uses-permission android:name="PACKAGE_NAME.permission.C2D_MESSAGE"/>
    
    </manifest>
    

    重要な箇所は、

    <receiver
        android:name="com.prime31.GCMBroadcastReceiver"
        android:permission="com.google.android.c2dm.permission.SEND">
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
            <action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
            <category android:name="PACKAGE_NAME"/>
        </intent-filter>
    </receiver>
    

      <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
      <uses-permission android:name="android.permission.INTERNET"/>
      <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
      <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
      <permission android:name="PACKAGE_NAME.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
      <uses-permission android:name="PACKAGE_NAME.permission.C2D_MESSAGE"/>
    

    ここ。

    PACKAGE_NAMEというのが3箇所あります。
    ここを自分のUnityProjectのBundleIdと同じにします。

    はじめ、1個目のPACKAGE_NAMEしか見えていなく、ここだけ直してビルドしていたらいっこうに通らなくてハマりました。
    そんときのエラーはこちら。
    Android SDK Manifest file error: INSTALL_PARSE_FAILED_MANIFEST_MALFORMED – Stack Overflow

    demoのシーンを使ってPushNotificationを試してみる

    demoにシーンがあるので、そちらを表示しておく。
    EventListenerとuiというGameObjectがあるので、そのソースを表示する。

    GoogleCloudMessagingEventListener.cs

    大事なメンバはnotificationReceivedEventメソッドとregistrationSucceededEventメソッドです。
    元記事のサンプルをもとに書くとこんな感じです。

    using UnityEngine;
    using System;
    using System.Text;
    using System.Collections;
    using System.Collections.Generic;
    
    public class GoogleCloudMessagingEventListener : MonoBehaviour
    {
    #if UNITY_ANDROID
      private string regURL;
      private const string host = "test.com/add/";
    
      //PNS ID provided by the publishing team
      private const string pnsID = "7";
    
      //PNS Environment = should be 'production' in the release build
      private const string pnsEnvironment = "sandbox";
    
      //APP version
      private const string pnsAppVersion = "1.0";
    
      void OnEnable()
      {
        // Listen to all events for illustration purposes
        GoogleCloudMessagingManager.notificationReceivedEvent += notificationReceivedEvent;
        GoogleCloudMessagingManager.registrationSucceededEvent += registrationSucceededEvent;
        GoogleCloudMessagingManager.unregistrationFailedEvent += unregistrationFailedEvent;
        GoogleCloudMessagingManager.registrationFailedEvent += registrationFailedEvent;
        GoogleCloudMessagingManager.unregistrationSucceededEvent += unregistrationSucceededEvent;
      }
    
    
      void OnDisable()
      {
        // Remove all event handlers
        GoogleCloudMessagingManager.notificationReceivedEvent -= notificationReceivedEvent;
        GoogleCloudMessagingManager.registrationSucceededEvent -= registrationSucceededEvent;
        GoogleCloudMessagingManager.unregistrationFailedEvent -= unregistrationFailedEvent;
        GoogleCloudMessagingManager.registrationFailedEvent -= registrationFailedEvent;
        GoogleCloudMessagingManager.unregistrationSucceededEvent -= unregistrationSucceededEvent;
      }
    
    
    
      void notificationReceivedEvent( Dictionary<string,object> jsonDict )
      {
        Debug.Log( "notificationReceivedEvent" );
        Prime31.Utils.logObject( jsonDict );
    
        int badgeNum = int.Parse(jsonDict["badge"].ToString());
        string openURL = jsonDict["open_url"].ToString();
        if(!string.IsNullOrEmpty(openURL))
        {
          Application.OpenURL(openURL);
        }
        GoogleCloudMessaging.setBadge(badgeNum);
        GoogleCloudMessaging.cancelAll();
        showBadge();
      }
    
    
      void registrationSucceededEvent( string registrationId )
      {
        Debug.Log( "registrationSucceededEvent: " + registrationId );
    
        StringBuilder urlString = new StringBuilder ();
        urlString.AppendFormat('?registration_id={0}',WWW.EscapeURL(registrationId));
        urlString.AppendFormat('?app_id={0}',WWW.EscapeURL(pnsID));
        urlString.AppendFormat('?app_version={0}',WWW.EscapeURL(pnsAppVersion));
        urlString.AppendFormat('?apns_env={0}',WWW.EscapeURL(pnsEnvironment));
    
        regURL = urlString.ToString ();
        Debug.Log("regURL: " + regURL);
    
        StartCoroutine ("RegDeviceForPush");
      }
    
    
      void unregistrationSucceededEvent()
      {
        Debug.Log( "UnregistrationSucceededEvent" );
      }
    
      IEnumerator RegDeviceForPush ()
      {
        WWW w = new WWW ("http://" + (host + regURL), UTF8Encoding.UTF8.GetBytes (regURL));
        yield return w;
      }
    
    #endif
    }
    

    GoogleCloudMessagingUI.cs

    重要な箇所は、

    GoogleCloudMessaging.register( "1234567890" );  // sender id
    

    string pushIOApiKey = "lkajdsflkajsdflkajsdflkjadsflkj";  // push key
    

    です。

    using UnityEngine;
    using System;
    using System.Net;
    using System.Collections;
    using System.Collections.Generic;
    using System.Security.Cryptography.X509Certificates;
    using System.Net.Security;
    using Prime31;
    using System.Text;
    using System.Security.Cryptography;
    
    
    public class GoogleCloudMessagingUI : Prime31.MonoBehaviourGUI
    {
    #if UNITY_ANDROID
      private string _registrationId;
    
      void Start()
      {
        // listen for successful registration so we can save off the registrationId in case we want to use it for Push.io registration.
        GoogleCloudMessagingManager.registrationSucceededEvent += regId =>
        {
          Debug.Log("regId: " + regId);
          _registrationId = regId;
        };
      }
    
    
      void OnGUI()
      {
        beginColumn();
    
        if( GUILayout.Button( "Check for Notifications" ) )
        {
          GoogleCloudMessaging.checkForNotifications();
        }
    
    
        if( GUILayout.Button( "Register" ) )
        {
          // replace this with your sender ID!!!
          GoogleCloudMessaging.register( "1234567890" );
        }
    
    
        if( GUILayout.Button( "Unregister" ) )
        {
          GoogleCloudMessaging.unRegister();
        }
    
    
        if( GUILayout.Button( "Cancel All Pending Notifications" ) )
        {
          GoogleCloudMessaging.cancelAll();
        }
    
    
        endColumn( false );
      }
    #endif
    }
    

    アプリを起動して、Registerボタンを押すと、以下のようにLogCatに出力されます。

    04-30 15:02:50.225: I/GCM(9377): GCM config loaded
    04-30 15:02:50.346: I/Prime31-GCMReceiver(17557): recieved broadcast. message type from bundle is null
    04-30 15:02:50.346: I/Prime31-GCMReceiver(17557): received a null message type so we arent interested. aborting
    04-30 15:02:50.406: I/Unity(17557): registrationSucceededEvent: lkajsdflkjasdflkjadsf;lkjadslfkjasd;lfkjalkjasdfl;kjasdf;lkjasdf;lkjasdf
    04-30 15:02:50.406: I/Unity(17557):
    04-30 15:02:50.406: I/Unity(17557): (Filename: ./artifacts/AndroidManagedGenerated/UnityEngineDebug.cpp Line: 53)
    04-30 15:02:50.406: I/Unity(17557): regId: lkajsdflkjasdflkjadsf;lkjadslfkjasd;lfkjalkjasdfl;kjasdf;lkjasdf;lkjasdf
    04-30 15:02:50.406: I/Unity(17557):
    04-30 15:02:50.406: I/Unity(17557): (Filename: ./artifacts/AndroidManagedGenerated/UnityEngineDebug.cpp Line: 53)

    ここのregistrationSucceededEventの中でregisterIdが取れるので、PushNotificationを配信するサーバーに登録しましょう。

    割りと書くコードも少なく、すっきり書けるのでこの方法が僕の中でいまのところ安定しています。
    これからいいAssetとかが見つかれば、そちらの記事をまた書こうかと思います。

    追記:

    タイミングの話し

    UnityでiOSのPushNotification(APNS)を実装する方法 | ひささん日記こちらにも書いたのですが、PushNotificationを受け取るタイミングでひとつ試してなかったのがありました。
    それが、アプリがバックグラウンドにいるときにPushNotificationを受け取って、その後にホーム画面のアプリアイコンをタップした場合にPushNotificationが来ていたかどうかを確認する方法です。
    長いですね。

    結論から言うと、おそらくこの場合のPushNotificationの確認はできません。
    調べたことを順に書いておきます。

    prime[31] – Unity plugin documentationここを見てみると、checkForNotificationsというメソッドがあることに気が付きました。
    書いてあるコメントにもなんだかそれっぽいことが書かれていたので、ここを解析していきました。

    googlecloudmessagingplugin.jarの中にあるclassファイルを全部解凍してみて、見てみると、
    GCMBroadcastReceiverというクラスがありました。
    この中で、

    private void sendNotification(Context context, Bundle bundle, String jsonPayload)
    {
        String launchClassName = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()).getComponent().getClassName();
        ComponentName comp = new ComponentName(context.getPackageName(), launchClassName);
        Intent notificationIntent = (new Intent()).setComponent(comp);
        notificationIntent.putExtra("notificationData", jsonPayload);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0x10000000);
        NotificationManager noteManager = (NotificationManager)context.getSystemService("notification");
        android.support.v4.app.NotificationCompat.Builder noteBuilder = new android.support.v4.app.NotificationCompat.Builder(context);
        noteBuilder.setContentIntent(pendingIntent);
        noteBuilder.setAutoCancel(true);
        noteBuilder.setSmallIcon(context.getApplicationInfo().icon);
        noteBuilder.setContentTitle(context.getApplicationInfo().loadLabel(context.getPackageManager()));
        if(bundle.containsKey("message"))
            noteBuilder.setContentText(bundle.getString("message"));
        else
            noteBuilder.setContentText("");
        String tickerText = "Push Notification Received (default tickerText)";
        noteBuilder.setDefaults(1);
        if(bundle.containsKey("message"))
            tickerText = bundle.getString("message");
        noteBuilder.setTicker(tickerText);
        noteBuilder.setWhen(System.currentTimeMillis());
        noteManager.notify(101, noteBuilder.build());
        Log.i("Prime31-GCMReceiver", "notification posted");
    }
    

    ここが、PushNotificationを受けたときに、メッセージを表示している箇所です。
    notificationIntent.putExtraで必要な情報をIntent経由で渡すんだろうなーという感じです。

    そして、GoogleCloudMessagingPluginクラスの中に、該当のクラスがあるので、ここで取得しているんだろうなーという印象。

    public void checkForNotifications()
    {
        Bundle intentExtras = _lastIntent == null ? getActivity().getIntent().getExtras() : _lastIntent.getExtras();
        if(intentExtras != null && intentExtras.containsKey("notificationData"))
            receivedNotification(intentExtras.getString("notificationData"));
    }
    

    とくに何かバグがありそうな気配もないですし、以前は有料?としてAssetStoreで売られていたAssetなので、そんな不備がある可能性は低い。

    そこでprime31のgithubに書かれている、送る側のコードに問題があるんじゃないかと思い、自分のコードは使わず、Simple PHP script showing how to send an Android push notification. Be sure to replace the API_ACCESS_KEY with a proper one from the Google API’s Console page. To use the script, just call scriptName.php?id=THE_DEVICE_REGISTRATION_IDこのコードを使いました。

    それでもやはりバックグラウンドで受けて、フォアグラウンドになったあとにcheckForNotificationsしてもうんともすんとも言わず。

    そのときのログに出ていたこのCANCELLEDが問題なのかなーとググってみても解決策はなし。

    05-02 20:56:00.291: W/GCM/DMM(9377): broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE pkg=hoge.hoge (has extras) }

    そこで再度、sendNotificationメソッドの中を見ていたら、PendingIntentというのを使っているのに気がついた。

    あ!
    あああ!

    android初心者プログラミング: PendingIntentによると。

    例えば、Notificationに使う場合は、
    通知バーをタップしたタイミングでIntentが発行されるという感じのようです。

    via: android初心者プログラミング: PendingIntent

    PushNotificationを受けて、通知バーよりタップされた場合にPendingIntent経由でextraDataが登録され、そのあとにcheckForNotificationsを実行するとPushNotificationで来た情報が取得できます。
    なんという限定的!

    これを回避するために、Broadcastする方法がありますが、このプラグインでは採用されていません。

    Intent intent = new Intent(ConstantsUtil.DISPLAY_MESSAGE_ACTION);
    intent.putExtra(ConstantsUtil.GCM_EXTRA_BADGE, badge);
    context.sendBroadcast(intent);
    

    解決策としては、アプリ起動時に毎回PushNotificationがあったかどうかを確認するAPIを呼ぶとかですかねー
    まだどうするのが最適解か考え中です。

    難しい、、、

    Androidの絵本 スマートフォンアプリ開発を始める9つの扉
    株式会社アンク
    翔泳社
    売り上げランキング: 100,308

    UnityでiOS StoreKit In App Purchase Pluginを使ってアプリ内課金をためす方法

    UnityでAndroid In App Billing Pluginを使ってアプリ内課金をためす方法 | ひささん日記でAndroid版を書いています。

    引き続き、prime31Asset Store – iOS StoreKit In App Purchase Pluginのプラグインを使って、iOSの課金のテストをしていきます。

    iOSで課金のテストをするには、iTunesConnectにてアプリを作成し口座登録をし課金アイテムを作成する必要があります。
    このときに、iOS Developer Programでやる必要がありEnterpriseではiTunesConnectにログインができません。

    以下、書くまでもない内容は項目だけ書いています。

    iOS Developer Programに登録する

    Certificates、AppID、Devices登録をしてProvisioning Profilesを事前に登録する

    iTunesConnectにアプリを登録

    Androidでは、実際に仮のapkを登録する必要がありましたが、ここでは必要ではないようで、
    単にアプリを作ればOKです。
    (申請するときと同じ項目いれないといけないのですんごくめんどうですが、、、)

    こちらのまとめでいけた!
    iTunesConnect アプリの登録方法 – 散歩しながら 〜〜アプリ開発〜〜

    口座情報を登録する

    結構面倒なんですが、これをやらないとアイテム作成のところで課金アイテムの登録ができませんでした。

    こちらのまとめでいけた!
    iTunesConnect 銀行口座の登録 – 散歩しながら 〜〜アプリ開発〜〜

    テストユーザーを登録する

    課金するときに使うテストユーザーを作成します。
    AppleIDに紐付いていないメアドでも大丈夫でした。

    こちらのまとめでいけた!
    iTunesConnect テストユーザーの作成 – 散歩しながら 〜〜アプリ開発〜〜

    課金用のアイテムを作成する

    iTunesConnectのアプリページの右側にManage In-App Purchasesボタンがあるのでクリックする。
    左上にあるCreate Newボタンをクリックする。
    講座情報の設定が済んでいれば、ここにいろんなアイテムの種類が並んでいると思います。

    それぞれの違いは以下のとおり。

    Consumable

        消費型

      Non-Consumable

         非消費型

      Auto-Renewable Subscriptions

        自動更新購読

      Free Subscription

        無料購読

      Non-Renewing Subscription

         非更新購読

    via: iTunesConnect アプリ内課金 プロダクト作成 手順 – 散歩しながら 〜〜アプリ開発〜〜

    今回は魔法石的なアイテムを作るので、Consumableを選択します。
    アイテムを作成するときにProduct IDはのちのち使うので、覚えておきましょう。

    via iTunesConnect アプリ内課金 プロダクト作成 手順 – 散歩しながら 〜〜アプリ開発〜〜

    UnityでiOS StoreKit In App Purchase Pluginを使ってみる

    StoreKitEventListener.cs

    そもそもHierarchyに追加したgameObjectが破棄されることがあるのか調査できていないのですが、これを追加。

    void Start ()
    {
        // このオブジェクトが破棄されないようにする
        DontDestroyOnLoad (gameObject);
    }
    

    StoreKitGUIManager.cs

    まずはStoreKitBinding.canMakePayments()でアプリ内の購入制限(子供に触らせる方とか)が掛かっているか確認する。
    (掛かっている場合の挙動はいろんなアプリを見て、促す仕組みが必要なのか要調査)

    if( GUILayout.Button( "Request Product Data" ) )
    {
      // array of product ID's from iTunesConnect. MUST match exactly what you have there!
      var productIdentifiers = new string[] { "magic_stone" };
      StoreKitBinding.requestProductData( productIdentifiers );
    }
    

    ここのstring配列にアイテムのProduct Idを入れる。

    購入が完了すると、StoreKitEventListener.purchaseSuccessful()が呼ばれるのでレシート情報をここで取得しAppサーバーに投げるなど。
    Androidの場合は、購入後に消費しないと何度も同じアイテムが買えなかったですが、iOSのほうは消費型アイテムなので、
    その処理は必要なさそうですね。

    今回もこちらのブログにすごく助けられました。
    [Unity][iOS] prime31 iOS StoreKit Pluginを使用したアプリ内課金の実装 : West Hill 開発メモ

    詳細! Objective-C iPhoneアプリ開発 入門ノート Xcode5+iOS7対応
    大重 美幸
    ソーテック社
    売り上げランキング: 752

    UnityでAndroid In App Billing Pluginを使ってアプリ内課金をためす方法

    prime31Asset Store – Android In App Billing PluginがIABを使うときによさそうなので、このAssetの使い方をメモしていきます。

    Google Play Developer Consoleにアプリを登録する

    アプリをいったん仮に登録しないと、アイテム画面に行けないのでまずはこちらをアップします。

    こちらからgmailアカウントをもとに登録していきます。
    すべてのアプリ – Google Play Developer Console
    $25かかってしまうのですが、これをしないとアプリ内課金の仕組みが使えません。

    間違えやすいのですが、Google Developers Consoleではないよ。

    ここでUnityからapkファイルを作成して、アップする必要があるのですが、公開用の署名が付いていないのとアップ時にエラーになってしまいます。
    Unityではこの鍵を作るところもサポートしてくれています。

    BuildSettings → AndroidのPlayerSettingsを開く。
    Publushing SettingsでCreate New Keystoreにチェックを入れる。
    BrowseKeystoreでKeystoreの保存場所を選択する。
    Passwordを入力する。
    AliasプルダウンからCreateNewKeyを選択する。
    Aliasには仮にtestと入力しました。
    Passwordを入力する。
    CreateKeyで作成する。
    作成したあとに、Aliasで今作成したAliasを選択する。

    via 【Unity】keystoreの生成 Android – Litのプログラムとかアプリ開発とか。

    では、Buildしてapkファイルを作成し、Consoleにアップしましょう!
    ちなみに、製品版にアップしてもただちに公開されるわけではないので大丈夫です。

    他にベータ版・アルファ版とアップ先がありますが、ここでは製品版にアップしてみました。

    Google Play Developer Consoleに課金用のアイテムを登録する

    アプリ内アイテムで新しいアイテムを追加をクリックすると、
    以下から選ぶようにいわれます。

    1. 管理対象の商品
    2. 管理対象外の商品
    3. 定期購入

    この違いは、以下のサイトから引用させていただきます。

    1.はアカウント単位で1回購入できるアイテム(電子書籍とか)。appleでいうNon-consumable
    2.は同一アカウントで何回も購入できるアイテム(回復系アイテムとか)。appleでいうConsumable
    3.これはappleのAuto-renewable subscriptionsかな?

    via: エンジニアブログはじめました: アンドロイドでアプリ内課金(v3)のテスト環境

    今回は魔法石のように何度でも買えるようにするために、管理対象外の商品を選択します。

    アイテムを作成したら、プルダウンから有効にしておきます。
    これを忘れると、いっこうにアイテム課金ができません。(ぼくはこれで結構ハマった!)

    UnityでAndroid In App Billing Pluginを使ってみる

    Android In App Billing Pluginをimportするとdemoがあるので、そのシーンに移動する。

    以下、[Unity][Android] (In App Billing ver3版) prime31 Android IAB Pluginを使用したアプリ内課金の実装 : West Hill 開発メモこちらの記事を参考に、必要そうな処理も追記しています。

    GoogleIABEventListener.cs

    そもそもHierarchyに追加したgameObjectが破棄されることがあるのか調査できていないのですが、これを追加。

    void Start ()
    {
        // このオブジェクトが破棄されないようにする
        DontDestroyOnLoad (gameObject);
    }
    

    IABUIManager.cs

    ConsoleのサービスとAPIから、ライセンスキーをinitの中に記述します。
    アプリケーションが終了した場合にunbindされるようにしておきます。

    void OnApplicationQuit() {
        Debug.Log("OnApplicationQuit");
        GoogleIAB.unbindService();
    }
    

    以下のボタンのところを追加したアイテムIDに変更しておきます。
    注意:消費系のアイテムの場合、GoogleIAB.purchaseProductしたあとにGoogleIAB.consumeProductをしないと再度買うことができません。

    if( GUILayout.Button( "Purchase Test Product" ) )
    {
        GoogleIAB.purchaseProduct( "magic_stone" );
    }
    
    
    if( GUILayout.Button( "Consume Test Purchase" ) )
    {
        GoogleIAB.consumeProduct( "magic_stone" );
    }
    

    ただ、IAB側の問題なのか調査できていないのですが、このボタンを順番に押した時に、GoogleIAB.consumeProductが失敗する場合がありました。
    ログを見ながら押していったのですが、GoogleIAB.consumeProductするのが速すぎたのかな。

    なので、GoogleIABEventListener.csの購入が成功したイベントハンドラでGoogleIAB.consumeProductするようにしてみました。
    実際にはこうゆうコードでやることになるのかな。

    // 購入が正常に終わると呼ばれる
    void purchaseSucceededEvent( GooglePurchase purchase )
    {
        Debug.Log( "purchaseSucceededEvent: " + purchase );
        GoogleIAB.consumeProduct(purchase.productId);
    }
    

    引数のGooglePurchaseに購入レシート情報が入っています。こちらを見て、何を買ったのかをアプリケーション・サーバーに通知するのかな。

    テスト課金って書いてあってもなんか怖いねw

    参考リンク

    AndroidBilling – 開発資料
    Google Play In-App Billing テスト方法 | Androidアプリ開発の紆余曲折
    android/アプリ内課金 – 初心者エンジニアの簡易メモ
    エンジニアブログはじめました: アンドロイドでアプリ内課金(v3)のテスト環境

    見れていないのですが、
    In-App Purchasing Unity Plugin for Android Tutorial – YouTube
    こちらの動画も参考になるかも。
    自前でやっているっぽいですが。

    iPhone&Androidアプリ内課金プログラミング完全ガイド (Smart Mobile Developer)
    佐藤 航陽 加藤 勝也 瀬戸 健二 日高 正博
    翔泳社
    売り上げランキング: 182,508

    UnityのAssetBundleを使うときのお作法

    AssetBundleについてはまだ勉強を始めたばかりで検証中なのですが、あまりにも情報がいっぱいあるがそれぞれでやり方が違う印象があるので、
    いろいろ書いていって最適解を出したいと思います。

    なので、この記事はまだ(仮)です。

    AssetBundleについて

    スマホアプリはインストール時にサイズを50MBにに抑えないとWifi経由でのインストールしかできなくなります。
    それだけではなく、オフラインのときでも使えるようにローカルにキャッシュしてほしいなど、制限が結構あります。

    そこでAssetBundleを使って、追加コンテンツとしてアプリ内にダウンロードできる仕組みです。

    Unityを使って何かしらのスマホアプリを作るなら、割りと使うことになりそうな仕組みですね。

    AssetBundleに含められるのは、

    • GameObject
    • Material
    • Texture
    • Prefab

    などなど。

    AssetBundleをiOS・Android用にビルドするにはiOS Pro・Android Proのライセンスが必要になります。
    なので、これらを使わなければお金は掛からないのですが、使うとなると途端に結構な金額のライセンスが必要です。

    デメリットとしてメモリを多く消費するという点があるようです。
    なので、このAssetBundleをどうゆう単位で作り、いつ破棄するかが結構重要になりそうですね。

    また、iOSとAndroidで別に書き出す必要があり、その書き出しのタイミングで最適化がされているようです。
    別々のファイルになってしまうので、Unity側でどのAssetBundleを取りに行くかの分岐が必要です。

    注意点として、ScriptはAssetBundleに含められません。
    iPhone側の話しですが、動的にScriptを生成することを許可していないからです。

    AssetBundleを使ってみる

    UnityのAssetBundleをじっくり理解する手順をまとめてみた。【アセットバンドル】 – NAVER まとめ
    こちらの記事が一番読みやすかったので、これにそって試してみました。

    まずはAssetBundleを作成するためのEditor拡張を入れます。
    BuildPipeline.BuildAssetBundle
    こちらにあるコードを参考にしてたんですが、書いてあるコードにはiOS・Android用のビルドが書かれていなかったので、
    Unity AssetBundle Examples.を参考にしました。

    // C# の例
    // プロジェクト ウィンドウの選択されたオブジェクトからアセットバンドルを作成
    // コンパイルした後は &quot;Menu&quot; -&gt; &quot;Assets&quot; へ移動して選択肢から一つを選択して
    // アセットバンドルをビルド
    using UnityEngine;
    using UnityEditor;
    
    public class ExportAssetBundles
    {
        [
         MenuItem("Assets/Build AssetBundle From Selection - Track dependencies")]
        static void ExportResource ()
        {
            // 保存ウィンドウのパネルを表示
            string path = EditorUtility.SaveFilePanel ("Save Resource", "", "New Resource", "unity3d");
            if (path.Length != 0) {
                // アクティブなセレクションに対してリソースファイルをビルド
    
                Object[] selection =
                    Selection.GetFiltered (typeof(Object), SelectionMode.DeepAssets);
    
                // via https://gist.github.com/yaeda/5410868
                // require iOS Pro, Android Pro Lisence
                // for Android
                BuildPipeline.BuildAssetBundle(Selection.activeObject,
                                               selection, path + ".android.unity3d",
                                               BuildAssetBundleOptions.CollectDependencies |
                                               BuildAssetBundleOptions.CompleteAssets,
                                               BuildTarget.Android);
    
                // for iPhone
                BuildPipeline.BuildAssetBundle(Selection.activeObject,
                                               selection, path + ".iphone.unity3d",
                                               BuildAssetBundleOptions.CollectDependencies |
                                               BuildAssetBundleOptions.CompleteAssets,
                                               BuildTarget.iPhone);
    
                // for WebPlayer
                BuildPipeline.BuildAssetBundle(Selection.activeObject,
                                               selection, path + ".unity3d",
                                               BuildAssetBundleOptions.CollectDependencies |
                                               BuildAssetBundleOptions.CompleteAssets,
                                               BuildTarget.WebPlayer);
    
                Selection.objects = selection;
            }
        }
    
        [
         MenuItem("Assets/Build AssetBundle From Selection - No dependency tracking")]
        static void ExportResourceNoTrack ()
        {
            // 保存ウィンドウのパネルを表示
            string path = EditorUtility.SaveFilePanel ("Save Resource", "", "New Resource", "unity3d");
            if (path.Length != 0) {
                // アクティブなセレクションに対してリソースファイルをビルド
    
                BuildPipeline.BuildAssetBundle (
                    Selection.activeObject,
                    Selection.objects, path);
            }
        }
    }
    

    ■gist
    AssetBundle用のエディター拡張

    Assets/Build AssetBundle From Selection – No dependency trackingのほうはまだ使えていないので、複数OS分のコードは書いていません。

    このクラスを、

    Assets/Editor

    に配置します。

    適当なPrefabを作成し、Prefabを選択した状態で右クリックから、Build AssetBundle From Selection – Track dependenciesを実行します。
    すると、

    • Asset.unity3d.android.unity3d
    • Asset.unity3d.iphone.unity3d
    • Asset.unity3d.unity3d

    のように、WebPlayer用・android用・iphone用の3つができました。
    これをDropboxに適当に配置し、URLをメモしておきます。

    ■参考リンク
    アセットバンドルのビルド / Building AssetBundles

    それでは、Unity側でAssetBundleを使ったコードを書いていきましょう。

    using System;
    using UnityEngine;
    using System.Collections;
    
    public class CachingLoadExample : MonoBehaviour
    {
        void Start ()
        {
            // Clear Cache
            Caching.CleanCache();
    
    #if   UNITY_ANDROID &amp;&amp; !UNITY_EDITOR
            string url = &quot;https://dl.dropboxusercontent.com/Asset.unity3d.android.unity3d
    #elif UNITY_IPHONE  &amp;&amp; !UNITY_EDITOR
            string url = &quot;https://dl.dropboxusercontent.com/Asset.unity3d.iphone.unity3d
    #else
            string url = &quot;https://dl.dropboxusercontent.com/Asset.unity3d.unity3d?dl=1
    #endif
    
            StartCoroutine (DownloadAndCache ("Particle System",
                                              url,
                                              1));
            StartCoroutine (DownloadAndCache ("Sprite",
                                              url,
                                              1));
        }
    
        public IEnumerator DownloadAndCache (string assetName, string url, int version = 1)
        {
            // キャッシュシステムの準備が完了するのを待ちます
            while (!Caching.ready)
                yield return null;
    
            // 同じバージョンが存在する場合はアセットバンドルをキャッシュからロードするか、またはダウンロードしてキャッシュに格納します。
            using (WWW www = WWW.LoadFromCacheOrDownload (url, version)) {
                yield return www;
                if (www.error != null) {
                    throw new Exception ("WWWダウンロードにエラーがありました:" + www.error);
                }
    
                AssetBundle bundle = www.assetBundle;
                if (assetName == "")
                    Instantiate (bundle.mainAsset);
                else
                    Instantiate (bundle.Load (assetName));
                // メモリ節約のため圧縮されたアセットバンドルのコンテンツをアンロード
                bundle.Unload (false);
    
            } // memory is freed from the web stream (www.Dispose() gets called implicitly)
    
            Debug.Log(Caching.IsVersionCached(url, 1));
            Debug.Log("DownloadAndCache end");
        }
    }
    

    ■gist
    AssetBundleを使うサンプル

    WWW.LoadFromCacheOrDownloadを使うのがミソっぽいです。

    ■参考リンク
    アセットバンドルのダウンロード / Downloading AssetBundles

    こちらの記事を読んでみると、WWW.LoadFromCacheOrDownloadを使うのがよさそうですね!

    AssetBundleの読み込み方法
    • 1番オススメ:LoadFromCacheOrDownload
      • ダウンロードしたものが展開された状態でストレージに保存される
        • 実行時の読み込みの際に、展開用のメモリが必要ない
        • その分、ストレージの容量は圧迫する
    • 2番目にオススメ:CreateFromFile
      • 圧縮していないAssetBundleしか使えない
      • 圧縮できないのでストレージの容量は圧迫する
    • 非おすすめ:WWW、CreateFromMemory
      • 展開に使う分のメモリが必要
      • 圧縮したAssetBundleが使える → ストレージ容量は節約できる
    • 過去の遺物:Resources
      • AssetBundleと比べて効率が悪い。今から作るならAssetBundleを使ったほうが良い

    via: Unite Japan 2013 の1日目に行ってきたメモ – 好き勝手に・げーあにん?

    これで、Buildすると各OSでもAssetBundleが使えます。
    でも、これでやっと使えるレベル、どう使う?単位は?という道はまだほど遠い。

    StreamingAssetsに配置してロードしてみる

    StreamingAssetsは簡単に言えば、ファイルをそのままアプリに持っていけるフォルダだ。そこに置いたものは何であれビルド時に余計なエンコードされること無くアプリ内に配置することができる。

    via: テラシュールウェア [Unity3D] StreamingAssetsについて

    いまいちまだStreamingAssetsをどうゆうときに使うのか分かっていないが、普通にFileアクセスはできずWWW経由でアクセスするようだ。
    これはAssetBundleにアクセスする方法と同じだから特に理解には困らないが、StreamingAssetsに置くなら、普通にAssetsディレクトリの下にPrefabとかを置くでもよいのかなーと思っていたり。
    ここは次第に実感が湧いてくるんだろう。

    このディレクトリにアクセスするなら、Streaming AssetとPersistent DataPathの注意点 Android、iOS Unity – 万年素人からGeekへの道こちらの記事が参考になる。

    では実際にファイルをロードするコードを書いてみよう。

    // StreamingAssetsからオブジェクトを取得する場合
    public IEnumerator LoadStreamingAssets(string assetName, string fileName, int version = 1) {
    #if   UNITY_ANDROID &amp;&amp; !UNITY_EDITOR
        string url = &quot;jar:file://&quot; + Application.dataPath + &quot;!/assets/&quot; + fileName;
        Debug.Log(&quot;AndroidUrl: &quot; + url);
    #elif UNITY_IPHONE  &amp;&amp; !UNITY_EDITOR
        string url = &quot;file://&quot; + Application.dataPath + &quot;/Raw/&quot; + fileName;
    #else
        string url = &quot;file://&quot; + Application.streamingAssetsPath + &quot;/&quot; + fileName; // Application.dataPath + &quot;/StreamingAssets/&quot; + fileName
    #endif
        Debug.Log("AssetBundleUrl: " + url);
    
        // キャッシュシステムの準備が完了するのを待ちます
        while (!Caching.ready)
            yield return null;
    
        // 同じバージョンが存在する場合はアセットバンドルをキャッシュからロードするか、またはダウンロードしてキャッシュに格納します。
        using (WWW www = WWW.LoadFromCacheOrDownload (url, version)) {
            yield return www;
            if (www.error != null) {
                throw new Exception ("WWWダウンロードにエラーがありました:" + www.error);
            }
    
            AssetBundle bundle = www.assetBundle;
            if (assetName == "")
                Instantiate (bundle.mainAsset);
            else
                Instantiate (bundle.Load (assetName));
            // メモリ節約のため圧縮されたアセットバンドルのコンテンツをアンロード
            bundle.Unload (false);
    
        } // memory is freed from the web stream (www.Dispose() gets called implicitly)
    
        Debug.Log(Caching.IsVersionCached(url, 1));
        Debug.Log("DownloadAndCache end");
    }
    

    各パスの説明は、ストリーミングアセット / Streaming Assetsに記載されている。

    あと、どうもversionを上げてもキャッシュを見に行ってしまうことがあるようで、その場合はurlの後ろにnow的なものを付けてアクセスするのが良さそうです。

    System.DateTime.Now.ToString("yyyyMMddhhmmss")
    

    こんな感じをQueryStringに含めるのがいいんですかね。

    アセット依存関係の管理 / Managing asset dependencies

    Unity – Unity Manual

    複数のAssetBundleに同じオブジェクトやシェーダーが含まれてしまう場合は、まったく別でメモリ確保されてしまうため、
    依存関係を管理することができるようです。

    BuildPipeline.PushAssetDependencies();
    BuildPipeline.PopAssetDependencies();
    

    このメソッドを使うようですが、結構ハードコーディングが必要になりそうだなーという印象です。

    keijiroさんのgithubリポジトリがすごく参考になります。
    keijiro/unity-shader-bundle

    参考リンク

    「もののけ大戦“陣”」製作事例
    Unity での asset bundle による追加コンテンツの扱い方
    Asset bundleなどの、Unity3d基礎知識

    こちらは間違いなく見たほうがよい。
    [Unite Japan 2013]シーン/メモリ/アセットバンドル