#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
namespace BrewMonster
{
///
/// Unity Editor Window for processing assets in the current folder and making them addressable in the "gfx" group.
///
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 foundAssetPaths = new List();
private List selectedFolderPaths = new List();
private string logOutput = "";
private bool hasScanned = false;
[MenuItem(MENU_PATH)]
public static void ShowWindow()
{
var window = GetWindow(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 newFolderPaths = new List();
// 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 = "";
}
}
///
/// Retrieves all files (excluding directories) from a directory and all its subdirectories.
///
/// The directory path to search (Unity asset path format)
/// List of file paths (Unity asset paths, excluding .meta files)
private List GetAllFilesRecursive(string directoryPath)
{
List filePaths = new List();
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;
}
///
/// Scans all selected folders for assets that are not marked as addressable.
///
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 allFiles = new List();
foreach (string folderPath in selectedFolderPaths)
{
List 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();
}
///
/// Processes all found assets by making them addressable and moving them to the "gfx" group.
///
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();
}
///
/// Gets an existing AddressableAssetGroup by name, or creates it if it doesn't exist.
///
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;
}
}
///
/// Triggers the next tool in the workflow: UpdateAddressablePathForGfx
///
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}");
}
}
///
/// Adds a log message to the output area.
///
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