
Что за флаг?
Кто устанавливал офицальные ассеты от Unity «Starter Assets — Third Person Character Controller» или «Starter Assets — First Person Character Controller» возможно замечал что в настройках проета (Project settings -> Player -> Other settings -> Script Compilation) появляется флаг STARTER_ASSETS_PACKAGES_CHECKED, но зачем он нужен? Давайте разбираться.
Исследуем скрипты
Для иследования был выбран ассет «Starter Assets — First Person Character Controller». Открываем скрипт «ThirdPersonController» и что мы видим:
... namespace StarterAssets { [RequireComponent(typeof(CharacterController))] #if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED [RequireComponent(typeof(PlayerInput))] #endif public class ThirdPersonController : MonoBehaviour { ...
... #if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED private PlayerInput _playerInput; #endif ...
... #if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED _playerInput = GetComponent<PlayerInput>(); #else ...
По всюду этот флаг используется в паре с флагом ENABLE_INPUT_SYSTEM, хм… интересно. Очевидно что флаг ENABLE_INPUT_SYSTEM отвечает за новую систему ввода, но вот второй флаг зачем он здесь и кто его устанавливает в настройках проекта? Смотрим дальше. Нашелся еще один флаг в скрипте «StarterAssetsDeployMenu.cs», но уже тут он используется один:
... #if STARTER_ASSETS_PACKAGES_CHECKED private static void CheckCameras(Transform targetParent, string prefabFolder) { CheckMainCamera(prefabFolder); GameObject vcam = GameObject.Find(CinemachineVirtualCameraName); ...
Из кода становиться понятно что он включает работу с кинемашиной. Интересно, значит получается что этот флаг контролирует подключение кода который в свой очередь находиться в двух пакетах «com.unity.inputsystem» и «com.unity.cinemachine». С этим вроде немного разобрались, но всетаки кто устанавливает этот флаг в настройках проекта?
Исследуем файлы проекта
После тщательного обследования файлов ассета, выявлена подозрительная библиотека Assets\StarterAssets\Editor\PackageChecker\StarterAssetsPackageChecker.dll давайте ее отрефлектим:
Hidden text
// Decompiled with JetBrains decompiler // Type: StarterAssetsPackageChecker.PackageChecker // Assembly: StarterAssetsPackageChecker, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null // MVID: 2A478D25-B4D8-4B2D-BB34-CB7D710194F5 // Assembly location: D:\YandexDisk\Development\Games\secrets-from-unity\Assets\StarterAssets\Editor\PackageChecker\StarterAssetsPackageChecker.dll using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEditor.PackageManager; using UnityEditor.PackageManager.Requests; using UnityEngine; namespace StarterAssetsPackageChecker { public static class PackageChecker { private static ListRequest _clientList; private static SearchRequest _compatibleList; private static List<PackageChecker.PackageEntry> _packagesToAdd; private static AddRequest[] _addRequests; private static bool[] _installRequired; private static PackageChecker.Settings _settings; [InitializeOnLoadMethod] private static void CheckPackage() { PackageChecker._settings = new PackageChecker.Settings(); string[] files = Directory.GetFiles(Application.dataPath, "PackageCheckerSettings.json", SearchOption.AllDirectories); if (files.Length != 0) JsonUtility.FromJsonOverwrite(File.ReadAllText(files[0]), (object) PackageChecker._settings); if (PackageChecker.CheckScriptingDefine(PackageChecker._settings.PackageCheckerScriptingDefine)) return; PackageChecker._packagesToAdd = new List<PackageChecker.PackageEntry>(); PackageChecker._clientList = (ListRequest) null; PackageChecker._compatibleList = (SearchRequest) null; PackageChecker._packagesToAdd = new List<PackageChecker.PackageEntry>(); foreach (string str in PackageChecker._settings.PackagesToAdd) { char[] chArray = new char[1]{ '@' }; string[] strArray = str.Split(chArray); PackageChecker.PackageEntry packageEntry = new PackageChecker.PackageEntry() { Name = strArray[0], Version = strArray.Length > 1 ? strArray[1] : (string) null }; PackageChecker._packagesToAdd.Add(packageEntry); } PackageChecker.SetScriptingDefine(PackageChecker._settings.PackageCheckerScriptingDefine); PackageChecker._compatibleList = Client.SearchAll(); while (!PackageChecker._compatibleList.IsCompleted) { if ((PackageChecker._compatibleList.Status == StatusCode.Failure || PackageChecker._compatibleList.Error != null) && PackageChecker._compatibleList.Error != null) { Debug.LogError((object) PackageChecker._compatibleList.Error.message); break; } } PackageChecker._clientList = Client.List(); while (!PackageChecker._clientList.IsCompleted) { if ((PackageChecker._clientList.Status == StatusCode.Failure || PackageChecker._clientList.Error != null) && PackageChecker._clientList.Error != null) { Debug.LogError((object) PackageChecker._clientList.Error.message); break; } } PackageChecker._addRequests = new AddRequest[PackageChecker._packagesToAdd.Count]; PackageChecker._installRequired = new bool[PackageChecker._packagesToAdd.Count]; for (int index = 0; index < PackageChecker._installRequired.Length; ++index) PackageChecker._installRequired[index] = false; List<UnityEditor.PackageManager.PackageInfo> packageInfoList1 = new List<UnityEditor.PackageManager.PackageInfo>(); List<UnityEditor.PackageManager.PackageInfo> packageInfoList2 = new List<UnityEditor.PackageManager.PackageInfo>(); foreach (UnityEditor.PackageManager.PackageInfo packageInfo in PackageChecker._compatibleList.Result) packageInfoList1.Add(packageInfo); foreach (UnityEditor.PackageManager.PackageInfo packageInfo in (IEnumerable<UnityEditor.PackageManager.PackageInfo>) PackageChecker._clientList.Result) packageInfoList2.Add(packageInfo); for (int index = 0; index < PackageChecker._packagesToAdd.Count; ++index) { if (PackageChecker._packagesToAdd[index].Version == null) { foreach (UnityEditor.PackageManager.PackageInfo packageInfo in packageInfoList1) { if (PackageChecker._packagesToAdd[index].Name == packageInfo.name && packageInfo.versions.verified != string.Empty) { PackageChecker._packagesToAdd[index].Version = packageInfo.versions.verified; PackageChecker._installRequired[index] = true; } } } foreach (UnityEditor.PackageManager.PackageInfo packageInfo in packageInfoList2) { if (PackageChecker._packagesToAdd[index].Name == packageInfo.name) { switch (PackageChecker.CompareVersion(PackageChecker._packagesToAdd[index].Version, packageInfo.version)) { case -1: PackageChecker._installRequired[index] = (EditorUtility.DisplayDialog("Confirm Package Downgrade", "The version of \"" + PackageChecker._packagesToAdd[index].Name + "\" in this project is " + packageInfo.version + ". The latest verified version is " + PackageChecker._packagesToAdd[index].Version + ". " + packageInfo.version + " is unverified. Would you like to downgrade it to the latest verified version? (Recommended)", "Yes", "No") ? 1 : 0) != 0; Debug.Log((object) ("<b>Package version ahead</b>: " + packageInfo.packageId + " is newer than latest verified version " + packageInfo.versions.verified + ", skipped install")); continue; case 0: PackageChecker._installRequired[index] = false; Debug.Log((object) ("<b>Package version match</b>: " + packageInfo.packageId + " matches latest verified version " + packageInfo.versions.verified + ". Skipped install")); continue; case 1: PackageChecker._installRequired[index] = (EditorUtility.DisplayDialog("Confirm Package Upgrade", "The version of \"" + PackageChecker._packagesToAdd[index].Name + "\" in this project is " + packageInfo.version + ". The latest verified version is " + PackageChecker._packagesToAdd[index].Version + ". Would you like to upgrade it to the latest version? (Recommended)", "Yes", "No") ? 1 : 0) != 0; Debug.Log((object) ("<b>Package version behind</b>: " + packageInfo.packageId + " is behind latest verified version " + packageInfo.versions.verified + ". prompting user install")); continue; default: continue; } } } } for (int index = 0; index < PackageChecker._packagesToAdd.Count; ++index) { if (PackageChecker._installRequired[index]) PackageChecker._addRequests[index] = PackageChecker.InstallSelectedPackage(PackageChecker._packagesToAdd[index].Name, PackageChecker._packagesToAdd[index].Version); } PackageChecker.ReimportPackagesByKeyword(); } private static AddRequest InstallSelectedPackage( string packageName, string packageVersion) { if (packageVersion != null) { packageName = packageName + "@" + packageVersion; Debug.Log((object) ("<b>Adding package</b>: " + packageName)); } AddRequest addRequest = Client.Add(packageName); while (!addRequest.IsCompleted) { if ((addRequest.Status == StatusCode.Failure || addRequest.Error != null) && addRequest.Error != null) { Debug.LogError((object) addRequest.Error.message); return (AddRequest) null; } } return addRequest; } private static void ReimportPackagesByKeyword() { AssetDatabase.Refresh(); AssetDatabase.ImportAsset(PackageChecker._settings.EditorFolderRoot, ImportAssetOptions.ImportRecursive); } public static int CompareVersion(string latestVerifiedVersion, string projectVersion) { string[] strArray1 = latestVerifiedVersion.Split('.'); string[] strArray2 = projectVersion.Split('.'); int index1 = 0; for (int index2 = 0; index1 < strArray1.Length || index2 < strArray2.Length; ++index2) { int num1 = 0; int num2 = 0; if (index1 < strArray1.Length) num1 = Convert.ToInt32(strArray1[index1]); if (index2 < strArray2.Length) num2 = Convert.ToInt32(strArray2[index2]); if (num1 > num2) return 1; if (num1 < num2) return -1; ++index1; } return 0; } private static bool CheckScriptingDefine(string scriptingDefine) => PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup).Contains(scriptingDefine); private static void SetScriptingDefine(string scriptingDefine) { BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup; string defineSymbolsForGroup = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup); if (defineSymbolsForGroup.Contains(scriptingDefine)) return; string defines = defineSymbolsForGroup + ";" + scriptingDefine; PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, defines); } public static void RemovePackageCheckerScriptingDefine() { BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup; string defineSymbolsForGroup = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup); if (!defineSymbolsForGroup.Contains(PackageChecker._settings.PackageCheckerScriptingDefine)) return; string defines = defineSymbolsForGroup.Replace(PackageChecker._settings.PackageCheckerScriptingDefine, ""); PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, defines); } private class PackageEntry { public string Name; public string Version; } [Serializable] private class Settings { public string EditorFolderRoot = "Assets/StarterAssets/"; public string[] PackagesToAdd = new string[2] { "com.unity.cinemachine", "com.unity.inputsystem" }; public string PackageCheckerScriptingDefine => "STARTER_ASSETS_PACKAGES_CHECKED"; } } }
И что мы тут видим? Наш флажочек STARTER_ASSETS_PACKAGES_CHECKED)
[Serializable] private class Settings { public string EditorFolderRoot = "Assets/StarterAssets/"; public string[] PackagesToAdd = new string[2] { "com.unity.cinemachine", "com.unity.inputsystem" }; public string PackageCheckerScriptingDefine => "STARTER_ASSETS_PACKAGES_CHECKED"; }
Так, я чувствую что мы уже близко к истине.
Разбираем потроха StarterAssetsPackageChecker.dll
Изучив код этой библиотеки, я пришол к выводу что это — автоматический инсталятор пакетов Unity. Очень интересно! Давайте расскажу как эта штука работает.
Главный метод запускается каждый раз, при «перезагрузки» редактора, о чем говорит аттрибут[InitializeOnLoadMethod]в этом методе идет поиск файлов с именем «PackageCheckerSettings.json», а затем настройки из этого файла мапятся на PackageChecker._settings.
... [InitializeOnLoadMethod] private static void CheckPackage() { PackageChecker._settings = new PackageChecker.Settings(); string[] files = Directory.GetFiles(Application.dataPath, "PackageCheckerSettings.json", SearchOption.AllDirectories); if (files.Length != 0) JsonUtility.FromJsonOverwrite(File.ReadAllText(files[0]), (object) PackageChecker._settings); ...
Давайте взглянем что находиться в файле «PackageCheckerSettings.json», и тут мы видим опять наши пакеты, а также какой-то «EditorFolderRoot»:
{ "EditorFolderRoot": "Assets/StarterAssets/", "PackagesToAdd": [ "com.unity.cinemachine", "com.unity.inputsystem" ] }
Вернемся в нашу dll. Далее по коду идет сравнение версий пакетов и их установка с помощью метода private static AddRequest InstallSelectedPackage. И тут же видим нашу заветную строчку, которая задает флаг STARTER_ASSETS_PACKAGES_CHECKED на уровне проекта:
PackageChecker.SetScriptingDefine(PackageChecker._settings.PackageCheckerScriptingDefine); ... private static void SetScriptingDefine(string scriptingDefine) { BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup; string defineSymbolsForGroup = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup); if (defineSymbolsForGroup.Contains(scriptingDefine)) return; string defines = defineSymbolsForGroup + ";" + scriptingDefine; PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, defines); }
Теперь стало все понятно, этот флаг фиксирует установку пакетов и при дальнейшем вызове метода CheckPackage() идет проверка, что если флаг установлен то установку пакетов уже не производим. Вауля!!!
if (PackageChecker.CheckScriptingDefine(PackageChecker._settings.PackageCheckerScriptingDefine)) return;
А что насчет строчки "EditorFolderRoot": "Assets/StarterAssets/" из конфига? А тут все просто она указывает на папку с ассетами которым нужно сделать реимпорт после установки пакетов
private static void ReimportPackagesByKeyword() { AssetDatabase.Refresh(); AssetDatabase.ImportAsset(PackageChecker._settings.EditorFolderRoot, ImportAssetOptions.ImportRecursive); }
Что в итоге?
Мы можем использовать библиотеку StarterAssetsPackageChecker.dll в паре с файлом PackageCheckerSettings.json в своем проекте для автоматической установки пакетов Unity. Просто закидываем их к себе в папку Editor и добавляем необходимые пакеты в файл конфигурации.
Чтобы я улучшил в библиотеке StarterAssetsPackageChecker.dll так это сделал бы свойство public string PackageCheckerScriptingDefine => "STARTER_ASSETS_PACKAGES_CHECKED" доступным для записи, чтобы можно было задавать произвольное имя флага в своих ассетах. Еще бы добавил итерацию по всем файлам PackageCheckerSettings.json находящимся в проекте, чтобы установить все зависимости, а не производить установку только по первому попавшемуся файлу.
Могу предположить что у каманды Unity это своего рода «заготовка» для будущей автоматизации установки пакетов, поэтому будем надеяться и верить что работа с пакетами станет еще проще и удобней. А также пожелаем Unity чтобы она добавила возможность добавлять scope в файлы манифеста с помощью кода.
Присоединяйтесь к моим соц сетям:
YouTube: https://www.youtube.com/channel/UC8Pm1hZfQMKE8nfSdYqKugg
VK: https://vk.com/stupenkovanton
GitHub: https://github.com/stupenkov
Linkedin: https://www.linkedin.com/in/stupenkov/
ссылка на оригинал статьи https://habr.com/ru/post/686526/
Добавить комментарий