Last active
January 6, 2025 09:39
-
-
Save adammyhre/def9c0350b332e48534522cd071e21bf to your computer and use it in GitHub Desktop.
Faster Unity Asset Importer (for use with Setup Script)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Reflection; | |
using UnityEditor; | |
using UnityEngine; | |
public static class PackageManagerHelper { | |
const string ServicesNamespace = "UnityEditor.PackageManager.UI.Internal"; | |
/// <summary> | |
/// Resolves a PackageManager service by its interface name. | |
/// Locates the service in the internal ServicesContainer based on the given interface name | |
/// and returns the instance of the resolved service. | |
/// </summary> | |
/// <param name="interfaceName">The name of the interface to resolve (e.g., "IPackageDatabase").</param> | |
/// <returns>The resolved service instance corresponding to the interface name.</returns> | |
/// <exception cref="Exception">Thrown if any required type, property, or method cannot be found | |
/// or if the service cannot be resolved.</exception> | |
static object ResolveService(string interfaceName) { | |
try { | |
// Retrieve the ServicesContainer type | |
var servicesContainerType = Type.GetType($"{ServicesNamespace}.ServicesContainer, UnityEditor") | |
?? throw new Exception($"Failed to find {ServicesNamespace}.ServicesContainer type."); | |
// Get the instance property from ScriptableSingleton | |
var instanceProperty = typeof(ScriptableSingleton<>).MakeGenericType(servicesContainerType) | |
.GetProperty("instance", BindingFlags.Static | BindingFlags.Public) | |
?? throw new Exception("Failed to find 'instance' property on ScriptableSingleton."); | |
var servicesContainerInstance = instanceProperty.GetValue(null) | |
?? throw new Exception("Failed to get ServicesContainer instance."); | |
// Retrieve the Resolve<T> method | |
var resolveMethod = servicesContainerType.GetMethod("Resolve", BindingFlags.Instance | BindingFlags.Public) | |
?? throw new Exception("Failed to find 'Resolve' method on ServicesContainer."); | |
// Resolve the interface type dynamically | |
var interfaceType = Type.GetType($"{ServicesNamespace}.{interfaceName}, UnityEditor") | |
?? throw new Exception($"Failed to find interface type: {ServicesNamespace}.{interfaceName}."); | |
// Invoke the generic Resolve<T> method | |
return resolveMethod.MakeGenericMethod(interfaceType).Invoke(servicesContainerInstance, null); | |
} | |
catch (Exception ex) { | |
throw new Exception($"Error resolving service '{interfaceName}': {ex.Message}"); | |
} | |
} | |
/// <summary> | |
/// Imports a package from the Package Manager by its name. | |
/// Uses the Unity Package Manager services to locate and import the package by triggering its installation. | |
/// </summary> | |
/// <param name="packageName">The name of the package to import (e.g., "com.unity.textmeshpro").</param> | |
/// <returns> | |
/// `true` if the package is successfully imported; | |
/// `false` if the service cannot be resolved, the package is not found, or an error occurs during installation. | |
/// </returns> | |
/// <exception cref="Exception">Thrown if any required service, method, or package property cannot be resolved.</exception> | |
public static bool ImportPackageByName(string packageName) { | |
try { | |
// Resolve IPackageOperationDispatcher | |
var dispatcher = ResolveService("IPackageOperationDispatcher") | |
?? throw new Exception("Failed to resolve IPackageOperationDispatcher."); | |
// Find package by name | |
var package = FindPackageByName(packageName) | |
?? throw new Exception($"Package '{packageName}' not found in the cache."); | |
// Retrieve m_AssetStorePackageInstaller | |
var installerField = dispatcher.GetType() | |
.GetField("m_AssetStorePackageInstaller", BindingFlags.Instance | BindingFlags.NonPublic) | |
?? throw new Exception("Failed to find m_AssetStorePackageInstaller field on IPackageOperationDispatcher."); | |
var assetStorePackageInstaller = installerField.GetValue(dispatcher) | |
?? throw new Exception("Failed to retrieve m_AssetStorePackageInstaller instance."); | |
// Retrieve Install method | |
var installMethod = assetStorePackageInstaller.GetType() | |
.GetMethod("Install", BindingFlags.Instance | BindingFlags.Public) | |
?? throw new Exception("Failed to find Install method on AssetStorePackageInstaller."); | |
// Resolve product and productId | |
var product = package.GetType().GetProperty("product", BindingFlags.Instance | BindingFlags.Public) | |
?.GetValue(package) | |
?? throw new Exception("Product information is null."); | |
var productId = Convert.ToInt64( | |
product.GetType().GetProperty("id", BindingFlags.Instance | BindingFlags.Public) | |
?.GetValue(product) | |
?? throw new Exception("Failed to retrieve 'id' property on package product.") | |
); | |
// Invoke the install method | |
installMethod.Invoke(assetStorePackageInstaller, new object[] { productId, false }); | |
Debug.Log($"Package '{packageName}' imported successfully."); | |
return true; | |
} | |
catch (Exception ex) { | |
Debug.LogError($"Error importing package '{packageName}': {ex.Message}"); | |
return false; | |
} | |
} | |
/// <summary> | |
/// Finds a package in the Unity Package Manager by its name. | |
/// Resolves the internal PackageCache and retrieves the package along with its version details. | |
/// </summary> | |
/// <param name="packageName">The name of the package to search for (e.g., "com.unity.textmeshpro").</param> | |
/// <returns> | |
/// The resolved package object if found; | |
/// `null` if the service, method, or package cannot be resolved. | |
/// </returns> | |
/// <exception cref="Exception">Thrown if any required service, method, or package property cannot be resolved.</exception> | |
static object FindPackageByName(string packageName) { | |
try { | |
// Resolve the internal PackageDatabase | |
var packageDatabase = ResolveService("IPackageDatabase") | |
?? throw new Exception("Failed to resolve IPackageDatabase."); | |
// Retrieve the GetPackageAndVersionByIdOrName method | |
var getPackageMethod = packageDatabase.GetType().GetMethod( | |
"GetPackageAndVersionByIdOrName", | |
BindingFlags.Instance | BindingFlags.Public | |
) | |
?? throw new Exception("Failed to find GetPackageAndVersionByIdOrName method on IPackageDatabase."); | |
// Prepare parameters and invoke the method | |
object[] parameters = { packageName, null, null, true }; | |
getPackageMethod.Invoke(packageDatabase, parameters); | |
return parameters[1]; // Return the resolved package | |
} | |
catch (Exception ex) { | |
Debug.LogError($"Error while resolving package '{packageName}': {ex.Message}"); | |
return null; | |
} | |
} | |
/// <summary> | |
/// Searches the user's Asset Store purchases based on a provided search text | |
/// and ensures the results are loaded into the PackageManager cache. | |
/// Utilizes Unity's Asset Store-related services to query purchased assets | |
/// and populate the cache for further use. | |
/// </summary> | |
/// <param name="searchText">The text to search for in the Asset Store purchases (e.g., "AutoRef - Constants in Code").</param> | |
/// <exception cref="Exception">Thrown if any required service or method cannot be resolved while executing the search.</exception> | |
public static void SearchAssetStorePurchases(string searchText) { | |
try { | |
// Resolve AssetStoreClientV2 | |
var assetStoreClient = ResolveService("AssetStoreClientV2") | |
?? throw new Exception("Failed to resolve AssetStoreClientV2."); | |
// Create PurchasesQueryArgs with the search text | |
var purchasesQueryArgsType = typeof(ScriptableSingleton<>).Assembly.GetType("UnityEditor.PackageManager.UI.Internal.PurchasesQueryArgs") | |
?? throw new Exception("Failed to find PurchasesQueryArgs type."); | |
var queryArgsConstructor = purchasesQueryArgsType.GetConstructor( | |
new[] { typeof(int), typeof(int), typeof(string), Type.GetType("UnityEditor.PackageManager.UI.Internal.PageFilters, UnityEditor") } | |
) | |
?? throw new Exception("Failed to find PurchasesQueryArgs constructor."); | |
var queryArgs = queryArgsConstructor.Invoke(new object[] { 0, 0, searchText, null }); | |
// Call ListPurchases | |
var listPurchasesMethod = assetStoreClient.GetType().GetMethod("ListPurchases", BindingFlags.Instance | BindingFlags.Public) | |
?? throw new Exception("Failed to find ListPurchases method on AssetStoreClientV2."); | |
listPurchasesMethod.Invoke(assetStoreClient, new object[] { queryArgs }); | |
Debug.Log($"Successfully triggered a search for '{searchText}' in the Asset Store."); | |
} | |
catch (Exception ex) { | |
Debug.LogError($"Error searching Asset Store purchases: {ex.Message}"); | |
} | |
} | |
// Testing only | |
public static void ClearAssetStoreOnlineCache() { | |
try { | |
// Resolve AssetStoreClientV2 and retrieve m_AssetStoreCache | |
var assetStoreClient = ResolveService("AssetStoreClientV2") | |
?? throw new Exception("Failed to resolve AssetStoreClientV2."); | |
var cacheField = assetStoreClient.GetType().GetField("m_AssetStoreCache", BindingFlags.Instance | BindingFlags.NonPublic) | |
?? throw new Exception("m_AssetStoreCache field not found on AssetStoreClientV2."); | |
var assetStoreCache = cacheField.GetValue(assetStoreClient) | |
?? throw new Exception("m_AssetStoreCache instance is null."); | |
// Find and invoke ClearOnlineCache from m_AssetStoreCache | |
var clearCacheMethod = assetStoreCache.GetType().GetMethod("ClearOnlineCache", BindingFlags.Instance | BindingFlags.Public) | |
?? throw new Exception("ClearOnlineCache method not found on IAssetStoreCache."); | |
clearCacheMethod.Invoke(assetStoreCache, null); | |
Debug.Log("Successfully cleared the Asset Store online cache."); | |
} | |
catch (Exception ex) { | |
Debug.LogError($"Error clearing Asset Store cache: {ex.Message}"); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Example | |
public static class ProjectSetup { | |
[MenuItem("Tools/Setup/First Time Setup (NEW)")] | |
public static async void ImportEssentials2() { | |
// IMPORTANT: Use Asset Names as they are diplayed in the Unity Package Manager | |
var displayNames = new[] { | |
"Odin Inspector and Serializer", | |
"Odin Validator", | |
"Hot Reload | Edit Code Without Compiling", | |
"PrimeTween · High-Performance Animations and Sequences", | |
"Editor Console Pro", | |
"Component Names", | |
"Debug.Log Extensions", | |
"Hierarchy Folders", | |
"Modular To Do Lists - Offline Project Management Tool", | |
"Selection History", | |
"Favorites Window", | |
"Color Studio", | |
"Fullscreen Editor", | |
"vTabs 2", | |
"Audio Preview Tool", | |
"Scene Selection Tool", | |
"Scene View Bookmark Tool", | |
"Better Hierarchy", | |
"Editor Auto Save", | |
"Any Object Finder", | |
"Animation Preview Pro", | |
"Bones Assistant", | |
"TimeScale Toolbar", | |
"Compact Events", | |
"Logwin.Log() - Debug Values Tracker", | |
"//TODO (Code Todo List)", | |
"Min/max Range Attribute", | |
"UI Preview for Prefabs and Canvases", | |
"Selection Keeper for Unity", | |
"Mouse Button Shortcuts and Selection History", | |
"Better Transform - Size, Notes, Global-Local workspace & child parent transform", | |
"Better Mesh Filter - Mesh Preview & Full-insight at a glance", | |
"Clipper Pro - The Ultimate Clipboard" | |
}; | |
// Fill PackageDatabase cache | |
foreach (var displayName in displayNames) { | |
PackageManagerHelper.SearchAssetStorePurchases(displayName); | |
await Task.Delay(500); // Small delay to ensure the search completes | |
} | |
// Perform Asset imports in parallel | |
await Task.WhenAll(displayNames.Select(displayName => TryImportAssetAsync(displayName, 3, 1000))); | |
// Import Local packages | |
ImportPackage("E:/Unity Storage/PlayerInputActions.unitypackage", false); | |
ImportPackage("E:/Unity Storage/DustParticles.unitypackage", false); | |
// Import TextMesh Pro | |
ImportPackage(TMP_EditorUtility.packageFullPath + "/Package Resources/TMP Essential Resources.unitypackage", false); | |
ImportPackage(TMP_EditorUtility.packageFullPath + "/Package Resources/TMP Examples & Extras.unitypackage", false); | |
// Update Shortcuts (Personal preference) | |
const string ShortcutId = "Main Menu/File/Compile"; | |
try { | |
var newBinding = new ShortcutBinding(new KeyCombination(KeyCode.F2)); | |
ShortcutManager.instance.RebindShortcut(ShortcutId, newBinding); | |
Debug.Log($"Shortcut {ShortcutId} rebinded to {newBinding}"); | |
} | |
catch (ArgumentException) { | |
Debug.LogError($"Shortcut {ShortcutId} not found"); | |
return; | |
} | |
// Set Company Name | |
string newCompanyName = "git-amend"; | |
PlayerSettings.companyName = newCompanyName; | |
Debug.Log($"Company Name updated to: {PlayerSettings.companyName}"); | |
} | |
static async Task TryImportAssetAsync(string displayName, int maxAttempts, int delayMilliseconds) { | |
for (int attempt = 1; attempt <= maxAttempts; attempt++) { | |
try { | |
if (PackageManagerHelper.ImportPackageByName(displayName)) { | |
Debug.Log($"Successfully imported package '{displayName}' on attempt {attempt}."); | |
return; | |
} | |
Debug.LogWarning($"Attempt {attempt}: Package '{displayName}' not found. Retrying..."); | |
} | |
catch (Exception ex) { | |
Debug.LogError($"Error importing package '{displayName}' on attempt {attempt}: {ex.Message}"); | |
} | |
await Task.Delay(delayMilliseconds); | |
} | |
Debug.LogError($"Failed to import package '{displayName}' after {maxAttempts} attempts."); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment