Add / Remove a folder jonction (symbolic link), inside unity


This devlog will show you how to have external folder inside your unity project. There are 2 main methods to have external folder:
  • Using symbolic links (can not be pushed in git)
  • Using jonctions (can be pushed in git)

We will see how to create jonctions. This script add also the nice functionality to visualize them, in a clever and optimized way.
You can visualize inside your project the files & directory inside a jonction thanks to the little indicator <=> and *, therefore be cautious about them when changing them.
You can even visualize in the hierarchy for a given gameObject if it has something related to a symlink folder:

- The main prefabs
- Any components
- Any fields or properties
For the safety of your projects and files, when working with jonctions and symlink links, always have a backup, or a save in a git repository.




Add a jonction from Unity

Here you see how easy it is to create a jonction from unity, in 2 steps:

First, Right click on a directory in the project view, and select SymLink/Add SymLink

Then select the external directory you want to add in the Folder Panel


And that's it ! Jonctions are visualized with this little indicator <=> in the project view.
You can't have a jonction inside another jonction parent.




Remove a jonction from Unity, but keep local files

You can also remove the link and keep the local data present, in 2 steps:

First, Right click on a directory with a jonction in the project view, and select SymLink/Remove SymLink

Then you have a choose between two options:

- Remove only the Link (keep the local files)

- Remove the directory (same as using the suppr button).
Don't worry, when delete a jonction from unity, the actual extern directory will not be deleted, only the one inside unity




Restore a jonction

And last, you can restore the link of a local directory with an external one.
First, Right click on a directory with no jonction in the project view, and select SymLink/Restore SymLink

Then select the external directory you want to link in the Folder Panel
Caution, this option will override files if they exist in both directory. See carefully bellow the 2 options you have. And make sure to always have your files saved in git before doing this procedure.
When restoring a link from a local directory, the program will merge the two directory. If there a 2 files wifh the same name, you have 2 choices

- keep the local ones (safer for your project !)
- override with the external file (lose links)

If you choose to override from external file, you will loose links of files inside unity. (you know, the famous "references missing" because the lost/incorect GUID of meta files...) You may want to select the option "keep local ones".







Scripts

Simply Put these scripts inside an Editor/ Folder, and that's it !

ExtSymLinks.cs
Download
Copy
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
Download
Copy
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
Download
Copy
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
Download
Copy
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
Download
Copy
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
Download
Copy
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
    }
}









See also:

Animator Extension: How to use advanced reflection step by step

Auto-play from the scene view the first animation of an Animator, without any additionnal script on gameObjects !


Switch Camera Projection

Swap the Projection camera from Perspective to Orthographic using a linear interpolation function coupled with an easing function