Unityで外部のzipファイルから複数枚画像を取り出す
今回は小技です。
※物理枠の動作確認がうまくいっていないので、こちらでお茶を濁す
外部ファイルとして、zip中の画像をバイト列で取り出し、テクスチャとして取得します。
準備
今回は、zipファイル中の画像をバイト列のリストとして取り出すクラス(ZipImageLoader.cs)と、それを使用するクラス(ChangeImageByZip.cs)に分けます。
- ZipImageLoader.cs
クリックで展開
using System.Collections; using System.Collections.Generic; using System.IO.Compression; using System.IO; using System.Threading.Tasks; using UnityEngine; /// <summary> /// ZIPファイルから複数画像を取り出すクラス /// </summary> public class ZipImageLoader : MonoBehaviour { /// <summary> /// バイト列 /// </summary> private List<byte[]> zipBytes = new List<byte[]>(); /// <summary> /// 読み込み完了フラグ /// </summary> public bool isLoadFinish = false; // Start is called before the first frame update public ZipImageLoader(string zipPath) { // 非同期で実行 Task.Run(() => { // zipファイルからzip アーカイブ形式の圧縮ファイルのパッケージを取得 using (ZipArchive archive = ZipFile.OpenRead(zipPath)) { // 圧縮ファイルパッケージから1ファイル分取り出し foreach (ZipArchiveEntry entry in archive.Entries) { // ファイルストリームを開く using (Stream stream = entry.Open()) { using (var ms = new MemoryStream()) { // ファイルストリームをメモリに展開する stream.CopyTo(ms); // メモリの内容をバイト列として格納 zipBytes.Add(ms.ToArray()); Debug.Log("Loaded No:" + zipBytes.Count); } } } } }); // 読み込み完了フラグを立てる isLoadFinish = true; } /// <summary> /// 画像数取得メソッド /// </summary> /// <returns></returns> public int GetImageNum() { if(isLoadFinish) { return this.zipBytes.Count; } else { return -1; } } /// <summary> /// 任意のindexの画像のバイト列を取得 /// </summary> /// <param name="index"></param> /// <returns></returns> public byte[] GetImage(int index) { if(!isLoadFinish) { // 読み込み未完了の場合はnullを返す return null; } if(index >= 0 && this.zipBytes.Count > index) { return this.zipBytes[index]; } return null; } /// <summary> /// 画像の全バイト列を取得 /// </summary> /// <param name="index"></param> /// <returns></returns> public List<byte[]> GetAllImages() { if (!isLoadFinish) { // 読み込み未完了の場合はnullを返す return null; } return this.zipBytes; } }
- ChangeImageByZip.cs
クリックで展開
using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; public class ChangeImageByZip : MonoBehaviour { /// <summary> /// zipファイル名 /// </summary> [SerializeField] private string zipFileName; /// <summary> /// zipファイルパス /// </summary> private string zipFilePath; /// <summary> /// zipファイル画像ローダ /// </summary> private ZipImageLoader zipImageLoader; /// <summary> /// 画像枚数 /// </summary> private int imageNum = -1; /// <summary> /// テクスチャリスト /// </summary> private List<Texture2D> textures; /// <summary> /// 画像index /// </summary> private int index = 0; /// <summary> /// 画像の表示先 /// </summary> [SerializeField] private SpriteRenderer spriteRenderer; // Start is called before the first frame update void Start() { // zipファイルパスを取得 GetZipFilePath(); // zipデータ取得クラスのインスタンスを作成 zipImageLoader = new ZipImageLoader(zipFilePath); // テクスチャ格納リストを定義 textures = new List<Texture2D>(); } // Update is called once per frame void Update() { CheckUsableImages(); } /// <summary> /// 画像が使用可能か確認し、格納する /// </summary> void CheckUsableImages() { if (imageNum == -1 && zipImageLoader.isLoadFinish) { imageNum = zipImageLoader.GetImageNum(); foreach(var bytes in zipImageLoader.GetAllImages()) { Texture2D texture = new Texture2D(2, 2); texture.LoadImage(bytes); textures.Add(texture); } SetTexture(0); } } /// <summary> /// テクスチャを反映する /// </summary> /// <param name="targetIndex"></param> void SetTexture(int targetIndex) { Debug.Log(textures.Count); if(textures.Count > targetIndex && targetIndex >= 0) { Sprite sprite = Sprite.Create(textures[targetIndex], new Rect(0, 0, textures[targetIndex].width, textures[targetIndex].height), new Vector2(0.5f, 0.5f)); spriteRenderer.sprite = sprite; index = targetIndex; } } /// <summary> /// トグル式にテクスチャを変更する /// </summary> public void ChangeTexture() { var targetIndex = index + 1; if(targetIndex >= textures.Count) { targetIndex = 0; } SetTexture(targetIndex); } /// <summary> /// zipファイルのパスを取得する /// </summary> void GetZipFilePath() { string dirPath; #if WINDOWS_UWP // HoloLens上での動作の場合、LocalAppData/AppName/LocalStateフォルダを参照する dirPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path; #else // Unity上での動作の場合、Assets/StreamingAssetsフォルダを参照する dirPath = UnityEngine.Application.streamingAssetsPath; #endif zipFilePath = Path.Combine(dirPath, zipFileName); } /// <summary> /// ゲームオブジェクト消滅時にTextureの後処理を行う /// </summary> private void OnDestroy() { foreach(var texture in textures) { // 生成したTexture2Dを削除 Destroy(texture); } } }
作成したChangeImageByZip.csをMRTKのボタンにアタッチします。
ボタンで使用するため、ButtonConfigHelperのOnClickにChangeImageByZip.csのChangeTexture()を設定します。
なお、ZipImageLoader.csについて、ビルドターゲットがUWPの場合は下記コンパイルエラーが出力されます。
Assets\ZipImageLoader.cs(30,20): error CS1069: The type name 'ZipArchive' could not be found in the namespace 'System.IO.Compression'. This type has been forwarded to assembly 'System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=■■■■■' Consider adding a reference to that assembly. Assets\ZipImageLoader.cs(30,41): error CS0103: The name 'ZipFile' does not exist in the current context Assets\ZipImageLoader.cs(33,26): error CS1069: The type name 'ZipArchiveEntry' could not be found in the namespace 'System.IO.Compression'. This type has been forwarded to assembly 'System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=■■■■■' Consider adding a reference to that assembly.
Unityが正しくライブラリ'System.IO.Compression'を認識できていないようです。
また、ZipFileもエラーが出力されているので、System.IO.Compression.FileSystem
も足りないようです。
そのため、下記を参考にAsset配下にcsc.rspファイルを作成します。
クリックで展開
-r:System.IO.Compression.dll -r:System.IO.Compression.FileSystem.dll
これでコンパイルエラーが無くなりました。
動作確認
今回は下記画像をzipファイルにし、読み込ませました。
作成したzipファイルをUnity上で実行する場合は/Assets/StreamingAssets/、Hololens上で実行する場合は/LocalAppData/[アプリ名]/LocalState/に配置します。
UnityEditor上で動作させた結果は以下です。
ボタンを押すたびに、zipファイルに格納した画像がトグル式に切り替わり表示されます。
Hololens2でも動作確認済みです。
ファイル別に読めるので画像だけでなく、容量の3Dモデルをまとめておくのにも役立つ気がします。
※MaterialのTextureを動的に切り替えるものを最初作成しましたが、動的に切り替わったり切り替わらなかったりして安定せず。。。
Inspector上で表示してれば表示されるようだけれど、なぜ表示しないとだめなのか定かじゃないです。。。
参考
ファイルの外部読み込み:
zipファイルからの読み込み: