zoukankan      html  css  js  c++  java
  • 发布WebGL的过程

      今天测试了一下发布 WebGL 的过程, 通过 Unity3D 创建, 相当麻烦, 它不仅对API有限制, 对测试Debug有限制, 也对服务器有要求, 并且现在的浏览器都很注重安全策略, 这些都增加了复杂度...

      流程大概如下:

      1. 做个简单场景, 放到 BuildSettings 里面去

      2. 如果有代码, 检查是不是有不能使用的API或是引用不能用的命名空间, 比如 System.Threading 这些, 即使引用了打包也不报错, 然后发布之后运行抛异常, 它就不动了.

      3. Build 出来的工程不能直接拖到浏览器运行, 360 / Firefox / Google Chrome 试过了都不让运行, 安全策略的问题

      4. 打开IIS服务, 创建本地服务器, 把生成的WebGL的工程拖进去, 绑定端口

      5. 添加Web.config文件, 添加各种文件流支持, 要不然浏览器会报Unexpected Token错误

      6. 使用各种浏览器直接 localhost:端口 打开都没有问题

      最简单的工程坑还是挺多, 按顺序看下来:

      2.1 WebGL多线程不能用, 所以Threading有关都不能用. 

      2.2 部署在服务器上, 所以文件读写都不能用, StreamingAssets的地址在本地变成了 [ http:/localhost:61281/StreamingAssets ] , 所以只能老实用WebRequest来进行下载了

      2.3 Resources文件夹下的东西还是能正常读取, 它的资源应该是会在加载时就全部下载了, 所以很大的话基本没有用户体验了, 不过小工程还是能用

      

      5.1 没有Web.config的话似乎任何传输都不正确, 就是资源 跨域/传输 之类的问题了

      其实还有很多问题, 中文输入法跟随啊, Shader啊.......

      先从搭建IIS开始:

      必须用服务器, 先打开本地的IIS服务, win10比win7快了至少10倍:

      启动完成后继续打开管理工具, 可以设置IIS了:

      在设置中设置本地硬盘映射, 直接设置到WebGL的输出目录:

      添加一个绑定端口, 免得多个地址冲突:

      设置好了之后, 需要在根目录添加 Web.config 文件支持资源类型文件传输:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
      <system.webServer>
        <staticContent>
          <remove fileExtension=".mem" />
          <remove fileExtension=".data" />
          <remove fileExtension=".unity3d" />
          <remove fileExtension=".jsbr" />
          <remove fileExtension=".membr" />
          <remove fileExtension=".databr" />
          <remove fileExtension=".unity3dbr" />
          <remove fileExtension=".jsgz" />
          <remove fileExtension=".memgz" />
          <remove fileExtension=".datagz" />
          <remove fileExtension=".unity3dgz" />
          <remove fileExtension=".json" />
          <remove fileExtension=".unityweb" />
          <remove fileExtension=".obj" />
          <remove fileExtension=".mjs" />
          <remove fileExtension="." />
          <remove fileExtension=".assetbundle" />
    
          <mimeMap fileExtension=".mem" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".data" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".unity3d" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".jsbr" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".membr" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".databr" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".unity3dbr" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".jsgz" mimeType="application/x-javascript; charset=UTF-8" />
          <mimeMap fileExtension=".memgz" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".datagz" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".unity3dgz" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".json" mimeType="application/json; charset=UTF-8" />
          <mimeMap fileExtension=".unityweb" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".obj" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".mjs" mimeType="text/javascript; charset=UTF-8" />
          <mimeMap fileExtension="." mimeType="application/octet-stream" />
          <mimeMap fileExtension=".assetbundle" mimeType="application/octet-stream" />
        </staticContent>
      </system.webServer>
    </configuration>

      我猜测在创建 Stream 的时候服务器会指定类型, 没有指定的就不知道怎么传了, 一般默认肯定是二进制传吧, 怎么这么无聊...

      这里有个特殊的就是无后缀的文件, 直接 "." 代表即可. "application/octet-stream" 就是二进制了吧...

      

      这样就能在本地浏览器打开了, 放在 Resources 中的 Txt 资源文件可以正常读取:

      text2.text = Resources.Load<TextAsset>("Test").text;    // Test Resources

      放在 StreamingAssets 下的 Txt 文件不能通过IO读取, 使用 UnityWebRequest 进行获取, 读取地址经过 System.Uri 加工:

        private void Start()
        {
            StartCoroutine(GetData(Application.streamingAssetsPath + "/Test.txt", (_handle) =>
            {
                text1.text = _handle.text;    // Test streamingAssetsPath
            }));
        }
    
        IEnumerator GetData(string loadPath, System.Action<DownloadHandler> succ)
        {
            var uri = new System.Uri(loadPath);
            UnityWebRequest www = UnityWebRequest.Get(uri.ToString());
            yield return www.SendWebRequest();
    
            if(www.isNetworkError || www.isHttpError)
            {
                Debug.Log(www.error);
            }
            else
            {
                Debug.Log(www.downloadHandler.text);
                succ.Invoke(www.downloadHandler);
            }
        }

      

      不过如果在Build的时候选择 [ Build & Run ] 的话, 它会临时起一个服务器来跑打包出来的工程, 不需要IIS也是可以的......

    (2020.07.22)

      最近想要测试一下资源远程加载的方案, 于是把加载逻辑添加了通过 UnityWebRequest 方式获取 AssetBundle 的逻辑, 然而这又是一个坑, 在编辑器下, 可以从网站上获取到 AssetBundle, 可是如果发布到服务器上, 并且资源在其它服务器, 就产生了一个跨域问题, 然后资源是无法传输的, 虽然理解这是 http 服务器设计的问题, 可是我网上查了半天也没解决, 真是神奇了...

      首先把打出的包放到服务器上 : 

      然后使用服务器的路径来下载包 : 

      看到 Sprite 的 AssetBundle 可以正确通过网址下载来, 并且正确加载出来了, 可是发布到服务器后 (资源服务器 localhost:12354, 运行服务器 localhost:44599), 因为跨域问题, 无法获取了 : 

    XMLHttpRequest cannot load http://localhost:12354/unityassets/AssetBundleManifest. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:44599' is therefore not allowed access.

      然后找跨域的相关描述, 是需要服务器添加一个 Access-Control-Allow-Origin 相关的返回? 

      在360浏览器里面可以查看到各个 http 请求的信息, F12 -> NetWork -> XHR -> xxxxx

      找到很多论坛都说添加一个 customheader 就行了, 下面这样 (https://enable-cors.org/server_iis7.html) :

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
     <system.webServer>
       <httpProtocol>
         <customHeaders>
           <add name="Access-Control-Allow-Origin" value="*" />
         </customHeaders>
       </httpProtocol>
     </system.webServer>
    </configuration>

      那么加到原有的 Web.config 文件里 : 

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
      <system.webServer>
        <httpProtocol>
          <customHeaders>
            <add name="Access-Control-Allow-Origin" value="*" />
          </customHeaders>
        </httpProtocol>
        <staticContent>
          <remove fileExtension=".mem" />
          <remove fileExtension=".data" />
          <remove fileExtension=".unity3d" />
          <remove fileExtension=".jsbr" />
          <remove fileExtension=".membr" />
          <remove fileExtension=".databr" />
          <remove fileExtension=".unity3dbr" />
          <remove fileExtension=".jsgz" />
          <remove fileExtension=".memgz" />
          <remove fileExtension=".datagz" />
          <remove fileExtension=".unity3dgz" />
          <remove fileExtension=".json" />
          <remove fileExtension=".unityweb" />
          <remove fileExtension=".obj" />
          <remove fileExtension=".mjs" />
          <remove fileExtension="." />
          <remove fileExtension=".assetbundle" />
    
          <mimeMap fileExtension=".mem" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".data" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".unity3d" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".jsbr" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".membr" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".databr" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".unity3dbr" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".jsgz" mimeType="application/x-javascript; charset=UTF-8" />
          <mimeMap fileExtension=".memgz" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".datagz" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".unity3dgz" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".json" mimeType="application/json; charset=UTF-8" />
          <mimeMap fileExtension=".unityweb" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".obj" mimeType="application/octet-stream" />
          <mimeMap fileExtension=".mjs" mimeType="text/javascript; charset=UTF-8" />
          <mimeMap fileExtension="." mimeType="application/octet-stream" />
          <mimeMap fileExtension=".assetbundle" mimeType="application/octet-stream" />
        </staticContent>    
      </system.webServer>
    </configuration>

      还是报错, 我就纳闷了, 然后有些人在 Unity 请求代码里面添加了一些头, 我也添加之后测试仍然报错, 无用 : 

        var unityWebRequest = UnityWebRequest.GetAssetBundle(url, 0);
    
        unityWebRequest.SetRequestHeader("Access-Control-Allow-Credentials", "true");
        unityWebRequest.SetRequestHeader("Access-Control-Allow-Headers", "Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time");
        unityWebRequest.SetRequestHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
        uwr.SetRequestHeader("Access-Control-Allow-Origin", "*");
    
        var request = unityWebRequest.SendWebRequest();
        // ...

      换了个错误再来一遍, 变成了认证错误之类的....

      继续再找, 看到一个说 IIS 跨域还要安装一个 CORS Module 的东西的 : 

    I had a similar issue recently. Most tutorial/documentation only suggests adding custom headers in the configuration. But this does not tell IIS to handle the CORS Pre-flight request by itself.
    
    To do so, you must install the CORS Module in IIS and add some configuration in the web.config file, as explained here: IIS CORS module Configuration Reference
    

      好吧, 进入微软找 CORS Module (https://www.iis.net/downloads/microsoft/iis-cors-module), 下载安装之后, 再打包一次, Unity 代码也使用最简单的看看 : 

                public void SendWebRequest()
                {
                    if(request == null)
                    {
                        var unityWebRequest = this.hash.HasValue ? UnityWebRequest.GetAssetBundle(url, hash.Value, 0) : UnityWebRequest.GetAssetBundle(url, 0);
                        request = unityWebRequest.SendWebRequest();
                        request.completed += OnLoaded;
                    }
                }

      结果居然可以读取了, 反正不知道是不是安装了 CORS Module, 能用就行了 : 

      没想到部署个 WebGL 测试也这么多幺蛾子, 这些服务器就不能给个省心的逻辑吗, 要啥功能给个界面式的功能列表也好啊, 如果明天用阿帕奇服务器, 又是查资料查半天, 心累...

       然后看一下各个默认文件夹在运行时的位置 : 

        Debug.Log("Application.dataPath : " + Application.dataPath);    // http://localhost:44599
        Debug.Log("Application.persistentDataPath : " + Application.persistentDataPath);    // /idbfs/9ed0d20bc957a25b21e872bbf1c2f671
        Debug.Log("Application.streamingAssetsPath : " + Application.streamingAssetsPath);    // http://localhost:44599/StreamingAssets
        Debug.Log("Application.temporaryCachePath : " + Application.temporaryCachePath);    // /tmp
        Debug.Log("Caching.currentCacheForWriting.path : " + Caching.currentCacheForWriting.path);    // /idbfs/9ed0d20bc957a25b21e872bbf1c2f671/UnityCache/Shared
        Debug.Log("System.Environment.CurrentDirectory : " + System.Environment.CurrentDirectory);    // /

      Application.dataPath 和 Application.streamingAssetsPath 是服务器的相对路径, 所以服务器资源可以正常读取, 而其它的应该都是本地路径, 至于这个路径在哪, 可能是浏览器的缓存路径, 我用360浏览器, 直接到下面去找看看 : 

      我看论坛他们是说用的 IndexedDB 存储临时文件的, 不知道用什么开来看...

      发现浏览器自带了查看器的样子, F12 -> Appliction ->  IndexedDB -> xxxx

       不知道这些是不是本地缓存, 我在初始界面就有一个加载代码 : 

        void Start()
        {
            // 屏幕右上角 
            AssetBundleMaster.Core.ABM_ResourceLoader.Instance.LoadAsync<Sprite>("Sprites/Pic002PNG", (_pic) =>
            {
                image.sprite = _pic;
                Debug.Log("Loaded Sprite " + _pic + " : " + Time.frameCount);
            });
            // 屏幕左下角
            rawImage.texture = AssetBundleMaster.Core.ABM_ResourceLoader.Instance.Load<Texture2D>("Textures/Pic001");
            Debug.Log("Loaded Texture2D " + rawImage.texture + " : " + Time.frameCount);
        }

      看看删除 IndexedDB 相关文件夹后第一次运行的情况, 因为屏幕左下角的读取使用的是同步读取 (UnityWebRequest 发送请求后马上返回) : 

      这样结果左下角是肯定没有图片的, 因为 UnityWebRequest 是远程请求, 必定不能马上得到结果...

      这时候发起的资源请求有3个 (使用Hash128作为参数的请求) : 

    assetBundleCreateRequest = UnityWebRequest.GetAssetBundle(loadPath, assetBundleManifest.GetAssetBundleHash(this.assetName), 0).SendWebRequest();
        [RuntimeInitializeOnLoadMethod]
        private static void StartUpRun()
        {
            AssetBundleMaster.Core.ABM_AssetLoadManager.Instance.OnModuleLoaded(() =>
            {
                Debug.Log("OnModuleLoaded At : " + Time.frameCount);
    
                AssetBundleMaster.Core.ABM_SceneLoader.Instance.LoadScene("Scenes/S1");
            });
    
        }

      1. 场景 : s1.assetbundle

      2. 右上角图片 : common.assetbundle

      3. 左下角图片 : pic001.assetbundle -- 因为是异步请求, 同步回调没有获得图片, 可是也进行了下载

      这时看到 IndexedDB 中显示的也是这三个文件 : 

       然后我关闭浏览器, 从新再打开网址 : 

      再打开一次的话, 异步加载的左下角图片, 居然在同步回调里面就能获取图片了, 这难道就是本地缓存的威力吗? 我再点击一下按钮加载一张新的图片覆盖左下角, 看看 IndexedDB中是不是有了新的图片了 : 

      左下角的图片改变了, 看看本地缓存 : 

      资源变成4个了, 关闭浏览器从新加载网页看看, 如果不是本地缓存的话, 不操作应该还是只会加载3个资源. 

      重新加载后还是4个资源, 说明 FILE_DATA 这个数据库这就是本地缓存无疑了. 当我们的 AssetBundleManifest 里面获取的 Hash128 跟本地不一样的时候, 就会去下载最新包了. 当然如果是 WebGL 的话 AssetBundleManifest 直接放服务器的 StreamingAssets 文件夹下就行了, 而资源一般会放到CDN服务器上, 所以前面搞了半天跨域的问题. 当然远程资源+缓存的模式, 也能作为PC, Android, IOS之类的平台加载逻辑也是可行的, 并且有 Cache.expirationDelay 这些自动删除逻辑在, 用不到的缓存资源能自动删除, 省了更新删除逻辑了...

      测试一下看看, 给另一个按钮添加清除缓存的功能 :

        clearBtn.onClick.AddListener(() =>
        {
            if(Caching.ClearCache())
            {
                Debug.Log("ClearCache Succ");
            }
            else
            {
                Debug.Log("ClearCache FAILED");
            }
        });

      ClearCache FAILED ......

      清除缓存失败, 这又是什么神操作, 找了下资料, 说要 Unload 掉所有已经加载的 AssetBundle 之后才能行, 修改代码来硬核一点的试试 : 

        void Start()
        {
            clearBtn.onClick.AddListener(() =>
            {
                StartCoroutine(ClearCache());
            });
        }
            
        IEnumerator ClearCache()
        {
            AssetBundle.UnloadAllAssetBundles(true);
            yield return new WaitForSeconds(1.0f);
            System.GC.Collect(0);
            yield return new WaitForSeconds(1.0f);
            yield return Resources.UnloadUnusedAssets();
            yield return new WaitForSeconds(1.0f);
            if(Caching.ClearCache())
            {
                Debug.Log("ClearCache Succ");
            }
            else
            {
                Debug.Log("ClearCache FAILED");
            }
        }

      居然还是不行!!! 震惊!! 删除了 AssetBundle 之后, UI 都变黑了 : 

      这就无语了, 虽然不是很大问题......

      

  • 相关阅读:
    日期和时间
    怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串?
    数据类型之间的转换:
    类的加载顺序,支出下列程序的输出结果
    内部类
    对象的类型转换
    简单继承
    封装
    计算a+b
    U盘删除文件时提示“文件或目录损坏且无法读取”的解决方法
  • 原文地址:https://www.cnblogs.com/tiancaiwrk/p/13214680.html
Copyright © 2011-2022 走看看