发布WebGL的过程

摘要:
xmlversion=“1.0”encoding=“UTF-8”?

  今天测试了一下发布 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开始:

发布WebGL的过程第1张

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

发布WebGL的过程第2张

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

发布WebGL的过程第3张

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

发布WebGL的过程第4张

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

发布WebGL的过程第5张发布WebGL的过程第6张

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

发布WebGL的过程第7张

<?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);
        }
    }

  

发布WebGL的过程第8张

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

(2020.07.22)

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

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

发布WebGL的过程第9张

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

发布WebGL的过程第10张

  看到 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

发布WebGL的过程第11张

  找到很多论坛都说添加一个 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的过程第12张

  没想到部署个 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浏览器, 直接到下面去找看看 : 

发布WebGL的过程第13张

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

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

发布WebGL的过程第14张

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

    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 发送请求后马上返回) : 

发布WebGL的过程第15张

  这样结果左下角是肯定没有图片的, 因为 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 中显示的也是这三个文件 : 

发布WebGL的过程第16张

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

发布WebGL的过程第17张

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

发布WebGL的过程第18张

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

发布WebGL的过程第19张

  资源变成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 都变黑了 : 

发布WebGL的过程第20张

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

  

免责声明:文章转载自《发布WebGL的过程》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇查询设计分析ncurses文档和源码以及demo下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

window+kafka

window环境搭建zookeeper,kafka集群 为了演示集群的效果,这里准备一台虚拟机(window 7),在虚拟机中搭建了单IP多节点的zookeeper集群(多IP节点的也是同理的),并且在本机(win 7)和虚拟机中都安装了kafka。 前期准备说明: 1.三台zookeeper服务器,本机安装一个作为server1,虚拟机安装两个(单IP)...

Linux| |对于UDP的学习

# 前序 UDP(用户数据报协议)没有连接的,是面向数据报的,是不可靠 # 套接字就是IP地址+端口号 IP地址:4字节 端口号:2字节,也就是说范围是0~65536 端口号分为 知名端口号 0--1023:http,ssh,ftp,telnet等一些协议端口号都是固定的,对于操作系统来说是不能对其进行分配的 一些固定的端口号 ssh服务器,使用22端口...

OpenStack是什么,OpenStack详解

1. OpenStack是什么 OpenStack官方的解释很官方,而且从不同角度,也有不同的理解,OpenStack可以理解为一个云操作系统 OpenStack旗下包含了一组由社区维护的开源项目,他们分别是OpenStackCompute(Nova),OpenStackObjectStorage(Swift),以及OpenStackImageServic...

Cocos网络篇[3.2](3) ——Socket连接(1)

【唠叨】     在客户端游戏开发中,使用HTTP进行网络通信的比较少,一般使用的都是Socket进行通信。而HTTP一般用于网页或者网页游戏。     使用第三方Socket通信库:ODSocket。 【参考】     http://blog.csdn.net/sight_/article/details/8138802 (Socket详解)...

使用Assetbundle时可能遇到的坑

原地址:http://www.cnblogs.com/realtimepixels/p/3652128.html 一 24 十一郎未分类 No Comments 转自 http://www.unitymanual.com/blog-3571-132.html 1.Editor版本不能读取与自己版本不同的assetbundle这个问题描述起来很简单:比如:...

ASP.NET基础知识整理

Asp.net六大对象 1.Request-->读取客户端在Web请求期间发送的值 常用方法: 1、Request.UrlReferrer请求的来源,可以根据这个判断从百度搜的哪个关键词、防下载盗链、防图片盗链,可以伪造(比如迅雷)。 (使用全局一般处理程序) 2、Request.UserHostAddress获得访问者的IP地址 3、Reque...