アセットバンドルの書き出しがおかしい

ときどきアセットバンドルの書き出しがおかしく、表示がごちゃごちゃに崩れることがある。
おそらく、アセットバンドルのリソースが更新されているにもかかわらず、アプリ側のリソースの使い方が更新されていないような感じで、画面がグチャグチャになる。
iOSでよく起こるけど、大概Unityから再度Xcodeプロジェクトを書き出すと直る。
それでもダメな場合は、Unityを再起動。
わりと頻繁に起こるんだけど、僕だけ?

uGUIのサイズ調整

低スペックの端末(Android)でuGUIのテクスチャ素材が多いため落ちる現象が起きました。
本来であればデザイン素材の見直しや、オブジェクトのロード、破棄するタイミングから見直しをするべきなのですが、どうしても時間がなく、テストフェーズも終わってしまったので下記のような対応をしてみた記録。。。

まずはたくさん使われているuGUIのテクスチャのインポートセッティング。
texture_import_settings
パッキングタグを設定しつつ、Max Sizeでそのテクスチャのサイズを小さくなるように設定。
本来、パッキングタグをつけてしまえば同じパッキングタグ同士がアトラス化され、2のべき乗のサイズになるので、Max Sizeを触る意味はないのですが、直接画像の解像度を変更するのはちょっとイヤなので、、、ここで無理やりサイズ調整。
サイズ調整できるのは2のべき乗なので、あまり小さくなってしまうものはサイズを変えず、うまく小さくできそうなものだけ変更。
sprite_packer
そして、Sprite Packerにてすべての画像を確認し、余白が大きいアトラスがあれば頑張ってサイズが小さくなるように、素材を1つ1つ調整。
逆に、いくらMax Sizeを小さくしてもアトラスが小さくならないものは意味がない。
あまり良くない方法ですが、、、緊急対応ということで。

TouchScriptでの領域の制限

3D空間のタッチやフリックなどのジェスチャーに便利なTouchScriptで、部分的にタッチやジェスチャーを判定する領域を制限したかったので調べてみた。
どの空間(レイヤー)でジェスチャーをジャッジするかというのが、TouchScript.Layersパッケージにあって、主に

  • CameraLayer
  • CameraLayer2D
  • FullscreenLayer
  • UILayer
  • この4つ。
    CameraLayerはRayを飛ばして、3D空間のオブジェクトにヒットしているかの判定をする。
    CameraLayer2Dは2Dアプリ用。
    FullscreenLayerは画面にタッチしてれば当たり判定が入る。
    UILayerはuGUIのSpriteに対してヒットすれば判定が入る。

    タッチ領域の制限にはLayer Maskというプロパティがあるので、これをつかってもできるかも??ですが、今回はUILayerを使うことに。

    touch_script_uilayer

    こんな感じでUILayerのGameObjectか、その下の階層にImageなどを配置してやるとその範囲でジェスチャーを拾ってくれる。
    (*追記17.01.19 : UILayerはこの画像の構造だと動かなくなったので、Canvasなど上のGameObjectに当てた方がいいっぽい)
    ちなみにTapGestureをFlickGestureの上に置いてしまうと、先に判定が入ってしまいフリックが拾いづらいので注意。
    あとはこんな風に自作のコンポーネントのGestureControllerにてジェスチャーを登録すれば完了。

    /// <summary>
    /// ジェスチャー判断用。
    /// </summary>
    [SerializeField]
    FlickGesture flickGesture;
    [SerializeField]
    TapGesture tapGesture;
    
    /// <summary>
    /// OnEnable.
    /// </summary>
    void OnEnable() {
    	this.flickGesture.Flicked += this.OnFlicked;
    	this.tapGesture.Tapped += this.OnTapped;
    }
    
    /// <summary>
    /// OnDisable.
    /// </summary>
    void OnDisable() {
    	this.flickGesture.Flicked -= this.OnFlicked;
    	this.tapGesture.Tapped -= this.OnTapped;
    }
    

    文字列からstatic/classプロパティの取得

    staticプロパティを文字列から取得する方法がわからなかったので調べてみた。
    ついでに、文字列でオブジェクトのプロパティの取り出しとメソッドの実行も。

    class TestClass {
    	public static string StaticPropName = "StaticValue";
    	public int propName = 100;
    	public string TestFunc(int num) {
    		return "TestFuncValue : " + num;
    	}
    }
    
    TestClass test = new TestClass();
    Type testType = test.GetType();
    print(testType.GetField("propName").GetValue(test));
    // 出力 : 100
    
    print(testType.GetMethod("TestFunc").Invoke(test, new object[]{ 777 }));
    // 出力 : TestFuncValue : 777
    
    Type typeOfTestClass = typeof(TestClass);
    print(typeOfTestClass.GetField("StaticPropName").GetValue(typeOfTestClass));
    // 出力 : StaticValue
    

    CanvasGroupのinteractable

    button_interactable_canvas_group_effect

    いまさらながらですがCanvasGroup。
    いままで、alphaが下層のImageとか(CanvasRendere)に影響して、まとめて透過させることは使ってたけど、interactableも便利。

    button_interactable_canvas_group

    下層のボタンとか(Selectable)のUIパーツを一気に操作不可にしてくれる。
    もちろん、Buttonなどの個別のinteractableは保持しつつ、上層の設定値を優先してくれる。
    各UIパーツからは、IsInteractable()メソッドにて上層の設定を含めたものが取得可能。

    using UnityEngine;
    using UnityEngine.UI;
    
    /// <summary>
    /// ボタンのinteractableの情報表示。
    /// </summary>
    public class ButtonInteractableStatus : MonoBehaviour {
    	/// <summary>
    	/// ステータス表示のText。
    	/// </summary>
    	[SerializeField]
    	Text statusTxt;
    
    	/// <summary>
    	/// Button.
    	/// </summary>
    	Button button;
    
    	/// <summary>
    	/// Start.
    	/// </summary>
    	void Start() {
    		this.button = this.GetComponent<Button>();
    	}
    
    	/// <summary>
    	/// Update.
    	/// </summary>
    	void Update() {
    		this.statusTxt.text = "interactable : " + this.button.interactable + "\n"
    			+ "IsInteractable : " + this.button.IsInteractable();
    	}
    }
    

    Invokeについて

    Invokeを使うと指定秒後に指定のメソッドが実行できて便利。
    でも引数が文字列なので、それが嫌な場合は

    ((Action)MethodName).Method.Name
    

    で取得できる。
    しかもInvokeはそのGameObjectが破棄されると自動で呼び出しも終わる。
    簡易的に遅延させたい場合は便利。

    参考)
    いんでぃーづ | Invoke , Coroutine , SendMessage で文字列を使わない方法
    Qiita | 【Unity】スクリプトの処理の実行タイミングを操作する

    using UnityEngine;
    using System;
    
    /// <summary>
    /// Invokeのテスト。
    /// </summary>
    public class InvokeTest : MonoBehaviour {
    	/// <summary>
    	/// Start.
    	/// </summary>
    	void Start() {
    		this.InvokeMethod();
    	}
    
    	/// <summary>
    	/// Invokeテスト。
    	/// </summary>
    	void InvokeMethod() {
    		print("InvokeMethod called.");
    		Invoke(((Action)this.InvokeMethod).Method.Name, 3f);
    	}
    }
    

    アセットバンドルDL時の容量不足のケース

    アセットバンドルを使った場合の容量不足について軽く試してみた。
    予想としてはIOExceptionが発生するかと思っていた。実際試してみると、アセットバンドルの形式やサイズなどにもよるかもしれないけどどうやら容量が足りない場合もメモリ上(?)にて展開されているようでとくにエラーは出ず、DLが終わり通常通り処理が続く。
    当然保存されていないので、アプリを落とすと次の起動の際に再度DLされる。
    AssetBundleManagerを使っている場合は、指定のアセットを取り出す際にAssetBundleManager.m_DownloadingErrorsに“{0} is not a valid asset bundle.”というエラーが格納される。
    あるべきはずアセットがないのはアセットが正しく保存されていない(おそらく容量が足りていない)という考えで、その際に容量不足のエラーを出すようにした。

    アセットバンドルではなく、通常のファイルをDLする場合や圧縮ファイルを解凍する場合に容量が足りない場合にはIOExceptionが発生するようで、その際に容量不足のエラーを出した。

    アセットバンドル更新時の削除

    以前ここで書いたキャッシュファイルの削除について実際に、アセットバンドルを更新することがあったので具体的な補足。

    AssetBundleManagerを使っているとアセットバンドルを更新すると自動的に新たなアセットバンドルをDLしてくれる。
    ただし、以前のアセットバンドルが削除されるわけではなくて、別のアセットバンドルとして保存される。
    でも、本当は以前のものは使わないので削除してほしい、、、
    こちらのサイトでも言及されているけど、アセットバンドルを削除するのはすこし厄介なので、maximumAvailableDiskSpaceを設定した方が簡単。
    以下の行程で挙動を確認し最大キャッシュ容量を設定し、以前のアセットバンドルが消えることを確認した。

    Continue…

    UIActivityのその他(more)を押すとスクリーンの方向が変わる

    uiactivity_more

    UnityにてSocialConnectorを使って、シェア機能を使っていて遭遇したんですが、横向きのみのアプリでこのSocialConnectorを使って、iOSのUIActiveViewControllerを呼び出し、そこからその他(more)を押すと許可するメディアの一覧が出てきます。
    そのタイミングで端末の回転にロックがかかっていなく、端末自体がアプリの方向とは違う方向に傾いているとUnityのスクリーンの方向(Screen.orientation)が変わってしまった。。(横向きアプリのはずが縦向きなどになる)
    1度発生してしまうとUnity上でScreen.orientationを書き換えてももう直らない。。。
    取れる方法は、Screen.orientationをみて、本来の値と違っていたらエラーを出すとかになるでしょうか。
    Unityのバグなのか、UIActivityのバグなのか、、、SocialConnectorではなさそうな気はするけど。

    UIActivityのローカライゼーション

    Unityで、Social Connectorを使うと簡単に、UIActivityが呼び出せる。

    uiactivity

    しかし、表記が英語になってしまう。
    info.plistにてLocalization native development regionjaなどに変えれば指定の言語で表記されるようになる。
    ただ、Localization native development regionは基本言語なので、ローカライズさせるには

    UIActivity

    こちらの値を変えたほうがいいかも。
    XCodeProjectUpdaterを使う場合には、自分でつけ足す必要がある。
    キーはCFBundleLocalizations、値はJapanseではなくja

    ちなみに、LINEへの投稿を増やしたい場合には、iOS9以降許可するスキームの設定が必要。
    XcodeProjectUpdaterにてApplication Queries Schemesの値を追加してみたけど、うまく反映されていなかった。
    みてみたらSetApplicationQueriesSchemesの処理が抜けているようなので(僕が消した??)、その処理も加えて無事LINEへの投稿もできるようになりました。