{"id":414631,"date":"2024-06-29T23:52:33","date_gmt":"2024-06-29T23:52:33","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=414631"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-06-29T23:52:33","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=414631","title":{"rendered":"<span>Unity: Selecting and uploading files by user on WebGL assembly<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>In this article, we will look at a way to give the user the ability to upload any files, such as textures.\u00a0And let&#8217;s touch on the topic of launching JS functions from C# within Unity. As a result, we will get a script that, by calling just one function, will open a window for selecting files. <\/p>\n<p>The standard way to add js scripts to a project is as follows:  <\/p>\n<ol>\n<li>\n<p>Create a Plugins folder, it&#8217;s a\u00a0<a href=\"https:\/\/translate.google.com\/website?sl=ru&amp;tl=en&amp;hl=ru&amp;client=webapp&amp;u=https:\/\/docs.unity3d.com\/2017.2\/Documentation\/Manual\/SpecialFolders.html\" rel=\"noopener noreferrer nofollow\">special folder for plugins<\/a>\u00a0.<\/p>\n<\/li>\n<li>\n<p>Create a .jslib file that will contain our JS code.<\/p>\n<\/li>\n<li>\n<p>Functions from JS can be called via:<\/p>\n<p>[DllImport(&#171;__Internal&#187;)] static extern void [JSFunctionName]();\u00a0, where [JSFunctionName] is the name of the function from .jslib .<\/p>\n<\/li>\n<li>\n<p>C# Methods can be called from JS via a GameObject name and a method name:<\/p>\n<p>MyGameInstance.SendMessage(&#8216;MyGameObject&#8217;, &#8216;MyFunction&#8217;, [var]);\u00a0, where MyGameObject is the name of a game object, MyFunction is the name of a method in any of components, [var] is a number or a string that will be passed to the method.\u00a0Works like GameObject.<a href=\"https:\/\/translate.google.com\/website?sl=ru&amp;tl=en&amp;hl=ru&amp;client=webapp&amp;u=https:\/\/docs.unity3d.com\/ScriptReference\/GameObject.SendMessage.html\" rel=\"noopener noreferrer nofollow\">SendMessage()<\/a>\u00a0.<\/p>\n<\/li>\n<\/ol>\n<p>In .jslib, it is mandatory to add functions to the main library using mergeInto(), examples:<\/p>\n<pre><code class=\"cs\">mergeInto(LibraryManager.library,  { \/\/ Your code here   Hello: function () {     window.alert(\"Hello, world!\");   } });<\/code><\/pre>\n<p>Or like this:  <\/p>\n<pre><code class=\"cs\">var SomeObject = { \/\/ Your code here   Hello: function () {     window.alert(\"Hello, world!\");   } };  mergeInto(LibraryManager.library, SomeObject);<\/code><\/pre>\n<p>See the\u00a0<a href=\"https:\/\/translate.google.com\/website?sl=ru&amp;tl=en&amp;hl=ru&amp;client=webapp&amp;u=https:\/\/docs.unity3d.com\/Manual\/webgl-interactingwithbrowserscripting.html\" rel=\"noopener noreferrer nofollow\">Unity documentation<\/a>\u00a0for more details .  <\/p>\n<details class=\"spoiler\">\n<summary>Advice<\/summary>\n<div class=\"spoiler__content\">\n<p>If you use Visual Studio, then I advise you to add an association with a JavaScript editor for .jslib files.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/3d5\/14a\/81e\/3d514a81e2178919a4ef66a81287d3b5.png\" width=\"auto\" height=\"auto\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/3d5\/14a\/81e\/3d514a81e2178919a4ef66a81287d3b5.png\"\/><figcaption><\/figcaption><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>We reviewed the basic information.\u00a0Now let&#8217;s start implementing feature that the user can upload a texture, for example for an avatar.  <\/p>\n<p>To request a file, we need a js script that will interact with the browser, since Unity does not provide direct access to the web form through C#.\u00a0And so, our script will look like this:  <\/p>\n<details class=\"spoiler\">\n<summary>Creating .jslib files<\/summary>\n<div class=\"spoiler__content\">\n<p>In Unity, you can&#8217;t create a .jslib file from the editor, for this you need to open a folder in the explorer and set the extension of a js file manually, this is long and inconvenient, so let&#8217;s add the following script that complements the editor to our project:  <\/p>\n<pre><code class=\"cs\">\/\/ Assets\/Editor\/JSLibFileCreator.cs using System.IO; using UnityEditor;  public class JSLibFileCreator {     [MenuItem(\"Assets\/Create\/JS Script\", priority = 80)]     private static void CreateJSLibFile()     {       \/\/ Script template so that the file is not empty initially         var asset =             \"mergeInto(LibraryManager.library,\\n\" +             \"{\\n\" +             \"\\t\/\/ Your code here\\n\" +             \"});\"; \/\/ We take the path to the current open folder in the Project window         string path = AssetDatabase.GetAssetPath(Selection.activeObject);         if (path == \"\")         {             path = \"Assets\";         }         else if (Path.GetExtension(path) != \"\")         {             path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), \"\");         } \/\/ Creating a .jslib file with a template         ProjectWindowUtil.CreateAssetWithContent(AssetDatabase.GenerateUniqueAssetPath(path + \"\/JSScript.jslib\"), asset);         \/\/ Saving Assets       AssetDatabase.SaveAssets();     } }<\/code><\/pre>\n<p>Now we can create .jslib files without too much headache, like this:  <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9b8\/834\/bdf\/9b8834bdff0156db6af4230541d49d50.png\" alt=\"You need to right-click in the Project window, select Create and then click on JS Script\" title=\"You need to right-click in the Project window, select Create and then click on JS Script\" width=\"auto\" height=\"auto\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9b8\/834\/bdf\/9b8834bdff0156db6af4230541d49d50.png\"\/><figcaption>You need to right-click in the Project window, select Create and then click on JS Script<\/figcaption><\/figure>\n<p>Created file:  <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/499\/e36\/b4a\/499e36b4a1613ab9dc206cc698372d4e.png\" width=\"auto\" height=\"auto\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/499\/e36\/b4a\/499e36b4a1613ab9dc206cc698372d4e.png\"\/><figcaption><\/figcaption><\/figure>\n<p>If we open it we will see our template:  <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/cb8\/207\/78b\/cb820778b1e1104f96ead7a55d8b638f.png\" width=\"auto\" height=\"auto\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/cb8\/207\/78b\/cb820778b1e1104f96ead7a55d8b638f.png\"\/><figcaption><\/figcaption><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<pre><code class=\"javascript\">\/\/ Assets\/Plugins\/WebGL\/JSFileUploader.jslib mergeInto(LibraryManager.library,     {         InitFileLoader: function (callbackObjectName, callbackMethodName) { \/\/ Strings received from C# must be decoded from UTF8             FileCallbackObjectName = UTF8ToString(callbackObjectName);             FileCallbackMethodName = UTF8ToString(callbackMethodName);            \/\/ Create an input to take files if there isn't one already             var fileuploader = document.getElementById('fileuploader');             if (!fileuploader) {                 console.log('Creating fileuploader...');                 fileuploader = document.createElement('input');                 fileuploader.setAttribute('style', 'display:none;');                 fileuploader.setAttribute('type', 'file');                 fileuploader.setAttribute('id', 'fileuploader');                 fileuploader.setAttribute('class', 'nonfocused');                 document.getElementsByTagName('body')[0].appendChild(fileuploader);                  fileuploader.onchange = function (e) {                     var files = e.target.files;                    \/\/ If the file is not selected, we complete the execution and call unfocus                   \/\/ Note: If you need to handle the case where the file is not                   \/\/ selected, then you can call SendMessage and pass                   \/\/ null, instead ResetFileLoader() if (files.length === 0) {                         ResetFileLoader();                         return;                     }                                        console.log('ObjectName: ' + FileCallbackObjectName + ';\\nMethodName: ' + FileCallbackMethodName + ';');                     SendMessage(FileCallbackObjectName, FileCallbackMethodName, URL.createObjectURL(files[0]));                 };             }              console.log('FileLoader initialized!');         },   \/\/ This function is called when the button is pressed, because browser protection doesn't skip click() call   \/\/ programmatically         RequestUserFile: function (extensions) {           \/\/ Decoding the string from UTF 8             var str = UTF8ToString(extensions);             var fileuploader = document.getElementById('fileuploader');            \/\/ If for some reason the fileuploader does not exist - set it           \/\/ This can happen in Blazor.NET projects             if (fileuploader === null)                 InitFileLoader(FileCallbackObjectName, FileCallbackMethodName);            \/\/ Set the received extensions             if (str !== null || str.match(\/^ *$\/) === null)                 fileuploader.setAttribute('accept', str);            \/\/ Focus on input and click             fileuploader.setAttribute('class', 'focused');             fileuploader.click();         },    \/\/ This function is called after the file is received.   \/\/ It can be called from RequestUserFile or fileUploader.onchange   \/\/ not from C#, which will be faster, but I'm using the call from C# as a mini-example   \/\/ of calling a function without parameters         ResetFileLoader: function () {             var fileuploader = document.getElementById('fileuploader');              if (fileuploader) {               \/\/ Removing input from focus                 fileuploader.setAttribute('class', 'nonfocused');             }         },     });<\/code><\/pre>\n<p>And let&#8217;s create a wrapper for convenient use of our js script:  <\/p>\n<pre><code class=\"cs\">\/\/ Assets\/Scripts\/FileUploader.cs using System; using System.Runtime.InteropServices; using UnityEngine;  \/\/ Helper component to get a file public class FileUploader : MonoBehaviour {     private void Start()     {     \/\/ We don't need to delete it on the new scene, because system is singletone         DontDestroyOnLoad(gameObject);     }      \/\/ This method is called from JS via SendMessage     void FileRequestCallback(string path)     {     \/\/ Sending the received link back to the FileUploaderHelper         FileUploaderHelper.SetResult(path);     } }  public static class FileUploaderHelper {     static FileUploader fileUploaderObject;     static Action&lt;string> pathCallback;      static FileUploaderHelper()     {         string methodName = \"FileRequestCallback\"; \/\/ We will not use reflection, so as not to complicate things, hardcode :)         string objectName = typeof(FileUploaderHelper).Name; \/\/ But not here        \/\/ Create a helper object for the FileUploader system         var wrapperGameObject = new GameObject(objectName, typeof(FileUploader));         fileUploaderObject = wrapperGameObject.GetComponent&lt;FileUploader>();        \/\/ Initializing the JS part of the FileUploader system         InitFileLoader(objectName, methodName);     }      \/\/\/ &lt;summary>     \/\/\/ Requests a file from the user.     \/\/\/ Should be called when the user clicks!     \/\/\/ &lt;\/summary>     \/\/\/ &lt;param name=\"callback\">Will be called after the user selects a file, the Http path to the file is passed as a parameter&lt;\/param>     \/\/\/ &lt;param name=\"extensions\">File extensions that can be selected, example: \".jpg, .jpeg, .png\"&lt;\/param>     public static void RequestFile(Action&lt;string> callback, string extensions = \".jpg, .jpeg, .png\")     {         RequestUserFile(extensions);         pathCallback = callback;     }      \/\/\/ &lt;summary>     \/\/\/ For internal use     \/\/\/ &lt;\/summary>     \/\/\/ &lt;param name=\"path\">The path to the file&lt;\/param>     public static void SetResult(string path)     {         pathCallback.Invoke(path);         Dispose();     }      private static void Dispose()     {         ResetFileLoader();         pathCallback = null;     }    \/\/ Below we declare external functions from our .jslib file     [DllImport(\"__Internal\")]     private static extern void InitFileLoader(string objectName, string methodName);      [DllImport(\"__Internal\")]     private static extern void RequestUserFile(string extensions);      [DllImport(\"__Internal\")]     private static extern void ResetFileLoader(); }<\/code><\/pre>\n<p>And for tests, we will create such a script that will receive a picture and set it as a user avatar:  <\/p>\n<pre><code class=\"cs\">\/\/ Assets\/Scripts\/AvatarController.cs using System.Collections; using UnityEngine; using UnityEngine.Networking; using UnityEngine.UI;  public class AvatarController : MonoBehaviour { \/\/ Link to the UI picture of the avatar in Canvas     public Image avatarImage;      \/\/ This method is called by the button (Button component)     public void UpdateAvatar()     {     \/\/ Requesting a file from the user         FileUploaderHelper.RequestFile((path) =>          {         \/\/ If the path is empty, ignore it.             if (string.IsNullOrWhiteSpace(path))                 return;              \/\/ Run a coroutine to load an image             StartCoroutine(UploadImage(path));             });     }      \/\/ Coroutine for image upload     IEnumerator UploadImage(string path)     {     \/\/ This is where the texture will be stored.         Texture2D texture;          \/\/ using to automatically call Dispose, create a request along the path to the file         using (UnityWebRequest imageWeb = new UnityWebRequest(path, UnityWebRequest.kHttpVerbGET))         { \/\/ We create a \"downloader\" for textures and pass it to the request             imageWeb.downloadHandler = new DownloadHandlerTexture();              \/\/ We send a request, execution will continue after the entire file have been downloaded             yield return imageWeb.SendWebRequest();              \/\/ Getting the texture from the \"downloader\"             texture = ((DownloadHandlerTexture)imageWeb.downloadHandler).texture;         }  \/\/ Create a sprite from a texture and pass it to the avatar image on the UI         avatarImage.sprite = Sprite.Create(             texture,              new Rect(0.0f, 0.0f, texture.width, texture.height),              new Vector2(0.5f, 0.5f));     } }<\/code><\/pre>\n<p>And also create a small scene:  <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b6f\/db3\/438\/b6fdb3438913480bab7eab9327505f21.png\" alt=\"The main parts are AvatarImage and LoadButton.\" title=\"The main parts are AvatarImage and LoadButton.\" width=\"auto\" height=\"auto\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b6f\/db3\/438\/b6fdb3438913480bab7eab9327505f21.png\"\/><figcaption>The main parts are AvatarImage and LoadButton.<\/figcaption><\/figure>\n<details class=\"spoiler\">\n<summary>Results of use on different browsers<\/summary>\n<div class=\"spoiler__content\">\n<details class=\"spoiler\">\n<summary>Edge<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f27\/71a\/244\/f2771a244952fc36913892c07de6d6a8.png\" width=\"auto\" height=\"auto\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f27\/71a\/244\/f2771a244952fc36913892c07de6d6a8.png\"\/><figcaption><\/figcaption><\/figure>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d9a\/6e0\/97a\/d9a6e097ae4f3f445d7443be16bec6e0.png\" width=\"auto\" height=\"auto\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d9a\/6e0\/97a\/d9a6e097ae4f3f445d7443be16bec6e0.png\"\/><figcaption><\/figcaption><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>Chrome<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/3e4\/f35\/37f\/3e4f3537f5df6750cec0f28d98f689dc.png\" width=\"auto\" height=\"auto\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/3e4\/f35\/37f\/3e4f3537f5df6750cec0f28d98f689dc.png\"\/><figcaption><\/figcaption><\/figure>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/82d\/d0d\/6a0\/82dd0d6a0b80c554dc21bbb6e4b02611.png\" width=\"auto\" height=\"auto\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/82d\/d0d\/6a0\/82dd0d6a0b80c554dc21bbb6e4b02611.png\"\/><figcaption><\/figcaption><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>Firefox<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8dc\/406\/0f1\/8dc4060f1d9de30e7bf2b9540b776956.png\" width=\"auto\" height=\"auto\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8dc\/406\/0f1\/8dc4060f1d9de30e7bf2b9540b776956.png\"\/><figcaption><\/figcaption><\/figure>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4b1\/e8c\/494\/4b1e8c494248db324a746963279c2899.png\" width=\"auto\" height=\"auto\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4b1\/e8c\/494\/4b1e8c494248db324a746963279c2899.png\"\/><figcaption><\/figcaption><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<\/div>\n<\/details>\n<p>As a result, we have a system for querying the user&#8217;s files, which returns the path to the selected file in 1 function call:  <\/p>\n<pre><code class=\"cs\">Action&lt;string> callback = (str) => { \/* Your file handler code here*\/ }; FileUploaderHelper.RequestFile(callback);  \/\/ Or so, if we need not pictures, but other, special files:  FileUploaderHelper.RequestFile(callback, \".txt, .docx, .csv\");<\/code><\/pre>\n<p>Thank you all for your attention, I hope my articles help you in your projects!\u00a0I will be glad to additions and criticism.  <\/p>\n<p>Code on GitHub:  <\/p>\n<p><a href=\"https:\/\/translate.google.com\/website?sl=ru&amp;tl=en&amp;hl=ru&amp;client=webapp&amp;u=https:\/\/github.com\/AlexMorOR\/Unity-UserFileUploader\" rel=\"noopener noreferrer nofollow\">AlexMorOR\/Unity-UserFileUploader: There is a script which allow you to request files from user.\u00a0(github.com)<\/a>  <\/p>\n<p>Translated for TechNation GlobalTalent Visa.<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/684936\/\"> https:\/\/habr.com\/ru\/articles\/684936\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>In this article, we will look at a way to give the user the ability to upload any files, such as textures.\u00a0And let&#8217;s touch on the topic of launching JS functions from C# within Unity. As a result, we will get a script that, by calling just one function, will open a window for selecting files. <\/p>\n<p>The standard way to add js scripts to a project is as follows:  <\/p>\n<ol>\n<li>\n<p>Create a Plugins folder, it&#8217;s a\u00a0<a href=\"https:\/\/translate.google.com\/website?sl=ru&amp;tl=en&amp;hl=ru&amp;client=webapp&amp;u=https:\/\/docs.unity3d.com\/2017.2\/Documentation\/Manual\/SpecialFolders.html\" rel=\"noopener noreferrer nofollow\">special folder for plugins<\/a>\u00a0.<\/p>\n<\/li>\n<li>\n<p>Create a .jslib file that will contain our JS code.<\/p>\n<\/li>\n<li>\n<p>Functions from JS can be called via:<\/p>\n<p>[DllImport(&#171;__Internal&#187;)] static extern void [JSFunctionName]();\u00a0, where [JSFunctionName] is the name of the function from .jslib .<\/p>\n<\/li>\n<li>\n<p>C# Methods can be called from JS via a GameObject name and a method name:<\/p>\n<p>MyGameInstance.SendMessage(&#8216;MyGameObject&#8217;, &#8216;MyFunction&#8217;, [var]);\u00a0, where MyGameObject is the name of a game object, MyFunction is the name of a method in any of components, [var] is a number or a string that will be passed to the method.\u00a0Works like GameObject.<a href=\"https:\/\/translate.google.com\/website?sl=ru&amp;tl=en&amp;hl=ru&amp;client=webapp&amp;u=https:\/\/docs.unity3d.com\/ScriptReference\/GameObject.SendMessage.html\" rel=\"noopener noreferrer nofollow\">SendMessage()<\/a>\u00a0.<\/p>\n<\/li>\n<\/ol>\n<p>In .jslib, it is mandatory to add functions to the main library using mergeInto(), examples:<\/p>\n<pre><code class=\"cs\">mergeInto(LibraryManager.library,  { \/\/ Your code here   Hello: function () {     window.alert(\"Hello, world!\");   } });<\/code><\/pre>\n<p>Or like this:  <\/p>\n<pre><code class=\"cs\">var SomeObject = { \/\/ Your code here   Hello: function () {     window.alert(\"Hello, world!\");   } };  mergeInto(LibraryManager.library, SomeObject);<\/code><\/pre>\n<p>See the\u00a0<a href=\"https:\/\/translate.google.com\/website?sl=ru&amp;tl=en&amp;hl=ru&amp;client=webapp&amp;u=https:\/\/docs.unity3d.com\/Manual\/webgl-interactingwithbrowserscripting.html\" rel=\"noopener noreferrer nofollow\">Unity documentation<\/a>\u00a0for more details .  <\/p>\n<details class=\"spoiler\">\n<summary>Advice<\/summary>\n<div class=\"spoiler__content\">\n<p>If you use Visual Studio, then I advise you to add an association with a JavaScript editor for .jslib files.<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<p>We reviewed the basic information.\u00a0Now let&#8217;s start implementing feature that the user can upload a texture, for example for an avatar.  <\/p>\n<p>To request a file, we need a js script that will interact with the browser, since Unity does not provide direct access to the web form through C#.\u00a0And so, our script will look like this:  <\/p>\n<details class=\"spoiler\">\n<summary>Creating .jslib files<\/summary>\n<div class=\"spoiler__content\">\n<p>In Unity, you can&#8217;t create a .jslib file from the editor, for this you need to open a folder in the explorer and set the extension of a js file manually, this is long and inconvenient, so let&#8217;s add the following script that complements the editor to our project:  <\/p>\n<pre><code class=\"cs\">\/\/ Assets\/Editor\/JSLibFileCreator.cs using System.IO; using UnityEditor;  public class JSLibFileCreator {     [MenuItem(\"Assets\/Create\/JS Script\", priority = 80)]     private static void CreateJSLibFile()     {       \/\/ Script template so that the file is not empty initially         var asset =             \"mergeInto(LibraryManager.library,\\n\" +             \"{\\n\" +             \"\\t\/\/ Your code here\\n\" +             \"});\"; \/\/ We take the path to the current open folder in the Project window         string path = AssetDatabase.GetAssetPath(Selection.activeObject);         if (path == \"\")         {             path = \"Assets\";         }         else if (Path.GetExtension(path) != \"\")         {             path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), \"\");         } \/\/ Creating a .jslib file with a template         ProjectWindowUtil.CreateAssetWithContent(AssetDatabase.GenerateUniqueAssetPath(path + \"\/JSScript.jslib\"), asset);         \/\/ Saving Assets       AssetDatabase.SaveAssets();     } }<\/code><\/pre>\n<p>Now we can create .jslib files without too much headache, like this:  <\/p>\n<figure class=\"\"><figcaption>You need to right-click in the Project window, select Create and then click on JS Script<\/figcaption><\/figure>\n<p>Created file:  <\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>If we open it we will see our template:  <\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<\/p>\n<\/div>\n<\/details>\n<pre><code class=\"javascript\">\/\/ Assets\/Plugins\/WebGL\/JSFileUploader.jslib mergeInto(LibraryManager.library,     {         InitFileLoader: function (callbackObjectName, callbackMethodName) { \/\/ Strings received from C# must be decoded from UTF8             FileCallbackObjectName = UTF8ToString(callbackObjectName);             FileCallbackMethodName = UTF8ToString(callbackMethodName);            \/\/ Create an input to take files if there isn't one already             var fileuploader = document.getElementById('fileuploader');             if (!fileuploader) {                 console.log('Creating fileuploader...');                 fileuploader = document.createElement('input');                 fileuploader.setAttribute('style', 'display:none;');                 fileuploader.setAttribute('type', 'file');                 fileuploader.setAttribute('id', 'fileuploader');                 fileuploader.setAttribute('class', 'nonfocused');                 document.getElementsByTagName('body')[0].appendChild(fileuploader);                  fileuploader.onchange = function (e) {                     var files = e.target.files;                    \/\/ If the file is not selected, we complete the execution and call unfocus                   \/\/ Note: If you need to handle the case where the file is not                   \/\/ selected, then you can call SendMessage and pass                   \/\/ null, instead ResetFileLoader() if (files.length === 0) {                         ResetFileLoader();                         return;                     }                                        console.log('ObjectName: ' + FileCallbackObjectName + ';\\nMethodName: ' + FileCallbackMethodName + ';');                     SendMessage(FileCallbackObjectName, FileCallbackMethodName, URL.createObjectURL(files[0]));                 };             }              console.log('FileLoader initialized!');         },   \/\/ This function is called when the button is pressed, because browser protection doesn't skip click() call   \/\/ programmatically         RequestUserFile: function (extensions) {           \/\/ Decoding the string from UTF 8             var str = UTF8ToString(extensions);             var fileuploader = document.getElementById('fileuploader');            \/\/ If for some reason the fileuploader does not exist - set it           \/\/ This can happen in Blazor.NET projects             if (fileuploader === null)                 InitFileLoader(FileCallbackObjectName, FileCallbackMethodName);            \/\/ Set the received extensions             if (str !== null || str.match(\/^ *$\/) === null)                 fileuploader.setAttribute('accept', str);            \/\/ Focus on input and click             fileuploader.setAttribute('class', 'focused');             fileuploader.click();         },    \/\/ This function is called after the file is received.   \/\/ It can be called from RequestUserFile or fileUploader.onchange   \/\/ not from C#, which will be faster, but I'm using the call from C# as a mini-example   \/\/ of calling a function without parameters         ResetFileLoader: function () {             var fileuploader = document.getElementById('fileuploader');              if (fileuploader) {               \/\/ Removing input from focus                 fileuploader.setAttribute('class', 'nonfocused');             }         },     });<\/code><\/pre>\n<p>And let&#8217;s create a wrapper for convenient use of our js script:  <\/p>\n<pre><code class=\"cs\">\/\/ Assets\/Scripts\/FileUploader.cs using System; using System.Runtime.InteropServices; using UnityEngine;  \/\/ Helper component to get a file public class FileUploader : MonoBehaviour {     private void Start()     {     \/\/ We don't need to delete it on the new scene, because system is singletone         DontDestroyOnLoad(gameObject);     }      \/\/ This method is called from JS via SendMessage     void FileRequestCallback(string path)     {     \/\/ Sending the received link back to the FileUploaderHelper         FileUploaderHelper.SetResult(path);     } }  public static class FileUploaderHelper {     static FileUploader fileUploaderObject;     static Action&lt;string> pathCallback;      static FileUploaderHelper()     {         string methodName = \"FileRequestCallback\"; \/\/ We will not use reflection, so as not to complicate things, hardcode :)         string objectName = typeof(FileUploaderHelper).Name; \/\/ But not here        \/\/ Create a helper object for the FileUploader system         var wrapperGameObject = new GameObject(objectName, typeof(FileUploader));         fileUploaderObject = wrapperGameObject.GetComponent&lt;FileUploader>();        \/\/ Initializing the JS part of the FileUploader system         InitFileLoader(objectName, methodName);     }      \/\/\/ &lt;summary>     \/\/\/ Requests a file from the user.     \/\/\/ Should be called when the user clicks!     \/\/\/ &lt;\/summary>     \/\/\/ &lt;param name=\"callback\">Will be called after the user selects a file, the Http path to the file is passed as a parameter&lt;\/param>     \/\/\/ &lt;param name=\"extensions\">File extensions that can be selected, example: \".jpg, .jpeg, .png\"&lt;\/param>     public static void RequestFile(Action&lt;string> callback, string extensions = \".jpg, .jpeg, .png\")     {         RequestUserFile(extensions);         pathCallback = callback;     }      \/\/\/ &lt;summary>     \/\/\/ For internal use     \/\/\/ &lt;\/summary>     \/\/\/ &lt;param name=\"path\">The path to the file&lt;\/param>     public static void SetResult(string path)     {         pathCallback.Invoke(path);         Dispose();     }      private static void Dispose()     {         ResetFileLoader();         pathCallback = null;     }    \/\/ Below we declare external functions from our .jslib file     [DllImport(\"__Internal\")]     private static extern void InitFileLoader(string objectName, string methodName);      [DllImport(\"__Internal\")]     private static extern void RequestUserFile(string extensions);      [DllImport(\"__Internal\")]     private static extern void ResetFileLoader(); }<\/code><\/pre>\n<p>And for tests, we will create such a script that will receive a picture and set it as a user avatar:  <\/p>\n<pre><code class=\"cs\">\/\/ Assets\/Scripts\/AvatarController.cs using System.Collections; using UnityEngine; using UnityEngine.Networking; using UnityEngine.UI;  public class AvatarController : MonoBehaviour { \/\/ Link to the UI picture of the avatar in Canvas     public Image avatarImage;      \/\/ This method is called by the button (Button component)     public void UpdateAvatar()     {     \/\/ Requesting a file from the user         FileUploaderHelper.RequestFile((path) =>          {         \/\/ If the path is empty, ignore it.             if (string.IsNullOrWhiteSpace(path))                 return;              \/\/ Run a<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-414631","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/414631","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=414631"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/414631\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=414631"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=414631"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=414631"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}