├── .gitignore ├── AssetDependencyGraph.meta ├── AssetDependencyGraph ├── AssetDependencyGraph-Editor.asmdef ├── AssetDependencyGraph-Editor.asmdef.meta ├── Editor.meta └── Editor │ ├── AssetDependencyGraph.cs │ └── AssetDependencyGraph.cs.meta ├── CONTRIBUTING.md ├── Images~ ├── Example.png └── Usage.png ├── LICENSE.md ├── README.md ├── README.md.meta ├── package.json └── package.json.meta /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]ibrary/ 2 | [Tt]emp/ 3 | [Oo]bj/ 4 | [Bb]uild/ 5 | [Bb]uilds/ 6 | Assets/AssetStoreTools* 7 | 8 | # Visual Studio cache directory 9 | .vs/ 10 | 11 | # Autogenerated VS/MD/Consulo solution and project files 12 | ExportedObj/ 13 | .consulo/ 14 | *.csproj 15 | *.unityproj 16 | *.sln 17 | *.suo 18 | *.tmp 19 | *.user 20 | *.userprefs 21 | *.pidb 22 | *.booproj 23 | *.svd 24 | *.pdb 25 | *.opendb 26 | 27 | # Unity3D generated meta files 28 | *.pidb.meta 29 | *.pdb.meta 30 | 31 | # Unity3D Generated File On Crash Reports 32 | sysinfo.txt 33 | 34 | # Builds 35 | *.apk 36 | *.unitypackage 37 | -------------------------------------------------------------------------------- /AssetDependencyGraph.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3766cfdb049e72541897ef8ec5086e6d 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /AssetDependencyGraph/AssetDependencyGraph-Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AssetDependencyGraph-Editor", 3 | "references": [], 4 | "optionalUnityReferences": [], 5 | "includePlatforms": [ 6 | "Editor" 7 | ], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": false, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [] 14 | } -------------------------------------------------------------------------------- /AssetDependencyGraph/AssetDependencyGraph-Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d7b2a9d5ed1e29b4285a308957689bf1 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /AssetDependencyGraph/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d1c7781d3b33cdb48a6006b20b6ad322 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /AssetDependencyGraph/Editor/AssetDependencyGraph.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEditor.UIElements; 4 | #if UNITY_2019_1_OR_NEWER 5 | using UnityEditor.Experimental.GraphView; 6 | using UnityEngine.UIElements; 7 | #else 8 | using UnityEditor.Experimental.UIElements.GraphView; 9 | using UnityEngine.Experimental.UIElements; 10 | using UnityEditor.Experimental.UIElements; 11 | using UnityEngine.Experimental.UIElements.StyleEnums; 12 | #endif 13 | using UnityEngine; 14 | 15 | public class AssetGraphView : GraphView 16 | { 17 | public AssetGraphView() 18 | { 19 | SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale); 20 | 21 | this.AddManipulator(new ContentDragger()); 22 | this.AddManipulator(new SelectionDragger()); 23 | this.AddManipulator(new RectangleSelector()); 24 | this.AddManipulator(new FreehandSelector()); 25 | 26 | VisualElement background = new VisualElement 27 | { 28 | style = 29 | { 30 | backgroundColor = new Color(0.17f, 0.17f, 0.17f, 1f) 31 | } 32 | }; 33 | Insert(0, background); 34 | 35 | background.StretchToParentSize(); 36 | } 37 | } 38 | 39 | public class AssetDependencyGraph : EditorWindow 40 | { 41 | private const float kNodeWidth = 250.0f; 42 | 43 | private GraphView m_GraphView; 44 | 45 | private readonly List m_AssetElements = new List(); 46 | private readonly Dictionary m_GUIDNodeLookup = new Dictionary(); 47 | private readonly List m_DependenciesForPlacement = new List(); 48 | 49 | #if !UNITY_2019_1_OR_NEWER 50 | private VisualElement rootVisualElement; 51 | #endif 52 | 53 | [MenuItem("Window/Analysis/Asset Dependency Graph")] 54 | public static void CreateTestGraphViewWindow() 55 | { 56 | var window = GetWindow(); 57 | window.titleContent = new GUIContent("Asset Dependency Graph"); 58 | } 59 | 60 | public void OnEnable() 61 | { 62 | m_GraphView = new AssetGraphView 63 | { 64 | name = "Asset Dependency Graph", 65 | }; 66 | 67 | var toolbar = new VisualElement 68 | { 69 | style = 70 | { 71 | flexDirection = FlexDirection.Row, 72 | flexGrow = 0, 73 | backgroundColor = new Color(0.25f, 0.25f, 0.25f, 0.75f) 74 | } 75 | }; 76 | 77 | var options = new VisualElement 78 | { 79 | style = { alignContent = Align.Center } 80 | }; 81 | 82 | toolbar.Add(options); 83 | toolbar.Add(new Button(ExploreAsset) 84 | { 85 | text = "Explore Asset", 86 | }); 87 | toolbar.Add(new Button(ClearGraph) 88 | { 89 | text = "Clear" 90 | }); 91 | 92 | var ts = new ToolbarSearchField(); 93 | ts.RegisterValueChangedCallback(x => { 94 | if (string.IsNullOrEmpty(x.newValue)) { 95 | m_GraphView.FrameAll(); 96 | return; 97 | } 98 | 99 | m_GraphView.ClearSelection(); 100 | // m_GraphView.graphElements.ForEach(y => { // BROKEN, Case 1268337 101 | m_GraphView.graphElements.ToList().ForEach(y => { 102 | if (y is Node node && y.title.IndexOf(x.newValue, StringComparison.OrdinalIgnoreCase) >= 0) { 103 | m_GraphView.AddToSelection(node); 104 | } 105 | }); 106 | 107 | m_GraphView.FrameSelection(); 108 | }); 109 | toolbar.Add(ts); 110 | 111 | #if !UNITY_2019_1_OR_NEWER 112 | rootVisualElement = this.GetRootVisualContainer(); 113 | #endif 114 | rootVisualElement.Add(toolbar); 115 | rootVisualElement.Add(m_GraphView); 116 | m_GraphView.StretchToParentSize(); 117 | toolbar.BringToFront(); 118 | } 119 | 120 | public void OnDisable() 121 | { 122 | rootVisualElement.Remove(m_GraphView); 123 | } 124 | 125 | private void ExploreAsset() 126 | { 127 | Object obj = Selection.activeObject; 128 | string assetPath = AssetDatabase.GetAssetPath(obj); 129 | 130 | // assetPath will be empty if obj is null or isn't an asset (a scene object) 131 | if (obj == null || string.IsNullOrEmpty(assetPath)) 132 | return; 133 | 134 | Group groupNode = new Group {title = obj.name}; 135 | Object mainObject = AssetDatabase.LoadMainAssetAtPath(assetPath); 136 | 137 | string[] dependencies = AssetDatabase.GetDependencies(assetPath, false); 138 | bool hasDependencies = dependencies.Length > 0; 139 | 140 | Node mainNode = CreateNode(mainObject, assetPath, true, hasDependencies); 141 | mainNode.userData = 0; 142 | 143 | mainNode.SetPosition(new Rect(0, 0, 0, 0)); 144 | m_GraphView.AddElement(groupNode); 145 | m_GraphView.AddElement(mainNode); 146 | 147 | groupNode.AddElement(mainNode); 148 | 149 | CreateDependencyNodes(dependencies, mainNode, groupNode, 1); 150 | 151 | m_AssetElements.Add(mainNode); 152 | m_AssetElements.Add(groupNode); 153 | groupNode.capabilities &= ~Capabilities.Deletable; 154 | 155 | groupNode.Focus(); 156 | 157 | mainNode.RegisterCallback(UpdateDependencyNodePlacement); 158 | } 159 | 160 | private void CreateDependencyNodes(string[] dependencies, Node parentNode, Group groupNode, int depth) 161 | { 162 | foreach (string dependencyString in dependencies) 163 | { 164 | Object dependencyAsset = AssetDatabase.LoadMainAssetAtPath(dependencyString); 165 | string[] deeperDependencies = AssetDatabase.GetDependencies(dependencyString, false); 166 | 167 | Node dependencyNode = CreateNode(dependencyAsset, AssetDatabase.GetAssetPath(dependencyAsset), 168 | false, deeperDependencies.Length > 0); 169 | 170 | if (!m_AssetElements.Contains(dependencyNode)) 171 | dependencyNode.userData = depth; 172 | 173 | CreateDependencyNodes(deeperDependencies, dependencyNode, groupNode, depth + 1); 174 | 175 | if (!m_GraphView.Contains(dependencyNode)) 176 | m_GraphView.AddElement(dependencyNode); 177 | 178 | Edge edge = new Edge 179 | { 180 | input = dependencyNode.inputContainer[0] as Port, 181 | output = parentNode.outputContainer[0] as Port, 182 | }; 183 | edge.input?.Connect(edge); 184 | edge.output?.Connect(edge); 185 | 186 | dependencyNode.RefreshPorts(); 187 | m_GraphView.AddElement(edge); 188 | 189 | if (!m_AssetElements.Contains(dependencyNode)) 190 | groupNode.AddElement(dependencyNode); 191 | 192 | edge.capabilities &= ~Capabilities.Deletable; 193 | m_AssetElements.Add(edge); 194 | m_AssetElements.Add(dependencyNode); 195 | 196 | if (!m_DependenciesForPlacement.Contains(dependencyNode)) 197 | m_DependenciesForPlacement.Add(dependencyNode); 198 | } 199 | } 200 | 201 | private Node CreateNode(Object obj, string assetPath, bool isMainNode, bool hasDependencies) 202 | { 203 | Node resultNode; 204 | string assetGUID = AssetDatabase.AssetPathToGUID(assetPath); 205 | if (m_GUIDNodeLookup.TryGetValue(assetGUID, out resultNode)) 206 | { 207 | int currentDepth = (int)resultNode.userData; 208 | resultNode.userData = currentDepth + 1; 209 | return resultNode; 210 | } 211 | 212 | if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var assetGuid, out long _)) 213 | { 214 | var objNode = new Node 215 | { 216 | title = obj.name, 217 | style = 218 | { 219 | width = kNodeWidth 220 | } 221 | }; 222 | 223 | objNode.extensionContainer.style.backgroundColor = new Color(0.24f, 0.24f, 0.24f, 0.8f); 224 | 225 | objNode.titleContainer.Add(new Button(() => 226 | { 227 | Selection.activeObject = obj; 228 | EditorGUIUtility.PingObject(obj); 229 | }) 230 | { 231 | style = 232 | { 233 | height = 16.0f, 234 | alignSelf = Align.Center, 235 | alignItems = Align.Center 236 | }, 237 | text = "Select" 238 | }); 239 | 240 | var infoContainer = new VisualElement 241 | { 242 | style = 243 | { 244 | paddingBottom = 4.0f, 245 | paddingTop = 4.0f, 246 | paddingLeft = 4.0f, 247 | paddingRight = 4.0f 248 | } 249 | }; 250 | 251 | infoContainer.Add(new Label 252 | { 253 | text = assetPath, 254 | #if UNITY_2019_1_OR_NEWER 255 | style = { whiteSpace = WhiteSpace.Normal } 256 | #else 257 | style = { wordWrap = true } 258 | #endif 259 | }); 260 | 261 | var typeName = obj.GetType().Name; 262 | if (isMainNode) 263 | { 264 | var prefabType = PrefabUtility.GetPrefabAssetType(obj); 265 | if (prefabType != PrefabAssetType.NotAPrefab) 266 | typeName = $"{prefabType} Prefab"; 267 | } 268 | 269 | var typeLabel = new Label 270 | { 271 | text = $"Type: {typeName}" 272 | }; 273 | infoContainer.Add(typeLabel); 274 | 275 | objNode.extensionContainer.Add(infoContainer); 276 | 277 | Texture assetTexture = AssetPreview.GetAssetPreview(obj); 278 | if (!assetTexture) 279 | assetTexture = AssetPreview.GetMiniThumbnail(obj); 280 | 281 | if (assetTexture) 282 | { 283 | AddDivider(objNode); 284 | 285 | objNode.extensionContainer.Add(new Image 286 | { 287 | image = assetTexture, 288 | scaleMode = ScaleMode.ScaleToFit, 289 | style = 290 | { 291 | paddingBottom = 4.0f, 292 | paddingTop = 4.0f, 293 | paddingLeft = 4.0f, 294 | paddingRight = 4.0f 295 | } 296 | }); 297 | } 298 | 299 | // Ports 300 | if (!isMainNode) 301 | { 302 | Port realPort = objNode.InstantiatePort(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(Object)); 303 | realPort.portName = "Dependent"; 304 | objNode.inputContainer.Add(realPort); 305 | } 306 | 307 | if (hasDependencies) 308 | { 309 | #if UNITY_2018_1 310 | Port port = objNode.InstantiatePort(Orientation.Horizontal, Direction.Output, typeof(Object)); 311 | #else 312 | Port port = objNode.InstantiatePort(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Object)); 313 | #endif 314 | port.portName = "Dependencies"; 315 | objNode.outputContainer.Add(port); 316 | objNode.RefreshPorts(); 317 | } 318 | 319 | resultNode = objNode; 320 | 321 | resultNode.RefreshExpandedState(); 322 | resultNode.RefreshPorts(); 323 | resultNode.capabilities &= ~Capabilities.Deletable; 324 | resultNode.capabilities |= Capabilities.Collapsible; 325 | } 326 | 327 | m_GUIDNodeLookup[assetGUID] = resultNode; 328 | return resultNode; 329 | } 330 | 331 | private static void AddDivider(Node objNode) 332 | { 333 | var divider = new VisualElement {name = "divider"}; 334 | divider.AddToClassList("horizontal"); 335 | objNode.extensionContainer.Add(divider); 336 | } 337 | 338 | private void ClearGraph() 339 | { 340 | foreach (var edge in m_AssetElements) 341 | { 342 | m_GraphView.RemoveElement(edge); 343 | } 344 | m_AssetElements.Clear(); 345 | 346 | foreach (var node in m_AssetElements) 347 | { 348 | m_GraphView.RemoveElement(node); 349 | } 350 | m_AssetElements.Clear(); 351 | m_GUIDNodeLookup.Clear(); 352 | } 353 | 354 | private void UpdateDependencyNodePlacement(GeometryChangedEvent e) 355 | { 356 | (e.target as VisualElement).UnregisterCallback(UpdateDependencyNodePlacement); 357 | 358 | // The current y offset in per depth 359 | var depthYOffset = new Dictionary(); 360 | 361 | foreach (var node in m_DependenciesForPlacement) 362 | { 363 | int depth = (int)node.userData; 364 | 365 | if (!depthYOffset.ContainsKey(depth)) 366 | depthYOffset.Add(depth, 0.0f); 367 | 368 | depthYOffset[depth] += node.layout.height; 369 | } 370 | 371 | // Move half of the node into negative y space so they're on either size of the main node in y axis 372 | var depths = new List(depthYOffset.Keys); 373 | foreach (int depth in depths) 374 | { 375 | if (depth == 0) 376 | continue; 377 | 378 | float offset = depthYOffset[depth]; 379 | depthYOffset[depth] = (0f - offset / 2.0f); 380 | } 381 | 382 | foreach (var node in m_DependenciesForPlacement) 383 | { 384 | int depth = (int)node.userData; 385 | node.SetPosition(new Rect(kNodeWidth * 1.5f * depth, depthYOffset[depth], 0, 0)); 386 | depthYOffset[depth] += node.layout.height; 387 | } 388 | 389 | m_DependenciesForPlacement.Clear(); 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /AssetDependencyGraph/Editor/AssetDependencyGraph.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4203aab0c53bc42d38d150b98e085083 3 | timeCreated: 1520438813 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | https://unity3d.com/legal/licenses/Unity_Contribution_Agreement 2 | -------------------------------------------------------------------------------- /Images~/Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Harry/Unity-AssetDependencyGraph/abc0e6551d18c54ed9f759cdd005eea0f14f8ac4/Images~/Example.png -------------------------------------------------------------------------------- /Images~/Usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Harry/Unity-AssetDependencyGraph/abc0e6551d18c54ed9f759cdd005eea0f14f8ac4/Images~/Usage.png -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Unity-AssetDependencyGraph © 2019 Unity Technologies 2 | Licensed under the Unity Companion License for Unity-dependent projects (see https://unity3d.com/legal/licenses/unity_companion_license). 3 | Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity - Asset Dependency Graph 2 | 3 | New License and Contribution Guidelines: 28/10/2020 4 | 5 | This project provides a basic Asset Dependency Graph for Unity using the new [GraphView](https://docs.unity3d.com/2019.2/Documentation/ScriptReference/Experimental.GraphView.GraphView.html) API. 6 | 7 | ![](Images~/Example.png?raw=true) 8 | 9 | ## Install instructions 10 | 1. Close Unity and open the `Packages/manifest.json` file 11 | 2. Add `"com.harryrose.assetdependencygraph": "https://github.com/Unity-Harry/Unity-AssetDependencyGraph.git",` to the `dependencies` section 12 | 13 | ## Usage 14 | 15 | The Asset Dependency Graph Window can be opened via the `Window > Analysis >Asset Dependency Graph` file menu 16 | 17 | ![](Images~/Usage.png?raw=true) 18 | 19 | Once the window is open: 20 | 1. Select the root asset you want to inspect in the Project window 21 | 2. Click the `Explore Asset` button in the graph window 22 | 23 | Any questions? Ask [@peanutbuffer](https://twitter.com/PeanutBuffer) 24 | 25 | ## Tested against 26 | 2019.2, 2019.1, 2018.4, 2018.3 27 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0df6ada5e93fc6844907ed3412ce6b74 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.harryrose.assetdependencygraph", 3 | "displayName": "Asset Dependency Graph", 4 | "version": "1.0.0", 5 | "unity": "2018.3", 6 | "description": "The Asset Dependency Graph tool allows you to visualize an asset’s dependency chain", 7 | "type" : "tool", 8 | "author": { 9 | "name": "Harry Rose", 10 | "url": "https://www.twitter.com/peanutbuffer" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0725540f2134fad41bacbb4454bd684e 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------