532 lines
19 KiB
C#
532 lines
19 KiB
C#
#if UNITY_EDITOR
|
|
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEditor.AddressableAssets;
|
|
using UnityEditor.AddressableAssets.Settings;
|
|
using UnityEngine;
|
|
|
|
namespace BrewMonster
|
|
{
|
|
/// <summary>
|
|
/// Unity Editor Window for processing assets in the current folder and making them addressable in the "gfx" group.
|
|
/// </summary>
|
|
public class AddressablesGfxProcessorWindow : EditorWindow
|
|
{
|
|
private const string GFX_GROUP_NAME = "gfx";
|
|
private const string MENU_PATH = "Tools/Addressable/GFX Adressable For Current Folder";
|
|
|
|
private Vector2 scrollPosition;
|
|
private List<string> foundAssetPaths = new List<string>();
|
|
private List<string> selectedFolderPaths = new List<string>();
|
|
private string logOutput = "";
|
|
private bool hasScanned = false;
|
|
|
|
[MenuItem(MENU_PATH)]
|
|
public static void ShowWindow()
|
|
{
|
|
var window = GetWindow<AddressablesGfxProcessorWindow>(false, "GFX Addressable Processor");
|
|
window.minSize = new Vector2(500, 400);
|
|
window.Show();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
UpdateCurrentFolder();
|
|
}
|
|
|
|
private void OnSelectionChange()
|
|
{
|
|
UpdateCurrentFolder();
|
|
Repaint();
|
|
}
|
|
|
|
private void UpdateCurrentFolder()
|
|
{
|
|
// Get all selected folders in Project window
|
|
List<string> newFolderPaths = new List<string>();
|
|
|
|
// Check all selected objects (supports multiple selection)
|
|
Object[] selectedObjects = Selection.objects;
|
|
if (selectedObjects != null && selectedObjects.Length > 0)
|
|
{
|
|
foreach (Object obj in selectedObjects)
|
|
{
|
|
if (obj == null)
|
|
continue;
|
|
|
|
string assetPath = AssetDatabase.GetAssetPath(obj);
|
|
if (!string.IsNullOrEmpty(assetPath))
|
|
{
|
|
if (AssetDatabase.IsValidFolder(assetPath))
|
|
{
|
|
// Add folder if not already in list
|
|
if (!newFolderPaths.Contains(assetPath))
|
|
{
|
|
newFolderPaths.Add(assetPath);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If a file is selected, get its directory
|
|
string folderPath = System.IO.Path.GetDirectoryName(assetPath).Replace('\\', '/');
|
|
if (!string.IsNullOrEmpty(folderPath) && !newFolderPaths.Contains(folderPath))
|
|
{
|
|
newFolderPaths.Add(folderPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try ProjectWindowUtil if no folders found (Unity 2019.4+)
|
|
if (newFolderPaths.Count == 0)
|
|
{
|
|
try
|
|
{
|
|
var method = typeof(ProjectWindowUtil).GetMethod("GetActiveFolderPath",
|
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
|
|
if (method != null)
|
|
{
|
|
string folderPath = (string)method.Invoke(null, null);
|
|
if (!string.IsNullOrEmpty(folderPath) && !newFolderPaths.Contains(folderPath))
|
|
{
|
|
newFolderPaths.Add(folderPath);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Fallback if method doesn't exist
|
|
}
|
|
}
|
|
|
|
// Check if folder selection has changed
|
|
bool foldersChanged = selectedFolderPaths.Count != newFolderPaths.Count;
|
|
if (!foldersChanged)
|
|
{
|
|
foreach (string folderPath in newFolderPaths)
|
|
{
|
|
if (!selectedFolderPaths.Contains(folderPath))
|
|
{
|
|
foldersChanged = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (foldersChanged)
|
|
{
|
|
selectedFolderPaths = newFolderPaths;
|
|
hasScanned = false;
|
|
foundAssetPaths.Clear();
|
|
logOutput = "";
|
|
}
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.LabelField("GFX Addressable Processor", EditorStyles.boldLabel);
|
|
EditorGUILayout.Space();
|
|
|
|
// Display selected folders
|
|
EditorGUILayout.LabelField("Selected Folder(s):", EditorStyles.boldLabel);
|
|
if (selectedFolderPaths.Count == 0)
|
|
{
|
|
EditorGUILayout.HelpBox("No folder selected. Select one or more folders in the Project window.", MessageType.Info);
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
for (int i = 0; i < selectedFolderPaths.Count; i++)
|
|
{
|
|
EditorGUILayout.SelectableLabel($"{i + 1}. {selectedFolderPaths[i]}",
|
|
EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight));
|
|
}
|
|
EditorGUILayout.EndVertical();
|
|
EditorGUILayout.HelpBox($"Scanning {selectedFolderPaths.Count} folder(s) and all subdirectories recursively.", MessageType.Info);
|
|
}
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
// Scan button
|
|
EditorGUI.BeginDisabledGroup(selectedFolderPaths.Count == 0);
|
|
if (GUILayout.Button("Scan", GUILayout.Height(30)))
|
|
{
|
|
ScanAssets();
|
|
}
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
// Display scan results
|
|
if (hasScanned)
|
|
{
|
|
EditorGUILayout.LabelField($"Found {foundAssetPaths.Count} non-addressable asset(s)", EditorStyles.boldLabel);
|
|
|
|
if (foundAssetPaths.Count > 0)
|
|
{
|
|
EditorGUILayout.Space();
|
|
|
|
// Process button
|
|
if (GUILayout.Button($"Process ({foundAssetPaths.Count} assets)", GUILayout.Height(30)))
|
|
{
|
|
ProcessAssets();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.HelpBox("All assets in this folder are already addressable.", MessageType.Info);
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.LabelField("Log Output:", EditorStyles.boldLabel);
|
|
|
|
// Log output area
|
|
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.ExpandHeight(true));
|
|
EditorGUILayout.TextArea(logOutput, EditorStyles.textArea, GUILayout.ExpandHeight(true));
|
|
EditorGUILayout.EndScrollView();
|
|
|
|
// Clear log button
|
|
if (GUILayout.Button("Clear Log"))
|
|
{
|
|
logOutput = "";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves all files (excluding directories) from a directory and all its subdirectories.
|
|
/// </summary>
|
|
/// <param name="directoryPath">The directory path to search (Unity asset path format)</param>
|
|
/// <returns>List of file paths (Unity asset paths, excluding .meta files)</returns>
|
|
private List<string> GetAllFilesRecursive(string directoryPath)
|
|
{
|
|
List<string> filePaths = new List<string>();
|
|
|
|
if (string.IsNullOrEmpty(directoryPath) || !AssetDatabase.IsValidFolder(directoryPath))
|
|
{
|
|
return filePaths;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Get the full system path from Unity asset path
|
|
string systemPath = System.IO.Path.GetFullPath(directoryPath);
|
|
if (!System.IO.Directory.Exists(systemPath))
|
|
{
|
|
return filePaths;
|
|
}
|
|
|
|
// Get the project root path (where Assets folder is)
|
|
string projectRoot = System.IO.Path.GetFullPath(Application.dataPath);
|
|
projectRoot = System.IO.Path.GetDirectoryName(projectRoot); // Go up one level from Assets
|
|
projectRoot = projectRoot.Replace('\\', '/');
|
|
|
|
// Get all files recursively (excluding directories)
|
|
string[] allFiles = System.IO.Directory.GetFiles(
|
|
systemPath,
|
|
"*.*",
|
|
System.IO.SearchOption.AllDirectories
|
|
);
|
|
|
|
// Convert system paths back to Unity asset paths and filter
|
|
foreach (string systemFile in allFiles)
|
|
{
|
|
// Skip .meta files
|
|
if (systemFile.EndsWith(".meta", System.StringComparison.OrdinalIgnoreCase))
|
|
continue;
|
|
|
|
// Convert to normalized path
|
|
string normalizedSystemPath = systemFile.Replace('\\', '/');
|
|
|
|
// Convert to Unity asset path relative to project root
|
|
if (normalizedSystemPath.StartsWith(projectRoot, System.StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
string unityPath = normalizedSystemPath.Substring(projectRoot.Length);
|
|
if (unityPath.StartsWith("/"))
|
|
unityPath = unityPath.Substring(1);
|
|
|
|
// Ensure it's a valid asset file (not a directory)
|
|
if (!string.IsNullOrEmpty(unityPath) && !AssetDatabase.IsValidFolder(unityPath))
|
|
{
|
|
filePaths.Add(unityPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
AddLog($"ERROR retrieving files: {e.Message}");
|
|
}
|
|
|
|
return filePaths;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scans all selected folders for assets that are not marked as addressable.
|
|
/// </summary>
|
|
private void ScanAssets()
|
|
{
|
|
foundAssetPaths.Clear();
|
|
logOutput = "";
|
|
AddLog("Starting scan...");
|
|
|
|
if (selectedFolderPaths.Count == 0)
|
|
{
|
|
AddLog("ERROR: No folder selected!");
|
|
hasScanned = true;
|
|
return;
|
|
}
|
|
|
|
AddLog($"Scanning {selectedFolderPaths.Count} folder(s):");
|
|
foreach (string folderPath in selectedFolderPaths)
|
|
{
|
|
AddLog($" - {folderPath}");
|
|
}
|
|
|
|
AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.Settings;
|
|
if (settings == null)
|
|
{
|
|
AddLog("ERROR: Addressable settings not found. Please ensure Addressables package is installed.");
|
|
hasScanned = true;
|
|
return;
|
|
}
|
|
|
|
// Get all files recursively from all selected folders (excluding directories)
|
|
List<string> allFiles = new List<string>();
|
|
foreach (string folderPath in selectedFolderPaths)
|
|
{
|
|
List<string> folderFiles = GetAllFilesRecursive(folderPath);
|
|
AddLog($"Found {folderFiles.Count} file(s) in {folderPath} and subdirectories");
|
|
allFiles.AddRange(folderFiles);
|
|
}
|
|
|
|
AddLog($"Found {allFiles.Count} total file(s) across all selected folders");
|
|
|
|
int nonAddressableCount = 0;
|
|
foreach (string assetPath in allFiles)
|
|
{
|
|
if (string.IsNullOrEmpty(assetPath))
|
|
continue;
|
|
|
|
// Double-check: skip meta files and folders (shouldn't happen, but safety check)
|
|
if (assetPath.EndsWith(".meta") || AssetDatabase.IsValidFolder(assetPath))
|
|
continue;
|
|
|
|
// Get GUID for the asset
|
|
string guid = AssetDatabase.AssetPathToGUID(assetPath);
|
|
if (string.IsNullOrEmpty(guid))
|
|
continue;
|
|
|
|
// Check if asset is already addressable
|
|
AddressableAssetEntry entry = settings.FindAssetEntry(guid);
|
|
if (entry == null)
|
|
{
|
|
foundAssetPaths.Add(assetPath);
|
|
nonAddressableCount++;
|
|
}
|
|
}
|
|
|
|
AddLog($"Found {nonAddressableCount} non-addressable asset(s)");
|
|
hasScanned = true;
|
|
Repaint();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes all found assets by making them addressable and moving them to the "gfx" group.
|
|
/// </summary>
|
|
private void ProcessAssets()
|
|
{
|
|
if (foundAssetPaths.Count == 0)
|
|
{
|
|
AddLog("No assets to process.");
|
|
return;
|
|
}
|
|
|
|
AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.Settings;
|
|
if (settings == null)
|
|
{
|
|
AddLog("ERROR: Addressable settings not found.");
|
|
return;
|
|
}
|
|
|
|
AddLog($"\n=== Processing {foundAssetPaths.Count} asset(s) ===");
|
|
|
|
// Get or create the "gfx" group
|
|
AddressableAssetGroup gfxGroup = GetOrCreateGroup(settings, GFX_GROUP_NAME);
|
|
if (gfxGroup == null)
|
|
{
|
|
AddLog("ERROR: Failed to get or create 'gfx' group.");
|
|
return;
|
|
}
|
|
|
|
int processedCount = 0;
|
|
int skippedCount = 0;
|
|
int errorCount = 0;
|
|
|
|
foreach (string assetPath in foundAssetPaths)
|
|
{
|
|
try
|
|
{
|
|
string guid = AssetDatabase.AssetPathToGUID(assetPath);
|
|
if (string.IsNullOrEmpty(guid))
|
|
{
|
|
AddLog($"ERROR: Could not get GUID for {assetPath}");
|
|
errorCount++;
|
|
continue;
|
|
}
|
|
|
|
// Check if asset is already addressable (double-check)
|
|
AddressableAssetEntry existingEntry = settings.FindAssetEntry(guid);
|
|
if (existingEntry != null)
|
|
{
|
|
// Check if it's already in the correct group
|
|
if (existingEntry.parentGroup == gfxGroup)
|
|
{
|
|
AddLog($"SKIP: {assetPath} is already in 'gfx' group");
|
|
skippedCount++;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// Move to gfx group
|
|
existingEntry.parentGroup = gfxGroup;
|
|
AddLog($"MOVED: {assetPath} to 'gfx' group");
|
|
processedCount++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Create new addressable entry
|
|
AddressableAssetEntry newEntry = settings.CreateOrMoveEntry(guid, gfxGroup);
|
|
if (newEntry != null)
|
|
{
|
|
// Set address to full asset path (including "Assets/")
|
|
// This allows UpdateAddressablePathForGfx to properly remove the prefix
|
|
newEntry.SetAddress(assetPath);
|
|
|
|
AddLog($"ADDED: {assetPath} -> Address: {newEntry.address}");
|
|
processedCount++;
|
|
}
|
|
else
|
|
{
|
|
AddLog($"ERROR: Failed to create entry for {assetPath}");
|
|
errorCount++;
|
|
}
|
|
}
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
AddLog($"ERROR processing {assetPath}: {e.Message}");
|
|
errorCount++;
|
|
}
|
|
}
|
|
|
|
// Save changes
|
|
if (processedCount > 0)
|
|
{
|
|
EditorUtility.SetDirty(settings);
|
|
AssetDatabase.SaveAssets();
|
|
AddLog($"\n=== Processing Complete ===");
|
|
AddLog($"Processed: {processedCount}");
|
|
AddLog($"Skipped: {skippedCount}");
|
|
AddLog($"Errors: {errorCount}");
|
|
}
|
|
else
|
|
{
|
|
AddLog("\nNo assets were processed.");
|
|
}
|
|
|
|
// Trigger the next tool
|
|
TriggerNextTool();
|
|
|
|
// Refresh the scan
|
|
ScanAssets();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an existing AddressableAssetGroup by name, or creates it if it doesn't exist.
|
|
/// </summary>
|
|
private AddressableAssetGroup GetOrCreateGroup(AddressableAssetSettings settings, string groupName)
|
|
{
|
|
if (settings == null || string.IsNullOrEmpty(groupName))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Try to find existing group
|
|
AddressableAssetGroup group = settings.FindGroup(groupName);
|
|
if (group != null)
|
|
{
|
|
AddLog($"Found existing group: {groupName}");
|
|
return group;
|
|
}
|
|
|
|
// Create new group
|
|
try
|
|
{
|
|
group = settings.CreateGroup(groupName, false, false, true, null);
|
|
if (group != null)
|
|
{
|
|
AddLog($"Created new group: {groupName}");
|
|
EditorUtility.SetDirty(settings);
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
else
|
|
{
|
|
AddLog($"ERROR: Failed to create group: {groupName}");
|
|
}
|
|
return group;
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
AddLog($"ERROR creating group {groupName}: {e.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers the next tool in the workflow: UpdateAddressablePathForGfx
|
|
/// </summary>
|
|
private void TriggerNextTool()
|
|
{
|
|
AddLog("\n=== Triggering next tool ===");
|
|
AddLog("Calling UpdateAddressablePathForGfx...");
|
|
|
|
try
|
|
{
|
|
// Call the static method from AddressableTools
|
|
AddressableTools.UpdateAddressablePathForGfx();
|
|
AddLog("Successfully called UpdateAddressablePathForGfx");
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
AddLog($"ERROR calling UpdateAddressablePathForGfx: {e.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a log message to the output area.
|
|
/// </summary>
|
|
private void AddLog(string message)
|
|
{
|
|
if (string.IsNullOrEmpty(logOutput))
|
|
{
|
|
logOutput = message;
|
|
}
|
|
else
|
|
{
|
|
logOutput += "\n" + message;
|
|
}
|
|
|
|
// Also log to Unity console
|
|
Debug.Log($"[GFX Addressable Processor] {message}");
|
|
|
|
Repaint();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|