首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Unity | 发布WebGL遇到的那些事儿

  • 25-01-18 15:43
  • 3531
  • 7181
blog.csdn.net

目录

一、跨域问题

二、InputFeild输入框不支持复制粘贴问题

三、读取本地文件失败问题

1. 先附个WebGL的页面效果

2. 在Unity Assets/Plugins目录下创建jslib文件FileDialog.jslib

3.Js模版

4.Unity中调用 

5.打包测试

四、补充说明


        最近想开发一个提效工具,用于删除现有的云端(比如阿里云、腾讯云等。我们公司的是未来云)资源,并上传新的文件(我们处理的是unity热更资源,包括bundle文件和zip文件)到云端。为了方便mac和windows都可用,准备用unity发布WebGL的方式实现。想着应该很简单,因为这个功能已经在Unity 编辑器内实现了,如下:

        还是年轻,想的太简单了。Unity发布WebGL后发现一堆一堆一堆坑,解决了一周时间,终于柳暗花明又一村。现在总结一下这些坑及解决方式。

  1. 发布WebGL后,需要部署到服务端,或者本地搭建环境模拟线上环境。
  2. 发布WebGL后,遇到了跨域问题,原先可成功调用的上传、删除等接口报405错误
  3. 发布WebGL后,InputFeild输入框不支持复制粘贴
  4. 最重要的问题,本地文件读取不了了

        接下来依次来解决234问题。

一、跨域问题

        解决方法:确保API服务器在响应头中设置了适当的CORS头,例如Access-Control-Allow-Origin。这可以是通配符 (*),但更安全和推荐的方法是指定确切的域名(如 http://example.com)

        幸好公司提供的未来云平台支持设置跨域(跨域问题直接让提供API的服务器伙伴解决):

二、InputFeild输入框不支持复制粘贴问题

        Unity插件unity-webgl-copy-and-paste-v0.2.0.unitypackage即可解决这个问题,看了下原理,也是采用和JS交互来解决的。

三、读取本地文件失败问题

        由于浏览器的安全设置,System.IO读取本地文件的大部分功能都会受限。比如之前获取本地文件夹中文件列表、读取本地文件的代码都不支持:

  1. private static Queue<string> GetLocalFileLists(string dirPath, bool onlyZip = false)
  2. {
  3. Queue<string> fileList = new Queue<string>();
  4. if (Directory.Exists(dirPath))
  5. {
  6. string[] files = Directory.GetFiles(dirPath);
  7. for (int i = 0; i < files.Length; i++)
  8. {
  9. Debug.Log("local files:" + files[i]);
  10. if (onlyZip && !files[i].Contains(".zip"))
  11. {
  12. Debug.Log("只上传zip文件,跳过:" + files[i]);
  13. continue;
  14. }
  15. fileList.Enqueue(files[i]);
  16. }
  17. }
  18. return fileList;
  19. }
  1. private static byte[] LoadData(string path)
  2. {
  3. Debug.Log("LoadData path:" + path);
  4. return System.IO.File.ReadAllBytes(path);
  5. }
  6. private static byte[] LoadData(string path)
  7. {
  8. FileStream fs = new FileStream(path, FileMode.Open);
  9. byte[] data = new byte[fs.Length];
  10. fs.Read(data, 0, data.Length);
  11. fs.Close();
  12. return data;
  13. }

        网上大佬前辈们和ChatGpt都建议使用Unity WebGL和JS交互来解决这个问题。先说一下原理:浏览器沙盒目录中的文件才支持读取。那我们需要利用JS创建一个文件夹选择对话框来选择要操作的文件,将文件列表发送给Unity WebGL,在Unity中利用UnityWebRequest将文件加载到浏览器沙盒目录下。就这么简单。来吧展示!

1. 先附个WebGL的页面效果

2. 在Unity Assets/Plugins目录下创建jslib文件FileDialog.jslib

  1. mergeInto(LibraryManager.library, {
  2. LoadFolder: function(_gameObjectName, _isZip) {
  3. console.log('Pointers:', {
  4. gameObjectName: _gameObjectName,
  5. isZip: _isZip
  6. });
  7. var gameObjectName = UTF8ToString(_gameObjectName);
  8. var isZip = UTF8ToString(_isZip);
  9. console.log('LoadFolder called for GameObject:', gameObjectName);
  10. console.log('LoadFolder called for ISZip:', isZip);
  11. // 创建新的文件输入元素
  12. console.log('Creating new file input element.');
  13. var fileInput = document.createElement('input');
  14. fileInput.type = 'file';
  15. fileInput.id = 'folderInput';
  16. fileInput.webkitdirectory = true; // 允许选择文件夹
  17. fileInput.multiple = true; // 允许多选文件
  18. fileInput.style.display = 'none'; // 隐藏元素
  19. document.body.appendChild(fileInput);
  20. // 定义事件处理函数
  21. var fileInputChangeHandler = function(event) {
  22. console.log('File selection changed.'); // 输出文件选择发生变化的信息
  23. var files = Array.from(event.target.files);
  24. console.log('Selected files:', files); // 输出所选文件的信息
  25. var fileNames = files.map(file => ({
  26. name: file.name,
  27. blobPath: URL.createObjectURL(file),
  28. localPath: file.webkitRelativePath || file.name
  29. }));
  30. var resultString = JSON.stringify({ files: fileNames });
  31. console.log('Sending file dialog result:', resultString); // 输出要发送到 Unity 的结果信息
  32. // 确保 gameInstance 已正确初始化
  33. if (window.gameInstance) {
  34. var message = isZip + "|" + resultString;
  35. window.gameInstance.SendMessage(gameObjectName, 'FileDialogResult', message);
  36. } else {
  37. console.error('gameInstance is not defined');
  38. }
  39. // 移除事件监听器并删除输入元素
  40. fileInput.removeEventListener('change', fileInputChangeHandler);
  41. document.body.removeChild(fileInput);
  42. };
  43. // 添加事件监听器
  44. fileInput.addEventListener('change', fileInputChangeHandler);
  45. console.log('Triggering file input click.');
  46. fileInput.click();
  47. }
  48. });

(1)LoadFolder函数支持Unity调用,该函数有两个参数,第一个是Unity中挂载脚本的物体名,第二个参数是我根据需求来设置传zip文件还是普通文件。所有C#传给js的字符串都需要用Pointer_stringify(或UTF8ToString 2021.2版本及以上)转化一遍,才能转化成js识别的字符串。官方文档:Interaction with browser scripting - Unity 手册

(2)调用LoadFolder函数,会创建文件夹选择对话框。当选择的文件有变化时,会触发fileInputChangeHandler函数,函数中会通过(gameInstance)Unity的SendMessage函数来进行通知,调用挂载脚本的FileDialogResult函数,传递文件列表。

(3)文件列表数据如下:

  1. Sending file dialog result: {"files":
  2. [
  3. {
  4. "name":".DS_Store",
  5. "blobPath":"blob:https://static0.xesimg.com/aa004c1f-947a-4237-8e15-cfd86b50281e",
  6. "localPath":"zip/.DS_Store"
  7. },
  8. {
  9. "name":"Android_Resource_base.zip",
  10. "blobPath":"blob:https://static0.xesimg.com/d3df1350-032a-4e2e-89d4-d2185f9015cf",
  11. "localPath":"zip/Android_Resource_base.zip"
  12. }
  13. ]}

        注意文件列表中的blobPath值(blob:xxx的形式),这种形式才能被WebRequest读取到,再加载到浏览器沙盒目录下。沙盒目录下路径为::/idbfs/bad4f2aac7af9d794a38b7e22b79d351/Res/Android_Resource_base.zip

(4)由于SendMessage只支持一个参数,在这里把isZip和文件列表信息合并在了一个字符串中。当然也可封装成一个json字符串。

(5)gameInstance是什么呢?gameInstance是unity运行实例,有的叫unityInstance或者别的东西,具体看自己js模版中定义的变量。

(6)JS代码中每次调用LoadFolder都创建fileInput对话框,及时销毁即可,防止内存泄漏。因为本人出现过【第一次调用LoadFolder函数isZip是true,第二次传的是false,但第二次isZip返回到unity的还是true】的问题及【change监听触发多次】的问题。可能在于 fileInputChangeHandler 函数中 isZip 变量的值没有及时更新,导致多次调用 LoadFolder 时使用的是上一次调用时的参数值。

3.Js模版

        补充index.html文件,来实例化gameInstance:

4.Unity中调用 

  1. [System.Serializable]
  2. public class FileList
  3. {
  4. public FileDetail[] files;
  5. }
  6. [System.Serializable]
  7. public class FileDetail
  8. {
  9. public string name;
  10. public string blobPath;
  11. public string localPath;
  12. }
  13. public class ToolView : MonoBehaviour
  14. {
  15. [DllImport("__Internal")]
  16. private static extern void LoadFolder(string gameObjectName, string isZip);
  17. private Button zipPathButton;
  18. private Button resPathButton;
  19. private void Awake()
  20. {
  21. zipPathButton = transform.Find("ZipPath/ZipPathButton").GetComponent
  22. zipPathButton.onClick.AddListener(() =>
  23. {
  24. GetLocalFileLists(true);
  25. });
  26. resPathButton = transform.Find("ResPath/ResPathButton").GetComponent
  27. resPathButton.onClick.AddListener(() =>
  28. {
  29. GetLocalFileLists(false);
  30. });
  31. }
  32. public void GetLocalFileLists(bool isZip = false)
  33. {
  34. string _iszip = isZip ? "true" : "false";
  35. string name = gameObject.name;
  36. Debug.Log("Unity GetLocalFileLists " + "isZip: " + _iszip + " name: " + name);
  37. LoadFolder(name, _iszip);
  38. }
  39. public void FileDialogResult(string message)
  40. {
  41. Debug.Log("Unity FileDialogResult: " + message);
  42. string[] messages = message.Split('|');
  43. string filesJson = messages[1];
  44. bool isZip = bool.Parse(messages[0]);
  45. if (isZip)
  46. {
  47. zipLocalFileList.Clear();
  48. }
  49. else
  50. {
  51. resLocalFileList.Clear();
  52. }
  53. var files = JsonUtility.FromJson(filesJson);
  54. needCopyCount = files.files.Length;
  55. Debug.Log("Received files:" + needCopyCount);
  56. copyCount = 0;
  57. foreach (var file in files.files)
  58. {
  59. StartCoroutine(CopyFile(file, isZip));
  60. }
  61. }
  62. int copyCount = 0;
  63. int needCopyCount = 0;
  64. IEnumerator CopyFile(FileDetail jsFileInfo, bool isZip = false)
  65. {
  66. // Debug.Log("Unity CopyFile: " + jsFileInfo.name + " - " + jsFileInfo.path);
  67. UnityWebRequest request = UnityWebRequest.Get(jsFileInfo.blobPath);
  68. //创建文件夹
  69. string dirPath = Path.Combine(Application.persistentDataPath, "Res");
  70. // Debug.Log("将被存至目录:" + dirPath);
  71. if (!Directory.Exists(dirPath))
  72. {
  73. Directory.CreateDirectory(dirPath);
  74. }
  75. string fullPath = Path.Combine(dirPath, jsFileInfo.name);
  76. request.downloadHandler = new DownloadHandlerFile(fullPath);//路径+文件名
  77. // Debug.Log("复制到沙盒ing:" + fullPath);
  78. yield return request.SendWebRequest();
  79. if (request.result == UnityWebRequest.Result.Success)
  80. {
  81. copyCount++;
  82. Debug.Log("复制到沙盒完成:" + fullPath + "," + copyCount);
  83. if (isZip)
  84. {
  85. if (fullPath.EndsWith(".zip"))
  86. {
  87. zipLocalFileList.Enqueue(fullPath);
  88. }
  89. }
  90. else
  91. {
  92. resLocalFileList.Enqueue(fullPath);
  93. }
  94. if (needCopyCount == copyCount)
  95. {
  96. if (isZip)
  97. {
  98. zipPathInputField.text = ".../" + jsFileInfo.localPath;
  99. }
  100. else
  101. {
  102. resPathInputField.text = ".../" + jsFileInfo.localPath;
  103. }
  104. }
  105. }
  106. else
  107. {
  108. Debug.Log(request.error);
  109. }
  110. }
  111. }

        文件拷贝到浏览器沙盒目录后, 即可使用​​​​​​​System.IO.File.ReadAllBytes(path)加载文件喽:

  1. while (localFileList.Count > 0)
  2. {
  3. string item = zipLocalFileList.Dequeue();
  4. byte[] data = LoadData(item);
  5. if (data != null)
  6. {
  7. Debug.Log("LoadData succeed");
  8. //...
  9. }
  10. }

5.打包测试

        注意Editor模式运行会报错EntryPointNotFoundException。需要打包运行测试。

四、补充说明

1.使用Chrome浏览器来运行WebGL,Safari浏览器无法弹出文件夹选择对话框。

2.运行WebGL偶现报错,清理浏览器缓存后解决,后续解决后补充。

注:本文转载自blog.csdn.net的烫青菜的文章"https://blog.csdn.net/weixin_39766005/article/details/144917512"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

134
游戏
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top