Секретики Unity3d. Зачем нужен флаг STARTER_ASSETS_PACKAGES_CHECKED в стартовых ассетах

от автора

Что за флаг?

Кто устанавливал офицальные ассеты от 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *