В этой статье мы рассмотрим способ как дать пользователю возможность загружать какие-либо файлы, к примеру текстуры. И немного затронем тему запуска 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 редактором
Мы разобрали базу, на случай, если Вы не знакомы с использованием 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 файлы без лишней головной боли, вот так:
Полученный файл:
Откроем его, и увидим наш шаблон:
// 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)); } }
И так же создадим небольшую сценку:
Результат использования на разных браузерах
Edge
Chrome
Firefox
В результате у нас есть система для запроса файлов пользователя, которая возвращает путь к выбранному файлу через 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/
Добавить комментарий