Files
SoulliesOfficial f26f9fd374 爆更
2026-03-20 12:07:44 -04:00

230 lines
7.5 KiB
C#

using LunaWolfStudiosEditor.ScriptableSheets.Layout;
using LunaWolfStudiosEditor.ScriptableSheets.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace LunaWolfStudiosEditor.ScriptableSheets.Scanning
{
public class ObjectScanner
{
private const string ProgressBarTitle = "Scriptable Sheets - Object Scanner";
private readonly Dictionary<Type, List<Object>> m_ObjectsByType = new Dictionary<Type, List<Object>>();
public Dictionary<Type, List<Object>> ObjectsByType => m_ObjectsByType;
private Type[] m_ObjectTypes;
public Type[] ObjectTypes => m_ObjectTypes;
private string[] m_ObjectTypeNames;
public string[] ObjectTypeNames => m_ObjectTypeNames;
private string[] m_FriendlyObjectTypeNames;
public string[] FriendlyObjectTypeNames => m_FriendlyObjectTypeNames;
private readonly Dictionary<Type, Dictionary<Object, List<Object>>> m_SubAssetsByTypeAndMainAsset = new Dictionary<Type, Dictionary<Object, List<Object>>>();
public Dictionary<Type, Dictionary<Object, List<Object>>> SubAssetsByTypeAndMainAsset => m_SubAssetsByTypeAndMainAsset;
public void ScanObjects(ScanSettings settings, SheetAsset sheetAsset)
{
try
{
m_ObjectsByType.Clear();
m_SubAssetsByTypeAndMainAsset.Clear();
// Refresh incase new folders or assets were created.
AssetDatabase.Refresh();
// Unity does not consider the root Packages folder as a valid folder so we need to check it separately.
if (settings.PathOption == ScanPathOption.Default && !AssetDatabase.IsValidFolder(settings.Path) && settings.Path != UnityConstants.Packages)
{
settings.Path = UnityConstants.DefaultAssetPath;
}
var scanPaths = settings.GetScanPaths();
var joinedPaths = settings.GetJoinedScanPaths(scanPaths);
if (settings.ShowProgressBar)
{
EditorUtility.DisplayProgressBar(ProgressBarTitle, $"Scanning for assets of type {sheetAsset} at path(s) {joinedPaths}", 0.40f);
}
var guids = AssetDatabase.FindAssets($"t:{sheetAsset}", scanPaths);
if (settings.ShowProgressBar)
{
EditorUtility.DisplayProgressBar(ProgressBarTitle, $"Loading assets of type {sheetAsset} at path(s) {joinedPaths}", 0.80f);
}
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
if (settings.IsPathExcluded(path))
{
continue;
}
var asset = AssetDatabase.LoadAssetAtPath<Object>(path);
if (asset == null)
{
continue;
}
var assetType = asset.GetType();
if (settings.IsFullTypeNameExcluded(assetType))
{
continue;
}
var nextAssetType = assetType;
// Map the asset to its type and base types.
while (nextAssetType.IsValidUnityObjectSubclass())
{
if (nextAssetType.IsValidConcreteType())
{
if (!m_ObjectsByType.TryGetValue(nextAssetType, out var objectsByType))
{
objectsByType = new List<Object>();
m_ObjectsByType[nextAssetType] = objectsByType;
}
objectsByType.Add(asset);
}
nextAssetType = nextAssetType.BaseType;
}
if (sheetAsset == SheetAsset.Scene)
{
continue;
}
var subAssets = AssetDatabase.LoadAllAssetsAtPath(path);
if (subAssets.Length <= 1)
{
continue;
}
foreach (var subAsset in subAssets)
{
if (subAsset == null || subAsset == asset)
{
continue;
}
if (sheetAsset == SheetAsset.Prefab)
{
if (subAsset is Component)
{
var subAssetGameObject = ((Component) subAsset).gameObject;
var isRootPrefab = asset == subAssetGameObject;
if (!settings.RootPrefabsOnly || isRootPrefab)
{
var subAssetType = subAsset.GetType();
if (settings.IsFullTypeNameExcluded(subAssetType))
{
continue;
}
if (!m_ObjectsByType.TryGetValue(subAssetType, out var objectsByType))
{
objectsByType = new List<Object>();
m_ObjectsByType[subAssetType] = objectsByType;
}
objectsByType.Add(subAsset);
}
}
}
else
{
if (AssetDatabase.IsSubAsset(subAsset))
{
var subAssetType = subAsset.GetType();
var nextSubAssetType = subAssetType;
while (nextSubAssetType.IsValidUnityObjectSubclass() && !settings.IsFullTypeNameExcluded(nextSubAssetType))
{
// For binary types like Textures we want to add all the valid subclass types.
// For ScriptableObjects it must be a scriptable object type.
if (sheetAsset != SheetAsset.ScriptableObject || nextSubAssetType.IsSubclassOf(typeof(ScriptableObject)))
{
if (!m_ObjectsByType.TryGetValue(nextSubAssetType, out var objectsByType))
{
objectsByType = new List<Object>();
m_ObjectsByType[nextSubAssetType] = objectsByType;
}
objectsByType.Add(subAsset);
if (!m_SubAssetsByTypeAndMainAsset.TryGetValue(nextSubAssetType, out var subAssetByMainAsset))
{
subAssetByMainAsset = new Dictionary<Object, List<Object>>();
m_SubAssetsByTypeAndMainAsset[nextSubAssetType] = subAssetByMainAsset;
}
if (!subAssetByMainAsset.TryGetValue(asset, out var subAssetList))
{
subAssetList = new List<Object>();
subAssetByMainAsset[asset] = subAssetList;
}
subAssetList.Add(subAsset);
}
nextSubAssetType = nextSubAssetType.BaseType;
}
}
}
}
}
if (settings.ShowProgressBar)
{
EditorUtility.DisplayProgressBar(ProgressBarTitle, $"Resolving {sheetAsset} types using {nameof(ScanOption)} {settings.Option}", 0.90f);
}
if (sheetAsset == SheetAsset.ScriptableObject && settings.Option == ScanOption.Assembly)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var objectTypes = new List<Type>();
foreach (var assembly in assemblies)
{
var types = assembly.GetTypes().Where
(
type => !settings.IsFullTypeNameExcluded(type) && type.IsSubclassOf(typeof(ScriptableObject))
&& type.IsSerializable
&& type.IsValidConcreteType()
&& !type.IsSubclassOf(typeof(Editor))
&& !type.IsSubclassOf(typeof(EditorWindow))
&& !type.IsScriptableSingleton()
);
objectTypes.AddRange(types);
}
m_ObjectTypes = objectTypes.Concat(m_ObjectsByType.Keys).Distinct().OrderBy(type => type.FullName).ToArray();
}
else
{
m_ObjectTypes = m_ObjectsByType.Keys.OrderBy(type => type.FullName).ToArray();
}
if (settings.ShowProgressBar)
{
EditorUtility.DisplayProgressBar(ProgressBarTitle, $"Finalizing {sheetAsset} type names", 0.98f);
}
m_ObjectTypeNames = m_ObjectTypes.Select(type => type.FullName).ToArray();
// Submenu once we start having a lot of Objects.
var separator = '.';
if (m_ObjectTypeNames.Length > SheetLayout.SubMenuThreshold)
{
var newSeparator = '/';
m_ObjectTypeNames = m_ObjectTypeNames.Select(s => s.Replace(separator, newSeparator)).ToArray();
separator = newSeparator;
}
m_FriendlyObjectTypeNames = m_ObjectTypeNames.Select(s => s.Substring(s.LastIndexOf(separator) + 1)).ToArray();
}
catch (Exception ex)
{
Debug.LogError($"{nameof(ScanObjects)} for {nameof(SheetAsset)} {sheetAsset} failed with error: {ex}");
}
finally
{
EditorUtility.ClearProgressBar();
}
}
}
}