unityでいってみよう!

unityがチョットワカル位の人のブログ

ComponentをスクリプトからResetしてみよう!

概要

Inspectorから各ComponentのコンテキストメニューにあるResetを実行することで、Componentの値を初期値にリセットすることが出来ますが、それをスクリプトから実行したいといお話しです。

 

結論

UnityEditor.Unsupported.SmartReset(UnityEngine.Object obj);

こちらのAPIの引数として該当するComponentを指定することで、そのComponentを初期値へリセットすることが出来ます。但し、Unsupportedという物騒なclassの中にあるメソッドですので、どんなトラブルが起きても誰も責任を取ってくれないことに注意して下さい。こちらで確認した限りではUnity2018.2から追加されており、Unity2023.1でも利用可能です。

また、UnityEditor空間の下にあるクラスである為、Editor上でのみ実行出来るAPIであることに注意して下さい。(Runtimeでは実行できません)

参考

github.com

 

Object.FindObjectsByTypeをつかってみよう!

まとめ

  • Object.FindObjectsOfType(Type type)の代わりにObject.FindObjectsByType(Type type, FindObjectsSortMode sortMode)を使用すると処理負荷が軽減します。
  • Object.FindObjectsOfType (type)としている箇所をObject.FindObjectsByType(type, FindObjectsSortMode.None)へ置換するだけなので作業コストは小さいものの処理負荷削減の効果があります。
  • 互換性の問題がある部分に関しては、Object.FindObjectsByType(type, FindObjectsSortMode.InstanceID)へ置き換えて下さい。処理負荷はObject.FindObjectsOfType(type)と変わらないですが、Unity2023以降も引き続き使用することが可能です。

詳細

Unity2023.1でAPI Object.FindObjectsByType()が追加されました。 このAPIでは引数としてFindObjectsSortModeが指定できるようになっており、FindObjectsSortMode.Noneを指定すると内部的なソートが行われない為、Object.FindObjectsOfTypeと比較して処理負荷が軽減される効果が期待できます。つまり、Object.FindObjectsOfTypeでは内部的にInstanceID順に並び替えを行っていましたが、ユーザーの視点から考えると不要である為、ソート処理をスキップすることでその分処理負荷が見込めるという訳です。 Object.FindObjectsOfTypeとの互換性を保ちたい場合は、FindObjectsSortMode.InstanceIDを指定することでObject.FindObjectsOfTypeと同様の結果を得ることが出来ます。但し、この引数で得られる結果として既に述べた通り、取得できるオブジェクトがInstanceID順に並んでいるか否かである為、その結果に依存するということはあまり関係ない筈です。 (UnityEditorでもこのAPI(から呼ばれるインターナル関数)を利用しており、そこではInstanceID順で並んでいる必要があった訳です。)

// 置き換える例
#if UNITY_2023_1_OR_NEWER
// meshsの配列の中身は不規則に並んでいる
var meshs = FindObjectsByType(typeof(Mesh),FindObjectsSortMode.None);
#else
// meshsの配列の中身はインスタンスIDでソートされている
var meshs = FindObjectsOfType(typeof(Mesh));
#endif

以上です。
この記事が参考になった方は読者になるボタンを押して頂けますと幸いです。

AssetBundleのモヤモヤを調べてみよう!

はじめに

AssetBundleの処理の流れがモヤモヤしていたので少し調べてみました。 調査を行ったUnityのバージョンはUnity2021.3.13f1です。他のバージョンではここで記載した内容と異なっている可能性があります。 また、自分の調査に不備があり間違っている可能性もありますが、自己責任でお願いします。

AssetBundle.LoadFromFile

AssetBundle.LoadFromFileの処理のシーケンスは大まかには下記の通りでした。

  1. 圧縮されたままのAssetBundleファイル全体をヒープ上に読み込む
  2. リングバッファを経由してAssetBundleをヒープ上に解凍する(非圧縮の場合は未調査です)
  3. PersistentManagerに展開されたAssetBundleを登録する

これまで、Texture等の巨大なサイズのリソースはこのタイミング(LoadFromFile)ではメモリへ読み込まれていないのかなと淡い期待をもっていましたが、そいう訳ではありませんでした。(残念) また瞬間的には、圧縮されたままのAssetBundleと解凍された状態のAssetBundleの両方がヒープ上に置かれていることに注意して下さい。 また、ユーザーから見た時、AssetBundleファイルは色々なAssetを含んだファイルという印象ですが、システム的な観点からとらえるとAssetBundleファイルはストレージ(もしくはディスクイメージ)でCABファイルはストレージ内のディレクトリすると色々合点が行きます。ストレージ(AssetBundle)はLoadFromFileでマウンとして、Unloadでマウントしたストレージを取り出す訳です。

AssetBundle.LoadAsset

AssetBundle.LoadAssetは名前から、ファイルIDとパスIDを引き出して、ファイルIDからどのディレクトリ(CAB)に置かれている何番目のファイルかを求め、シリアライズデータを読み込みAssetを生成します。Assetがリソースの場合、resSファイルからストリームを読み込みます。(メモリーに乗っているので高速)

まとめ

個人的にはLoadFromFileではヘッダーおよびシリアライズファイル部分のみメモリーに読み込んで、サイズの大きいテクスチャ等が含まれてresSファイルは実際にAssetを読み込むAPIである、LoadAssetのタイミングでメモリーに乗ると思っていましたが以外でした。

以上

セーフエリアとカットアウトについて解説してみよう!

概要

iPhoneX以降ディスプレイ内に内蔵カメラ等が組み込まれたことにより、画面上に表示されない領域(いわゆるノッチ)というものが現れました。一方、Androidプラットフォームでもノッチが組み込まれたデバイスがリリースされるようになり、Androidではノッチをカットアウトと呼んでいます。 公式ドキュメントによると、カットアウトに公式に対応しているデバイスAndroid 9(APIレベル28)、Android8.1以前でも一部のデバイスメーカーでは独自にサポートしているものもあるようです。 また、カットアウトのレイアウトはメーカーが好き勝手に決めてよいわけではなく、下記のルールがに関する記載があります。

  • 1 つの辺に最大で 1 つのカットアウトを含めることができる。
  • バイスに 3 つ以上のカットアウトを含めることはできない。
  • バイスの長辺にカットアウトを含めることはできない。
  • 特別なフラグが設定されていない縦向き表示の場合、ステータスバーを少なくともカットアウトの高さまで拡張する必要がある。
  • 全画面表示または横向き表示の場合、デフォルトでカットアウト領域全体をレターボックス表示する必要がある。

Unityのノッチ対応

このノッチ(カットアウト)に対応する為にUnityではスクリプトリファレンスを見るとUnity2017.2からsafeAreaに、Unity2019.4からcutoutsというプロパティをScreenクラスへ追加し対応を行っています。

セーフエリアとカットアウトの違い

セーフエリアは画面からノッチ部分を含まない矩形領域を指していますが、カットアウトはノッチ部分の矩形領域を指しています。 また、カットアウトは一つとは限らず、Androidでは最大3個の定義が可能です。(その為、cutoutsプロパティは配列になっています)

この違いをビジュアルで説明する為にサンプルプロジェクトGithub上で公開しています。

下記の画像の青の部分がセーフエリア、緑の部分がカットアウトを視覚化した矩形領域です。 セーフエリアのみに描画するよりも、カットアウトを避けるようにレイアウトした方がより画面が広く使えることが分かります。

https://user-images.githubusercontent.com/29646672/214475190-42213393-ae81-44a4-b66a-6c94c5d086b7.png

また、最近のスマートフォンの画面の4隅は丸くなっていますが、Android端末ではその部分はセーフエリアに含まれてしまっており、カットアウトの対象にも含まれていない為、描画が欠けてしまう可能性があることに注意が必要です。

iOSとカットアウト

iPhone11ではカットアウトが確認できますがiPhone14ではカットアウトが確認出来ませんでした。 また、Androidと異なりiOSでは画面の四隅のラウンド部分が含まれないようにセーフエリアが設けられているという部分の違いもあります。

[

iPhone11実機での結果

シュミレーターでの実行結果
その答えはUnityが出力するxcodeのプロジェクトに含まれているUnityView.mmで定義されているGetCutoutToScreenRatioにあります。

Apple does not provide the cutout width and height in points/pixels. They *do* however list the size of the cutout and screen in mm for accessory makers. We can use this information to calculate the percentage of the screen is cutout.
This information can be found here - https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf

iOSにはカットアウトを取得する為のAPIが用意されていない為、Unityはアクセサリーデザインドキュメントを見て値を設定しているようです。この関数内を見る限り、Unity2021.3.13f1ではiPhone14用の係数が定義されていません。 新しいiPhoneがリリースされたら、それに対応したUnityへバージョンアップするか、上記デザインドキュメントを見ながらこの関数を修正する必要があります。(なんてこった!)

まとめ

  • Android 9以上であればセーフエリア、カットアウト共に取得可能
  • iOSではセーフエリア・カットアウト共に取得可能であるが、カットアウトに関しては新しめの機種はUnity側が対応していない可能性がある
  • ディスプレイの四隅が丸みを帯びている場合、Androidはセーフエリアに4隅が含まれている為、描画されない領域がでてしまう可能性があるが、iOSではセーフエリアに4隅が含まれないようなマージンが設定されている

Canvasにセーフエリアを設定する術は下記が分かり易そうです。 tsubakit1.hateblo.jp

参考資料

developer.apple.com

以上

AssetBundleをダンプしてみよう!

目的

AssetBundleをダンプしてAssetBundleの構造を理解します。

検証環境

Unity2021.3l7f1で検証を行いましたが、Unity2017以降であれば特に変更は無いと思います。

Tools

AssetBundleをダンプする為にはWebExtract・binary2textの2種類のToolを使用します。

これらのToolはUnityのインストール先ディレクトリ以下のEditor\Data\Toolsにありますのでこちらにパスを通すなりしてご利用下さい。

※これらのToolは念の為、AssetBundleをビルドしたUnityと同じバージョンのものをご利用下さい。

尚、これらのToolをUnityEditor上からGUIで実行するUnityCommandLineToolsというパッケージがありますので、そちらを利用するとコマンドラインから実行する必要が無いので便利です。

 WebExtract

圧縮されたAssetBundleを解凍する為のToolです。

コマンドライン

WebExtract AssetBundleFilePath

引数としてAssetBundleのパスを渡すのみで、オプション等はありません。

binary2text

Serializeされたファイルを人間が読めるテキストファイルへ変換するToolです。

コマンドライン

binary2text シリアライズファイルへのパス 出力先ファイルへのパス [-detailed][-largebinaryhashonly][-hexfloat]

指定可能なオプションは下記の通りです。

-detailed:詳細情報を付与します。

オプション無し

ID: -8471220842657547617 (ClassID: 115) MonoScript

オプションあり

ID: -8471220842657547617 (ClassID: 115)     0: MonoScript [size: 40, children: 6 pathID: -1417479521]

-largebinaryhashonly:巨大なバイナリデータはHash値のみを出力します

オプションなし

    m_FontData  (vector)
        size 350200 (int)
        data (char) #0: 0 1 0 0 0 19 1 0 0 4 0 0 F F T M a 16 139 S 0 5 W 220 0
        data (char) #25: 0 0 28 G D E F } 254 t 1 0 5 3 4 0 0 0 136 G P O S 17 176
        data (char) #50: r 161 0 5 12 140 0 0 K N G S U B O Q . 14 0 5 3 188 0 0 8
        data (char) #75: 208 O S / 2 0 166 203 151 0 0 1 184 0 0 0 ` c m a p 18 152 [ 196
        data (char) #100: 0 0 * 132 0 0 6 . c v t   J 218 K 250 0 0 ; 172 0 0 2 136 f
        data (char) #125: p g m ~ a 182 17 0 0 0 180 0 0 7 180 g a s p 0 24 0 9 0 5
...(以下略)...
    m_Ascent 14.4844 (float)

オプションあり

m_FontData  (vector)
    size 350200 (int)
        ArrayDataHash 63eb1d53e6c1708c
m_Ascent 14.4844 (float)

-hexfloat:float1をHexで出力します

オプション無し

m_DefValue[0] 1 (float)
bytes[0] 213 (UInt8)

オプションあり

m_DefValue[0] 1(0x3f800000) (float)
bytes[0] d5 (UInt8)

解凍・テキスト化!

早速、WebExtract/binary2textをそれぞれ利用して解凍 → テキストファイル化を行って行きます。

解凍

AssetBundleのファイル名がrawimageprefabの場合、解凍するコマンドは次の通りです。

WebExtract rawimageprefab

コマンドを実行すると、AssetBundleと同じ階層にAssetBundle名のディレクトリが作成されています。 そのディレクトリの中にはCAB-から始まる拡張子が無いファイルと拡張子が.resSのファイルが出力されます。 拡張子が無いファイルがシリアライズファイルで、.resSがリソースファイルです。2

CABファイルにはAssetをシリアライズ化したデータで構成されています。 また、既に記載した通り、AssetをAssetBundle化した場合はシリアライズファイル名はCAB-となりますが、SceneをAssetBundle化した場合はCAB-ではなくBuildPlayer-YYYY(YYYYはScene名)となります。(Addressablesでビルドした場合はBuildPlayer-とならないようです。)

CAB-の後ろに続く文字列はAssetBundleのファイル名をHash化したものです。 Hash値はAssetBundleに埋め込まれている為、AssetBundleをバイナリエディタ等で確認するとHash値を確認する事が出来ます。

テキストファイル化

上記で解凍したシリアライズファイル(拡張子が無いファイル)をbinary2textに渡すことで人間が読めるテキストファイルに出力することが出来ます。

オプションは必要に応じて指定すれば良いと思いますが、バイナリデータが含まれていると可読性が落ちる為、-largebinaryhashonlyを指定することをお勧めします。

上記で解凍したシリアライズファイルをテキストファイルへ変換するコマンドは下記の通りです。

binary2text CAB-82deffcb2a40184f7f37651f72bd2b50 CAB-82deffcb2a40184f7f37651f72bd2b50.text -largebinaryhashonly

テキストファイル解説

上記で作成したテキストファイル(CAB-82deffcb2a40184f7f37651f72bd2b50.text )を確認していきましょう!

External References

まず初めにExternal Referencesという行から始まっていることを確認出来る筈です。 その次の行からpathという文字から始まる行が続いていると思います。3 このブロックにはAssetBundle内から他のAssetBundleやリソースファイルを参照している場合、そのファイルへのパスが追加されます。

External References
path(1): "archive:/CAB-5bc189da2eff62b169e56057bb58bfdf/CAB-5bc189da2eff62b169e56057bb58bfdf" GUID: 00000000000000000000000000000000 Type: 0
path(2): "Library/unity default resources" GUID: 0000000000000000e000000000000000 Type: 0

path()で括られている数値は後程説明するm_FileIDの値です。 CAB-に続く文字列はAssetBundleのファイル名をHash化したものです。AssetBundleのファイル名そのものでは無い為、可読性が悪いですが、Hash値はAssetBundleファイルに含まれている為、一致するAssetBundleを見つける為にはAssetBundleファイルをgrepして下さい。

オブジェクトの定義

External Referencesブロックに続いて、IDから始まる行が確認できる筈です。 この行は一つのAssetを定義するブロックの先頭になります。

ID: 1 (ClassID: 142) AssetBundle

IDに続く数値はAssetBundle内でのオブジェクトIDです。このAssetBundle内では重複しませんが、他のAssetBundleとは重複しています。ClassIDに続く数値はYAML形式のClassIDで、その後にClassIDと対になる実際のClass名が続きます。 次の行からは、そのオブジェクトのプロパティ情報で構成されます。 プロパティ情報は、

プロパティ名  プロパティの値  プロパティの型 

で構成されます。

m_Name "rawimageprefab" (string)

プロパティ名の後にPPtrが続く場合、そのプロパティがポインタである事を表しています。

ID: 5387879631776031585 (ClassID: 224) RectTransform
    m_GameObject  (PPtr<GameObject>)
        m_FileID 0 (int)
        m_PathID 8680877022782407731 (SInt64)
...

ポインタはm_FileIDとm_PathIDから実態の場所を検索することが可能です。

m_FileID :実態が存在するファイルID 0:このAssetBundle内に存在している 0以外:External Referencesで指定されているpath(数値)

プロパティ名の後にPPtrが続く場合、そのプロパティがポインタである事を表しています。

ID: 5387879631776031585 (ClassID: 224) RectTransform m_GameObject (PPtr) m_FileID 0 (int) m_PathID 8680877022782407731 (SInt64) ...

m_PathID:オブジェクトID

上記の例では、m_FileIDが0、m_PathIDが8680877022782407731 とあるので、このファイル内で8680877022782407731 を検索すると下記のように定義されていることが分かります。

ID: 8680877022782407731 (ClassID: 1) GameObject
    m_Component  (vector)
        size 3 (int)
        data  (ComponentPair)
            component  (PPtr<Component>)
                m_FileID 0 (int)
                m_PathID 5387879631776031585 (SInt64)
        data  (ComponentPair)
            component  (PPtr<Component>)
                m_FileID 0 (int)
                m_PathID -4813361763885006494 (SInt64)
        data  (ComponentPair)
            component  (PPtr<Component>)
                m_FileID 0 (int)
                m_PathID 7863041924028432304 (SInt64)
 
    m_Layer 0 (unsigned int)
    m_Name "RawImagePrefab" (string)
    m_Tag 0 (UInt16)
    m_IsActive 1 (bool)

基本的には以上です。

最後に

テキストファイルを読む場合、先ず初めにExternal Referencesを確認し、次にID: 1 (ClassID: 142) AssetBundleを検索するのが良いでしょう。このブロックはAssetBundleクラスであり、m_PreloadTableプロパティで定義されているオブジェクトを順番に見ていくと、このAssetBundleに含まれているオブジェクトや他のファイルに依存しているオブジェクトを確認することが出来ます。


  1. floatだけではなくbyte値もHexで出力します。(むしろbyte値のHex化が本命?)
  2. Textureのようなシリアライズがされないデータが含まれる場合、シリアライズファイルとは別にシリアライズファイルと同盟で拡張子がresSのファイルが合わせて作成されます。
  3. AssetBundleが他のファイルと依存関係が無い場合は、pathから始まる行は存在しません。

UnityEditorからadbコマンドを実行してみよう!

概要

Android SDKに含まれるadbコマンドをUnityEditor上から実行する方法に関するお話しです。

結論

スクリプトリファレンスには記載が無いですが、実はUnityEditor.Android配下にADBクラスが存在しているのでそれを使えば簡単にUnityEditor上からadbコマンドを実行することが可能です。

スクリプトリファレンス

ADB

class in UnityEditor.Android

説明

UnityEditorからadbコマンドを実行する為のシングルトンクラスです。

Public関数

GetInstance

public static ADB GetInstance()

説明

ADBクラスのインスタンスを取得します。 ※ADBクラスはシングルトンであり、newではなくこちらのAPIを使用してインスタンスを取得します。

GetADBPath

public string GetADBPath()

説明

UnityEditorが参照しているadbへのパスを取得します。

Run

public string Run(string[] command,string errorMsg)

パラメータ
command adbコマンドに渡す引数
errorMsg エラーが発生した場合に表示する文字列
説明

引数で指定したコマンドをadbで実行します。

実行例

接続されているデバイス上で(X,Y)=(100,200)のスクリーン座標でTouchイベントを発生させる

var x = 100;
var y = 200;

ADB.GetInstance().Run(new string[] { "shell", "input", "touchscreen", "motionevent", "DOWN", $"{x}", $"{y}" }, "");

Runの引数が文字列の配列になっている為、コマンド単位で区切っていますが、string.Join(" ", command)としているだけなので、初めから連結して渡しても問題ありません。

接続されているデバイス上でバックボタン(ESCAPE)イベントを発生させる

ADB.GetInstance().Run(new string[] { "shell","input","keyboard","keyevent","4" }, "");

AndroidDeveloperリファレンスによるとESCAPEのキーコードは4です。

その他

エラーが出た場合、他の誰かがadbを実行している可能性が高いです。 タスクマネージャーからadbのプロセスをKILLしてから改めて試してみてください、

以上

BuildAssetBundleOptionsを解説してみよう!

概要

AssetBundleのビルドオプションであるBuildAssetBundleOptionsに関しての解説です。 スクリプトリファレンス読めば判ることから何を言っているかわからんことまで解説しています。

尚、Unity2021.3時点での内容で記載しています。

AssetBundleのファイル名に影響を与えるオプション

AppendHashToAssetBundleName

AssetBundleのファイル名にHash値を追加します。

例)

sample -> sample_a074e0fd9e9a81367f7390e93a632095

このオプションの意図としては、既存のAssetBundleに変更を加えた場合に同じ名前の場合、なんらかの原因でキャッシュが更新されないというトラブルを回避する為に意図的に名前を変更し別ファイルとすることでその問題を回避する為に用意されていると思われます。

AssetBundleの中身に影響を与えるオプション

AssetBundleStripUnityVersion

このオプションを有効にすることで、AssetBundleからUnityのバージョンを取り除くことが出来ます。 これによりUnityのバージョンアップを行った際に、AssetBundleの中身がバージョン番号以外には差異が無い場合、このオプションを有効にすることで同一ファイルとみなすことが出来ます。 バージョン番号自体が何かに利用されていることも無さそうなので、積極的に有効にすべきオプションの一つです。

DisableWriteTypeTree

AssetBundleにTypeTreeの情報を含めなくなります。TypeTreeとはざっくりいうとAssetBundleに含まれるClassのTypeに関する情報です。 通常はAssetBundleに含まれるAssetがどの様なClassであるのかという情報が含まれているのですが、このオプションを有効にするとAssetのClassIDとシリアライズ情報のみになり、Unityや使用しているパッケージのバージョンが固定されていれば問題ありませんが、AssetBu構成要素の不一致が発生した時、正常に動作しないリスクは高まります。 このオプションを有効にすることでファイルサイズの削減が見込める為、リスクを背負う覚悟があるのであれば有効にしてみるのもありですが、全くお勧め出来ません。

AssetBundleのビルド条件に影響を与えるオプション

ForceRebuildAssetBundle

AseetBundleの構成内容変更の有無に関わらず、全てのAssetBundleをビルドの対象として処理します。 兎に角フルビルドをかけるという時に有効にします。

StrictMode

ビルド中にエラーが発生した場合、ビルドを停止します。 通常、停止しないエラーにはシェーダーコンパイル等があるようですが詳細は未確認です。 エラーを無視すると後で困った事になる為、有効にしておきましょう。

IgnoreTypeTreeChanges

AssetBundleのHash値に変化があった(AssetBundleの構成要素に変化があった)場合、そのAssetBundleをBuildの対象とします。 AssetBundleのHashの求め方はこれはこれでボリュームがあるのでここでは割愛しますが、AssetBundleに含まれるClassのTypeTree(Classを構成する情報)のHash値を比較するか否かを設定します。 このオプションを有効にすると、AssetBundleに含まれるClassにUnityのバージョン間で差異(例えば、変数の数が増減している等)があったとしてもAssetBundleはビルドの対象とはなりません。 「Classの内容に変更があってもUnityがきっと何とかしてくれる」と信じている人はオプションを有効にしてみると良いかもしれませんが、全くお勧めしません。

LoadAsset時に使用する引数に制限をかけるオプション

AssetBundleからAssetをロードするAPIであるAssetBundle.LoadAsset(string name)nameに使用出来る文字列を制御する為のオプションです。nameの部分には通常、フルパスのアッセトファイル名(例 Assets/Prefabs/Player.prefab)、アセットファイル名(例 Player)、拡張子付きアセットファイル名(例 Player.prefab)の何れかを使用することが出来ますが、制限を付ける事によりAssetBundleがロードされた時のランタイムメモリを削減する効果が期待できます。(AssetBundleのサイズ自体には変化が無いことに注意して下さい。) 1個あたりはたいしたことはないですがちりも積もればということで可能であれば有効にしておくべきでしょう。

DisableLoadAssetByFileName

アセットファイル名(例 Player)でのアクセスを無効化します。

DisableLoadAssetByFileNameWithExtension

拡張子付きアセットファイル名(例 Player.prefab)でのアクセスを無効化します。

その他

DryRunBuild

ビルドプロセスの内、AssetBundleManifestの生成迄を行います。 AssetBundleの依存関係やHash値などの確認のみ行いたい場合に便利です。

DeterministicAssetBundle

有効な場合、アセットバンドルに保管されているオブジェクト ID のハッシュを使用して、アセットバンドルを作成するとありますが、設定の如何に関わらずこのオプションは常に有効として扱われます。 よって意味の無いオプションとなります。