发布WebGL的过程
Posted tiancaiKG
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了发布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"); unityWebRequest.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 中显示的也是这三个文件 :
然后我关闭浏览器, 从新再打开网址 :
再打开一次的话, 异步加载的左下角图片, 居然在同步回调里面就能获取图片了, 这难道就是本地缓存的威力吗? ( 2020.07.28 -- 经测试并不一定能在同步回调中返回, 应该跟IO速度有关, 并不是进行了同步加载 ). 我再点击一下按钮加载一张新的图片覆盖左下角, 看看 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 都变黑了 :
这就无语了, 虽然不是很大问题......
(2020.11.19)
补充一下在Windows下的缓存测试, 缓存路径在 C:\\Users\\XXXX\\AppData\\LocalLow\\Unity\\DefaultCompany_工程名称 文件夹下, 设定了 expirationDelay 之后, 需要程序下载对象, 然后超过过期时间程序还在运行, 它才能帮你删除对应的缓存(在程序退出的时候), 也就是一个运行期间的功能, 不是 Cookie 那样通过系统维护的东西. 也就是说运行时间需要超过设定时间, 才会删除缓存, 非常搞笑......
(2020.07.28)
今天才发现 IndexedDB 其实就是键值对, 里面存储的数据能够跟 AssetBundle 对应得上 :
今天新打的包在浏览器的缓存中查看, 它的 __data 大小跟服务器上的原始文件 s1.assetbundle 大小是一样的 :
这样就知道它在浏览器中的缓存是以 [/idbfs/用户ID/UnityCache/Shared/文件名/文件哈希值/__data] 的key来做存储的, 还不知道它的用户ID是怎样来的, 不过默认路径根目录可以通过 Caching.currentCacheForWriting.path 来获得, 得到的是 :
/idbfs/bc9cc5aa7eef26ca01726eef3f44ae31/UnityCache/Shared
这个路径, 如果可以调用 IndexedDB 的API的话, 也是可以自己查询的...
以上是关于发布WebGL的过程的主要内容,如果未能解决你的问题,请参考以下文章