エイバースの中の人

アプリとWEBサービスを作っているABARSのBLOGです。

assetbundle を含む記事

Unity 5.5で使うAssetBundle

AssetBundleは、UnityのAssetを別ファイルに格納できる仕組みです。GooglePlayで配信できるapkファイルには100MBの制約があるため、アセットをAssetBundleに分離する必要があります。

まず、AssetBundleに格納するアセットをエディタで指定します。指定は、アセット単位でも、フォルダ単位でも行うことができます。フォルダに指定した場合、フォルダ内の全てのアセットが格納されます。尚、AssetBundleには階層構造が存在せず、最終階層のファイル名で識別されます。

assetbundle


AssetBundleをビルドするには、Editorスクリプトで実行する必要があります。AssetBundleにはコンパイル済みのシェーダが含まれるため、iOS向け、Android向けで異なるファイルが生成されます。Unity Cloud Buildでビルドした際のパスと合わせるため、StreamingAssets/AssetBundles/iOS or Androidに格納するのがオススメです。

using UnityEditor;
using UnityEngine;

using System.IO;

public class CreateAssetBundles
{
    [MenuItem("Assets/Build AssetBundles")]
    static void BuildAllAssetBundles()
    {
        var platform = "Standalone";
        #if UNITY_ANDROID
            platform="Android";
        #endif
        #if UNITY_IOS
            platform="iOS";
        #endif

        if (!Directory.Exists(Application.streamingAssetsPath+"/AssetBundles"))
        {
	        Directory.CreateDirectory(Application.streamingAssetsPath+"/AssetBundles");
	    }

        if (!Directory.Exists(Application.streamingAssetsPath+"/AssetBundles/"+platform))
        {
            Directory.CreateDirectory(Application.streamingAssetsPath+"/AssetBundles/"+platform);
        }

        Debug.Log("Build asset bundles for "+EditorUserBuildSettings.activeBuildTarget);
        BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath+"/AssetBundles/"+platform, BuildAssetBundleOptions.None, EditorUserBuildSettings.activeBuildTarget);
    }
}


生成したAssetBundleは、 WWW.LoadFromCacheOrDownloadで読み込むことができます。 WWW.LoadFromCacheOrDownloadを使用した際、キャッシュに存在すればキャッシュから、存在しなければ指定したパスから読み込みます。キャッシュに存在するかどうかは、Caching.IsVersionCached(url,BUNDLE_VERSION)で取得することができ、キャッシュのクリアはCaching.CleanCache();で行うことができます。BUNDLE_VERSIONは固定値を入れることが推奨されています。BUNDLE_VERSIONを上げた場合、旧バージョンのアセットが消去されないため、キャッシュ容量が増加し続けるためです。そのため、crc値を書き換えることで、キャッシュクリアする方法が推奨されています。crc値は、生成したAssetBundleと同じフォルダにあるmanifestファイルに記載されています。

			// ダウンロード処理
			WWW www=null;
			if(CHECK_ASSET_BUNDLE_CRC){
				www = WWW.LoadFromCacheOrDownload(url, BUNDLE_VERSION, crc);
			}else{
				www = WWW.LoadFromCacheOrDownload(url, BUNDLE_VERSION);
			}
			while (!www.isDone)
			{
				progress_cnt=www.progress;
				yield return null;
			}

			// エラー処理
			if(!string.IsNullOrEmpty(www.error))
			{
				load_failed=true;
				errror_detail=url;
				Debug.Log(www.error);
				yield break;
			}

			// Asset Bundleをキャッシュ
			assetBundleCache[bundlename] = www.assetBundle;

			// リクエストは開放
			www.Dispose();


AssetBundleには、高圧縮低速のLZMA形式と、低圧縮高速のLZ4形式があります。WWW.LoadFromCacheOrDownloadでLZMA形式をロードすると、自動的にLZ4形式に変換してキャッシュします。そのため、AssetBundleはLZMA形式で生成して問題ありません。

AssetBundleには依存関係があります。とあるPrefabをAssetBundleに格納した場合、そのPrefabに紐付いたAssetが自動的に検索され、AssetBundleに格納されます。しかし、このままだと、複数のPrefabから参照されるオブジェクトが、複数のAssetBundleに格納されることになり、ファイル容量が増大してしまいます。この問題は、複数のPrefabから参照されるオブジェクトを、別のAssetBundleに格納することで回避できます。その場合、とあるPrefabのAssetBundle.LoadAssetを呼ぶまでに、複数のPrefabから参照されるオブジェクトを格納したAssetBundleが読み込まれている必要があり、その依存関係がDependenciesに記載されています。尚、AssetBundleを読み込む順番は、Dependencies順でなくてもかまいません。あくまで、AssetBundle.LoadAssetを呼ぶまでに依存関係が解決されていればよいです。そのため、WWW.LoadFromCacheOrDownloadをCoroutineで並列化して、高速化することができます。

ManifestFileVersion: 0
CRC: 903982090
Hashes:
  AssetFileHash:
    serializedVersion: 2
    Hash: 378b24330402254e95eb1568de8f4e56
  TypeTreeHash:
    serializedVersion: 2
    Hash: a3429469e80eaecda247646a582aa377
HashAppended: 0
ClassTypes:
- Class: 1
  Script: {instanceID: 0}
Assets:
- Assets/AssersBundleResources/***/***.prefab
Dependencies:
- AssetBundles/iOS/models_***


AssetBundleからGameObjectを取得するには、Resources.Loadと同様に、assetBundle.LoadAssetで取得可能です。その際、階層構造の指定はできず、最終的なファイル名でロードします。

	// Asset BundleからGameObjectを取得
	public GameObject GetObject(string assetbundle_id,string assetName){
		try
		{
			GameObject obj=assetBundleCache[assetbundle_id].LoadAsset(string.Format("{0}", assetName));
			if(obj==null){
				Debug.Log(""+assetbundle_id+" "+assetName+" not found");
			}

	#if UNITY_EDITOR
			if(obj!=null){
				if(obj.GetComponent()==null){
					obj.AddComponent();
				}
			}
	#endif
			return obj;
		}
		catch (NullReferenceException e)
		{
			Debug.Log(e.ToString());
			return null;
		}


作成したAssetBundleは、manifestファイルと一緒に、CDNなどに上げて、ランタイムでロードします。

CDNのデータは配信の過程で破損する可能性があります。アセットバンドルが破損していた場合、本来はstring.IsNullOrEmpty(www.error)でエラーを検出することができるはずです。しかし、実際に破損したファイルを生成して読み込ませたところ、Windows、Android、Macでは正常にエラーを検出できますが、iOSの場合、エラーが返ってきません。そのため、www.assetBundle.GetAllAssetNames()を呼び出すことで、アセットバンドルが正しく読み込めたかどうかの確認が必要です。

// iOSで破損ファイルがエラーチェックを抜けるのでアセットリストを取得して例外が起きないか確認
string[] asset_list;
try{
	asset_list=www.assetBundle.GetAllAssetNames();
	if(asset_list.Length>=1){
		Debug.Log("Asset Load Check Success : "+asset_list[0]);
	}
}catch(Exception e){
	is_error=true;
	Debug.Log("Asset Load Check Failed : "+e);
}

AssetBundleでしか参照しないコードが存在すると例外が起きる

AssetBundleでしか参照しないコードが存在し、iOSビルドの設定のStrip Engine CodeがONの場合、AssetBundleからGameObjectを取得しようとした際に、PersistentManager.cppでEXC_BAD_ACCESSの例外が飛びます。対策としては、Strip Engine CodeをOFFにするとよいようです。

AssetBundleにPrefabを入れるとShaderがMissingになる

AssetBundleに敵のモデルのPrefabを入れてロードした場合、iOSの実機では動きますが、EditorではShaderがMissingになり、黒くなったり、ピンクになったりします。

本質的には、iOS向けに書き出したAssetBundleに含まれるコンパイル済みシェーダが、EDITORに対応していないのが問題なのですが、AssetBundleをEDITOR向けに書き出すのはコストが高いです。

そこで、We're looking for feedback on the artist features in the 2017.1 beta, help us out by filling outを参考に、以下のスクリプトでシェーダを当て直すとよいようです。GetComponentsInChildrenにtrueを入れることで、非アクティブのオブジェクトにも適用するように修正しています。

using UnityEngine;
using System.Collections;
 
 
public class ReApplyShaders : MonoBehaviour
{
    public Renderer[] renderers;
    public Material[] materials;
    public string[] shaders;
 
    void Awake()
    {
        renderers = GetComponentsInChildren(true);
    }
 
    void Start ()
    {
        foreach(var rend in renderers)
        {
            materials = rend.sharedMaterials;
            shaders =  new string[materials.Length];
 
            for( int i = 0; i < materials.Length; i++)
            {
                shaders[i] = materials[i].shader.name;
            }        
 
            for( int i = 0; i < materials.Length; i++)
            {
                materials[i].shader = Shader.Find(shaders[i]);
            }
        }
    }
}
Search
Profile

abars

アプリとWEBサービスを開発しています。最近はUnityとGAE/pyが主戦場。

ブラウザ向けMMOのメトセライズデストラクタ、イラストSNSのイラストブック、東証の適時開示情報を検索できるTDnetSearchを開発しています。

かつてエンターブレインのTECH Win誌でATULADOを連載しました。

サイト:ABARS
Twitter:abars
Github:abars

Twitter
TopHatenar
HotEntry
Counter

アクセス解析付きカウンター。あなたのBLOGにもどうですか?登録はこちらから。

TOP/ BLOG/ LECTURE/ ONLINE/ RUINA/ ADDON/ THREAD/ METHUSELAYZE/ IPHONE/ MET_IPHONE/ ENGLISH/ RANKING