Unity發布WebGL后加載本地文件


Unity發布WebGL平台的程序是不可以直接訪問用戶電腦的文件的。

但是在使用瀏覽器的時候,很多的網站都可以彈出一個窗口,選擇文件並打開。

像下面這種(這里↓可以點擊)

代碼其實就一句:<input type="file" />

所以,我就有個思路,想辦法用Unity調用這個組件,直接從這個組件里面獲取到文件流。

直接開整,先在Unity里面搞個WebGl.jslib文件。這個文件是與webgl交互用的。

詳情參照Unity官網:https://docs.unity3d.com/Manual/webgl-interactingwithbrowserscripting.html

文件里面的內容如下:

mergeInto(LibraryManager.library, {
  clickSelectFileBtn:function () {
    console.log("Enter");
    clickSelectFileBtn();
  },
});

在Unity里面搞個C#交互腳本,去調用這里面的 clickSelectFileBtn() 方法

    [DllImport("__Internal")]
    private static extern void clickSelectFileBtn();
    /// <summary>
    /// 點擊Open按鈕
    /// </summary>
    public static void ClickSelectFileBtn()
    {
        clickSelectFileBtn();
    }

第一行的方法名字與.jslib文件里面的方法名一致。

這樣在Unity里面調用到 ClickSelectFileBtn() 的時候就可以直接調用到.jslib的 clickSelectFileBtn() 方法了。

接下來,看下Webgl端接收代碼

打完WebGL包在路徑下會生成兩個(沒有StreamingAssets)~~~~三個文件夾(有StreamingAssets)和一個index.html文件。

 

 

 打開index.html文件:

 1 <!DOCTYPE html>
 2 <html lang="en-us">
 3   <head>
 4     <meta charset="utf-8">
 5     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 6     <title>Unity WebGL Player | Modeleditor</title>
 7     <link rel="shortcut icon" href="TemplateData/favicon.ico">
 8     <link rel="stylesheet" href="TemplateData/style.css">
 9     <script src="TemplateData/UnityProgress.js"></script>
10     <script src="Build/UnityLoader.js"></script>
11     <script>
12       var unityInstance = UnityLoader.instantiate("unityContainer", "Build/Web.json", {onProgress: UnityProgress});
13     </script>
14   </head>
15   <body>
16     <div class="webgl-content">
17       <div id="unityContainer" style="width: 960px; height: 600px"></div>
18       <div class="footer">
19         <div class="webgl-logo"></div>
20         <div class="fullscreen" onclick="unityInstance.SetFullscreen(1)"></div>
21         <div class="title">Modeleditor</div>
22       </div>
23     </div>
24   </body>
25 </html>

第12行,unity已經幫我們獲取到了Unity的實例化,可以直接使用這個變量往unity發消息。

那我們剛才unity發送的消息怎么接收呢?

只需要在<script>   </script>之間插入一個叫clickSelectFileBtn()的方法就行。

function clickSelectFileBtn() {

}

這時候就可以點擊Unity里面的按鈕,觸發webgl里面的方法了。

在回到初衷,我們就是想用到打開文件的組件,<input type="file" />寫在哪?

只要放到<body> </body>之間即可。

保存后刷新下網頁,發現頁面左上角多了一個選擇文件組件

這樣還是行,需求是點擊unity按鈕,觸發這個組件,當前這樣只能手動點擊觸發,而且顯示在這直接當bug處理。

在這個組件里面添加一個id,方便之后用代碼獲取到,使用style="display:none"直接把組件隱藏,還要添加一個事件fileImport(),去處理打開的文件得到文件流,再發給unity。

    <input type="file" id="files" style="display:none" onchange="fileImport()">

 

接下來就直接上代碼:

   var unityInstance = UnityLoader.instantiate("unityContainer", "Build/Web.json", {onProgress: UnityProgress});
     function sendMessageToUnity(s) {
        //發送給unity
        unityInstance.SendMessage("TestObj", "GetBase64", s);
     }

     function clickSelectFileBtn() {
        var tempFileLayout = document.getElementById('files');
        tempFileLayout.click();
     }

     function fileImport() {
        //獲取讀取我文件的File對象
        var selectedFile = document.getElementById('files').files[0];
        if (selectedFile != null) {
            var reader = new FileReader();
            reader.readAsDataURL(selectedFile);
            reader.onload = function (e) {
                var base64Str = e.currentTarget.result.substring(e.currentTarget.result.indexOf(',') + 1);
                sendMessageToUnity(base64Str);
            }
        }
     }
unityInstance.SendMessage("TestObj", "GetBase64", s);
這里第一個參數是物體在場景中的名字,第二個參數是掛在這個物體上的腳本中方法名,第三個參數是方法的參數。

在unity里面建個物體叫TestObj,隨便寫個腳本,

    public void GetBase64(string base64Str)
    {
        byte[] bs = Convert.FromBase64String(base64Str);
    }

 

就這樣是完全可以傳輸1Mb以內的貼圖,文本等文件。

但是如果文件過大,就會報異常莫名奇怪的堆棧溢出!

Uncaught RuntimeError: memory access out of bounds

 

 

剛開始是以為unity發布web的時候內存設置得太小了,想修改webgl內存大小,發現Unity在2019版本中的PlayerSetting移除了這個選項WebGL memory size。

但是使用Editor編輯器發現還是可以獲取到這個屬性。

using UnityEngine;
using UnityEditor;

public class ChangeWebGlMemeorySize : Editor
{
    [MenuItem("Tool/設置webgl內存為256")]
    public static void ChangeMemorySize()
    {
        PlayerSettings.WebGL.memorySize = 256;
    }

    [MenuItem("Tool/查看webgl內存大小")]
    public static void GetMemorySize()
    {
        Debug.Log(PlayerSettings.WebGL.memorySize);
    }

}

然而,事情並沒有預想的那么順利,修改完webgl的內存后,還是一直報這個堆棧溢出的錯誤。

最后終於找到問題:竟然是Webgl的SendMessage給Unity的時候參數不能過大,超過一定字節的時候就會報這個溢出錯誤!!!

所以解決辦法:

                    var base64Str = e.currentTarget.result.substring(e.currentTarget.result.indexOf(',') + 1);
                    arr = [];
                    step = 3000;
                    for (var i = 0, l = base64Str.length; i < l; i += step)
                    {
                        arr.push(base64Str.slice(i, i + step))
                    }
                    sendMessageToUnity("Start");
                    for (i = 0; i < arr.length; i++)
                    {
                        sendMessageToUnity(arr[i]);
                    }
                    sendMessageToUnity("End");

相應的在Unity接受字符串的地方改為:

    StringBuilder stringBuilder = new StringBuilder();
    public void GetBase64(string base64Str)
    {
        if (base64Str == "Start")
        {
            stringBuilder.Clear();
        }
        else if (base64Str == "End")
        {
            byte[] bs = Convert.FromBase64String(stringBuilder.ToString());
        }
        else
        {
            stringBuilder.Append(base64Str);
        }
    }

直接改為將字符串分成大小一定的段落,分批的發給Unity,注意要使用 StringBuilder  不然內存也會爆炸!

 

2021.06.10更新

發現了新方法,速度更快,內存更少

直接使用: URL.createObjectURL(file)

獲取到的緩存url,不用分段發送文件了,可以直接發送給Unity使用。

        function SelectFile(){
            var file=document.getElementById('files').files[0];
            SetFileUrlByDragEnd(file);
        }
        function SetFileUrlByDragEnd(file) {
            unityInstance.SendMessage("TestObj", "SendUrl", file.name+"|"+URL.createObjectURL(file));
        }

在unity端獲取到網址后直接用UnityWebrequest請求就行了。

 

就這樣,拜拜~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM