テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】AssetBundleが使用しているアセット一覧を渡してlink.xmlを生成する

問題:AssetBundleからセットを取得する時にCould not produce class with ID XXXが出る

 AssetBundleを使用している場合、AssetBundleでしか使用していないアセットのコードがビルドからストリップ(削除)されてしまう事があります。特にIL2CPPを利用環境ではよく起こる現象で、iOSAndroidといったプラットフォームを本番向けにビルドすると起こるかもしれません。

 この挙動は特にAssemblyDefenitionFile等でアセンブリが分かれている時に顕著に起こります。ポジティブに言えばAssemblyDefenitionFileを分割すればビルド速度やサイズを削減できると言えますが、悪く言えば非常に簡単にコードがストリップされます。特にUnity 2018から多くのUnity標準機能がpackageに移行しAssemblyDefenitionFileで分割されているので、割と簡単にコードストリップによる動作不全を起こすかもしれません。例えば2D系機能やTimeline等もそれに該当します。

 例えば下の画像では「スプライト画像を表示するTimelineを含むPrefab」をAssetBundleに格納し実行時に生成するというコードを実行した結果です。「Could not produce class with ID 212 (SpriteRendererのコードが無いよ!)」のエラーが発生しています。実はコレを解決しても次にTImelineについてのエラーが出ます。

f:id:tsubaki_t1:20191102202407j:plain
AssetBundleでしか使用していないコンポーネントを使用するとよく見る風景

解決方法:コードがストリップされないようにする

 この解決法は大雑把に3つです。

  1. link.xmlホワイトリストを記述する
  2. ストリップされないコード(AssemblyDefenitionFile等で分割されていないコード)からクラスを参照する
  3. ResourcesやScene等から指定のコンポーネントやアセットを参照する

 正規ルート1です。本来はこちらでやるべきです。 とは言え、正直なところlink.xmlを記述するのは面倒くさいです。ゲームをプレイしてエラーを探す(≒クラッシュするまで色々と試す)は完全に理にかなっていないです。

f:id:tsubaki_t1:20191102204518j:plain
ゲームが使用しているコンポーネントホワイトリスト一覧に記述

 なのでは「他のスクリプトや標準で含まれるアセットから参照されているので消されない」という考えを使用して、とりあえずゲームが使用しているPrefab(データを抜いた物)をResourcesやゲームに含めるSceneにでも放り込むという力技も考えられます。面倒なことに頭を悩ませたくない場合の力技として有効な一手です。ただ「気づいたら含まれている」ケースが多いので、link.xmlで記述するのが正規ルートでしょう。

f:id:tsubaki_t1:20191102204416j:plain
Resourcesやゲームに含めるSceneに入れるでもホワイトリスト入りはする

解決手順:link.xmlホワイトリスト)を自動生成する

 link.xmlを自動生成する方法を考えてみます。内容は単純で「AssetBundleに使用しているアセットが使用しているコンポーネント、及び、AssetBundleに格納したデータをlink.xmlに格納する」というものです。ルールは下の通りなので上辺のコンポーネントとデータを網羅しておけば、概ね問題ないという判断です。

  1. link.xmlに指定されているコンポーネントはストリップ対象にならない
  2. ストリップ対象ではないコンポーネントが使用しているクラスはストリップの対象にならない

手順1:LinkXmlGeneratorの導入

まず下のLinkXmlGeneratorを適当なEditorフォルダ以下に配置します。

AssetBundleNameLinkXMLGenerator.cs · GitHub

手順2:link.xmlを生成

 LinkXmlGeneratorでホワイトリストを作ります。覚えておくべきAPIは2つです。 AddAssetsにAssetBundleが使用しているアセットを全部登録して、Saveを実行。あとはlink.xmlをマージするなり、普通にAsset直下に配置すればOKです。

 AssetBundlenameを使用せずAssetBundleを生成している場合、このAPIでアセットを登録する感じです。

// アセットが使用しているクラスをホワイトリストに登録する
LinkXmlGenerator.AddAssets(string[] assetPaths);

// link.xmlをpathに書き出す
LinkXmlGenerator.Save(string path);

 逆に、AssetBundle nameを使用している場合は下のコードで行けます。
 導入後、メニュー>Assets>CreateLink.xmlでファイルを作ってくれます。

using UnityEditor;
using UnityEngine;

public class AssetBundleChecker 
{
    [MenuItem("Assets/CreateLink.xml")]
    static void Create()
    {
        var generator = new LinkXmlGenerator();
        // UnityEditor.Animations.AnimatorControllerを取得してしまうので、RuntimeAnimatorControllerに変更
        generator.SetTypeConversion(typeof(UnityEditor.Animations.AnimatorController), typeof(RuntimeAnimatorController));

        // AssetBundleNameを持つアセットを全て取得して、LinkXmlGeneratorに登録する。
        foreach( var bundleName in AssetDatabase.GetAllAssetBundleNames())
        {
            var assetPaths = AssetDatabase.GetAssetPathsFromAssetBundle(bundleName);
            generator.AddAssets(assetPaths);
        }

        // link.xmlファイルを保存
        generator.Save("Assets/link.xml");
        AssetDatabase.Refresh();
    }
}

f:id:tsubaki_t1:20191102204148j:plain

補足:Addressableの場合は自動で解決してくれる

 Addressableの場合は自動でlink.xmlを生成してくれます。

tsubakit1.hateblo.jp

 なお以前はlink.xmlをStreamingAssetsに生成して使用していましたが、現在はプロジェクトファイル\Library\com.unity.addressables\StreamingAssetsCopy\aa\プラットフォーム以下に格納されており、これをゲームビルド時にコピーすることで実現しています。

f:id:tsubaki_t1:20191102204217j:plain

関連

 AssetBundleManifestから使用するコンポーネントを確認するアプローチを取りたい場合は下の記事。コンポーネントが使用しているAssemblyファイルを直接探しに行くスタイルです。

tsubakit1.hateblo.jp