Что за флаг?
Кто устанавливал офицальные ассеты от 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/
Styrbo
В целом такая автоматизация уже существует - в зависимостях к пакету можно прописать нужные пакеты https://docs.unity3d.com/Manual/upm-dependencies.html
stupenkov Автор
Это то все понятно, но вы похоже не читаете название статьи "Зачем нужен флаг STARTER_ASSETS_PACKAGES_CHECKED в стартовых ассетах". Смысл статьи добраться до истины для чего нужен этот флаг, а вывод этой статьи о том что у Unity есть заготовка библиотеки для автоматизации установки пакетов. Т.е. если вы создаете .packageunity то зависимости туда не падают а с помощью этой dll можно автоматизировать установку всех зависимостей.
dm_bondarev
на этот вопрос давно ответили разработчики, первая ссылка в гугле https://forum.unity.com/threads/say-hello-to-the-new-starter-asset-packages.1123051/#post-7260505
Suvitruf
С этим проблема в том, что если ставить пакет из файла, то эти зависимости не установятся.