В этой статье мы рассмотрим способ как дать пользователю возможность загружать какие-либо файлы, к примеру текстуры. И немного затронем тему запуска JS функций из C# в рамках Unity.
Стандартный способ добавления js скриптов в проект следующий:
-
Создать папку Plugins, это специальная папка для плагинов.
-
Создать файл .jslib, в котором будет содержаться наш JS код.
-
Функции из JS можно вызывать через:
[DllImport(«__Internal»)] static extern void [JSFunctionName](); , где [JSFunctionName] — название функции из .jslib .
-
По стандарту, методы из C# в JS можно вызывать через имя GameObject и название метода:
MyGameInstance.SendMessage(‘MyGameObject’, ‘MyFunction’, [var]); , где MyGameObject — имя игрового объекта, MyFunction — имя метода в любом из компонентов, [var] — число или строка, которая будет передана в метод. Работает как GameObject.SendMessage().
В .jslib обязательно нужно добавлять функции в основную библиотеку, при помощи mergeInto(), примеры:
mergeInto(LibraryManager.library, { // Your code here Hello: function () { window.alert("Hello, world!"); } });
Или следующим образом:
var SomeObject = { // Your code here Hello: function () { window.alert("Hello, world!"); } }; mergeInto(LibraryManager.library, SomeObject);
Подробнее говорится в документации Unity.
Совет
Если вы пользуетесь Visual Studio, то советую добавить для .jslib файлов ассоциацию с JavaScript редактором
![](https://habrastorage.org/getpro/habr/upload_files/3d5/14a/81e/3d514a81e2178919a4ef66a81287d3b5.png)
Мы разобрали базу, на случай, если Вы не знакомы с использованием JS скриптов из C# на Unity. Теперь приступим к реализации получения текстуры, которую пользователь сможет загрузить, к примеру для аватара.
Для запроса файла нам необходим js скрипт, который будет взаимодействовать с браузером, так как Unity не предоставляет прямого доступа к веб-форме через C#. И так, наш скрипт будет выглядеть следующим образом:
Создание .jslib файлов
В Unity нельзя создать .jslib файл из редактора, для этого нужно открывать папку в проводнике и задавать расширение вручную, это долго и не удобно, поэтому добавим следующий скрипт, дополняющий редактор, в наш проект:
// Assets/Editor/JSLibFileCreator.cs using System.IO; using UnityEditor; public class JSLibFileCreator { [MenuItem("Assets/Create/JS Script", priority = 80)] private static void CreateJSLibFile() { // Шаблон скрипта, что бы файл не был пустым изначально var asset = "mergeInto(LibraryManager.library,\n" + "{\n" + "\t// Your code here\n" + "});"; // Берем путь до текущей открытой папки в окне Project string path = AssetDatabase.GetAssetPath(Selection.activeObject); if (path == "") { path = "Assets"; } else if (Path.GetExtension(path) != "") { path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), ""); } // Создаем .jslib файл с шаблоном ProjectWindowUtil.CreateAssetWithContent(AssetDatabase.GenerateUniqueAssetPath(path + "/JSScript.jslib"), asset); // Сохраняем ассеты AssetDatabase.SaveAssets(); } }
Теперь мы можем создавать .jslib файлы без лишней головной боли, вот так:
![Нужно нажать ПКМ в окне Project, выбрать Create и затем нажать на JS Script Нужно нажать ПКМ в окне Project, выбрать Create и затем нажать на JS Script](https://habrastorage.org/getpro/habr/upload_files/9b8/834/bdf/9b8834bdff0156db6af4230541d49d50.png)
Полученный файл:
![](https://habrastorage.org/getpro/habr/upload_files/499/e36/b4a/499e36b4a1613ab9dc206cc698372d4e.png)
Откроем его, и увидим наш шаблон:
![](https://habrastorage.org/getpro/habr/upload_files/cb8/207/78b/cb820778b1e1104f96ead7a55d8b638f.png)
// Assets/Plugins/WebGL/JSFileUploader.jslib mergeInto(LibraryManager.library, { InitFileLoader: function (callbackObjectName, callbackMethodName) { // Полученные из C# строки необходимо декодировать из UTF8 FileCallbackObjectName = UTF8ToString(callbackObjectName); FileCallbackMethodName = UTF8ToString(callbackMethodName); // Создаем input для взятия файлов, если такого еще нет 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; // Если файл не выбран - завершаем выполнение и вызываем unfocus // Пометка: Если необходимо обрабатывать случай, когда файл не // выбран, то тут можно вызывать SendMessage и передавать ему // null, вместо ResetFileLoader() if (files.length === 0) { ResetFileLoader(); return; } console.log('ObjectName: ' + FileCallbackObjectName + ';\nMethodName: ' + FileCallbackMethodName + ';'); SendMessage(FileCallbackObjectName, FileCallbackMethodName, URL.createObjectURL(files[0])); }; } console.log('FileLoader initialized!'); }, // Эта функция вызывается на нажатие кнопки, т.к. защита браузера не пропускает вызов click() // программно RequestUserFile: function (extensions) { // Переводим строку из UTF8 var str = UTF8ToString(extensions); var fileuploader = document.getElementById('fileuploader'); // Если по каким-то причинам fileuploader не существует - задаем его // Это может случится в проектах Blazor.NET if (fileuploader === null) InitFileLoader(FileCallbackObjectName, FileCallbackMethodName); // Задаем полученные расширения if (str !== null || str.match(/^ *$/) === null) fileuploader.setAttribute('accept', str); // Фокус на инпут и клик fileuploader.setAttribute('class', 'focused'); fileuploader.click(); }, // Эта функция вызывается после получения файла // Её можно вызывать из RequestUserFile или fileUploader.onchange // а не из C#, что будет быстрее, но я использую вызов из C# как мини-пример // вызова функции без параметров ResetFileLoader: function () { var fileuploader = document.getElementById('fileuploader'); if (fileuploader) { // Убираем инпут из фокуса fileuploader.setAttribute('class', 'nonfocused'); } }, });
И создадим обертку, для удобного использования js скрипта:
// Assets/Scripts/FileUploader.cs using System; using System.Runtime.InteropServices; using UnityEngine; // Компонент - помошник, для получения файла public class FileUploader : MonoBehaviour { private void Start() { // Делаем его неуничтожимым DontDestroyOnLoad(gameObject); } // Этот метод вызывается из JS через SendMessage void FileRequestCallback(string path) { // Отсылаем полученную ссылку обратно в FileUploaderHelper FileUploaderHelper.SetResult(path); } } public static class FileUploaderHelper { static FileUploader fileUploaderObject; static Action<string> pathCallback; static FileUploaderHelper() { string methodName = "FileRequestCallback"; // Не будем использовать рефлекцию, чтобы не усложнять, захардкодим :) string objectName = typeof(FileUploaderHelper).Name; // А здесь используем // Создаем объект - помошник для системы FileUploader var wrapperGameObject = new GameObject(objectName, typeof(FileUploader)); fileUploaderObject = wrapperGameObject.GetComponent<FileUploader>(); // Инициализируем JS часть системы FileUploader InitFileLoader(objectName, methodName); } /// <summary> /// Запрашивает файл у пользователя. /// Должен вызываться при клике пользователя! /// </summary> /// <param name="callback">Будет вызван после выбора файла пользователем, в качестве параметра передается Http путь к файлу</param> /// <param name="extensions">Расширения файлов, которые можно выбрать, пример: ".jpg, .jpeg, .png"</param> public static void RequestFile(Action<string> callback, string extensions = ".jpg, .jpeg, .png") { RequestUserFile(extensions); pathCallback = callback; } /// <summary> /// Для внутреннего использования /// </summary> /// <param name="path">Путь к файлу</param> public static void SetResult(string path) { pathCallback.Invoke(path); Dispose(); } private static void Dispose() { ResetFileLoader(); pathCallback = null; } // Ниже мы объявляем внешнии функции из нашего .jslib файла [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(); }
И для тестов создадим такой скриптик, который будет получать картинку и задавать ее как аватар пользователя:
// Assets/Scripts/AvatarController.cs using System.Collections; using UnityEngine; using UnityEngine.Networking; using UnityEngine.UI; public class AvatarController : MonoBehaviour { // Ссылка на UI картинку аватара в Canvas public Image avatarImage; // Этот метод вызывается кнопкой (Button компонент) public void UpdateAvatar() { // Запрашиваем файл у пользователя FileUploaderHelper.RequestFile((path) => { // Если путь пустой - игнорируем if (string.IsNullOrWhiteSpace(path)) return; // Запускаем корутину для загрузки картинки StartCoroutine(UploadImage(path)); }); } // Корутина для загрузки картинки IEnumerator UploadImage(string path) { // Тут будет хранится текстура Texture2D texture; // using для автоматического вызова Dispose, создаем запрос по пути к файлу using (UnityWebRequest imageWeb = new UnityWebRequest(path, UnityWebRequest.kHttpVerbGET)) { // Задаем "скачиватель" для текстур и передаем запросу imageWeb.downloadHandler = new DownloadHandlerTexture(); // Отправляем запрос, выполнение продолжится после выгрузки всего файла yield return imageWeb.SendWebRequest(); // Получаем текстуру из "скачивателя" texture = ((DownloadHandlerTexture)imageWeb.downloadHandler).texture; } // Создаем спрайт из текстуры и передаем в картинку аватара на UI avatarImage.sprite = Sprite.Create( texture, new Rect(0.0f, 0.0f, texture.width, texture.height), new Vector2(0.5f, 0.5f)); } }
И так же создадим небольшую сценку:
![Главные части - AvatarImage и Button. Главные части - AvatarImage и Button.](https://habrastorage.org/getpro/habr/upload_files/b6f/db3/438/b6fdb3438913480bab7eab9327505f21.png)
Результат использования на разных браузерах
Edge
![](https://habrastorage.org/getpro/habr/upload_files/f27/71a/244/f2771a244952fc36913892c07de6d6a8.png)
![](https://habrastorage.org/getpro/habr/upload_files/d9a/6e0/97a/d9a6e097ae4f3f445d7443be16bec6e0.png)
Chrome
![](https://habrastorage.org/getpro/habr/upload_files/3e4/f35/37f/3e4f3537f5df6750cec0f28d98f689dc.png)
![](https://habrastorage.org/getpro/habr/upload_files/82d/d0d/6a0/82dd0d6a0b80c554dc21bbb6e4b02611.png)
Firefox
![](https://habrastorage.org/getpro/habr/upload_files/8dc/406/0f1/8dc4060f1d9de30e7bf2b9540b776956.png)
![](https://habrastorage.org/getpro/habr/upload_files/4b1/e8c/494/4b1e8c494248db324a746963279c2899.png)
В результате у нас есть система для запроса файлов пользователя, которая возвращает путь к выбранному файлу через 1 вызов функции:
Action<string> callback = (str) => { /* Your file handler code here*/ }; FileUploaderHelper.RequestFile(callback); // Или так, если нам нужны не картинки, а другие, особые файлы: FileUploaderHelper.RequestFile(callback, ".txt, .docx, .csv");
Благодарю всех за внимание, надеюсь мои статьи помогают Вам в Ваших проектах! Буду рад дополнениям и критике, но хочу оправдаться, что система была вырезана из моего проекта, а не написана с нуля, поэтому тут могут быть лишние части.
Код на GitHub:
ссылка на оригинал статьи https://habr.com/ru/post/684772/
Добавить комментарий