You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PlumberUltimateAds/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSearchFun...

1962 lines
77 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using UnityEngine.UI;
#if UNITY_2017_1_OR_NEWER
using UnityEngine.U2D;
using UnityEngine.Playables;
#endif
#if UNITY_2018_2_OR_NEWER
using UnityEditor.U2D;
#endif
#if UNITY_2017_3_OR_NEWER
using UnityEditor.Compilation;
#endif
#if UNITY_2017_2_OR_NEWER
using UnityEngine.Tilemaps;
#endif
#if ASSET_USAGE_ADDRESSABLES
using UnityEngine.AddressableAssets;
#endif
using Object = UnityEngine.Object;
namespace AssetUsageDetectorNamespace
{
public partial class AssetUsageDetector
{
#region Helper Classes
#if UNITY_2017_3_OR_NEWER
#pragma warning disable 0649 // The fields' values are assigned via JsonUtility
[Serializable]
private struct AssemblyDefinitionReferences
{
public string reference; // Used by AssemblyDefinitionReferenceAssets
public List<string> references; // Used by AssemblyDefinitionAssets
}
#pragma warning restore 0649
#endif
#if UNITY_2018_1_OR_NEWER
#pragma warning disable 0649 // The fields' values are assigned via JsonUtility
[Serializable]
private struct ShaderGraphReferences // Used by old Shader Graph serialization format
{
[Serializable]
public struct JSONHolder
{
public string JSONnodeData;
}
[Serializable]
public class TextureHolder
{
public string m_SerializedTexture;
public string m_SerializedCubemap;
public string m_Guid;
public string GetTexturePath()
{
string guid = ExtractGUIDFromString( !string.IsNullOrEmpty( m_SerializedTexture ) ? m_SerializedTexture : m_SerializedCubemap );
if( string.IsNullOrEmpty( guid ) )
guid = m_Guid;
return string.IsNullOrEmpty( guid ) ? null : AssetDatabase.GUIDToAssetPath( guid );
}
}
[Serializable]
public struct PropertyData
{
public string m_Name;
public string m_DefaultReferenceName;
public string m_OverrideReferenceName;
public TextureHolder m_Value;
public string GetName()
{
if( !string.IsNullOrEmpty( m_OverrideReferenceName ) )
return m_OverrideReferenceName;
if( !string.IsNullOrEmpty( m_DefaultReferenceName ) )
return m_DefaultReferenceName;
if( !string.IsNullOrEmpty( m_Name ) )
return m_Name;
return "Property";
}
}
[Serializable]
public struct NodeData
{
public string m_Name;
public string m_FunctionSource; // Custom Function node's Source field
public string m_SerializedSubGraph; // Sub-graph node
public List<JSONHolder> m_SerializableSlots;
public string GetSubGraphPath()
{
string guid = ExtractGUIDFromString( m_SerializedSubGraph );
return string.IsNullOrEmpty( guid ) ? null : AssetDatabase.GUIDToAssetPath( guid );
}
}
[Serializable]
public struct NodeSlotData
{
public TextureHolder m_Texture;
public TextureHolder m_TextureArray;
public TextureHolder m_Cubemap;
public string GetTexturePath()
{
if( m_Texture != null )
return m_Texture.GetTexturePath();
if( m_Cubemap != null )
return m_Cubemap.GetTexturePath();
if( m_TextureArray != null )
return m_TextureArray.GetTexturePath();
return null;
}
}
public List<JSONHolder> m_SerializedProperties;
public List<JSONHolder> m_SerializableNodes;
// String can be in one of the following formats:
// "guid":"GUID_VALUE"
// "guid": "GUID_VALUE"
// "guid" : "GUID_VALUE"
private static string ExtractGUIDFromString( string str )
{
if( !string.IsNullOrEmpty( str ) )
{
int guidStartIndex = str.IndexOf( "\"guid\"" );
if( guidStartIndex >= 0 )
{
guidStartIndex += 6;
guidStartIndex = str.IndexOf( '"', guidStartIndex );
if( guidStartIndex > 0 )
{
guidStartIndex++;
int guidEndIndex = str.IndexOf( '"', guidStartIndex );
if( guidEndIndex > 0 )
return str.Substring( guidStartIndex, guidEndIndex - guidStartIndex );
}
}
}
return null;
}
}
#pragma warning restore 0649
#endif
#endregion
// Dictionary to quickly find the function to search a specific type with
private Dictionary<Type, Func<object, ReferenceNode>> typeToSearchFunction;
// Dictionary to associate special file extensions with their search functions
private Dictionary<string, Func<object, ReferenceNode>> extensionToSearchFunction;
// An optimization to fetch & filter fields and properties of a class only once
private readonly Dictionary<Type, VariableGetterHolder[]> typeToVariables = new Dictionary<Type, VariableGetterHolder[]>( 4096 );
private readonly List<VariableGetterHolder> validVariables = new List<VariableGetterHolder>( 32 );
// All MonoScripts in objectsToSearchSet
private readonly List<MonoScript> monoScriptsToSearch = new List<MonoScript>();
private readonly List<Type> monoScriptsToSearchTypes = new List<Type>();
// Path(s) of .cginc, .cg, .hlsl and .glslinc assets in assetsToSearchSet
private readonly HashSet<string> shaderIncludesToSearchSet = new HashSet<string>();
#if UNITY_2017_3_OR_NEWER
// Path(s) of the Assembly Definition Files in objectsToSearchSet (Value: files themselves)
private readonly Dictionary<string, Object> assemblyDefinitionFilesToSearch = new Dictionary<string, Object>( 8 );
#endif
// An optimization to fetch an animation clip's curve bindings only once
private readonly Dictionary<AnimationClip, EditorCurveBinding[]> animationClipUniqueBindings = new Dictionary<AnimationClip, EditorCurveBinding[]>( 256 );
private bool searchPrefabConnections;
private bool searchMonoBehavioursForScript;
private bool searchTextureReferences;
#if UNITY_2018_1_OR_NEWER
private bool searchShaderGraphsForSubGraphs;
#endif
private bool searchSerializableVariablesOnly;
private bool prevSearchSerializableVariablesOnly;
private BindingFlags fieldModifiers, propertyModifiers;
private BindingFlags prevFieldModifiers, prevPropertyModifiers;
// Unity's internal function that returns a SerializedProperty's corresponding FieldInfo
private delegate FieldInfo FieldInfoGetter( SerializedProperty p, out Type t );
#if UNITY_2019_3_OR_NEWER
private readonly FieldInfoGetter fieldInfoGetter = (FieldInfoGetter) Delegate.CreateDelegate( typeof( FieldInfoGetter ), typeof( Editor ).Assembly.GetType( "UnityEditor.ScriptAttributeUtility" ).GetMethod( "GetFieldInfoAndStaticTypeFromProperty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
#else
private readonly FieldInfoGetter fieldInfoGetter = (FieldInfoGetter) Delegate.CreateDelegate( typeof( FieldInfoGetter ), typeof( Editor ).Assembly.GetType( "UnityEditor.ScriptAttributeUtility" ).GetMethod( "GetFieldInfoFromProperty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
#endif
private readonly Func<Object> lightmapSettingsGetter = (Func<Object>) Delegate.CreateDelegate( typeof( Func<Object> ), typeof( LightmapEditorSettings ).GetMethod( "GetLightmapSettings", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
private readonly Func<Object> renderSettingsGetter = (Func<Object>) Delegate.CreateDelegate( typeof( Func<Object> ), typeof( RenderSettings ).GetMethod( "GetRenderSettings", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
#if UNITY_2021_2_OR_NEWER
private readonly Func<Cubemap> defaultReflectionProbeGetter = (Func<Cubemap>) Delegate.CreateDelegate( typeof( Func<Cubemap> ), typeof( RenderSettings ).GetProperty( "defaultReflection", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ).GetGetMethod( true ) );
#endif
#if ASSET_USAGE_ADDRESSABLES
private readonly Func<SpriteAtlas, Sprite[]> spriteAtlasPackedSpritesGetter = (Func<SpriteAtlas, Sprite[]>) Delegate.CreateDelegate( typeof( Func<SpriteAtlas, Sprite[]> ), typeof( SpriteAtlasExtensions ).GetMethod( "GetPackedSprites", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
private readonly PropertyInfo assetReferenceSubObjectTypeGetter = typeof( AssetReference ).GetProperty( "SubOjbectType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
#endif
#if ASSET_USAGE_VFX_GRAPH
private static Type vfxResourceType => typeof( Editor ).Assembly.GetType( "UnityEditor.VFX.VisualEffectResource" ) ?? Array.Find( AppDomain.CurrentDomain.GetAssemblies(), ( assembly ) => assembly.GetName().Name == "UnityEditor.VFXModule" ).GetType( "UnityEditor.VFX.VisualEffectResource" );
private readonly Func<string, object> vfxResourceGetter = (Func<string, object>) Delegate.CreateDelegate( typeof( Func<string, object> ), vfxResourceType.GetMethod( "GetResourceAtPath", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
private readonly MethodInfo vfxResourceContentsGetter = vfxResourceType.GetMethod( "GetContents", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
private readonly MethodInfo vfxSerializableObjectValueGetter = Array.Find( Array.Find( AppDomain.CurrentDomain.GetAssemblies(), ( assembly ) => assembly.GetName().Name == "Unity.VisualEffectGraph.Editor" ).GetType( "UnityEditor.VFX.VFXSerializableObject" ).GetMethods( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ), ( methodInfo ) => methodInfo.Name == "Get" && !methodInfo.IsGenericMethod );
#endif
private void InitializeSearchFunctionsData( Parameters searchParameters )
{
if( typeToSearchFunction == null )
{
typeToSearchFunction = new Dictionary<Type, Func<object, ReferenceNode>>()
{
{ typeof( GameObject ), SearchGameObject },
{ typeof( Material ), SearchMaterial },
{ typeof( Shader ), SearchShader },
{ typeof( MonoScript ), SearchMonoScript },
{ typeof( RuntimeAnimatorController ), SearchAnimatorController },
{ typeof( AnimatorOverrideController ), SearchAnimatorController },
{ typeof( AnimatorController ), SearchAnimatorController },
{ typeof( AnimatorStateMachine ), SearchAnimatorStateMachine },
{ typeof( AnimatorState ), SearchAnimatorState },
{ typeof( AnimatorStateTransition ), SearchAnimatorStateTransition },
{ typeof( BlendTree ), SearchBlendTree },
{ typeof( AnimationClip ), SearchAnimationClip },
{ typeof( TerrainData ), SearchTerrainData },
{ typeof( LightmapSettings ), SearchLightmapSettings },
{ typeof( RenderSettings ), SearchRenderSettings },
#if UNITY_2017_1_OR_NEWER
{ typeof( SpriteAtlas ), SearchSpriteAtlas },
#endif
};
}
if( extensionToSearchFunction == null )
{
extensionToSearchFunction = new Dictionary<string, Func<object, ReferenceNode>>()
{
{ "compute", SearchShaderSecondaryAsset },
{ "cginc", SearchShaderSecondaryAsset },
{ "cg", SearchShaderSecondaryAsset },
{ "glslinc", SearchShaderSecondaryAsset },
{ "hlsl", SearchShaderSecondaryAsset },
#if UNITY_2017_3_OR_NEWER
{ "asmdef", SearchAssemblyDefinitionFile },
#endif
#if UNITY_2019_2_OR_NEWER
{ "asmref", SearchAssemblyDefinitionFile },
#endif
#if UNITY_2018_1_OR_NEWER
{ "shadergraph", SearchShaderGraph },
{ "shadersubgraph", SearchShaderGraph },
#endif
#if ASSET_USAGE_VFX_GRAPH
{ "vfx", SearchVFXGraphAsset },
{ "vfxoperator", SearchVFXGraphAsset },
{ "vfxblock", SearchVFXGraphAsset },
#endif
};
}
fieldModifiers = searchParameters.fieldModifiers | BindingFlags.Instance | BindingFlags.DeclaredOnly;
propertyModifiers = searchParameters.propertyModifiers | BindingFlags.Instance | BindingFlags.DeclaredOnly;
searchSerializableVariablesOnly = !searchParameters.searchNonSerializableVariables;
if( prevFieldModifiers != fieldModifiers || prevPropertyModifiers != propertyModifiers || prevSearchSerializableVariablesOnly != searchSerializableVariablesOnly )
typeToVariables.Clear();
prevFieldModifiers = fieldModifiers;
prevPropertyModifiers = propertyModifiers;
prevSearchSerializableVariablesOnly = searchSerializableVariablesOnly;
searchPrefabConnections = false;
searchMonoBehavioursForScript = false;
searchTextureReferences = false;
#if UNITY_2018_1_OR_NEWER
searchShaderGraphsForSubGraphs = false;
#endif
#if ASSET_USAGE_VFX_GRAPH
bool searchVFXGraphs = false;
#endif
foreach( Object obj in objectsToSearchSet )
{
if( obj is Texture || obj is Sprite )
searchTextureReferences = true;
else if( obj is MonoScript )
{
searchMonoBehavioursForScript = true;
Type monoScriptType = ( (MonoScript) obj ).GetClass();
if( monoScriptType != null && !monoScriptType.IsSealed )
{
monoScriptsToSearch.Add( (MonoScript) obj );
monoScriptsToSearchTypes.Add( monoScriptType );
}
}
else if( obj is GameObject )
searchPrefabConnections = true;
#if UNITY_2017_3_OR_NEWER
else if( obj is UnityEditorInternal.AssemblyDefinitionAsset )
assemblyDefinitionFilesToSearch[AssetDatabase.GetAssetPath( obj )] = obj;
#endif
#if ASSET_USAGE_VFX_GRAPH
else if( !searchVFXGraphs && ( obj is Shader || obj is Mesh || obj.GetType().Name.StartsWithFast( "PointCache" ) || obj.GetType().Name == "ShaderGraphVfxAsset" ) )
searchVFXGraphs = true;
#endif
}
// We need to search for class/interface inheritance references manually because AssetDatabase.GetDependencies doesn't take that into account
if( monoScriptsToSearch.Count > 0 )
{
alwaysSearchedExtensionsSet.Add( "cs" );
alwaysSearchedExtensionsSet.Add( "dll" );
}
foreach( string path in assetsToSearchPathsSet )
{
string extension = Utilities.GetFileExtension( path );
if( extension == "hlsl" || extension == "cginc" || extension == "cg" || extension == "glslinc" )
shaderIncludesToSearchSet.Add( path );
#if UNITY_2018_1_OR_NEWER
else if( extension == "shadersubgraph" )
searchShaderGraphsForSubGraphs = true;
#endif
}
// AssetDatabase.GetDependencies doesn't take #include lines in shader source codes into consideration. If we are searching for references
// of a potential #include target (shaderIncludesToSearchSet), we must search all shader assets and check their #include lines manually
if( shaderIncludesToSearchSet.Count > 0 )
{
alwaysSearchedExtensionsSet.Add( "shader" );
alwaysSearchedExtensionsSet.Add( "compute" );
alwaysSearchedExtensionsSet.Add( "cginc" );
alwaysSearchedExtensionsSet.Add( "cg" );
alwaysSearchedExtensionsSet.Add( "glslinc" );
alwaysSearchedExtensionsSet.Add( "hlsl" );
}
#if UNITY_2017_3_OR_NEWER
// AssetDatabase.GetDependencies doesn't return references from Assembly Definition Files to their Assembly Definition References,
// so if we are searching for an Assembly Definition File's usages, we must search all Assembly Definition Files' references manually.
if( assemblyDefinitionFilesToSearch.Count > 0 )
{
alwaysSearchedExtensionsSet.Add( "asmdef" );
#if UNITY_2019_2_OR_NEWER
alwaysSearchedExtensionsSet.Add( "asmref" );
#endif
}
#endif
#if UNITY_2018_1_OR_NEWER
// AssetDatabase.GetDependencies doesn't work with Shader Graph assets. We must search all Shader Graph assets in the following cases:
// searchTextureReferences: to find Texture references used in various nodes and properties
// searchShaderGraphsForSubGraphs: to find Shader Sub-graph references in other Shader Graph assets
// shaderIncludesToSearchSet: to find .cginc, .cg, .glslinc and .hlsl references used in Custom Function nodes
if( searchTextureReferences || searchShaderGraphsForSubGraphs || shaderIncludesToSearchSet.Count > 0 )
{
alwaysSearchedExtensionsSet.Add( "shadergraph" );
alwaysSearchedExtensionsSet.Add( "shadersubgraph" );
}
#endif
#if ASSET_USAGE_VFX_GRAPH
if( searchTextureReferences || searchVFXGraphs )
{
alwaysSearchedExtensionsSet.Add( "vfx" );
alwaysSearchedExtensionsSet.Add( "vfxoperator" );
alwaysSearchedExtensionsSet.Add( "vfxblock" );
}
#endif
}
private ReferenceNode SearchGameObject( object obj )
{
GameObject go = (GameObject) obj;
ReferenceNode referenceNode = PopReferenceNode( go );
// Check if this GameObject's prefab is one of the selected assets
if( searchPrefabConnections )
{
#if UNITY_2018_3_OR_NEWER
Object prefab = go;
while( prefab = PrefabUtility.GetCorrespondingObjectFromSource( prefab ) )
#else
Object prefab = PrefabUtility.GetPrefabParent( go );
if( prefab )
#endif
{
if( objectsToSearchSet.Contains( prefab ) && assetsToSearchRootPrefabs.ContainsFast( prefab as GameObject ) )
{
referenceNode.AddLinkTo( GetReferenceNode( prefab ), "Prefab object" );
if( searchParameters.searchRefactoring != null )
searchParameters.searchRefactoring( new PrefabMatch( go, prefab ) );
}
}
}
// Search through all the components of the object
Component[] components = go.GetComponents<Component>();
for( int i = 0; i < components.Length; i++ )
referenceNode.AddLinkTo( SearchObject( components[i] ), isWeakLink: true );
return referenceNode;
}
private ReferenceNode SearchComponent( object obj )
{
Component component = (Component) obj;
// Ignore Transform component (no object field to search for)
if( component is Transform )
return null;
ReferenceNode referenceNode = PopReferenceNode( component );
if( searchMonoBehavioursForScript && component is MonoBehaviour )
{
// If a searched asset is script, check if this component is an instance of it
// Although SearchVariablesWithSerializedObject can detect these references with SerializedObject, it isn't possible when reflection is used in Play mode
MonoScript script = MonoScript.FromMonoBehaviour( (MonoBehaviour) component );
if( objectsToSearchSet.Contains( script ) )
{
referenceNode.AddLinkTo( GetReferenceNode( script ) );
if( searchParameters.searchRefactoring != null )
searchParameters.searchRefactoring( new BehaviourUsageMatch( component.gameObject, script, component ) );
}
}
if( component is Animation )
{
// Search animation clips for references
if( searchParameters.searchRefactoring == null )
{
foreach( AnimationState anim in (Animation) component )
referenceNode.AddLinkTo( SearchObject( anim.clip ) );
}
else
{
AnimationClip[] clips = AnimationUtility.GetAnimationClips( component.gameObject );
bool modifiedClips = false;
for( int i = 0; i < clips.Length; i++ )
{
referenceNode.AddLinkTo( SearchObject( clips[i] ) );
if( objectsToSearchSet.Contains( clips[i] ) )
{
searchParameters.searchRefactoring( new AnimationSystemMatch( component, clips[i], ( newValue ) =>
{
clips[i] = (AnimationClip) newValue;
modifiedClips = true;
} ) );
}
}
if( modifiedClips )
AnimationUtility.SetAnimationClips( (Animation) component, clips );
}
// Search the objects that are animated by this Animation component for references
SearchAnimatedObjects( referenceNode );
}
else if( component is Animator )
{
// Search animation clips for references (via AnimatorController)
RuntimeAnimatorController animatorController = ( (Animator) component ).runtimeAnimatorController;
referenceNode.AddLinkTo( SearchObject( animatorController ) );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( animatorController ) )
searchParameters.searchRefactoring( new AnimationSystemMatch( component, animatorController, ( newValue ) => ( (Animator) component ).runtimeAnimatorController = (RuntimeAnimatorController) newValue ) );
// Search the objects that are animated by this Animator component for references
SearchAnimatedObjects( referenceNode );
}
#if UNITY_2017_2_OR_NEWER
else if( component is Tilemap )
{
// Search the tiles for references
TileBase[] tiles = new TileBase[( (Tilemap) component ).GetUsedTilesCount()];
( (Tilemap) component ).GetUsedTilesNonAlloc( tiles );
if( tiles != null )
{
for( int i = 0; i < tiles.Length; i++ )
{
referenceNode.AddLinkTo( SearchObject( tiles[i] ), "Tile" );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( tiles[i] ) )
searchParameters.searchRefactoring( new OtherSearchMatch( component, tiles[i], ( newValue ) => ( (Tilemap) component ).SwapTile( tiles[i], (TileBase) newValue ) ) );
}
}
}
#endif
#if UNITY_2017_1_OR_NEWER
else if( component is PlayableDirector )
{
// Search the PlayableAsset's scene bindings for references
PlayableAsset playableAsset = ( (PlayableDirector) component ).playableAsset;
if( playableAsset != null && !playableAsset.Equals( null ) )
{
foreach( PlayableBinding binding in playableAsset.outputs )
{
Object bindingValue = ( (PlayableDirector) component ).GetGenericBinding( binding.sourceObject );
referenceNode.AddLinkTo( SearchObject( bindingValue ), "Binding: " + binding.streamName );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( bindingValue ) )
searchParameters.searchRefactoring( new AnimationSystemMatch( component, bindingValue, ( newValue ) => ( (PlayableDirector) component ).SetGenericBinding( binding.sourceObject, newValue ) ) );
}
}
}
#endif
else if( component is ParticleSystemRenderer )
{
// Search ParticleSystemRenderer's custom meshes for references (at runtime, they can't be searched with reflection, unfortunately)
if( isInPlayMode && !AssetDatabase.Contains( component ) )
{
Mesh[] meshes = new Mesh[( (ParticleSystemRenderer) component ).meshCount];
int meshCount = ( (ParticleSystemRenderer) component ).GetMeshes( meshes );
bool modifiedMeshes = false;
for( int i = 0; i < meshCount; i++ )
{
referenceNode.AddLinkTo( SearchObject( meshes[i] ), "Renderer Module: Mesh" );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( meshes[i] ) )
{
searchParameters.searchRefactoring( new OtherSearchMatch( component, meshes[i], ( newValue ) =>
{
meshes[i] = (Mesh) newValue;
modifiedMeshes = true;
} ) );
}
}
if( modifiedMeshes )
( (ParticleSystemRenderer) component ).SetMeshes( meshes, meshCount );
}
}
else if( component is ParticleSystem )
{
// At runtime, some ParticleSystem properties can't be searched with reflection, search them manually here
if( isInPlayMode && !AssetDatabase.Contains( component ) )
{
ParticleSystem particleSystem = (ParticleSystem) component;
try
{
ParticleSystem.CollisionModule collisionModule = particleSystem.collision;
#if UNITY_2020_2_OR_NEWER
for( int i = 0, j = collisionModule.planeCount; i < j; i++ )
#else
for( int i = 0, j = collisionModule.maxPlaneCount; i < j; i++ )
#endif
{
Transform plane = collisionModule.GetPlane( i );
referenceNode.AddLinkTo( SearchObject( plane ), "Collision Module: Plane" );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( plane ) )
searchParameters.searchRefactoring( new OtherSearchMatch( collisionModule, plane, component, ( newValue ) => collisionModule.SetPlane( i, (Transform) newValue ) ) );
}
}
catch { }
try
{
ParticleSystem.TriggerModule triggerModule = particleSystem.trigger;
#if UNITY_2020_2_OR_NEWER
for( int i = 0, j = triggerModule.colliderCount; i < j; i++ )
#else
for( int i = 0, j = triggerModule.maxColliderCount; i < j; i++ )
#endif
{
Component collider = triggerModule.GetCollider( i );
referenceNode.AddLinkTo( SearchObject( collider ), "Trigger Module: Collider" );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( collider ) )
searchParameters.searchRefactoring( new OtherSearchMatch( triggerModule, collider, component, ( newValue ) => triggerModule.SetCollider( i, (Component) newValue ) ) );
}
}
catch { }
#if UNITY_2017_1_OR_NEWER
try
{
ParticleSystem.TextureSheetAnimationModule textureSheetAnimationModule = particleSystem.textureSheetAnimation;
for( int i = 0, j = textureSheetAnimationModule.spriteCount; i < j; i++ )
{
Sprite sprite = textureSheetAnimationModule.GetSprite( i );
referenceNode.AddLinkTo( SearchObject( sprite ), "Texture Sheet Animation Module: Sprite" );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( sprite ) )
searchParameters.searchRefactoring( new OtherSearchMatch( textureSheetAnimationModule, sprite, component, ( newValue ) => textureSheetAnimationModule.SetSprite( i, (Sprite) newValue ) ) );
}
}
catch { }
#endif
#if UNITY_5_5_OR_NEWER
try
{
ParticleSystem.SubEmittersModule subEmittersModule = particleSystem.subEmitters;
for( int i = 0, j = subEmittersModule.subEmittersCount; i < j; i++ )
{
ParticleSystem subEmitterSystem = subEmittersModule.GetSubEmitterSystem( i );
referenceNode.AddLinkTo( SearchObject( subEmitterSystem ), "Sub Emitters Module: ParticleSystem" );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( subEmitterSystem ) )
searchParameters.searchRefactoring( new OtherSearchMatch( subEmittersModule, subEmitterSystem, component, ( newValue ) => subEmittersModule.SetSubEmitterSystem( i, (ParticleSystem) newValue ) ) );
}
}
catch { }
#endif
}
}
SearchVariablesWithSerializedObject( referenceNode );
return referenceNode;
}
private ReferenceNode SearchMaterial( object obj )
{
const string TEXTURE_PROPERTY_PREFIX = "m_SavedProperties.m_TexEnvs[";
Material material = (Material) obj;
ReferenceNode referenceNode = PopReferenceNode( material );
// We used to search only the shader and the Texture properties in this function but it has changed for 2 major reasons:
// 1) Materials can store more than these references now. For example, HDRP materials can have references to other HDRP materials
// 2) It wasn't possible to search Texture properties that were no longer used by the shader
// Thus, we are searching every property of the material using SerializedObject
SearchVariablesWithSerializedObject( referenceNode );
// Post-process the found results and convert links that start with TEXTURE_PROPERTY_PREFIX to their readable names
SerializedObject materialSO = null;
for( int i = referenceNode.NumberOfOutgoingLinks - 1; i >= 0; i-- )
{
List<string> linkDescriptions = referenceNode[i].descriptions;
for( int j = linkDescriptions.Count - 1; j >= 0; j-- )
{
int texturePropertyPrefixIndex = linkDescriptions[j].IndexOf( TEXTURE_PROPERTY_PREFIX );
if( texturePropertyPrefixIndex >= 0 )
{
texturePropertyPrefixIndex += TEXTURE_PROPERTY_PREFIX.Length;
int texturePropertyEndIndex = linkDescriptions[j].IndexOf( ']', texturePropertyPrefixIndex );
if( texturePropertyEndIndex > texturePropertyPrefixIndex )
{
int texturePropertyIndex;
if( int.TryParse( linkDescriptions[j].Substring( texturePropertyPrefixIndex, texturePropertyEndIndex - texturePropertyPrefixIndex ), out texturePropertyIndex ) )
{
if( materialSO == null )
materialSO = new SerializedObject( material );
string propertyName = materialSO.FindProperty( "m_SavedProperties.m_TexEnvs.Array.data[" + texturePropertyIndex + "].first" ).stringValue;
if( material.HasProperty( propertyName ) )
linkDescriptions[j] = "[Property: " + propertyName + "]";
else if( searchParameters.searchUnusedMaterialProperties )
{
// Move unused references to the end of the list so that used references come first
linkDescriptions.Add( "[Property (UNUSED): " + propertyName + "]" );
linkDescriptions.RemoveAt( j );
}
else
linkDescriptions.RemoveAt( j );
}
}
}
}
if( linkDescriptions.Count == 0 ) // All shader properties were unused and we weren't searching for unused material properties
referenceNode.RemoveLink( i );
}
// At runtime, Textures assigned to clone materials can't be searched with reflection, search them manually here
if( searchTextureReferences && isInPlayMode && !AssetDatabase.Contains( material ) )
{
Shader shader = material.shader;
int shaderPropertyCount = ShaderUtil.GetPropertyCount( shader );
for( int i = 0; i < shaderPropertyCount; i++ )
{
if( ShaderUtil.GetPropertyType( shader, i ) == ShaderUtil.ShaderPropertyType.TexEnv )
{
string propertyName = ShaderUtil.GetPropertyName( shader, i );
Texture assignedTexture = material.GetTexture( propertyName );
if( objectsToSearchSet.Contains( assignedTexture ) )
{
referenceNode.AddLinkTo( GetReferenceNode( assignedTexture ), "Shader property: " + propertyName );
if( searchParameters.searchRefactoring != null )
searchParameters.searchRefactoring( new OtherSearchMatch( material, assignedTexture, ( newValue ) => material.SetTexture( propertyName, (Texture) newValue ) ) );
}
}
}
}
return referenceNode;
}
// Searches default Texture values assigned to shader properties, as well as #include references in shader source code
private ReferenceNode SearchShader( object obj )
{
Shader shader = (Shader) obj;
ReferenceNode referenceNode = PopReferenceNode( shader );
if( searchTextureReferences )
{
ShaderImporter shaderImporter = AssetImporter.GetAtPath( AssetDatabase.GetAssetPath( shader ) ) as ShaderImporter;
if( shaderImporter != null )
{
int shaderPropertyCount = ShaderUtil.GetPropertyCount( shader );
for( int i = 0; i < shaderPropertyCount; i++ )
{
if( ShaderUtil.GetPropertyType( shader, i ) == ShaderUtil.ShaderPropertyType.TexEnv )
{
string propertyName = ShaderUtil.GetPropertyName( shader, i );
Texture defaultTexture = shaderImporter.GetDefaultTexture( propertyName );
#if UNITY_2018_1_OR_NEWER
if( !defaultTexture )
defaultTexture = shaderImporter.GetNonModifiableTexture( propertyName );
#endif
if( objectsToSearchSet.Contains( defaultTexture ) )
{
referenceNode.AddLinkTo( GetReferenceNode( defaultTexture ), "Default Texture: " + propertyName );
if( searchParameters.searchRefactoring != null )
searchParameters.searchRefactoring( new AssetImporterDefaultValueMatch( shaderImporter, defaultTexture, propertyName, null ) );
}
}
}
}
}
// Search shader source code for #include references
if( shaderIncludesToSearchSet.Count > 0 )
SearchShaderSourceCodeForCGIncludes( referenceNode );
return referenceNode;
}
// Searches .compute, .cginc, .cg, .hlsl and .glslinc assets for #include references
private ReferenceNode SearchShaderSecondaryAsset( object obj )
{
if( shaderIncludesToSearchSet.Count == 0 )
return null;
ReferenceNode referenceNode = PopReferenceNode( obj );
SearchShaderSourceCodeForCGIncludes( referenceNode );
return referenceNode;
}
// Searches class/interface inheritances and default UnityEngine.Object values assigned to script variables
private ReferenceNode SearchMonoScript( object obj )
{
MonoScript script = (MonoScript) obj;
Type scriptType = script.GetClass();
if( scriptType == null || ( !scriptType.IsSubclassOf( typeof( MonoBehaviour ) ) && !scriptType.IsSubclassOf( typeof( ScriptableObject ) ) ) )
return null;
ReferenceNode referenceNode = PopReferenceNode( script );
// Check for class/interface inheritance references
for( int i = monoScriptsToSearch.Count - 1; i >= 0; i-- )
{
if( monoScriptsToSearchTypes[i] != scriptType && monoScriptsToSearchTypes[i].IsAssignableFrom( scriptType ) )
referenceNode.AddLinkTo( GetReferenceNode( monoScriptsToSearch[i] ), monoScriptsToSearchTypes[i].IsInterface ? "Implements interface" : "Extends class" );
}
MonoImporter scriptImporter = AssetImporter.GetAtPath( AssetDatabase.GetAssetPath( script ) ) as MonoImporter;
if( scriptImporter != null )
{
VariableGetterHolder[] variables = GetFilteredVariablesForType( scriptType );
for( int i = 0; i < variables.Length; i++ )
{
if( variables[i].isSerializable && !variables[i].IsProperty )
{
Object defaultValue = scriptImporter.GetDefaultReference( variables[i].Name );
if( objectsToSearchSet.Contains( defaultValue ) )
{
referenceNode.AddLinkTo( GetReferenceNode( defaultValue ), "Default variable value: " + variables[i].Name );
if( searchParameters.searchRefactoring != null )
searchParameters.searchRefactoring( new AssetImporterDefaultValueMatch( scriptImporter, defaultValue, variables[i].Name, variables ) );
}
}
}
}
return referenceNode;
}
private ReferenceNode SearchAnimatorController( object obj )
{
RuntimeAnimatorController controller = (RuntimeAnimatorController) obj;
ReferenceNode referenceNode = PopReferenceNode( controller );
if( controller is AnimatorController )
{
AnimatorControllerLayer[] layers = ( (AnimatorController) controller ).layers;
for( int i = 0; i < layers.Length; i++ )
{
if( objectsToSearchSet.Contains( layers[i].avatarMask ) )
{
referenceNode.AddLinkTo( GetReferenceNode( layers[i].avatarMask ), layers[i].name + " Mask" );
if( searchParameters.searchRefactoring != null )
searchParameters.searchRefactoring( new AnimationSystemMatch( layers[i], layers[i].avatarMask, controller, ( newValue ) => layers[i].avatarMask = (AvatarMask) newValue ) );
}
referenceNode.AddLinkTo( SearchObject( layers[i].stateMachine ) );
}
}
else
{
if( controller is AnimatorOverrideController )
{
RuntimeAnimatorController parentController = ( (AnimatorOverrideController) controller ).runtimeAnimatorController;
if( objectsToSearchSet.Contains( parentController ) )
{
referenceNode.AddLinkTo( GetReferenceNode( parentController ) );
if( searchParameters.searchRefactoring != null )
searchParameters.searchRefactoring( new AnimationSystemMatch( controller, parentController, ( newValue ) => ( (AnimatorOverrideController) controller ).runtimeAnimatorController = (RuntimeAnimatorController) newValue ) );
}
if( searchParameters.searchRefactoring != null )
{
List<KeyValuePair<AnimationClip, AnimationClip>> overrideClips = new List<KeyValuePair<AnimationClip, AnimationClip>>( ( (AnimatorOverrideController) controller ).overridesCount );
( (AnimatorOverrideController) controller ).GetOverrides( overrideClips );
bool modifiedOverrideClips = false;
for( int i = overrideClips.Count - 1; i >= 0; i-- )
{
if( objectsToSearchSet.Contains( overrideClips[i].Value ) )
{
searchParameters.searchRefactoring( new AnimationSystemMatch( controller, overrideClips[i].Value, ( newValue ) =>
{
overrideClips[i] = new KeyValuePair<AnimationClip, AnimationClip>( overrideClips[i].Key, (AnimationClip) newValue );
modifiedOverrideClips = true;
} ) );
}
}
if( modifiedOverrideClips )
( (AnimatorOverrideController) controller ).ApplyOverrides( overrideClips );
}
}
AnimationClip[] animClips = controller.animationClips;
for( int i = 0; i < animClips.Length; i++ )
referenceNode.AddLinkTo( SearchObject( animClips[i] ) );
}
return referenceNode;
}
private ReferenceNode SearchAnimatorStateMachine( object obj )
{
AnimatorStateMachine animatorStateMachine = (AnimatorStateMachine) obj;
ReferenceNode referenceNode = PopReferenceNode( animatorStateMachine );
ChildAnimatorStateMachine[] stateMachines = animatorStateMachine.stateMachines;
for( int i = 0; i < stateMachines.Length; i++ )
referenceNode.AddLinkTo( SearchObject( stateMachines[i].stateMachine ), "Child State Machine" );
ChildAnimatorState[] states = animatorStateMachine.states;
for( int i = 0; i < states.Length; i++ )
referenceNode.AddLinkTo( SearchObject( states[i].state ) );
if( searchMonoBehavioursForScript )
{
StateMachineBehaviour[] behaviours = animatorStateMachine.behaviours;
for( int i = 0; i < behaviours.Length; i++ )
{
MonoScript script = MonoScript.FromScriptableObject( behaviours[i] );
if( objectsToSearchSet.Contains( script ) )
{
referenceNode.AddLinkTo( GetReferenceNode( script ) );
if( searchParameters.searchRefactoring != null )
searchParameters.searchRefactoring( new BehaviourUsageMatch( animatorStateMachine, script, behaviours[i] ) );
}
}
}
return referenceNode;
}
private ReferenceNode SearchAnimatorState( object obj )
{
AnimatorState animatorState = (AnimatorState) obj;
ReferenceNode referenceNode = PopReferenceNode( animatorState );
referenceNode.AddLinkTo( SearchObject( animatorState.motion ), "Motion" );
if( searchParameters.searchRefactoring != null && animatorState.motion as AnimationClip && objectsToSearchSet.Contains( animatorState.motion ) )
searchParameters.searchRefactoring( new AnimationSystemMatch( animatorState, animatorState.motion, ( newValue ) => animatorState.motion = (Motion) newValue ) );
if( searchMonoBehavioursForScript )
{
StateMachineBehaviour[] behaviours = animatorState.behaviours;
for( int i = 0; i < behaviours.Length; i++ )
{
MonoScript script = MonoScript.FromScriptableObject( behaviours[i] );
if( objectsToSearchSet.Contains( script ) )
{
referenceNode.AddLinkTo( GetReferenceNode( script ) );
if( searchParameters.searchRefactoring != null )
searchParameters.searchRefactoring( new BehaviourUsageMatch( animatorState, script, behaviours[i] ) );
}
}
}
return referenceNode;
}
private ReferenceNode SearchAnimatorStateTransition( object obj )
{
// Don't search AnimatorStateTransition objects, it will just return duplicate results of SearchAnimatorStateMachine
return PopReferenceNode( obj );
}
private ReferenceNode SearchBlendTree( object obj )
{
BlendTree blendTree = (BlendTree) obj;
ReferenceNode referenceNode = PopReferenceNode( blendTree );
ChildMotion[] children = blendTree.children;
for( int i = 0; i < children.Length; i++ )
{
referenceNode.AddLinkTo( SearchObject( children[i].motion ), "Motion" );
if( searchParameters.searchRefactoring != null && children[i].motion as AnimationClip && objectsToSearchSet.Contains( children[i].motion ) )
searchParameters.searchRefactoring( new AnimationSystemMatch( blendTree, children[i].motion, ( newValue ) => children[i].motion = (Motion) newValue ) );
}
return referenceNode;
}
private ReferenceNode SearchAnimationClip( object obj )
{
AnimationClip clip = (AnimationClip) obj;
ReferenceNode referenceNode = PopReferenceNode( clip );
// Get all curves from animation clip
EditorCurveBinding[] objectCurves = AnimationUtility.GetObjectReferenceCurveBindings( clip );
for( int i = 0; i < objectCurves.Length; i++ )
{
// Search through all the keyframes in this curve
ObjectReferenceKeyframe[] keyframes = AnimationUtility.GetObjectReferenceCurve( clip, objectCurves[i] );
bool modifiedKeyframes = false;
for( int j = 0; j < keyframes.Length; j++ )
{
referenceNode.AddLinkTo( SearchObject( keyframes[j].value ), "Keyframe: " + keyframes[j].time );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( keyframes[j].value ) )
{
searchParameters.searchRefactoring( new AnimationSystemMatch( clip, keyframes[j].value, ( newValue ) =>
{
keyframes[j].value = newValue;
modifiedKeyframes = true;
} ) );
}
}
if( modifiedKeyframes )
AnimationUtility.SetObjectReferenceCurve( clip, objectCurves[i], keyframes );
}
// Get all events from animation clip
AnimationEvent[] events = AnimationUtility.GetAnimationEvents( clip );
bool modifiedEvents = false;
for( int i = 0; i < events.Length; i++ )
{
referenceNode.AddLinkTo( SearchObject( events[i].objectReferenceParameter ), "AnimationEvent: " + events[i].time );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( events[i].objectReferenceParameter ) )
{
searchParameters.searchRefactoring( new AnimationSystemMatch( clip, events[i].objectReferenceParameter, ( newValue ) =>
{
events[i].objectReferenceParameter = newValue;
modifiedEvents = true;
} ) );
}
}
if( modifiedEvents )
AnimationUtility.SetAnimationEvents( clip, events );
return referenceNode;
}
// TerrainData's properties like tree/detail/layer definitions aren't exposed to SerializedObject so use reflection instead
private ReferenceNode SearchTerrainData( object obj )
{
ReferenceNode referenceNode = PopReferenceNode( obj );
SearchVariablesWithReflection( referenceNode );
return referenceNode;
}
private ReferenceNode SearchLightmapSettings( object obj )
{
ReferenceNode referenceNode = PopReferenceNode( obj );
referenceNode.AddLinkTo( SearchObject( LightmapSettings.lightProbes ), "Light Probes" );
LightmapData[] lightmaps = LightmapSettings.lightmaps;
if( lightmaps != null )
{
for( int i = 0; i < lightmaps.Length; i++ )
referenceNode.AddLinkTo( SearchObject( lightmaps[i] ), "Lightmap" );
}
SearchVariablesWithSerializedObject( referenceNode, true );
return referenceNode;
}
private ReferenceNode SearchRenderSettings( object obj )
{
ReferenceNode referenceNode = PopReferenceNode( obj );
#if UNITY_2021_2_OR_NEWER
referenceNode.AddLinkTo( SearchObject( defaultReflectionProbeGetter() ), "Default Reflection Probe" );
#else
referenceNode.AddLinkTo( SearchObject( ReflectionProbe.defaultTexture ), "Default Reflection Probe" );
#endif
SearchVariablesWithSerializedObject( referenceNode, true );
return referenceNode;
}
#if UNITY_2017_1_OR_NEWER
private ReferenceNode SearchSpriteAtlas( object obj )
{
SpriteAtlas spriteAtlas = (SpriteAtlas) obj;
ReferenceNode referenceNode = PopReferenceNode( spriteAtlas );
SerializedObject spriteAtlasSO = new SerializedObject( spriteAtlas );
if( spriteAtlas.isVariant )
{
SerializedProperty masterAtlasProperty = spriteAtlasSO.FindProperty( "m_MasterAtlas" );
Object masterAtlas = masterAtlasProperty.objectReferenceValue;
if( objectsToSearchSet.Contains( masterAtlas ) )
{
referenceNode.AddLinkTo( SearchObject( masterAtlas ), "Master Atlas" );
if( searchParameters.searchRefactoring != null )
searchParameters.searchRefactoring( new SerializedPropertyMatch( spriteAtlas, masterAtlas, masterAtlasProperty ) );
}
}
SerializedProperty packables = spriteAtlasSO.FindProperty( "m_EditorData.packables" );
if( packables != null )
{
for( int i = 0, length = packables.arraySize; i < length; i++ )
{
SerializedProperty packedSpriteProperty = packables.GetArrayElementAtIndex( i );
Object packedSprite = packedSpriteProperty.objectReferenceValue;
SearchSpriteAtlas( referenceNode, packedSprite );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( packedSprite ) )
searchParameters.searchRefactoring( new SerializedPropertyMatch( spriteAtlas, packedSprite, packedSpriteProperty ) );
}
}
#if UNITY_2018_2_OR_NEWER
else
{
Object[] _packables = spriteAtlas.GetPackables();
if( _packables != null )
{
for( int i = 0; i < _packables.Length; i++ )
SearchSpriteAtlas( referenceNode, _packables[i] );
}
}
#endif
return referenceNode;
}
private void SearchSpriteAtlas( ReferenceNode referenceNode, Object packedAsset )
{
if( packedAsset == null || packedAsset.Equals( null ) )
return;
referenceNode.AddLinkTo( SearchObject( packedAsset ), "Packed Texture" );
if( packedAsset is Texture )
{
// Search the Texture's sprites if the Texture asset isn't included in the "SEARCHED OBJECTS" list (i.e. user has
// added only a Sprite sub-asset of the Texture to the list, not the Texture asset itself). Otherwise, references to
// both the Texture and its sprites will be found which can be considered as duplicate references
if( AssetDatabase.IsMainAsset( packedAsset ) && !assetsToSearchSet.Contains( packedAsset ) )
{
Object[] textureSubAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath( AssetDatabase.GetAssetPath( packedAsset ) );
for( int i = 0; i < textureSubAssets.Length; i++ )
{
if( textureSubAssets[i] is Sprite )
referenceNode.AddLinkTo( SearchObject( textureSubAssets[i] ), "Packed Texture" );
}
}
}
else if( packedAsset.IsFolder() )
{
// Search all Sprites in the folder
string[] texturesInFolder = AssetDatabase.FindAssets( "t:Texture2D", new string[] { AssetDatabase.GetAssetPath( packedAsset ) } );
if( texturesInFolder != null )
{
for( int i = 0; i < texturesInFolder.Length; i++ )
{
string texturePath = AssetDatabase.GUIDToAssetPath( texturesInFolder[i] );
TextureImporter textureImporter = AssetImporter.GetAtPath( texturePath ) as TextureImporter;
if( textureImporter != null && textureImporter.textureType == TextureImporterType.Sprite )
{
// Search the Texture and its sprites
SearchSpriteAtlas( referenceNode, AssetDatabase.LoadMainAssetAtPath( texturePath ) );
}
}
}
}
}
#endif
#if UNITY_2017_3_OR_NEWER
// Find references from an Assembly Definition File to its Assembly Definition References
private ReferenceNode SearchAssemblyDefinitionFile( object obj )
{
if( assemblyDefinitionFilesToSearch.Count == 0 )
return null;
AssemblyDefinitionReferences assemblyDefinitionFile = JsonUtility.FromJson<AssemblyDefinitionReferences>( ( (TextAsset) obj ).text );
ReferenceNode referenceNode = PopReferenceNode( obj );
if( !string.IsNullOrEmpty( assemblyDefinitionFile.reference ) )
{
if( assemblyDefinitionFile.references == null )
assemblyDefinitionFile.references = new List<string>( 1 ) { assemblyDefinitionFile.reference };
else
assemblyDefinitionFile.references.Add( assemblyDefinitionFile.reference );
}
if( assemblyDefinitionFile.references != null )
{
for( int i = 0; i < assemblyDefinitionFile.references.Count; i++ )
{
#if UNITY_2019_1_OR_NEWER
string assemblyPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyReference( assemblyDefinitionFile.references[i] );
#else
string assemblyPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName( assemblyDefinitionFile.references[i] );
#endif
if( !string.IsNullOrEmpty( assemblyPath ) )
{
Object searchedAssemblyDefinitionFile;
if( assemblyDefinitionFilesToSearch.TryGetValue( assemblyPath, out searchedAssemblyDefinitionFile ) )
referenceNode.AddLinkTo( GetReferenceNode( searchedAssemblyDefinitionFile ), "Referenced Assembly" );
}
}
}
return referenceNode;
}
#endif
#if UNITY_2018_1_OR_NEWER
// Searches Shader Graph assets for references
private ReferenceNode SearchShaderGraph( object obj )
{
if( !searchTextureReferences && !searchShaderGraphsForSubGraphs && shaderIncludesToSearchSet.Count == 0 )
return null;
ReferenceNode referenceNode = PopReferenceNode( obj );
// Shader Graph assets are JSON files, they must be crawled manually to find references
string graphJson = File.ReadAllText( AssetDatabase.GetAssetPath( (Object) obj ) );
if( graphJson.IndexOf( "\"m_ObjectId\"", 0, Mathf.Min( 200, graphJson.Length ) ) >= 0 )
{
// New Shader Graph serialization format is used: https://github.com/Unity-Technologies/Graphics/pull/222
// Iterate over all these occurrences: "guid\": \"GUID_VALUE\" (\" is used instead of " because it is a nested JSON)
IterateOverValuesInString( graphJson, new string[] { "\"guid\\\"" }, '"', ( guid ) =>
{
if( guid.Length > 1 )
{
if( guid[guid.Length - 1] == '\\' )
guid = guid.Substring( 0, guid.Length - 1 );
string referencePath = AssetDatabase.GUIDToAssetPath( guid );
if( !string.IsNullOrEmpty( referencePath ) && assetsToSearchPathsSet.Contains( referencePath ) )
{
Object reference = AssetDatabase.LoadMainAssetAtPath( referencePath );
if( objectsToSearchSet.Contains( reference ) )
referenceNode.AddLinkTo( GetReferenceNode( reference ), "Used in graph" );
}
}
} );
if( shaderIncludesToSearchSet.Count > 0 )
{
// Iterate over all these occurrences: "m_FunctionSource": "GUID_VALUE" (this one is not nested JSON)
IterateOverValuesInString( graphJson, new string[] { "\"m_FunctionSource\"" }, '"', ( guid ) =>
{
string referencePath = AssetDatabase.GUIDToAssetPath( guid );
if( !string.IsNullOrEmpty( referencePath ) && assetsToSearchPathsSet.Contains( referencePath ) )
{
Object reference = AssetDatabase.LoadMainAssetAtPath( referencePath );
if( objectsToSearchSet.Contains( reference ) )
referenceNode.AddLinkTo( GetReferenceNode( reference ), "Used in node: Custom Function" );
}
} );
}
}
else
{
// Old Shader Graph serialization format is used. Although we could use the same search method as the new serialization format (which
// is potentially faster), this alternative search method yields more information about references
ShaderGraphReferences shaderGraph = JsonUtility.FromJson<ShaderGraphReferences>( graphJson );
if( shaderGraph.m_SerializedProperties != null )
{
for( int i = shaderGraph.m_SerializedProperties.Count - 1; i >= 0; i-- )
{
string propertyJSON = shaderGraph.m_SerializedProperties[i].JSONnodeData;
if( string.IsNullOrEmpty( propertyJSON ) )
continue;
ShaderGraphReferences.PropertyData propertyData = JsonUtility.FromJson<ShaderGraphReferences.PropertyData>( propertyJSON );
if( propertyData.m_Value == null )
continue;
string texturePath = propertyData.m_Value.GetTexturePath();
if( string.IsNullOrEmpty( texturePath ) || !assetsToSearchPathsSet.Contains( texturePath ) )
continue;
Texture texture = AssetDatabase.LoadAssetAtPath<Texture>( texturePath );
if( objectsToSearchSet.Contains( texture ) )
referenceNode.AddLinkTo( GetReferenceNode( texture ), "Default Texture: " + propertyData.GetName() );
}
}
if( shaderGraph.m_SerializableNodes != null )
{
for( int i = shaderGraph.m_SerializableNodes.Count - 1; i >= 0; i-- )
{
string nodeJSON = shaderGraph.m_SerializableNodes[i].JSONnodeData;
if( string.IsNullOrEmpty( nodeJSON ) )
continue;
ShaderGraphReferences.NodeData nodeData = JsonUtility.FromJson<ShaderGraphReferences.NodeData>( nodeJSON );
if( !string.IsNullOrEmpty( nodeData.m_FunctionSource ) )
{
string customFunctionPath = AssetDatabase.GUIDToAssetPath( nodeData.m_FunctionSource );
if( !string.IsNullOrEmpty( customFunctionPath ) && assetsToSearchPathsSet.Contains( customFunctionPath ) )
{
Object customFunction = AssetDatabase.LoadMainAssetAtPath( customFunctionPath );
if( objectsToSearchSet.Contains( customFunction ) )
referenceNode.AddLinkTo( GetReferenceNode( customFunction ), "Used in node: " + nodeData.m_Name );
}
}
if( searchShaderGraphsForSubGraphs )
{
string subGraphPath = nodeData.GetSubGraphPath();
if( !string.IsNullOrEmpty( subGraphPath ) && assetsToSearchPathsSet.Contains( subGraphPath ) )
{
Object subGraph = AssetDatabase.LoadMainAssetAtPath( subGraphPath );
if( objectsToSearchSet.Contains( subGraph ) )
referenceNode.AddLinkTo( GetReferenceNode( subGraph ), "Used as Sub-graph" );
}
}
if( nodeData.m_SerializableSlots == null )
continue;
for( int j = nodeData.m_SerializableSlots.Count - 1; j >= 0; j-- )
{
string nodeSlotJSON = nodeData.m_SerializableSlots[j].JSONnodeData;
if( string.IsNullOrEmpty( nodeSlotJSON ) )
continue;
string texturePath = JsonUtility.FromJson<ShaderGraphReferences.NodeSlotData>( nodeSlotJSON ).GetTexturePath();
if( string.IsNullOrEmpty( texturePath ) || !assetsToSearchPathsSet.Contains( texturePath ) )
continue;
Texture texture = AssetDatabase.LoadAssetAtPath<Texture>( texturePath );
if( objectsToSearchSet.Contains( texture ) )
referenceNode.AddLinkTo( GetReferenceNode( texture ), "Used in node: " + nodeData.m_Name );
}
}
}
}
return referenceNode;
}
#endif
#if ASSET_USAGE_VFX_GRAPH
private ReferenceNode SearchVFXGraphAsset( object obj )
{
ReferenceNode referenceNode = PopReferenceNode( obj );
object vfxResource = vfxResourceGetter( AssetDatabase.GetAssetPath( (Object) obj ) );
foreach( Object vfxResourceContent in (Object[]) vfxResourceContentsGetter.Invoke( vfxResource, null ) )
referenceNode.AddLinkTo( SearchObject( vfxResourceContent ) );
return referenceNode;
}
#endif
// Find references from an Animation/Animator component to the objects that it animates
private void SearchAnimatedObjects( ReferenceNode referenceNode )
{
GameObject root = ( (Component) referenceNode.nodeObject ).gameObject;
AnimationClip[] clips = AnimationUtility.GetAnimationClips( root );
for( int i = 0; i < clips.Length; i++ )
{
AnimationClip clip = clips[i];
if( !clip )
continue;
bool isClipUnique = true;
for( int j = i - 1; j >= 0; j-- )
{
if( clips[j] == clip )
{
isClipUnique = false;
break;
}
}
if( !isClipUnique )
continue;
EditorCurveBinding[] uniqueBindings;
if( !animationClipUniqueBindings.TryGetValue( clip, out uniqueBindings ) )
{
// Calculate all the "unique" paths that the animation clip's curves have
// Both float curves (GetCurveBindings) and object reference curves (GetObjectReferenceCurveBindings) are checked
List<EditorCurveBinding> _uniqueBindings = new List<EditorCurveBinding>( 2 );
EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings( clip );
for( int j = 0; j < bindings.Length; j++ )
{
string bindingPath = bindings[j].path;
if( string.IsNullOrEmpty( bindingPath ) ) // Ignore the root animated object
continue;
bool isBindingUnique = true;
for( int k = _uniqueBindings.Count - 1; k >= 0; k-- )
{
if( bindingPath == _uniqueBindings[k].path )
{
isBindingUnique = false;
break;
}
}
if( isBindingUnique )
_uniqueBindings.Add( bindings[j] );
}
bindings = AnimationUtility.GetObjectReferenceCurveBindings( clip );
for( int j = 0; j < bindings.Length; j++ )
{
string bindingPath = bindings[j].path;
if( string.IsNullOrEmpty( bindingPath ) ) // Ignore the root animated object
continue;
bool isBindingUnique = true;
for( int k = _uniqueBindings.Count - 1; k >= 0; k-- )
{
if( bindingPath == _uniqueBindings[k].path )
{
isBindingUnique = false;
break;
}
}
if( isBindingUnique )
_uniqueBindings.Add( bindings[j] );
}
uniqueBindings = _uniqueBindings.ToArray();
animationClipUniqueBindings[clip] = uniqueBindings;
}
string clipName = clip.name;
for( int j = 0; j < uniqueBindings.Length; j++ )
referenceNode.AddLinkTo( SearchObject( AnimationUtility.GetAnimatedObject( root, uniqueBindings[j] ) ), "Animated via clip: " + clipName );
}
}
// Search #include references in shader source code
private void SearchShaderSourceCodeForCGIncludes( ReferenceNode referenceNode )
{
string shaderPath = AssetDatabase.GetAssetPath( (Object) referenceNode.nodeObject );
// Iterate over all these occurrences: #include "INCLUDE_REFERENCE" or #include_with_pragmas "INCLUDE_REFERENCE"
IterateOverValuesInString( File.ReadAllText( shaderPath ), new string[] { "#include ", "#include_with_pragmas " }, '"', ( include ) =>
{
bool isIncludePotentialReference = shaderIncludesToSearchSet.Contains( include );
if( !isIncludePotentialReference )
{
// Get absolute path of the #include
include = Path.GetFullPath( Path.Combine( Path.GetDirectoryName( shaderPath ), include ) );
int trimStartLength = Directory.GetCurrentDirectory().Length + 1; // Convert absolute path to a Project-relative path
if( include.Length > trimStartLength )
{
include = include.Substring( trimStartLength ).Replace( '\\', '/' );
isIncludePotentialReference = shaderIncludesToSearchSet.Contains( include );
}
}
if( isIncludePotentialReference )
{
Object cgShader = AssetDatabase.LoadMainAssetAtPath( include );
if( objectsToSearchSet.Contains( cgShader ) )
referenceNode.AddLinkTo( GetReferenceNode( cgShader ), "Used with #include" );
}
} );
}
// Search through variables of an object with SerializedObject
private void SearchVariablesWithSerializedObject( ReferenceNode referenceNode, bool forceUseSerializedObject = false )
{
Object unityObject = (Object) referenceNode.nodeObject;
if( !isInPlayMode || unityObject.IsAsset() || forceUseSerializedObject )
{
#if ASSET_USAGE_ADDRESSABLES
// See: https://github.com/yasirkula/UnityAssetUsageDetector/issues/29
if( searchParameters.addressablesSupport && unityObject.name == "Deprecated EditorExtensionImpl" )
return;
#endif
SerializedObject so = new SerializedObject( unityObject );
SerializedProperty iterator = so.GetIterator();
SerializedProperty iteratorVisible = so.GetIterator();
if( iterator.Next( true ) )
{
bool iteratingVisible = iteratorVisible.NextVisible( true );
#if UNITY_2018_3_OR_NEWER
bool searchPrefabOverridesOnly = searchParameters.hideReduntantPrefabVariantLinks && unityObject.IsAsset() && PrefabUtility.GetCorrespondingObjectFromSource( unityObject ) != null;
#endif
bool enterChildren;
do
{
// Iterate over NextVisible properties AND the properties that have corresponding FieldInfos (internal Unity
// properties don't have FieldInfos so we are skipping them, which is good because search results found in
// those properties aren't interesting and mostly confusing)
bool shouldMoveVisibleIterator = iteratingVisible && SerializedProperty.EqualContents( iterator, iteratorVisible );
bool isVisible = shouldMoveVisibleIterator || iterator.type == "Array";
if( !isVisible )
{
Type propFieldType;
isVisible = fieldInfoGetter( iterator, out propFieldType ) != null;
}
if( !isVisible )
enterChildren = false;
#if UNITY_2018_3_OR_NEWER
else if( searchPrefabOverridesOnly && !iterator.prefabOverride )
enterChildren = false;
#endif
else
{
Object propertyValue;
ReferenceNode searchResult;
switch( iterator.propertyType )
{
case SerializedPropertyType.ObjectReference:
propertyValue = iterator.objectReferenceValue;
searchResult = SearchObject( PreferablyGameObject( propertyValue ) );
enterChildren = false;
break;
case SerializedPropertyType.ExposedReference:
propertyValue = iterator.exposedReferenceValue;
searchResult = SearchObject( PreferablyGameObject( propertyValue ) );
enterChildren = false;
break;
#if UNITY_2019_3_OR_NEWER
case SerializedPropertyType.ManagedReference:
object managedReferenceValue = GetRawSerializedPropertyValue( iterator );
propertyValue = managedReferenceValue as Object;
searchResult = SearchObject( PreferablyGameObject( managedReferenceValue ) );
enterChildren = false;
break;
#endif
case SerializedPropertyType.Generic:
#if ASSET_USAGE_ADDRESSABLES
if( searchParameters.addressablesSupport && iterator.type.StartsWithFast( "AssetReference" ) && GetRawSerializedPropertyValue( iterator ) is AssetReference assetReference )
{
propertyValue = GetAddressablesAssetReferenceValue( assetReference );
searchResult = SearchObject( PreferablyGameObject( propertyValue ) );
enterChildren = false;
}
else
#endif
#if ASSET_USAGE_VFX_GRAPH
if( vfxSerializableObjectValueGetter != null && iterator.type == "VFXSerializableObject" && GetRawSerializedPropertyValue( iterator ) is object vfxSerializableObject )
{
object vfxSerializableObjectValue = vfxSerializableObjectValueGetter.Invoke( vfxSerializableObject, null );
propertyValue = vfxSerializableObjectValue as Object;
searchResult = SearchObject( PreferablyGameObject( vfxSerializableObjectValue ) );
enterChildren = false;
}
else
#endif
{
propertyValue = null;
searchResult = null;
enterChildren = true;
}
break;
default:
propertyValue = null;
searchResult = null;
enterChildren = false;
break;
}
if( searchResult != null && searchResult != referenceNode )
{
string propertyPath = iterator.propertyPath;
// m_RD.texture is a redundant reference that shows up when searching sprites
if( !propertyPath.EndsWithFast( "m_RD.texture" ) )
{
referenceNode.AddLinkTo( searchResult, "Variable: " + propertyPath.Replace( ".Array.data[", "[" ) ); // "arrayVariable.Array.data[0]" becomes "arrayVariable[0]"
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( propertyValue ) )
searchParameters.searchRefactoring( new SerializedPropertyMatch( unityObject, propertyValue, iterator ) );
}
}
}
if( shouldMoveVisibleIterator )
iteratingVisible = iteratorVisible.NextVisible( enterChildren );
} while( iterator.Next( enterChildren ) );
return;
}
}
// Use reflection algorithm as fallback
SearchVariablesWithReflection( referenceNode );
}
// Search through variables of an object with reflection
private void SearchVariablesWithReflection( ReferenceNode referenceNode )
{
// Get filtered variables for this object
VariableGetterHolder[] variables = GetFilteredVariablesForType( referenceNode.nodeObject.GetType() );
for( int i = 0; i < variables.Length; i++ )
{
// When possible, don't search non-serializable variables
if( searchSerializableVariablesOnly && !variables[i].isSerializable )
continue;
try
{
object variableValue = variables[i].Get( referenceNode.nodeObject );
if( variableValue == null || variableValue.Equals( null ) )
continue;
// Values stored inside ICollection objects are searched using IEnumerable,
// no need to have duplicate search entries
if( !( variableValue is ICollection ) )
{
#if ASSET_USAGE_ADDRESSABLES
if( searchParameters.addressablesSupport && variableValue is AssetReference )
{
variableValue = GetAddressablesAssetReferenceValue( (AssetReference) variableValue );
if( variableValue == null || variableValue.Equals( null ) )
continue;
}
#endif
ReferenceNode searchResult = SearchObject( PreferablyGameObject( variableValue ) );
if( searchResult != null && searchResult != referenceNode )
{
referenceNode.AddLinkTo( searchResult, ( variables[i].IsProperty ? "Property: " : "Variable: " ) + variables[i].Name );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( variableValue as Object ) )
searchParameters.searchRefactoring( new ReflectionMatch( referenceNode.nodeObject, (Object) variableValue, variables[i].variable ) );
}
}
if( variableValue is IEnumerable && !( variableValue is Transform ) )
{
// If the field is IEnumerable (possibly an array or collection), search through members of it
// Note that Transform IEnumerable (children of the transform) is not iterated
int index = 0;
List<Object> foundReferences = null;
foreach( object element in (IEnumerable) variableValue )
{
ReferenceNode searchResult = SearchObject( PreferablyGameObject( element ) );
if( searchResult != null && searchResult != referenceNode )
{
referenceNode.AddLinkTo( searchResult, string.Concat( variables[i].IsProperty ? "Property: " : "Variable: ", variables[i].Name, "[", index + "]" ) );
if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( element as Object ) )
{
if( foundReferences == null )
foundReferences = new List<Object>( 2 ) { (Object) element };
else if( !foundReferences.Contains( (Object) element ) )
foundReferences.Add( (Object) element );
}
}
index++;
}
if( foundReferences != null )
{
for( int j = foundReferences.Count - 1; j >= 0; j-- )
searchParameters.searchRefactoring( new ReflectionMatch( referenceNode.nodeObject, foundReferences[j], variableValue ) );
}
}
}
catch( UnassignedReferenceException ) { }
catch( MissingReferenceException ) { }
catch( MissingComponentException ) { }
catch( NotImplementedException ) { }
catch( Exception e )
{
// Unknown exceptions usually occur when variableValue is an IEnumerable and its enumerator throws an unhandled exception in MoveNext or Current
StringBuilder sb = Utilities.stringBuilder;
sb.Length = 0;
sb.EnsureCapacity( callStack.Count * 50 + 1000 );
sb.Append( "Skipped searching " ).Append( referenceNode.nodeObject.GetType().FullName ).Append( "." ).Append( variables[i].Name ).AppendLine( " because it threw exception:" ).Append( e ).AppendLine();
Object latestUnityObjectInCallStack = AppendCallStackToStringBuilder( sb );
Debug.LogWarning( sb.ToString(), latestUnityObjectInCallStack );
}
}
}
// Get filtered variables for a type
private VariableGetterHolder[] GetFilteredVariablesForType( Type type )
{
VariableGetterHolder[] result;
if( typeToVariables.TryGetValue( type, out result ) )
return result;
// This is the first time this type of object is seen, filter and cache its variables
// Variable filtering process:
// 1- skip Obsolete variables
// 2- skip primitive types, enums and strings
// 3- skip common Unity types that can't hold any references (e.g. Vector3, Rect, Color, Quaternion)
//
// P.S. IsIgnoredUnityType() extension function handles steps 2) and 3)
validVariables.Clear();
// Filter the fields
if( fieldModifiers != ( BindingFlags.Instance | BindingFlags.DeclaredOnly ) )
{
Type currType = type;
while( currType != typeof( object ) )
{
FieldInfo[] fields = currType.GetFields( fieldModifiers );
for( int i = 0; i < fields.Length; i++ )
{
FieldInfo field = fields[i];
// Skip obsolete fields
if( Attribute.IsDefined( field, typeof( ObsoleteAttribute ) ) )
continue;
// Skip primitive types
if( field.FieldType.IsIgnoredUnityType() )
continue;
#if UNITY_2021_2_OR_NEWER
// "ref struct"s can't be accessed via reflection
if( field.FieldType.IsByRefLike )
continue;
#endif
// Additional filtering for fields:
// 1- Ignore "m_RectTransform", "m_CanvasRenderer" and "m_Canvas" fields of Graphic components
string fieldName = field.Name;
if( typeof( Graphic ).IsAssignableFrom( currType ) &&
( fieldName == "m_RectTransform" || fieldName == "m_CanvasRenderer" || fieldName == "m_Canvas" ) )
continue;
VariableGetVal getter = field.CreateGetter( type );
if( getter != null )
validVariables.Add( new VariableGetterHolder( field, getter, searchSerializableVariablesOnly ? field.IsSerializable() : true ) );
}
currType = currType.BaseType;
}
}
if( propertyModifiers != ( BindingFlags.Instance | BindingFlags.DeclaredOnly ) )
{
Type currType = type;
while( currType != typeof( object ) )
{
PropertyInfo[] properties = currType.GetProperties( propertyModifiers );
for( int i = 0; i < properties.Length; i++ )
{
PropertyInfo property = properties[i];
// Skip obsolete properties
if( Attribute.IsDefined( property, typeof( ObsoleteAttribute ) ) )
continue;
// Skip primitive types
if( property.PropertyType.IsIgnoredUnityType() )
continue;
#if UNITY_2021_2_OR_NEWER
// "ref struct"s can't be accessed via reflection
if( property.PropertyType.IsByRefLike )
continue;
#endif
// Skip properties without a getter function
MethodInfo propertyGetter = property.GetGetMethod( true );
if( propertyGetter == null )
continue;
// Skip indexer properties
if( property.GetIndexParameters().Length > 0 )
continue;
// No need to check properties with 'override' keyword
if( propertyGetter.GetBaseDefinition().DeclaringType != propertyGetter.DeclaringType )
continue;
string propertyName = property.Name;
// Ignore "gameObject", "transform", "rectTransform" and "attachedRigidbody" properties of components to get more useful results
if( typeof( Component ).IsAssignableFrom( currType ) && ( propertyName == "gameObject" ||
propertyName == "transform" || propertyName == "attachedRigidbody" || propertyName == "rectTransform" ) )
continue;
// Ignore "canvasRenderer" and "canvas" properties of Graphic components to get more useful results
else if( typeof( Graphic ).IsAssignableFrom( currType ) &&
( propertyName == "canvasRenderer" || propertyName == "canvas" ) )
continue;
// Prevent accessing properties of Unity that instantiate an existing resource (causing memory leak)
else if( typeof( MeshFilter ).IsAssignableFrom( currType ) && propertyName == "mesh" )
continue;
// Same as above
else if( ( propertyName == "material" || propertyName == "materials" ) &&
( typeof( Renderer ).IsAssignableFrom( currType ) || typeof( Collider ).IsAssignableFrom( currType ) ||
#if !UNITY_2019_3_OR_NEWER
#pragma warning disable 0618
typeof( GUIText ).IsAssignableFrom( currType ) ||
#pragma warning restore 0618
#endif
typeof( Collider2D ).IsAssignableFrom( currType ) ) )
continue;
// Ignore certain Material properties that are already searched via SearchMaterial function (also, if a material doesn't have a _Color or _BaseColor
// property and its "color" property is called, it logs an error to the console, so this rule helps avoid that scenario, as well)
else if( ( propertyName == "color" || propertyName == "mainTexture" ) && typeof( Material ).IsAssignableFrom( currType ) )
continue;
// Ignore "parameters" property of Animator since it doesn't contain any useful data and logs a warning to the console when Animator is inactive
else if( typeof( Animator ).IsAssignableFrom( currType ) && propertyName == "parameters" )
continue;
// Ignore "spriteAnimator" property of TMP_Text component because this property adds a TMP_SpriteAnimator component to the object if it doesn't exist
else if( propertyName == "spriteAnimator" && currType.Name == "TMP_Text" )
continue;
// Ignore "meshFilter" property of TextMeshPro and TMP_SubMesh components because this property adds a MeshFilter component to the object if it doesn't exist
else if( propertyName == "meshFilter" && ( currType.Name == "TextMeshPro" || currType.Name == "TMP_SubMesh" ) )
continue;
// Ignore "users" property of TerrainData because it returns the Terrains in the scene that use that TerrainData. This causes issues with callStack because TerrainData
// is already in callStack when Terrains are searched via "users" property of it and hence, Terrain->TerrainData references for that TerrainData can't be found in scenes
// (this is how callStack works, it prevents searching an object if it's already in callStack to avoid infinite recursion)
else if( propertyName == "users" && typeof( TerrainData ).IsAssignableFrom( currType ) )
continue;
else
{
VariableGetVal getter = property.CreateGetter();
if( getter != null )
validVariables.Add( new VariableGetterHolder( property, getter, searchSerializableVariablesOnly ? property.IsSerializable() : true ) );
}
}
currType = currType.BaseType;
}
}
result = validVariables.ToArray();
// Cache the filtered fields
typeToVariables.Add( type, result );
return result;
}
// Credit: http://answers.unity.com/answers/425602/view.html
// Returns the raw System.Object value of a SerializedProperty
private object GetRawSerializedPropertyValue( SerializedProperty property )
{
object result = property.serializedObject.targetObject;
string[] path = property.propertyPath.Replace( ".Array.data[", "[" ).Split( '.' );
for( int i = 0; i < path.Length; i++ )
{
string pathElement = path[i];
int arrayStartIndex = pathElement.IndexOf( '[' );
if( arrayStartIndex < 0 )
result = GetFieldValue( result, pathElement );
else
{
string variableName = pathElement.Substring( 0, arrayStartIndex );
int arrayEndIndex = pathElement.IndexOf( ']', arrayStartIndex + 1 );
int arrayElementIndex = int.Parse( pathElement.Substring( arrayStartIndex + 1, arrayEndIndex - arrayStartIndex - 1 ) );
result = GetFieldValue( result, variableName, arrayElementIndex );
}
}
return result;
}
// Credit: http://answers.unity.com/answers/425602/view.html
private object GetFieldValue( object source, string fieldName )
{
if( source == null )
return null;
FieldInfo fieldInfo = null;
Type type = source.GetType();
while( fieldInfo == null && type != typeof( object ) )
{
fieldInfo = type.GetField( fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly );
type = type.BaseType;
}
if( fieldInfo != null )
return fieldInfo.GetValue( source );
PropertyInfo propertyInfo = null;
type = source.GetType();
while( propertyInfo == null && type != typeof( object ) )
{
propertyInfo = type.GetProperty( fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.IgnoreCase );
type = type.BaseType;
}
if( propertyInfo != null )
return propertyInfo.GetValue( source, null );
if( fieldName.Length > 2 && fieldName.StartsWith( "m_", StringComparison.OrdinalIgnoreCase ) )
return GetFieldValue( source, fieldName.Substring( 2 ) );
return null;
}
// Credit: http://answers.unity.com/answers/425602/view.html
private object GetFieldValue( object source, string fieldName, int arrayIndex )
{
IEnumerable enumerable = GetFieldValue( source, fieldName ) as IEnumerable;
if( enumerable == null )
return null;
if( enumerable is IList )
return ( (IList) enumerable )[arrayIndex];
IEnumerator enumerator = enumerable.GetEnumerator();
for( int i = 0; i <= arrayIndex; i++ )
enumerator.MoveNext();
return enumerator.Current;
}
#if ASSET_USAGE_ADDRESSABLES
private Object GetAddressablesAssetReferenceValue( AssetReference assetReference )
{
Object result = assetReference.editorAsset;
if( !result )
return null;
string subObjectName = assetReference.SubObjectName;
if( !string.IsNullOrEmpty( subObjectName ) )
{
if( result is SpriteAtlas )
{
Sprite[] packedSprites = spriteAtlasPackedSpritesGetter( (SpriteAtlas) result );
if( packedSprites != null )
{
for( int i = 0; i < packedSprites.Length; i++ )
{
if( packedSprites[i] && packedSprites[i].name == subObjectName )
return packedSprites[i];
}
}
}
else
{
Type subObjectType = (Type) assetReferenceSubObjectTypeGetter.GetValue( assetReference, null ) ?? typeof( Object );
Object[] subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath( AssetDatabase.GetAssetPath( result ) );
for( int k = 0; k < subAssets.Length; k++ )
{
if( subAssets[k] && subAssets[k].name == subObjectName && subObjectType.IsAssignableFrom( subAssets[k].GetType() ) )
return subAssets[k];
}
}
}
return result;
}
#endif
// Iterates over all occurrences of specific key-value pairs in string
// Example1: #include "VALUE" valuePrefix=#include, valueWrapperChar="
// Example2: "guid": "VALUE" valuePrefix="guid", valueWrapperChar="
private void IterateOverValuesInString( string str, string[] valuePrefixes, char valueWrapperChar, Action<string> valueAction )
{
for( int i = 0; i < valuePrefixes.Length; i++ )
{
string valuePrefix = valuePrefixes[i];
int valueStartIndex, valueEndIndex = 0;
while( true )
{
valueStartIndex = str.IndexOf( valuePrefix, valueEndIndex );
if( valueStartIndex < 0 )
break;
valueStartIndex = str.IndexOf( valueWrapperChar, valueStartIndex + valuePrefix.Length );
if( valueStartIndex < 0 )
break;
valueStartIndex++;
valueEndIndex = str.IndexOf( valueWrapperChar, valueStartIndex );
if( valueEndIndex < 0 )
break;
if( valueEndIndex > valueStartIndex )
valueAction( str.Substring( valueStartIndex, valueEndIndex - valueStartIndex ) );
}
}
}
// If obj is Component, switches to its GameObject
private object PreferablyGameObject( object obj )
{
Component component = obj as Component;
return ( component != null && !component.Equals( null ) ) ? component.gameObject : obj;
}
}
}