Unity WebGL(运行时)中的EditorUtility.OpenFilePanel

10
我希望用户在Unity WebGL游戏中从电脑中选择图像,但我无法找到任何库或代码来实现此功能。我需要在UnityWebGL(Runtime)中实现类似于EditorUtility.OpenFilePanel的功能。
是否有办法在Unity WebGL构建中获取此打开对话框?或者是否有办法在JavaScript中实现它?例如,我从用户处获取图像并将其传递给我的C#代码。
2个回答

15
这听起来可能很简单,但实际上做起来相当复杂,原因是WebGL构建在浏览器中运行,并受到许多安全限制的约束,其中包括限制其对本地文件系统的访问。不过,可以用一种hacky的方式实现。
想法是使用HTML文件输入打开文件浏览对话框。我们可以使用ExternalEval从Unity代码调用它,了解更多信息请参见: http://docs.unity3d.com/Manual/UnityWebPlayerandbrowsercommunication.html http://docs.unity3d.com/ScriptReference/Application.ExternalEval.html 然而,这并不容易。问题在于所有现代浏览器只允许在用户单击事件的结果下显示文件对话框,作为安全限制,你无法对此做任何事情。
好的,我们可以创建一个按钮,并在单击时打开文件对话框,这样行得通,对吗? 错误。如果我们仅创建 Unity 按钮并处理点击,则不会起作用,因为 Unity 有自己的事件管理,它与帧率同步,因此事件仅在实际的 JavaScript 事件结束后发生。这几乎是与此处描述的问题相同,http://docs.unity3d.com/Manual/webgl-cursorfullscreen.html 除了 Unity 没有很好的内置解决方案。
因此,这里是黑客:点击是鼠标按下 + 鼠标松开,对吧?我们向 HTML 文档添加点击侦听器,然后在 Unity 中监听我们的按钮的鼠标按下。当它按下时,我们知道下一个 UP 将是 click,因此我们在 HTML 文档中标记一些标志以记住它。然后,在文档中获得点击时,我们可以查看此标志并得出我们的按钮已被点击。然后,我们调用打开文件对话框的 JavaScript 函数,并使用 SendMessage http://docs.unity3d.com/Manual/webgl-interactingwithbrowserscripting.html 将结果发送回 Unity。最后。

但是等等,问题是当我们在浏览器中运行时,我们不能简单地获取文件路径。我们的应用程序不允许获取有关用户计算机的任何信息,这又是一种安全限制。我们能做的最好的事情就是使用URL.CreateObjectURL获取blob URL,这在大多数浏览器上都可以工作,http://caniuse.com/#search=createobjecturl

我们可以使用WWW类从中检索数据,只需记住此URL仅从应用程序范围内可访问。

因此,尽管解决方案非常巧妙,但也是可行的。以下是一个示例代码,允许用户选择图像,并将其设置为材质纹理。

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;

public class OpenFileDialog : MonoBehaviour, IPointerDownHandler {

    public Renderer preview;
    public Text text;

    void Start() {
        Application.ExternalEval(
            @"
document.addEventListener('click', function() {

    var fileuploader = document.getElementById('fileuploader');
    if (!fileuploader) {
        fileuploader = document.createElement('input');
        fileuploader.setAttribute('style','display:none;');
        fileuploader.setAttribute('type', 'file');
        fileuploader.setAttribute('id', 'fileuploader');
        fileuploader.setAttribute('class', 'focused');
        document.getElementsByTagName('body')[0].appendChild(fileuploader);

        fileuploader.onchange = function(e) {
        var files = e.target.files;
            for (var i = 0, f; f = files[i]; i++) {
                window.alert(URL.createObjectURL(f));
                SendMessage('" + gameObject.name +@"', 'FileDialogResult', URL.createObjectURL(f));
            }
        };
    }
    if (fileuploader.getAttribute('class') == 'focused') {
        fileuploader.setAttribute('class', '');
        fileuploader.click();
    }
});
            ");
    }

    public void OnPointerDown (PointerEventData eventData)  {
        Application.ExternalEval(
            @"
var fileuploader = document.getElementById('fileuploader');
if (fileuploader) {
    fileuploader.setAttribute('class', 'focused');
}
            ");
    }

    public void FileDialogResult(string fileUrl) {
        Debug.Log(fileUrl);
        text.text = fileUrl;
        StartCoroutine(PreviewCoroutine(fileUrl));
    }

    IEnumerator PreviewCoroutine(string url) {
        var www = new WWW(url);
        yield return www;
        preview.material.mainTexture = www.texture;
    }
}

如果有人能找到更简单的方法,请分享,但我真的怀疑这是可能的。希望这可以帮助你。


6

哇。Yuri Nudelman的解决方案是令人印象深刻的黑魔法,并且仍然是目前找到的最佳解决方案。但是:现在WWW-class和ExternalEval已经被弃用。

它可以运行(有警告),但将在不久的将来停止工作。

因此,为了帮助任何想要实现这一点的人:

两个JavaScript函数必须放在“Plugins”文件夹内的.jslib中。第一个像这样:

mergeInto(
  LibraryManager.library,
  {
    AddClickListenerForFileDialog: function () {
      document.addEventListener('click', function () {

        var fileuploader = document.getElementById('fileuploader');
        if (!fileuploader) {
          fileuploader = document.createElement('input');
          fileuploader.setAttribute('style', 'display:none;');
          fileuploader.setAttribute('type', 'file');
          fileuploader.setAttribute('id', 'fileuploader');
          fileuploader.setAttribute('class', '');
          document.getElementsByTagName('body')[0].appendChild(fileuploader);

          fileuploader.onchange = function (e) {
            var files = e.target.files;
            for (var i = 0, f; f = files[i]; i++) {
              window.alert(URL.createObjectURL(f));
              SendMessage('BrowserFileLoading', 'FileDialogResult', URL.createObjectURL(f));
            }
          };
        }
        if (fileuploader.getAttribute('class') == 'focused') {
          fileuploader.setAttribute('class', '');
          fileuploader.click();
        }
      });
    }
  }
);

请注意,我做了两个更改: a) 我删除了“focused”。这样可以防止脚本在程序开始时触发:
   fileuploader.setAttribute('class', '');

b) 我手动添加了Unity游戏对象的名称。这个名称必须与您放置(Unity)脚本的游戏对象相同:

   SendMessage('BrowserFileLoading', 'FileDialogResult', URL.createObjectURL(f));

您可以使用以下方式调用此外部函数:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using UnityEngine.Networking;
using System.Runtime.InteropServices;
public class BrowserFileLoadingDialog : MonoBehaviour
{
  [DllImport("__Internal")] private static extern void AddClickListenerForFileDialog();

  void Start()
  {
    AddClickListenerForFileDialog();
  }

  public void FileDialogResult(string fileUrl)
  {
    Debug.Log(fileUrl);
    UrlTextField.text = fileUrl;
    StartCoroutine(LoadBlob(fileUrl));
  }

  IEnumerator LoadBlob(string url)
  {
    UnityWebRequest webRequest = UnityWebRequest.Get(url);
    yield return webRequest.SendWebRequest();

    if (!webRequest.isNetworkError && !webRequest.isHttpError)
    {
      // Get text content like this:
      Debug.Log(webRequest.downloadHandler.text);

    }
}

第二个脚本(可以放在相同的.jslib文件中)看起来像这样:
mergeInto(
  LibraryManager.library,
  {
    FocusFileUploader: function () {
      var fileuploader = document.getElementById('fileuploader');
      if (fileuploader) {
          fileuploader.setAttribute('class', 'focused');
      }
    }
  }
);

没有太大的改变,它与上面那个的使用方式相同,应该(就像Yuri Nudelman建议的那样)在CursorDown事件中调用。

它提供的是 Blob 名称而不是文件名和路径。 - Savad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接