Below are some of the tools I scripted using C# in Unity and Python:
This is a C# script in Unity, which allows you to select a folder within the project, and then generate a text file with a list of all the assets in that folder (and its subfolders), their GUIDs, address paths, and a list of assets they are referenced by.
Click here to view the code block
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using NestedDictionary =
System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, int>>;
/// <summary>
/// Select a game folder through the Tools menu
/// The script gets a list of assets from the selected folder, and adds them to a dictionary
/// The script then goes through a list of all game assets and gets their guids
/// then compares the guids to the assets' guids in the selected folder, and if they match
/// adds those to a nested dictionary
/// The script then prompts you to save a text file in which it lists all the assets in the
/// selected folder, the asset's guid, and a list of game assets its referenced by
/// </summary>
public class FileListGenerator
{
/// <summary>
/// A dictionary inside a dictionary: D(selectedAssetGUID,D(searchedAssetGUID,timesReferenced))
/// </summary>
private NestedDictionary _assetsReferencedBy = new NestedDictionary();
/// <summary>
/// Add an option "Generate File List" to the Tools menu
/// </summary>
[MenuItem("Tools/Generate File List")]
// A wrapper for the primary function GenerateFileList()
public static void Main()
{
var fileListGenerator = new FileListGenerator();
fileListGenerator.GenerateFileList();
}
/// <summary>
/// The primary function that generates text file (in which it lists all the assets in the
/// selected folder, the asset's guid, and a list of game assets its referenced by)
/// </summary>
private void GenerateFileList()
{
string[] selectedAssets = SelectAFolder();
PopulateDictionary(selectedAssets);
List<string> allAssets = GetAListOfAllGameAssets();
// Go through each asset in a list of all game assets and get the guids
foreach (var asset in allAssets)
{
var foundGuids = GetGuids(asset);
Debug.Log("There are " + foundGuids.Count + " guids in " + Path.GetFileName(asset));
// Store the guids and the asset they came from in the nested dictionary
AddFoundGuidsToDictionary(foundGuids, asset);
}
PrintOutList();
}
/// <summary>
/// Browse and select a folder for which you want to generate a file list
/// </summary>
private string[] SelectAFolder()
{
string selectedFolderPath = EditorUtility.OpenFolderPanel("Select Folder", "Assets/Bingo", "");
Debug.Log("Selected folder path: " + selectedFolderPath);
// Returns a list of assets from the selected folder and it's subfolders
string[] selectedAssets = Directory.GetFiles(selectedFolderPath, "*.*", SearchOption.AllDirectories);
return selectedAssets;
}
/// <summary>
/// Populate the dictionary with all the assets from the selected folder and its subfolders
/// </summary>
private void PopulateDictionary(string[] selectedAssets)
{
// Go through each file in the selected folder
foreach (string selectedAsset in selectedAssets)
{
// Ignore files if their extension is .meta or .DS_Store
if (Path.GetExtension(selectedAsset) == ".meta" || Path.GetExtension(selectedAsset) == ".DS_Store")
{
continue;
}
// Get the file path for the asset
string pathToSelectedAsset = "Assets" + selectedAsset.Replace
(Application.dataPath, "").Replace('\\', '/');
// Get the GUID for the asset
string selectedAssetGuid = AssetDatabase.AssetPathToGUID(pathToSelectedAsset);
// Convert GUID back to asset path
var realAssetPath = AssetDatabase.GUIDToAssetPath(selectedAssetGuid);
if (string.IsNullOrEmpty(realAssetPath))
{
Debug.LogError("No asset found for GUID: " + pathToSelectedAsset);
continue;
}
// Check to see if the assetsReferencedBy dictionary contains the selectedAsset GUID
// if it doesn't, add it to the dictionary
if (!_assetsReferencedBy.ContainsKey(selectedAssetGuid))
{
_assetsReferencedBy.Add(selectedAssetGuid, new Dictionary<string, int>());
}
}
}
/// <summary>
/// Print out a text file with a list of selected assets and files they are referenced by
/// </summary>
private void PrintOutList()
{
string outputPath = EditorUtility.SaveFilePanel("Save Text File", "", "", "txt");
using (StreamWriter writer = new StreamWriter(outputPath, true))
foreach (KeyValuePair<string, Dictionary<string, int>> entry in _assetsReferencedBy)
{
// Convert GUID back to asset path
var guidToAsset = AssetDatabase.GUIDToAssetPath(entry.Key);
// Get a file name with extension for the asset
var selectedAssetName = Path.GetFileName(guidToAsset);
writer.WriteLine(selectedAssetName + " " + "GUID: " + entry.Key + '\n'
+ "Path: " + guidToAsset + '\n'
+ "Referenced By: ");
foreach (KeyValuePair<string, int> entry2 in entry.Value)
{
// Get a file name with extension for the asset
var referencedAssetName = Path.GetFileName(entry2.Key);
writer.WriteLine('\t' + referencedAssetName);
}
writer.WriteLine('\n');
}
Debug.Log("File list generated at: " + outputPath);
}
/// <summary>
/// Get a list of all game assets
/// </summary>
private List<string> GetAListOfAllGameAssets()
{
// Create a list to hold all of the game's files to search for references in.
var allPathsToAssetsList = new List<string>();
// Specify what type of files you want to search for references in and add them to the above list
// This will search through prefabs, materials, scenes, controllers, vfx graphs, assets
// ("AddRange" appends these items to the end of the allPathsToAssetsList array)
allPathsToAssetsList.AddRange(Directory.GetFiles(Application.dataPath, "*.prefab",
SearchOption.AllDirectories));
allPathsToAssetsList.AddRange(Directory.GetFiles(Application.dataPath, "*.mat", SearchOption.AllDirectories));
allPathsToAssetsList.AddRange(Directory.GetFiles(Application.dataPath, "*.unity", SearchOption.AllDirectories));
allPathsToAssetsList.AddRange(Directory.GetFiles(Application.dataPath, "*.controller",
SearchOption.AllDirectories));
allPathsToAssetsList.AddRange(Directory.GetFiles(Application.dataPath, "*.vfx", SearchOption.AllDirectories));
allPathsToAssetsList.AddRange(Directory.GetFiles(Application.dataPath, "*.asset", SearchOption.AllDirectories));
return allPathsToAssetsList;
}
/// <summary>
/// Go through each line of text in every file and look for the lines with guid information in it
/// Pick out the guid and add it to the foundGuids list
/// </summary>
private List<string> GetGuids(string asset)
{
var text = File.ReadAllText(asset);
var lines = text.Split('\n');
List<string> foundGuids = new List<string>();
foreach (var line in lines)
{
string pattern = "guid:\\s*(\\w{32})";
if (!line.Contains("guid:"))
{
continue;
}
MatchCollection matches = Regex.Matches(line, pattern);
foreach (Match match in matches)
{
string foundGuid = match.Groups[1].ToString();
foundGuids.Add(foundGuid);
}
}
return foundGuids;
}
/// <summary>
/// Add found guids to the dictionary
/// </summary>
private void AddFoundGuidsToDictionary(List<string> foundGuids, string searchedAsset)
{
foreach (string foundGuid in foundGuids)
{
if (_assetsReferencedBy.ContainsKey(foundGuid))
{
if (!_assetsReferencedBy[foundGuid].ContainsKey(searchedAsset))
{
_assetsReferencedBy[foundGuid].Add(searchedAsset, 0);
}
_assetsReferencedBy[foundGuid][searchedAsset]++;
}
}
}
}
Duplicate Folder with Internal Dependencies
This C# Unity script duplicates a folder with internal dependencies through the right-click context menu.
Click here to view the code block
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Linq;
using Object = UnityEngine.Object;
/// <summary>
///
/// This script duplicates a folder with internal dependencies through the right-click context menu.
///
/// The folder must be selected either in a one-column
/// layout, or in the right-hand pane if you are using a two-column layout, due to Unity's quirks:
/// https://forum.unity.com/threads/selection-activeobject-doesnt-return-folders-in-unity4.182418/
///
/// Forum Quote: " the default view of the Project Hierarchy was changed from one column to two and apparently when you
/// have it in 2 columns the Selection.activeObject does not function properly. So just click on the context menu
/// dropdown icon the upper right hand corner of the of the Project Hierarchy view and select "One Column Layout".
/// It does work in two-column layout, but only if you select the folder in the right panel.
/// Selecting in the folder/hierarchy panel (left side) doesn't."
///
/// </summary>
public class DuplicateFolder : MonoBehaviour
{
[MenuItem("Assets/Duplicate Folder (with internal dependencies)")]
static void Duplicate()
{
// Get the selected object in the Project window
Object selectedFolder = Selection.activeObject;
// Get the folder path of the selected object
string selectedFolderPath = AssetDatabase.GetAssetPath(selectedFolder);
// Debug.Log("Selected folder path: " + selectedFolderPath);
try
{
// Create a new name for the duplicate directory by adding "(Copy)" to the end of the original name
string duplicateName = selectedFolder.name + " (Copy)";
// Debug.Log("Duplicate name: " + duplicateName);
// Create the destination path by replacing the original name with the duplicate name
string destinationPath = selectedFolderPath.Replace(selectedFolder.name, duplicateName);
// Debug.Log("Destination path: " + destinationPath);
CopyDirectory(selectedFolderPath, destinationPath);
// Refresh the AssetDatabase to show the new directory
AssetDatabase.Refresh();
}
catch (NullReferenceException)
{
Debug.LogError("Try duplicating the folder by selecting it in the right-hand pane if " +
"using a two-column Project layout, or switch to the one-column Project layout and " +
"re-run the script again.");
}
}
// Recursively retrieve the GUID of all the files contained in the .meta of the destination folder
// generate a correspondence table between the original GUID and the new ones from GUID.Generate
// replace the original GUID with the new ones in all files
static void CopyDirectory(string sourcePath, string destinationPath)
{
CopyDirectoryRecursively(sourcePath, destinationPath);
List<string> metaFiles = GetFilesRecursively(destinationPath, (f) => f.EndsWith(".meta"));
List<(string originalGuid, string newGuid)> guidTable = new List<(string originalGuid, string newGuid)>();
foreach (string metaFile in metaFiles)
{
StreamReader file = new StreamReader(metaFile);
file.ReadLine();
string guidLine = file.ReadLine();
file.Close();
string originalGuid = guidLine.Substring(6, guidLine.Length - 6);
Debug.Log("Original guid: " + originalGuid);
string newGuid = GUID.Generate().ToString().Replace("-", "");
Debug.Log("New guid: " + newGuid);
guidTable.Add((originalGuid, newGuid));
}
List<string> allFiles = GetFilesRecursively(destinationPath);
foreach (string fileToModify in allFiles)
{
string content = File.ReadAllText(fileToModify);
foreach (var guidPair in guidTable)
{
content = content.Replace(guidPair.originalGuid, guidPair.newGuid);
}
if (Path.GetExtension(fileToModify) != ".png")
{
File.WriteAllText(fileToModify, content);
}
Debug.Log("fileToModify: " + fileToModify + " content: " + content);
}
}
private static void CopyDirectoryRecursively(string sourceDirName, string destDirName)
{
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
Debug.Log("Source Directory Name: " + sourceDirName);
DirectoryInfo[] dirs = dir.GetDirectories();
if (!Directory.Exists(destDirName))
{
Directory.CreateDirectory(destDirName);
Debug.Log("Destination Directory Name: " + destDirName);
}
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string tempPath = Path.Combine(destDirName, file.Name);
Debug.Log("tempPath: " + tempPath);
file.CopyTo(tempPath, false);
Debug.Log("file: " + file);
}
foreach (DirectoryInfo subdir in dirs)
{
string tempPath = Path.Combine(destDirName, subdir.Name);
CopyDirectoryRecursively(subdir.FullName, tempPath);
Debug.Log("Source Directory Name: " + subdir.FullName);
Debug.Log("Destination Directory Name: " + tempPath);
}
}
private static List<string> GetFilesRecursively(string path, Func<string, bool> criteria = null, List<string> files = null)
{
if (files == null)
{
files = new List<string>();
}
files.AddRange(Directory.GetFiles(path).Where(f =>criteria == null || criteria(f)));
foreach (string directory in Directory.GetDirectories(path))
{
GetFilesRecursively(directory, criteria, files);
Debug.Log("Get Files Recursively: " + directory + " " + criteria + " " + files);
}
return files;
}
}
A small C# script that displays vertices on a 2D Sprite in Unity. Useful for debugging polygon triangulation algorithms.
Click here to view the code block
using UnityEngine;
public class DebugVertexGizmos : MonoBehaviour
{
[Range(0, 1)]
public float sphereSize = 0.1f;
void OnDrawGizmos()
{
SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
if (spriteRenderer == null)
{
return;
}
// Get the sprite from the sprite renderer
Sprite sprite = spriteRenderer.sprite;
if (sprite == null)
{
return;
}
// Get the vertices of the sprite
Vector2[] vertices = sprite.vertices;
foreach (Vector2 vertex in vertices)
{
// Convert the vertex position from local space to world space
Vector3 worldVertex = transform.TransformPoint(vertex);
// Need a way to draw these points in the game scene view
// Draw a yellow sphere at the transform's position
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(worldVertex, sphereSize);
}
}
}
This is a Python script (written using PyCharm and Qt Designer) that allows you to select a folder, and rename all or some files in that folder using a number of options.
Reference Search & Replace
This tool searches the project for all objects in which the selected object is referenced. It can then also replace that object with another.
Click here to view the code block
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
public class ReferenceSearchAndReplace : EditorWindow
{
private string guidToFind;
private string replacementGuid;
private Object searchedObject;
private Object replacementObject;
private Dictionary<Object, int> referenceObjects = new Dictionary<Object, int>();
private Vector2 scrollPosition;
private Stopwatch searchTimer = new Stopwatch();
string message = "";
[MenuItem( "Ronin Tools/ArtTools/Reference Search & Replace" )]
static void Init()
{
GetWindow( typeof(ReferenceSearchAndReplace), false, "Search & Replace" );
}
void OnGUI()
{
DisplayMainMenu();
if ( GUILayout.Button( "Search for References" ) )
{
SearchForReferences();
}
if ( searchedObject != null && replacementObject != null )
{
// Check if searchedObject and replacementObject are of the same type
if ( searchedObject.GetType() == replacementObject.GetType() )
{
if ( GUILayout.Button( "Replace " + searchedObject.name + " with " + replacementObject.name ) )
{
SearchForReferences();
ReplaceGuids( referenceObjects, guidToFind, replacementGuid );
}
}
else
{
// Display an error message indicating that the objects are not of the same type
message = "Objects are not of the same type. Cannot perform replacement.";
}
}
if ( GUILayout.Button( "Clear Results" ) )
{
ClearResults();
}
if ( searchedObject != null )
{
DisplayReferenceObjectList( referenceObjects );
}
}
private void DisplayMainMenu()
{
EditorGUILayout.Space();
GUILayout.Label( "Drag & drop an object below to find where it's referenced across the project.",
EditorStyles.helpBox );
EditorGUILayout.BeginHorizontal();
GUILayout.Label( "Searched Object:" );
searchedObject =
EditorGUILayout.ObjectField( searchedObject != null ? searchedObject : (Object) null, typeof(Object), false );
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
GUILayout.Label( "If you want to replace the above object, drag & drop it below." +
" Both objects have to be of the same type.", EditorStyles.helpBox );
EditorGUILayout.BeginHorizontal();
GUILayout.Label( "Replacement Object:" );
replacementObject =
EditorGUILayout.ObjectField( replacementObject != null ? replacementObject : (Object) null, typeof(Object),
false );
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
if ( searchedObject != null )
{
var pathToAsset = AssetDatabase.GetAssetPath( searchedObject );
guidToFind = AssetDatabase.AssetPathToGUID( pathToAsset );
GUILayout.Label( searchedObject.name + "'s GUID: " + guidToFind );
EditorGUILayout.Space();
}
if ( replacementObject != null )
{
var pathToAsset = AssetDatabase.GetAssetPath( replacementObject );
replacementGuid = AssetDatabase.AssetPathToGUID( pathToAsset );
GUILayout.Label( replacementObject.name + "'s GUID: " + replacementGuid );
EditorGUILayout.Space();
}
}
private void ClearResults()
{
searchedObject = null;
replacementObject = null;
message = "";
referenceObjects.Clear();
}
private void SearchForReferences()
{
searchTimer.Restart();
referenceObjects.Clear();
var pathToAsset = AssetDatabase.GUIDToAssetPath( guidToFind );
if ( string.IsNullOrEmpty( pathToAsset ) )
{
Debug.LogError( "no asset found for GUID: " + guidToFind );
return;
}
searchedObject = AssetDatabase.LoadAssetAtPath<Object>( pathToAsset );
var exts = new[] { "*.prefab", "*.mat", "*.unity", "*.vfx", "*.controller", "*.asset" };
var allPathsToAssetsList =
exts.SelectMany( x => Directory.EnumerateFiles( Application.dataPath, x, SearchOption.AllDirectories ) ).ToList();
string assetPath;
int totalAssets = allPathsToAssetsList.Count;
int currentAsset = 0;
for ( int i = 0; i < allPathsToAssetsList.Count; i++ )
{
assetPath = allPathsToAssetsList[i];
var text = File.ReadAllText( assetPath );
var lines = text.Split( '\n' );
for ( int j = 0; j < lines.Length; j++ )
{
var line = lines[j];
if ( line.Contains( guidToFind ) )
{
var pathToReferenceAsset = assetPath.Replace( Application.dataPath, string.Empty );
pathToReferenceAsset = pathToReferenceAsset.Replace( ".meta", string.Empty );
var path = "Assets" + pathToReferenceAsset;
path = path.Replace( @"\", "/" ); // fix OSX/Windows path
var asset = AssetDatabase.LoadAssetAtPath<Object>( path );
if ( asset != null )
{
// Search the dictionary for asset, add to dictionary if not found, if found - set the value
if ( !referenceObjects.TryGetValue( asset, out int count ) )
{
referenceObjects.Add( asset, 1 );
}
else
{
referenceObjects[asset] = count + 1;
}
}
else
{
Debug.LogError( path + " could not be loaded" );
}
}
}
currentAsset++;
// Update progress bar
float progress = (float) currentAsset / totalAssets;
string progressInfo = $"Searching for references: {currentAsset}/{totalAssets}";
EditorUtility.DisplayProgressBar( "Search Progress", progressInfo, progress );
message = searchedObject.name + " is referenced by " + referenceObjects.Count + " asset(s).";
}
// Clear progress bar
EditorUtility.ClearProgressBar();
searchTimer.Stop();
}
private void ReplaceGuids( Dictionary<Object, int> referenceObjects, string guidToFind, string replacementGuid )
{
int totalObjects = referenceObjects.Count;
int currentObject = 0;
List<string> editedFiles = new List<string>();
foreach ( var referenceObject in referenceObjects.Keys )
{
currentObject++;
// Calculate progress percentage
float progress = (float) currentObject / totalObjects;
// Display progress bar
EditorUtility.DisplayProgressBar( "Replacing GUIDs", "Processing object " + currentObject + " of " + totalObjects,
progress );
var assetPath = AssetDatabase.GetAssetPath( referenceObject );
var text = File.ReadAllText( assetPath );
var newText = text.Replace( guidToFind, replacementGuid );
File.WriteAllText( assetPath, newText );
// Mark the serialized object as dirty
EditorUtility.SetDirty( referenceObject );
editedFiles.Add( assetPath );
}
message = "Overwriting file data of: \n" + string.Join( ",\n ", editedFiles );
// Clear progress bar when done
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh( ImportAssetOptions.Default );
}
private void DisplayReferenceObjectList( Dictionary<Object, int> referenceObjectsDictionary )
{
GUILayout.Label( "Last search duration: " + searchTimer.Elapsed );
GUILayout.Label( message );
EditorGUILayout.Space();
scrollPosition = EditorGUILayout.BeginScrollView( scrollPosition );
foreach ( var referenceObject in referenceObjectsDictionary )
{
var referencingObject = referenceObject.Key;
var referenceCount = referenceObject.Value;
EditorGUILayout.ObjectField( referencingObject.name + " (" + referenceCount + ")", referencingObject,
typeof(Object), false );
}
EditorGUILayout.EndScrollView();
}
}
Particle Systems Check
This tool searches all the particle systems in the project for a specified “Max Particles” settings. In the screenshot above, it finds a list of prefabs that contain particle systems with Max Particles set to 1000 or above.
Click here to view the code block
// Particle Systems Check Tool
// This checks all the prefabs in the project for any particle systems that have
// their default Max Number of particles set to a user-specified number or more.
// If the user doesn't specify a number, it will search for 1000 as the Max Particles number
// If the user types in something that's not a number and can't be converted to an integer,
// it will currently default to 0 Max Particles
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Diagnostics;
public class ParticleSystemsCheck : EditorWindow
{
string mySearchString = "1000"; // a string variable to hold the user-entered text. Initialized to 1000 by default.
int mySearchInt = 0; // an int variable to hold the converted value from mySearchString
string errorMessage = ""; // a string variable to hold the error message in case user input is not convertable to int
// a dictionary to hold the particle system name, and a list of prefabs that are using that particle system
Dictionary<string, List<GameObject>> particleSystemDict = new Dictionary<string, List<GameObject>>();
private Stopwatch searchTimer = new Stopwatch();
int prefabCount = 0;
int particleSystemCount = 0;
Vector2 scrollPosition;
[MenuItem( "Ronin Tools/ArtTools/Particle Systems Check" )]
public static void Initialize()
{
GetWindow( typeof( ParticleSystemsCheck ) );
}
void OnGUI()
{
this.minSize = new Vector2( 600, 540 );
GUILayout.BeginHorizontal();
if ( GUILayout.Button( "Are Max Particles set to this number or above? Check all Particle Systems", GUILayout.ExpandWidth( true ) ) )
{
ClearCountAndResetTimer();
CheckParticleSystems();
}
ParseSearchString();
GUILayout.EndHorizontal();
if ( GUILayout.Button( "Clear Search Results" ) )
{
ClearSearchResults();
}
// Call the function to display the ParticleSystems list
DisplayParticleSystemsList( particleSystemDict);
}
private void ParseSearchString()
{
mySearchString = GUILayout.TextField( mySearchString, GUILayout.ExpandWidth( true ) );
// Convert the string to int and display error message if conversion fails
if ( !int.TryParse( mySearchString, out mySearchInt ) )
{
errorMessage = "Input is not a valid integer, defaulting to 0 Max Particles search.";
}
else
{
errorMessage = "";
}
}
private void CheckParticleSystems()
{
// Check Particle Systems
// Get all prefab asset paths in the project
string[] prefabAssetPaths = AssetDatabase.FindAssets( "t:Prefab" );
// Initialize progress variables
float progress = 0f;
float totalProgress = prefabAssetPaths.Length;
// Loop through each prefab asset path
foreach ( string prefabAssetPath in prefabAssetPaths )
{
// Update progress bar
progress++;
float progressPercentage = progress / totalProgress;
EditorUtility.DisplayProgressBar("Checking Particle Systems",
"Checking " + progress + " of " + prefabAssetPaths.Length + " prefabs.", progressPercentage);
// Load the prefab object
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>( AssetDatabase.GUIDToAssetPath( prefabAssetPath ) );
// Check if the prefab has a ParticleSystem component
ParticleSystem[] particleSystems = prefab.GetComponentsInChildren<ParticleSystem>( true );
if ( particleSystems != null && particleSystems.Length > 0 )
{
prefabCount++; // Increment the prefab count
// Loop through each ParticleSystem in the prefab
foreach ( ParticleSystem particleSystem in particleSystems )
{
// Check if the maxParticles setting is set to mySearchString or higher
if ( particleSystem.main.maxParticles >= mySearchInt )
{
string particleSystemName = particleSystem.name;
// Add the prefab to the dictionary entry for this ParticleSystem name
if ( !particleSystemDict.ContainsKey( particleSystemName ) )
{
particleSystemDict[particleSystemName] = new List<GameObject>();
}
particleSystemDict[particleSystemName].Add( prefab );
// Print the prefab's path and ParticleSystem's name to the debug log
// UnityEngine.Debug.Log( "Prefab path: " + AssetDatabase.GetAssetPath( prefab ) + ", ParticleSystem name: " + particleSystem.name );
particleSystemCount++; // Increment the ParticleSystem count
}
}
}
}
// Clear progress bar
EditorUtility.ClearProgressBar();
searchTimer.Stop();
}
private void ClearCountAndResetTimer()
{
prefabCount = 0;
particleSystemCount = 0;
searchTimer.Reset();
searchTimer.Start();
}
private void DisplayParticleSystemsList( Dictionary<string, List<GameObject>> particleSystemDict )
{
GUILayout.BeginVertical();
GUILayout.Label( "Found: " + prefabCount + " prefabs that contain particle systems." );
GUILayout.Label( "Found: " + particleSystemCount + " particle systems where Max Particles are set to " + mySearchInt + " or above." );
GUILayout.Label( "Search duration: " + searchTimer.Elapsed);
EditorGUILayout.Space();
GUILayout.Label( "Error Message: " + errorMessage);
GUILayout.EndVertical();
EditorGUILayout.Space();
GUILayout.BeginVertical();
EditorGUILayout.Space();
scrollPosition = EditorGUILayout.BeginScrollView( scrollPosition );
// This displays a list of found objects
foreach ( var entry in particleSystemDict )
{
var particleSystemName = entry.Key; // Particle System name
var prefabs = entry.Value; // Prefab that contains that Particle System
EditorGUILayout.Space();
EditorGUILayout.LabelField( "Particle System name: '" + particleSystemName + "', in the following prefabs: " );
foreach ( GameObject prefab in prefabs )
{
EditorGUILayout.ObjectField( prefab, typeof( GameObject ), false );
}
}
GUILayout.EndVertical();
GUILayout.EndScrollView();
EditorGUILayout.Space();
}
private void ClearSearchResults()
{
mySearchString = "1000";
// Reset variables
prefabCount = 0;
particleSystemCount = 0;
particleSystemDict.Clear();
// Reset scroll position
scrollPosition = Vector2.zero;
}
}
Asset Bundle Missing Reference Check
This tool checks the asset bundles for missing references.
Click here to view the code block
// AssetBundleSOs Missing References Check Tool
// This checks all the AssetBundleSOs for missing references
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Object = UnityEngine.Object;
public class AssetBundleMissingReferencesCheck : EditorWindow
{
private List<ScriptableObject> assetBundleSOs;
private List<Object> missingReferences;
private bool showResults;
[MenuItem( "Ronin Tools/Asset Bundles/Missing References Check", priority = 20004 )]
public static void Initialize()
{
GetWindow( typeof(AssetBundleMissingReferencesCheck), true, "AssetBundleSO Missing References Check" );
}
void OnGUI()
{
minSize = new Vector2( 600, 300 );
GUILayout.BeginHorizontal();
if ( GUILayout.Button( "Check AssetBundleSOs for Missing References" ) )
{
assetBundleSOs = FindAssetBundleSOs();
missingReferences = CheckMissingReferences( assetBundleSOs );
showResults = true;
Repaint();
}
if ( GUILayout.Button( "Clear Results" ) && missingReferences != null )
{
missingReferences.Clear();
showResults = false;
Repaint();
}
GUILayout.EndHorizontal();
if ( showResults )
{
DisplayMissingReferences( missingReferences );
}
else if ( showResults && missingReferences.Count == 0 )
{
EditorGUILayout.Space();
GUILayout.Label( "No missing references found." );
EditorGUILayout.Space();
}
}
// Find all ScriptableObject assets in the project that have "AssetBundleSO" in their name
List<ScriptableObject> FindAssetBundleSOs()
{
string[] guids = AssetDatabase.FindAssets( "t:ScriptableObject" );
// Initialize progress variables
float progress = 0f;
float totalProgress = guids.Length;
List<ScriptableObject> assetBundleSOs = new List<ScriptableObject>();
foreach ( string guid in guids )
{
// Update progress bar
progress++;
float progressPercentage = progress / totalProgress;
EditorUtility.DisplayProgressBar("Checking Missing References",
"Checking " + progress + " of " + guids.Length + " AssetBundles.", progressPercentage);
string path = AssetDatabase.GUIDToAssetPath( guid );
ScriptableObject asset = AssetDatabase.LoadAssetAtPath( path, typeof(ScriptableObject) ) as ScriptableObject;
if ( asset?.name.Contains( "AssetBundleSO" ) == true )
{
assetBundleSOs.Add( asset );
}
}
// Clear progress bar
EditorUtility.ClearProgressBar();
return assetBundleSOs;
}
// Checks a given list of ScriptableObject assets for missing object references
// Each asset is processed only once, even if it has multiple missing references
List<Object> CheckMissingReferences( List<ScriptableObject> assets )
{
List<Object> missingReferences = new List<Object>();
// Loop through each AssetBundleSO object
foreach ( ScriptableObject asset in assets )
{
// For each asset, a SerializedObject is created to access the serialized properties of the asset
SerializedObject serializedObject = new SerializedObject( asset );
// Iterate through all the visible properties of the asset
SerializedProperty property = serializedObject.GetIterator();
while ( property.NextVisible( true ) )
{
// If the property is an object reference, get the actual asset that the property refers to
if ( property.propertyType == SerializedPropertyType.ObjectReference )
{
Object reference = GetAssetFromReference( property );
// If the reference is null, it means that the asset reference is missing
if ( reference == null )
{
string referenceElement = property.displayName;
Debug.LogWarning( "Missing Reference: " + referenceElement + " in " + asset );
if ( !missingReferences.Contains( asset ) )
{
// The asset with missing references is added to the missingReferences list
missingReferences.Add( asset );
break; // Only add asset to list once
}
}
}
}
}
return missingReferences; // return a list of assets with missing references
}
// Returns the asset that the given object reference property refers to, or null if it is null or not an asset
private Object GetAssetFromReference( SerializedProperty property )
{
if ( property == null || property.propertyType != SerializedPropertyType.ObjectReference )
{
return null;
}
// If the property is a reference to an asset, return that asset
Object asset = property.objectReferenceValue;
if ( asset != null && AssetDatabase.Contains( asset ) )
{
return asset;
}
// If the property is a reference to a prefab, return the prefab asset
GameObject go = property.objectReferenceValue as GameObject;
if ( go != null && PrefabUtility.IsPartOfPrefabAsset( go ) )
{
return PrefabUtility.GetCorrespondingObjectFromSource( go );
}
return null;
}
private void DisplayMissingReferences( List<Object> missingReferences )
{
// Define a GUIStyle with italic font style
GUIStyle italicLabelStyle = new GUIStyle( GUI.skin.label );
italicLabelStyle.fontStyle = FontStyle.Italic;
EditorGUILayout.Space();
GUILayout.Label( "AssetBundleSOs with missing references:", italicLabelStyle );
EditorGUILayout.Space();
if ( missingReferences.Count == 0 )
{
GUILayout.BeginHorizontal();
GUILayout.Label( "None found." );
GUILayout.EndHorizontal();
}
else
{
foreach ( Object asset in missingReferences )
{
GUILayout.BeginHorizontal();
GUILayout.Label( asset.name );
if ( GUILayout.Button( "Select", GUILayout.ExpandWidth( false ) ) )
{
Selection.activeObject = asset;
EditorGUIUtility.PingObject( asset );
}
GUILayout.EndHorizontal();
}
}
}
}