├── Demo.gif ├── README.md └── SubAssetDragAndDrop.cs /Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maligan/unity-subassets-drag-and-drop/667fe7c875651d23c680f882eb72f39672e0a323/Demo.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SubAssetDragAndDrop 2 | Unity plugin which allow add/remove subasset with simple Drag&Drop operation 3 | 4 | # Usage 5 | Perform Drag&Drop operation in project hierarhy window while pressing `Alt` button 6 | 7 | ![Demo](Demo.gif) 8 | 9 | # Known Issues 10 | 11 | * Moving `AnimationController` into `Prefab` throw [exception](https://issuetracker.unity3d.com/issues/copying-a-layer-from-script-throws-pointer-errors-and-deletes-transitions), and animator asset breaks 12 | 13 | # License 14 | 15 | * [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/) 16 | 17 | # Alternatives 18 | 19 | * [mob-sakai/SubAssetEditor](https://github.com/mob-sakai/SubAssetEditor) 20 | 21 | 22 | -------------------------------------------------------------------------------- /SubAssetDragAndDrop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using UnityEngine; 5 | 6 | namespace UnityEditor 7 | { 8 | // 9 | // This extension allow to drag&drop subassets while pressing 'Alt' 10 | // 11 | 12 | [InitializeOnLoad] 13 | public static class SubAssetDragAndDrop 14 | { 15 | static SubAssetDragAndDrop() 16 | { 17 | EditorApplication.projectWindowItemOnGUI += OnProjectWindowItemOnGUI; 18 | } 19 | 20 | private static void OnProjectWindowItemOnGUI(string guid, Rect selectionRect) 21 | { 22 | // Break - key modifier doen't pressed 23 | var activated = Event.current.alt; 24 | if (activated == false) return; 25 | 26 | // Break - OnGUI() call not for mouse target 27 | var within = selectionRect.Contains(Event.current.mousePosition); 28 | if (within == false) return; 29 | 30 | // Break - destination match one of sources 31 | var target = AssetDatabase.GUIDToAssetPath(guid); 32 | var targetInSources = Array.IndexOf(DragAndDrop.paths, target) != -1; 33 | if (targetInSources) return; 34 | 35 | // Break - unity default moving 36 | var targetIsFolder = AssetDatabase.IsValidFolder(target); 37 | if (targetIsFolder) 38 | foreach (var asset in DragAndDrop.objectReferences) 39 | if (AssetDatabase.IsMainAsset(asset)) return; 40 | 41 | // Break - there is Unity restriction to use GameObjects as SubAssets 42 | foreach (var obj in DragAndDrop.objectReferences) 43 | if (obj is GameObject) return; 44 | 45 | if (Event.current.type == EventType.DragUpdated) 46 | { 47 | DragAndDrop.visualMode = DragAndDropVisualMode.Move; 48 | Event.current.Use(); 49 | } 50 | else if (Event.current.type == EventType.DragPerform) 51 | { 52 | Move(DragAndDrop.objectReferences, target); 53 | DragAndDrop.AcceptDrag(); 54 | Event.current.Use(); 55 | } 56 | } 57 | 58 | private static void Move(IEnumerable sources, string destinationPath) 59 | { 60 | var destinationIsFolder = AssetDatabase.IsValidFolder(destinationPath); 61 | 62 | foreach (var source in sources) 63 | { 64 | var sourcePath = AssetDatabase.GetAssetPath(source); 65 | var sourceIsMain = AssetDatabase.IsMainAsset(source); 66 | var sourceAssets = new List() { source }; 67 | if (sourceIsMain) 68 | sourceAssets.AddRange(AssetDatabase.LoadAllAssetRepresentationsAtPath(sourcePath)); 69 | 70 | // Peform move assets from source file to destination 71 | foreach (var asset in sourceAssets) 72 | MoveAsset(asset, destinationPath); 73 | 74 | // Remove asset file if it is empty now 75 | if (sourceIsMain) 76 | AssetDatabase.DeleteAsset(sourcePath); 77 | } 78 | 79 | AssetDatabase.SaveAssets(); 80 | AssetDatabase.Refresh(); 81 | } 82 | 83 | private static void MoveAsset(UnityEngine.Object asset, string destinationPath) 84 | { 85 | // Find hidden references (before source move) 86 | var assetRefs = GetHiddenReferences(asset); 87 | 88 | // Move asset 89 | var destinationIsFolder = AssetDatabase.IsValidFolder(destinationPath); 90 | if (destinationIsFolder) 91 | { 92 | var assetName = asset.name + "." + GetFileExtention(asset); 93 | var assetPath = Path.Combine(destinationPath, assetName); 94 | var assetUniquePath = AssetDatabase.GenerateUniqueAssetPath(assetPath); 95 | 96 | AssetDatabase.RemoveObjectFromAsset(asset); 97 | AssetDatabase.CreateAsset(asset, assetUniquePath); 98 | } 99 | else 100 | { 101 | AssetDatabase.RemoveObjectFromAsset(asset); 102 | AssetDatabase.AddObjectToAsset(asset, destinationPath); 103 | } 104 | 105 | // Move attached hidden references 106 | foreach (var reference in assetRefs) 107 | { 108 | AssetDatabase.RemoveObjectFromAsset(reference); 109 | AssetDatabase.AddObjectToAsset(reference, asset); 110 | } 111 | } 112 | 113 | private static List GetHiddenReferences(UnityEngine.Object asset, string refsPath = null, List refs = null) 114 | { 115 | if (refsPath == null) 116 | refsPath = AssetDatabase.GetAssetPath(asset); 117 | 118 | if (refs == null) 119 | refs = new List(); 120 | 121 | var iterator = new SerializedObject(asset).GetIterator(); 122 | while (iterator.Next(true)) 123 | { 124 | if (iterator.propertyType == SerializedPropertyType.ObjectReference) 125 | { 126 | var obj = iterator.objectReferenceValue; 127 | if (obj != null && (obj.hideFlags & HideFlags.HideInHierarchy) != 0) 128 | { 129 | if (refs.IndexOf(obj) == -1 && AssetDatabase.GetAssetPath(obj) == refsPath) 130 | { 131 | refs.Add(obj); 132 | GetHiddenReferences(obj, refsPath, refs); 133 | } 134 | } 135 | } 136 | } 137 | 138 | return refs; 139 | } 140 | 141 | /// Thanks to mob-sakai (https://github.com/mob-sakai/SubAssetEditor) for full mapping list 142 | private static string GetFileExtention(UnityEngine.Object obj) 143 | { 144 | if (obj is AnimationClip) 145 | return "anim"; 146 | else if (obj is UnityEditor.Animations.AnimatorController) 147 | return "controller"; 148 | else if (obj is AnimatorOverrideController) 149 | return "overrideController"; 150 | else if (obj is Material) 151 | return "mat"; 152 | else if (obj is Texture) 153 | return "png"; 154 | else if (obj is ComputeShader) 155 | return "compute"; 156 | else if (obj is Shader) 157 | return "shader"; 158 | else if (obj is Cubemap) 159 | return "cubemap"; 160 | else if (obj is Flare) 161 | return "flare"; 162 | else if (obj is ShaderVariantCollection) 163 | return "shadervariants"; 164 | else if (obj is LightmapParameters) 165 | return "giparams"; 166 | else if (obj is GUISkin) 167 | return "guiskin"; 168 | else if (obj is PhysicMaterial) 169 | return "physicMaterial"; 170 | else if (obj is PhysicsMaterial2D) 171 | return "physicsMaterial2D"; 172 | else if (obj is UnityEngine.Audio.AudioMixer) 173 | return "mixer"; 174 | else if (obj is UnityEngine.U2D.SpriteAtlas) 175 | return "spriteatlas"; 176 | else if (obj is TextAsset) 177 | return "txt"; 178 | else if (obj is GameObject) 179 | return "prefab"; 180 | else if (obj is ScriptableObject) 181 | return "asset"; 182 | 183 | return null; 184 | } 185 | } 186 | } 187 | --------------------------------------------------------------------------------