LoginSignup
2
1

More than 5 years have passed since last update.

Unity WebGLでJSZipを使ってzip圧縮してみる

Posted at

Unity WegGLでJSZipを使ってzip圧縮してみたのでやったことをメモしておく。

やったこと

WebGLのカスタムテンプレートを設定してJSZipを配置

Using WebGL Templates - Unity マニュアル を主に参照

  • 以下を場所からdefaultテンプレートを取得
Both minimal and default templates can be found in the Unity installation folder under Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\WebGLTemplates on Windows or /PlaybackEngines/WebGLSupport/BuildTools/WebGLTemplates on Mac.
  • Assets/WebGLTemplates に取得したDefaultテンプレート配置
  • player settingsで配置したDefaultテンプレートを選択
  • Assets/WebGLTemplates/Default/TemplateData にJSZipのdownload JSZipから取得したjszip.min.jsを配置
  • Assets/WebGLTemplates/Default/index.html に以下を追加
<script src="TemplateData/jszip.min.js"></script>

JSZipを使うnative pluginを作成

以下の記事がかなり参考になった。
Unity(WebGL)でC#の関数からブラウザー側のJavaScript関数を呼び出すまたはその逆(JS⇒C#)に関する知見(プラグイン形式[.jslib]) - Qiita

Assets/Plugins/WebGL/ZipArchiver.jslib

var ZipArchiver = {
    $member: {
      zipData: null
    },

    $convertUrlToBlob: function(url, func) {
      var xhr = new XMLHttpRequest();
      xhr.responseType = "blob";
      xhr.open("GET", url, true);
      xhr.onload = function(oEvent) {
        func(xhr.status === 200 ? xhr.response : null);
      }
      xhr.send();
    },

    $getPtrFromString: function(str) {
      var size = lengthBytesUTF8(str) + 1;
      var buffer = _malloc(size);
      stringToUTF8(str, buffer, size);
      return buffer;
    },

    InitZip: function()
    {
      member.zipData = new JSZip();
    },

    AddZipData: function(filename, data) 
    {
      member.zipData.file(Pointer_stringify(filename), Pointer_stringify(data));
    },

    AddZipBase64Data: function(filename, data)
    {
      member.zipData.file(Pointer_stringify(filename), Pointer_stringify(data), {base64: true});
    },

    AddZipBlobURL: function(filename, data, callback)
    {
      var fname = Pointer_stringify(filename);
      var blob = convertUrlToBlob(Pointer_stringify(data), function(blob) {
        console.log(fname);
        member.zipData.file(fname, blob);
        Runtime.dynCall('v', callback, []);
      });
    },

    GenerateZipBlobURL: function(callback)
    {
      member.zipData.generateAsync({type: "blob"}).then(function(content) {
        var buffer = getPtrFromString(URL.createObjectURL(content));
        Runtime.dynCall('vi', callback, [buffer]);
      });
    }
};

autoAddDeps(ZipArchiver, '$member');
autoAddDeps(ZipArchiver, '$convertUrlToBlob');
autoAddDeps(ZipArchiver, '$getPtrFromString');

mergeInto(LibraryManager.library, ZipArchiver);
  • はまったところ
    • letが使えない
    • XMLHttpRequestを非同期で実行しないとblobが正しくとれずバイナリが壊れる(コードが間違っていた可能性もあり)
    • $memberの中にfunctionのメンバを入れられない

ZipArchiver.cs

using AOT;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;
using UnityEngine.Events;

public class ZipArchiver {

    private delegate void GenerateBlobURLCallback(System.IntPtr ptr);
    private delegate void AddZipBlobURLCallback();
    private static UnityAction<string> generateBlobURLCallback;
    private static UnityAction addZipBlobURLCallback;

#if UNITY_WEBGL && !UNITY_EDITOR
    [DllImport("__Internal")]
    private static extern void InitZip();

    [DllImport("__Internal")]
    private static extern void AddZipData(string filename, string data);

    [DllImport("__Internal")]
    private static extern void AddZipBlobURL(string filename, string bloburl, AddZipBlobURLCallback callback);

    [DllImport("__Internal")]
    private static extern void GenerateZipBlobURL(GenerateBlobURLCallback callback);

    [DllImport("__Internal")]
    private static extern void AddZipBase64Data(string filename, string data);
#endif

    public ZipArchiver()
    {
        Init();
    }

    public void Init()
    {
#if UNITY_WEBGL && !UNITY_EDITOR
        InitZip();
#endif
    }

    public void AddData(string filename, byte[] data)
    {
#if UNITY_WEBGL && !UNITY_EDITOR
        AddZipBase64Data(filename, Convert.ToBase64String(data));
#endif
    }

    public void AddData(string filename, string data)
    {
#if UNITY_WEBGL && !UNITY_EDITOR
        AddZipData(filename, data);
#endif
    }

    public void AddBlobURL(string filename, string bloburl, UnityAction callback)
    {
        addZipBlobURLCallback = callback;
#if UNITY_WEBGL && !UNITY_EDITOR
        AddZipBlobURL(filename, bloburl, Callback);
#endif
    }

    public void GenerateBlobURL(UnityAction<string> callback)
    {
        generateBlobURLCallback = callback;
#if UNITY_WEBGL && !UNITY_EDITOR
        GenerateZipBlobURL(Callback);
#endif
    }

    [MonoPInvokeCallback(typeof(GenerateBlobURLCallback))]
    private static void Callback(System.IntPtr ptr)
    {
        string value = Marshal.PtrToStringAuto(ptr);
        generateBlobURLCallback.Invoke(value);
    }

    [MonoPInvokeCallback(typeof(AddZipBlobURLCallback))]
    private static void Callback()
    {
        addZipBlobURLCallback.Invoke();
    }
}

試しに使ってみる

以下のスクリプトをカメラに設定してWebGLビルドしてブラウザで実行しコンソールを確認する。
コンソールに出力されたblob URLをブラウザにコピペするとzipがダウンロードできて中身を確認可能。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ZipArchiverTest : MonoBehaviour {

    private bool isCapture = true;

    void OnPostRender()
    {
        if (isCapture)
        {
            isCapture = false;
            ZipArchiver archiver = new ZipArchiver();
            archiver.AddData("test.txt", "test");

            var texture = new Texture2D(Screen.width, Screen.height);
            texture.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
            texture.Apply();

            archiver.AddData("test.jpg", texture.EncodeToJPG());
            archiver.GenerateBlobURL((string url) =>
            {
                //Debug.Log("bloburl:" + url);
                ZipArchiver a = new ZipArchiver();
                a.AddData("hoge.txt", "hoge");
                a.AddBlobURL("test.zip", url, () =>
                {
                    a.GenerateBlobURL((string u) =>
                    {
                        Debug.Log("bloburl:" + u);
                    });
                });
            });
        }
    }
}
2
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1