Add / remove a folder junction (symbolic link) inside Unity

This devlog shows you how to have an external folder inside your Unity project. There are two main methods to do so:
- Using symbolic links (they cannot be pushed to git)
- Using junctions (they can be pushed to git)
We will see how to create junctions. The script also adds a nice way to visualize them, in a clever and optimized way.
Visualizing junctions

You can see, inside your project, which files and directories belong to a junction thanks to the little indicators <=> and *, so be cautious when modifying them.

You can even see in the hierarchy whether a given GameObject has something related to a symlinked folder:
- The main prefab
- Any component
- Any field or property
For the safety of your projects and files, when working with junctions and symbolic links, always keep a backup, or have everything saved in a git repository.
Add a junction from Unity

Here you can see how easy it is to create a junction from Unity, in two steps:
- Right-click on a directory in the project view, and select SymLink/Add SymLink.
- Select the external directory you want to add in the folder panel.
And that's it! Junctions are shown with the little <=> indicator in the project view.
You can't have a junction inside another junction.
Remove a junction from Unity, but keep the local files

You can also remove the link and keep the local data, in two steps:
- Right-click on a directory with a junction in the project view, and select SymLink/Remove SymLink.
- Then choose between two options:
- Remove only the link (keep the local files)
- Remove the directory (same as pressing the Delete key)
Don't worry: when you delete a junction from Unity, the actual external directory is not deleted, only the one inside Unity.
Restore a junction

Finally, you can restore the link between a local directory and an external one:
- Right-click on a directory with no junction in the project view, and select SymLink/Restore SymLink.
- Select the external directory you want to link in the folder panel.
Caution: this option will override files if they exist in both directories. Look carefully at the two options you have below, and make sure your files are always saved in git before doing this procedure.
When restoring a link from a local directory, the program merges the two directories. If two files have the same name, you have two choices:
- Keep the local ones (safer for your project!)
- Override with the external file (links are lost)
If you choose to override with the external files, you will lose the references to those files inside Unity (you know, the famous "missing references" caused by lost or incorrect GUIDs in meta files…). You probably want to select the "keep local ones" option.
Scripts
Simply put these scripts inside an Editor/ folder, and that's it!
ExtSymLinks.cs
using UnityEditor;
using UnityEngine;
using System.IO;
using hedCommon.extension.editor;
using System.Collections.Generic;
using hedCommon.extension.runtime;
using extUnityComponents.transform;
using extUnityComponents;
using System;
using System.Reflection;
using hedCommon.time;
namespace hedCommon.symlinks
{
/// <summary>
/// An editor utility for easily creating symlinks in your project.
///
/// Adds a Menu item under `Assets/Create/Folder(Symlink)`, and
/// draws a small indicator in the Project view for folders that are
/// symlinks.
/// </summary>
public static class ExtSymLinks
{
private static GUIStyle _symlinkMarkerStyle = null;
private static GUIContent _guiContent = null;
private static GUIStyle SymlinkMarkerStyle
{
get
{
if (_symlinkMarkerStyle == null)
{
_symlinkMarkerStyle = new GUIStyle(EditorStyles.label);
_symlinkMarkerStyle.normal.textColor = Color.blue;
_symlinkMarkerStyle.alignment = TextAnchor.MiddleRight;
}
return _symlinkMarkerStyle;
}
}
public static void DisplayBigMarker(Rect r, string toolTip)
{
_guiContent = new GUIContent("<=>", toolTip);
GUI.Label(r, _guiContent, ExtSymLinks.SymlinkMarkerStyle);
}
public static void DisplayTinyMarker(Rect r, string toolTip)
{
_guiContent = new GUIContent("* ", toolTip);
GUI.Label(r, _guiContent, ExtSymLinks.SymlinkMarkerStyle);
}
public static void ResetSymLinksDatas()
{
SymLinksOnProjectWindowItemGUI.ResetSymLinksDatas();
SymLinksOnHierarchyItemGUI.ResetSymLinksDatas();
}
}
}
SymLinksCreation.cs
using UnityEditor;
using UnityEngine;
using System.IO;
using hedCommon.extension.editor;
using System.Collections.Generic;
using hedCommon.extension.runtime;
using extUnityComponents.transform;
using extUnityComponents;
using System;
using System.Reflection;
using hedCommon.time;
namespace hedCommon.symlinks
{
/// <summary>
/// An editor utility for easily creating symlinks in your project.
///
/// Adds a Menu item under `Assets/Create/Folder(Symlink)`, and
/// draws a small indicator in the Project view for folders that are
/// symlinks.
/// </summary>
[InitializeOnLoad]
public static class SymLinksCreation
{
private const FileAttributes FOLDER_SYMLINK_ATTRIBS = FileAttributes.Directory | FileAttributes.ReparsePoint;
/// <summary>
/// add a folder link at the current selection in the project view
/// </summary>
[MenuItem("Assets/SymLink/Add Folder Link", false, 20)]
static void DoTheSymlink()
{
string targetPath = GetSelectedPathOrFallback();
FileAttributes attribs = File.GetAttributes(targetPath);
if ((attribs & FileAttributes.Directory) != FileAttributes.Directory)
targetPath = Path.GetDirectoryName(targetPath);
if (IsSymLinkFolder(targetPath))
{
throw new System.Exception("directory is already a symLink");
}
if (IsFolderHasParentSymLink(targetPath, out string pathLinkFound))
{
throw new System.Exception("parent " + pathLinkFound + " is a symLink, doen't allow recursive symlinks");
}
ExtWindowComands.OpenFolderPanel(out string sourceFolderPath, out string sourceFolderName);
targetPath = targetPath + "/" + sourceFolderName;
if (Directory.Exists(targetPath))
{
UnityEngine.Debug.LogWarning(string.Format("A folder already exists at this location, aborting link.\n{0} -> {1}", sourceFolderPath, targetPath));
return;
}
string commandeLine = "/C mklink /J \"" + targetPath + "\" \"" + sourceFolderPath + "\"";
ExtWindowComands.Execute(commandeLine);
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
ExtSymLinks.ResetSymLinksDatas();
}
/// <summary>
/// Restore a lost link
/// WARNING: it will override identical files
/// </summary>
[MenuItem("Assets/SymLink/Restore Folder Link", false, 20)]
private static void RestoreSymLink()
{
string targetPath = GetSelectedPathOrFallback();
string directoryName = Path.GetFileName(targetPath);
FileAttributes attribs = File.GetAttributes(targetPath);
if ((attribs & FileAttributes.Directory) != FileAttributes.Directory)
{
targetPath = Path.GetDirectoryName(targetPath);
}
if (IsSymLinkFolder(targetPath))
{
throw new System.Exception("directory " + directoryName + " is already a symLinkFolder");
}
ExtWindowComands.OpenFolderPanel(out string sourceFolderPath, out string sourceFolderName);
if (string.IsNullOrEmpty(sourceFolderName))
{
return;
}
if (directoryName != sourceFolderName)
{
throw new System.Exception("source and target have different names");
}
int choice = EditorUtility.DisplayDialogComplex("Restore SymLink", "If 2 files have the same names," +
"which one do you want to keep ?", "Keep Local ones /!\\", "cancel procedure", "Override with new ones /!\\");
if (choice == 1)
{
return;
}
try
{
//Place the Asset Database in a state where
//importing is suspended for most APIs
AssetDatabase.StartAssetEditing();
ExtFileEditor.DuplicateDirectory(targetPath, out string newPathCreated);
ExtFileEditor.DeleteDirectory(targetPath);
string commandeLine = "/C mklink /J \"" + targetPath + "\" \"" + sourceFolderPath + "\"";
ExtWindowComands.Execute(commandeLine);
ExtFileEditor.CopyAll(new DirectoryInfo(newPathCreated), new DirectoryInfo(targetPath), choice == 2);
ExtFileEditor.DeleteDirectory(newPathCreated);
}
finally
{
//By adding a call to StopAssetEditing inside
//a "finally" block, we ensure the AssetDatabase
//state will be reset when leaving this function
AssetDatabase.StopAssetEditing();
}
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
ExtSymLinks.ResetSymLinksDatas();
}
[MenuItem("Assets/SymLink/Remove Folder Link", false, 20)]
private static void RemoveSymLink()
{
string targetPath = GetSelectedPathOrFallback();
string directoryName = Path.GetFileName(targetPath);
FileAttributes attribs = File.GetAttributes(targetPath);
if ((attribs & FileAttributes.Directory) != FileAttributes.Directory)
{
targetPath = Path.GetDirectoryName(targetPath);
}
if (!IsSymLinkFolder(targetPath))
{
throw new System.Exception("directory " + directoryName + " is not a symLinkFolder");
}
int choice = EditorUtility.DisplayDialogComplex("Remove SymLink", "Do you want to Remove the link only ?", "Remove Link Only", "Cancel", "Remove Link and Directory /!\\");
if (choice == 1)
{
return;
}
string commandeLine = "/C rmdir \"" + ReformatPathForWindow(targetPath) + "\"";
if (choice == 2)
{
ExtWindowComands.Execute(commandeLine);
}
else
{
try
{
//Place the Asset Database in a state where
//importing is suspended for most APIs
AssetDatabase.StartAssetEditing();
ExtFileEditor.DuplicateDirectory(targetPath, out string newPathCreated);
ExtWindowComands.Execute(commandeLine);
ExtFileEditor.RenameDirectory(newPathCreated, directoryName, false);
}
finally
{
//By adding a call to StopAssetEditing inside
//a "finally" block, we ensure the AssetDatabase
//state will be reset when leaving this function
AssetDatabase.StopAssetEditing();
}
}
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
ExtSymLinks.ResetSymLinksDatas();
}
#region UseFull Function
public static bool IsFolderHasParentSymLink(string pathFolder, out string pathSymLinkFound)
{
pathSymLinkFound = "";
while (!string.IsNullOrEmpty(pathFolder))
{
string directoryName = Path.GetDirectoryName(pathFolder);
if (IsSymLinkFolder(directoryName))
{
pathSymLinkFound = directoryName;
return (true);
}
pathFolder = directoryName;
}
return (false);
}
public static bool IsSymLinkFolder(string targetPath)
{
if (string.IsNullOrEmpty(targetPath))
{
return (false);
}
FileAttributes attribs = File.GetAttributes(targetPath);
if ((attribs & FOLDER_SYMLINK_ATTRIBS) != FOLDER_SYMLINK_ATTRIBS)
{
return (false);
}
else
{
return (true);
}
}
/// <summary>
/// return the path of the current selected folder (or the current folder of the asset selected)
/// </summary>
/// <returns></returns>
public static string GetSelectedPathOrFallback()
{
string path = "Assets";
foreach (UnityEngine.Object obj in Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets))
{
path = AssetDatabase.GetAssetPath(obj);
if (!string.IsNullOrEmpty(path) && File.Exists(path))
{
path = Path.GetDirectoryName(path);
break;
}
}
return ReformatPathForUnity(path);
}
/// <summary>
/// change a path from
/// Assets\path\of\file
/// to
/// Assets/path/of/file
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static string ReformatPathForUnity(string path, char characterReplacer = '-')
{
string formattedPath = path.Replace('\\', '/');
formattedPath = formattedPath.Replace('|', characterReplacer);
return (formattedPath);
}
/// <summary>
/// change a path from
/// Assets/path/of/file
/// to
/// Assets\path\of\file
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static string ReformatPathForWindow(string path)
{
string formattedPath = path.Replace('/', '\\');
formattedPath = formattedPath.Replace('|', '-');
return (formattedPath);
}
#endregion
}
}
SymLinksOnHierarchyItemGUI.cs
using UnityEditor;
using UnityEngine;
using System.IO;
using hedCommon.extension.editor;
using System.Collections.Generic;
using hedCommon.extension.runtime;
using extUnityComponents.transform;
using extUnityComponents;
using System;
using System.Reflection;
using hedCommon.time;
namespace hedCommon.symlinks
{
/// <summary>
/// An editor utility for easily creating symlinks in your project.
///
/// Adds a Menu item under `Assets/Create/Folder(Symlink)`, and
/// draws a small indicator in the Project view for folders that are
/// symlinks.
/// </summary>
[InitializeOnLoad]
public static class SymLinksOnHierarchyItemGUI
{
private const string SYMLINK_TOOLTIP = "In Symlink: ";
private const int MAX_SETUP_IN_ONE_FRAME = 10;
private const float TIME_BETWEEN_2_CHUNK_SETUP = 0.1f;
//Don't use dictionnary ! it crash !!
private static List<int> _gameObjectsId = new List<int>(300);
private static List<UnityEngine.Object> _gameObjectsList = new List<UnityEngine.Object>(300);
private static List<bool> _gameObjectsHasBeenSetup = new List<bool>(300);
private static List<bool> _gameObjectsIsInFrameWork = new List<bool>(300);
private static List<string> _toolTipInfo = new List<string>(300);
private static bool _needToSetup = false;
private static int _settupedGameObject = 0;
private static EditorChronoWithNoTimeEditor _timerBetween2ChunkSetup = new EditorChronoWithNoTimeEditor();
/// <summary>
/// Static constructor subscribes to projectWindowItemOnGUI delegate.
/// </summary>
static SymLinksOnHierarchyItemGUI()
{
EditorApplication.hierarchyWindowItemOnGUI -= OnHierarchyItemGUI;
EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyItemGUI;
EditorApplication.hierarchyChanged -= OnHierarchyWindowChanged;
EditorApplication.hierarchyChanged += OnHierarchyWindowChanged;
_needToSetup = false;
_settupedGameObject = 0;
}
public static void ResetSymLinksDatas()
{
_gameObjectsId.Clear();
_gameObjectsList.Clear();
_toolTipInfo.Clear();
_gameObjectsHasBeenSetup.Clear();
_gameObjectsIsInFrameWork.Clear();
_settupedGameObject = 0;
}
[UnityEditor.Callbacks.DidReloadScripts]
private static void OnScriptsReloaded()
{
_needToSetup = true;
}
private static void OnHierarchyWindowChanged()
{
_needToSetup = true;
}
/// <summary>
/// called at every frameGUI for every gaemObjects inside hierarchy
/// </summary>
/// <param name="instanceId"></param>
/// <param name="selectionRect"></param>
private static void OnHierarchyItemGUI(int instanceId, Rect selectionRect)
{
try
{
if (_needToSetup)
{
ResetHasBeenSetupOfGameObjects();
_needToSetup = false;
}
bool succes = _gameObjectsId.ContainIndex(instanceId, out int index);
if (!succes)
{
UnityEngine.Object targetGameObject = EditorUtility.InstanceIDToObject(instanceId);
if (targetGameObject == null)
{
return;
}
_gameObjectsId.Add(instanceId);
_gameObjectsList.Add(targetGameObject);
_toolTipInfo.Add(SYMLINK_TOOLTIP);
_gameObjectsHasBeenSetup.Add(false);
_gameObjectsIsInFrameWork.Add(false);
index = _gameObjectsId.Count - 1;
SetupGameObject(index);
}
if (!_gameObjectsHasBeenSetup[index])
{
SetupGameObject(index);
}
if (_gameObjectsIsInFrameWork[index])
{
DisplayMarker(selectionRect, _toolTipInfo[index]);
}
}
catch (Exception e) { Debug.Log(e); }
}
private static void ResetHasBeenSetupOfGameObjects()
{
_settupedGameObject = 0;
for (int i = 0; i < _gameObjectsHasBeenSetup.Count; i++)
{
_gameObjectsHasBeenSetup[i] = false;
_toolTipInfo[i] = SYMLINK_TOOLTIP;
}
}
/// <summary>
/// From an instance id, setup the info:
/// HasBennSetup at true
/// determine if gameObject is inside framework or not !
/// </summary>
/// <param name="instanceId"></param>
/// <param name="info"></param>
private static void SetupGameObject(int index)
{
if (_settupedGameObject > MAX_SETUP_IN_ONE_FRAME)
{
_timerBetween2ChunkSetup.StartChrono(TIME_BETWEEN_2_CHUNK_SETUP);
_settupedGameObject = 0;
return;
}
if (_timerBetween2ChunkSetup.IsRunning())
{
return;
}
string toolTip = _toolTipInfo[index];
bool prefab = DetermineIfGameObjectIsInSymLink.IsPrefabsAndInSymLink(_gameObjectsList[index] as GameObject, ref SymLinksOnProjectWindowItemGUI.AllSymLinksAssetPathSaved, ref toolTip);
bool component = DetermineIfGameObjectIsInSymLink.HasComponentInSymLink(_gameObjectsList[index] as GameObject, ref SymLinksOnProjectWindowItemGUI.AllSymLinksAssetPathSaved, ref toolTip);
bool assets = DetermineIfGameObjectIsInSymLink.HasSymLinkAssetInsideComponent(_gameObjectsList[index] as GameObject, ref SymLinksOnProjectWindowItemGUI.AllSymLinksAssetPathSaved, ref toolTip);
bool isSomethingInsideFrameWork = prefab || component || assets;
_toolTipInfo[index] = toolTip;
_gameObjectsHasBeenSetup[index] = true;
_gameObjectsIsInFrameWork[index] = isSomethingInsideFrameWork;
_settupedGameObject++;
}
/// <summary>
/// display the marker at the given position
/// </summary>
/// <param name="selectionRect"></param>
private static void DisplayMarker(Rect selectionRect, string toolTip)
{
Rect r = new Rect(selectionRect);
r.x = r.width - 20;
r.width = 18;
ExtSymLinks.DisplayTinyMarker(selectionRect, toolTip);
}
}
}
SymLinksOnProjectWindowItemGUI.cs
using UnityEditor;
using UnityEngine;
using System.IO;
using hedCommon.extension.editor;
using System.Collections.Generic;
using hedCommon.extension.runtime;
using extUnityComponents.transform;
using extUnityComponents;
using System;
using System.Reflection;
using hedCommon.time;
namespace hedCommon.symlinks
{
/// <summary>
/// An editor utility for easily creating symlinks in your project.
///
/// Adds a Menu item under `Assets/Create/Folder(Symlink)`, and
/// draws a small indicator in the Project view for folders that are
/// symlinks.
/// </summary>
[InitializeOnLoad]
public static class SymLinksOnProjectWindowItemGUI
{
public static List<string> AllSymLinksAssetPathSaved = new List<string>(300);
public static void AddPathOfSymLinkAsset(string path)
{
AllSymLinksAssetPathSaved.AddIfNotContain(path);
}
/// <summary>
/// Static constructor subscribes to projectWindowItemOnGUI delegate.
/// </summary>
static SymLinksOnProjectWindowItemGUI()
{
EditorApplication.projectWindowItemOnGUI -= OnProjectWindowItemGUI;
EditorApplication.projectWindowItemOnGUI += OnProjectWindowItemGUI;
}
public static void ResetSymLinksDatas()
{
AllSymLinksAssetPathSaved.Clear();
}
/// <summary>
/// Draw a little indicator if folder is a symlink
/// </summary>
/// <param name="guid"></param>
/// <param name="r"></param>
private static void OnProjectWindowItemGUI(string guid, Rect r)
{
try
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (!string.IsNullOrEmpty(path))
{
FileAttributes attribs = File.GetAttributes(path);
DetermineIfAssetIsOrIsInSymLink.UpdateSymLinksParent(path, ref AllSymLinksAssetPathSaved);
if (DetermineIfAssetIsOrIsInSymLink.IsAttributeASymLink(attribs))
{
ExtSymLinks.DisplayBigMarker(r, "this folder is a symlink");
}
else if (DetermineIfAssetIsOrIsInSymLink.IsAttributeAFileInsideASymLink(path, attribs, ref AllSymLinksAssetPathSaved))
{
ExtSymLinks.DisplayTinyMarker(r, "this object is inside a symlink folder");
}
}
}
catch {}
}
}
}
DetermineIfAssetIsOrIsInSymLink.cs
using extUnityComponents;
using extUnityComponents.transform;
using hedCommon.extension.runtime;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace hedCommon.symlinks
{
public static class DetermineIfAssetIsOrIsInSymLink
{
private const FileAttributes FOLDER_SYMLINK_ATTRIBS = FileAttributes.Directory | FileAttributes.ReparsePoint;
private const FileAttributes FILE_SYMLINK_ATTRIBS = FileAttributes.Directory & FileAttributes.Archive;
/// <summary>
/// is this
/// </summary>
/// <param name="path"></param>
/// <param name="attribs"></param>
/// <returns></returns>
public static bool IsAttributeAFileInsideASymLink(string path, FileAttributes attribs, ref List<string> allSymLinksAssetPathSaved)
{
return (attribs & FILE_SYMLINK_ATTRIBS) == FILE_SYMLINK_ATTRIBS && path.ContainIsPaths(allSymLinksAssetPathSaved);
}
public static bool IsAttributeASymLink(FileAttributes attribs)
{
return (attribs & FOLDER_SYMLINK_ATTRIBS) == FOLDER_SYMLINK_ATTRIBS;
}
public static void UpdateSymLinksParent(string path, ref List<string> allSymLinksAssetPathSaved)
{
while (!string.IsNullOrEmpty(path))
{
FileAttributes attribs = File.GetAttributes(path);
if (IsAttributeASymLink(attribs))
{
allSymLinksAssetPathSaved.AddIfNotContain(path);
return;
}
path = ExtPaths.GetDirectoryFromCompletPath(path);
}
}
//end of class
}
}
DetermineIfGameObjectIsInSymLink.cs
using extUnityComponents;
using extUnityComponents.transform;
using hedCommon.extension.runtime;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace hedCommon.symlinks
{
public static class DetermineIfGameObjectIsInSymLink
{
private const BindingFlags _flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic;
private static List<System.Type> _allNonPersistentTypeComponents = new List<System.Type>()
{
typeof(Transform),
typeof(GameObject),
typeof(TransformHiddedTools),
typeof(AnimatorHiddedTools),
typeof(MeshFilterHiddedTools),
typeof(RectTransformHiddedTools),
typeof(RectTransformHiddedTools),
typeof(RigidBodyAdditionalMonobehaviourSettings),
};
private static List<string> _allForbiddenPropertyName = new List<string>()
{
"material",
"materials",
"mesh"
};
private static List<System.Type> _allForbiddenPropertyType = new List<Type>()
{
typeof(Transform),
typeof(GameObject)
};
/// <summary>
/// Determine if a gameObject is a prefab, if it is, determine if it's inside a symLink
/// </summary>
/// <param name="g">possible prefabs to test</param>
/// <returns>true if gameObject IS a prefabs, AND inside a symLink folder</returns>
public static bool IsPrefabsAndInSymLink(GameObject g, ref List<string> allSymLinksAssetPathSaved, ref string toolTipInfo)
{
bool isPrefab = ExtPrefabsEditor.IsPrefab(g, out GameObject prefab);
if (!isPrefab)
{
return (false);
}
UnityEngine.Object parentObject = PrefabUtility.GetCorrespondingObjectFromSource(prefab);
string path = AssetDatabase.GetAssetPath(parentObject);
DetermineIfAssetIsOrIsInSymLink.UpdateSymLinksParent(path, ref allSymLinksAssetPathSaved);
FileAttributes attribs = File.GetAttributes(path);
bool isInPrefab = DetermineIfAssetIsOrIsInSymLink.IsAttributeAFileInsideASymLink(path, attribs, ref allSymLinksAssetPathSaved);
if (isInPrefab)
{
toolTipInfo += "\n - Prefab root";
}
return (isInPrefab);
}
/// <summary>
/// return true if this gameObject has at least one component inside a symLink
/// </summary>
/// <param name="gameObject"></param>
/// <returns></returns>
public static bool HasComponentInSymLink(GameObject g, ref List<string> allSymLinksAssetPathSaved, ref string toolTip)
{
bool hasComponent = false;
MonoBehaviour[] monoBehaviours = g.GetComponents<MonoBehaviour>();
for (int i = 0; i < monoBehaviours.Length; i++)
{
MonoBehaviour mono = monoBehaviours[i];
if (mono != null && mono.hideFlags == HideFlags.None && !_allNonPersistentTypeComponents.Contains(mono.GetType()))
{
MonoScript script = MonoScript.FromMonoBehaviour(mono); // gets script as an asset
string path = script.GetPath();
DetermineIfAssetIsOrIsInSymLink.UpdateSymLinksParent(path, ref allSymLinksAssetPathSaved);
FileAttributes attribs = File.GetAttributes(path);
if (DetermineIfAssetIsOrIsInSymLink.IsAttributeAFileInsideASymLink(path, attribs, ref allSymLinksAssetPathSaved))
{
toolTip += "\n - " + script.name + " c#";
hasComponent = true;
}
}
}
return (hasComponent);
}
public static bool HasSymLinkAssetInsideComponent(GameObject g, ref List<string> allSymLinksAssetPathSaved, ref string toolTip)
{
bool hasAsset = false;
Component[] components = g.GetComponents<Component>();
if (components == null || components.Length == 0)
{
return (false);
}
for (int i = 0; i < components.Length; i++)
{
Component component = components[i];
if (component == null)
{
continue;
}
Type componentType = component.GetType();
if (_allNonPersistentTypeComponents.Contains(componentType))
{
continue;
}
try
{
PropertyInfo[] properties = componentType.GetProperties(_flags);
foreach (PropertyInfo property in properties)
{
if (_allForbiddenPropertyName.Contains(property.Name))
{
continue;
}
if (_allForbiddenPropertyType.Contains(property.PropertyType))
{
continue;
}
if (property.IsDefined(typeof(ObsoleteAttribute), true))
{
continue;
}
UnityEngine.Object propertyValue = property.GetValue(component, null) as UnityEngine.Object;
if (propertyValue is UnityEngine.Object && !propertyValue.IsTruelyNull())
{
string path = propertyValue.GetPath();
DetermineIfAssetIsOrIsInSymLink.UpdateSymLinksParent(path, ref allSymLinksAssetPathSaved);
FileAttributes attribs = File.GetAttributes(path);
if (DetermineIfAssetIsOrIsInSymLink.IsAttributeAFileInsideASymLink(path, attribs, ref allSymLinksAssetPathSaved))
{
toolTip += "\n - " + propertyValue.name + " in " + ShortType(component.GetType().ToString());
hasAsset = true;
}
}
}
FieldInfo[] fields = componentType.GetFields(_flags);
foreach (FieldInfo field in fields)
{
if (field.IsDefined(typeof(ObsoleteAttribute), true))
{
continue;
}
UnityEngine.Object fieldValue = field.GetValue(component) as UnityEngine.Object;
if (fieldValue is UnityEngine.Object && !fieldValue.IsTruelyNull())
{
string path = fieldValue.GetPath();
DetermineIfAssetIsOrIsInSymLink.UpdateSymLinksParent(path, ref allSymLinksAssetPathSaved);
FileAttributes attribs = File.GetAttributes(path);
if (DetermineIfAssetIsOrIsInSymLink.IsAttributeAFileInsideASymLink(path, attribs, ref allSymLinksAssetPathSaved))
{
toolTip += "\n - " + fieldValue.name + " in " + ShortType(component.GetType().ToString());
hasAsset = true;
}
}
}
}
catch { }
}
return (hasAsset);
}
private static string ShortType(string fullType)
{
return (Path.GetExtension(fullType).Replace(".", ""));
}
//end of class
}
}
Source files
- Download ExtSymLinks.cs
- Download SymLinksCreation.cs
- Download SymLinksOnHierarchyItemGUI.cs
- Download SymLinksOnProjectWindowItemGUI.cs
- Download DetermineIfAssetIsOrIsInSymLink.cs
- Download DetermineIfGameObjectIsInSymLink.cs
See also
- Animator Extension: how to use advanced reflection step by step: auto-play the first animation of an Animator from the scene view, without any additional script on GameObjects.
- Switch camera projection: swap the camera projection from perspective to orthographic using linear interpolation coupled with an easing function.



