Asset Bundle 名を自動で設定して Google Cloud Storage 経由で取り扱う

# はじめに

* [前回](http://qiita.com/nirasan/items/6a6d2c45077f5e6605e4) Google Cloud Storage に AssetBundle をアップロードするところまでできた
* 今回は AssetBundle の命名規則を決め、自動で設定し、アップロードし、ダウンロードして表示するところまでやってみる

# リソースと AssetBundle 名の対応を定義する

* 今回はアイテムの画像を AssetBundle で管理する場合を考える
* ResourceConstant クラスを定義して、アイテムの id を渡したら AssetBundle 名と Asset 名が取得できるようにする

```cs
public class ResourceConstant
{
	public class ResourceInfo
	{
		public string AssetBundleName;
		public string AssetName;
	}

	/// * アイテムのリソース情報取得
	/// * id は 10000 で割った値をひとつのシリーズとする
	///   * 初期シリーズを 10000 番台とする
	///   * アプリの更新があればシリーズ番号を追加していく想定
	///   * アプリの更新があればシリーズ番号を追加していく想定
	/// * ファイルは Assets/ExternalResources/items/[SERIES]/[ID].png というパスに保存する
	/// * AssetBundle 名は item-[SERIES] になる
	/// * Asset 名は [ID] になる（拡張子なし）
	public static ResourceInfo GetItem (long id)
	{
		var series = id / 10000L;
		return new ResourceInfo {
			AssetBundleName = string.Format ("{0}/item-{1}", AssetBundles.Utility.GetPlatformName (), series),
			AssetName = id.ToString (),
		};
	}
}
```

# Asset 保存時に AssetBundle 名を自動で設定する

* `AssetPostprocessor` を継承したクラスを定義するとインポート前後に実行されるメソッドを実装することができる
* 今回は画像リソースを扱う想定で `OnPreprocessTexture` メソッドを実装して画像のインポート前に AssetBundle 名を設定する
* 以下のスクリプトを任意の Editor ディレクトリ以下に保存する

```cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;

public class TexturePostprocessor : AssetPostprocessor
{
	const string basePath = "Assets/ExternalResources/";

	// Textureファイルのインポート設定 Textureファイルがインポートされる直前に呼び出される
	void OnPreprocessTexture ()
	{
		// インポート時のTextureファイルを設定するクラス
		var importer = assetImporter as TextureImporter;

		Debug.Log ("importing texture: " + importer.assetPath);

		// Assets/ExternalResources/items/ 以下のファイルを処理対象にする
		if (importer.assetPath.Contains (basePath + "item/"))
		{
			// Assets/ExternalResources/items/1/10001.png の AssetBundle 名を items-1 とする
			importer.assetBundleName = Path.GetDirectoryName (importer.assetPath).Replace (basePath, "").Replace ("/", "-");
			return;
		}
	}
}
```

# AssetBundle をビルドしてアップロードする

* http://qiita.com/nirasan/items/6a6d2c45077f5e6605e4 の通りにおこなう
* ダウンロード時に認証ができないため今回はとりあえずコンソールから各ファイルの公開権限を一般公開に変更した

# AssetBundle をダウンロードして画像を表示する

* AssetBundle Manager を使って AssetBundle をダウンロードして Cube のテクスチャとして表示してみる
* ほとんど AssetBundle Manager のサンプルのまま
* 異なる点として `Initialize` の `AssetBundleManager.SetSourceAssetBundleURL` でダウンロード先の URL を指定している
* さらに `Start` では読み込む AssetBundle 名と Asset 名を ID から導出して指定している

```cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using AssetBundles;

public class AssetBundleLoadTestScene : MonoBehaviour
{
	public string assetBundleName;
	public long id;
	public GameObject cube;

	// Use this for initialization
	IEnumerator Start ()
	{
		yield return StartCoroutine (Initialize ());

		// Load asset.
		var info = ResourceConstant.GetItem (id);
		yield return StartCoroutine (InstantiateGameObjectAsync (info.AssetBundleName, info.AssetName));
	}

	// Initialize the downloading url and AssetBundleManifest object.
	protected IEnumerator Initialize ()
	{
		// Don't destroy this gameObject as we depend on it to run the loading script.
		DontDestroyOnLoad (gameObject);

		var url = string.Format (@"https://storage.googleapis.com/{0}/", "uploaded-badget-name");
		Debug.Log ("source asset bundle url: " + url);
		AssetBundleManager.SetSourceAssetBundleURL (url);

		// Initialize AssetBundleManifest which loads the AssetBundleManifest object.
		var request = AssetBundleManager.Initialize ();
		if (request != null) yield return StartCoroutine (request);
	}

	protected IEnumerator InstantiateGameObjectAsync (string assetBundleName, string assetName)
	{
		// This is simply to get the elapsed time for this phase of AssetLoading.
		float startTime = Time.realtimeSinceStartup;

		// Load asset from assetBundle.
		AssetBundleLoadAssetOperation request = AssetBundleManager.LoadAssetAsync (assetBundleName, assetName, typeof(Texture2D));
		if (request == null) yield break;
		yield return StartCoroutine (request);

		// Get the asset.
		var texture = request.GetAsset<Texture> ();

		if (texture != null)
		{
			cube.GetComponent<MeshRenderer> ().materials [0].mainTexture = texture;
		}

		// Calculate and display the elapsed time.
		float elapsedTime = Time.realtimeSinceStartup - startTime;
		Debug.Log (assetName + (texture == null ? " was not" : " was") + " loaded successfully in " + elapsedTime + " seconds");
	}
}
```

# さいごに

* Google Cloud Storege 側でファイルの公開権限を一般公開にするのがどうなのかは気になっている
* 認証入れるにしても JWT でやるなら秘密鍵をアプリに持たせなければいけないはずなので悪意のあるユーザーに対しては結局公開状態になってしまう気がするのでとりあえずもっと考える必要がありそう
* ゲームサーバーをプロキシにすればユーザー単位でアクセスコントロールできたりするか