├── .gitignore ├── Documents~ ├── Screenshots.md └── imgs │ ├── icon_PlayableGraphMonitor.png │ ├── img_sample_circular_references.png │ ├── img_sample_large_graph.png │ ├── img_sample_multi_playable_outputs.png │ ├── img_sample_playablegraph_monitor.png │ ├── img_sample_playablegraph_monitor_old.png │ └── img_sample_playout_with_multi_outputs.png ├── Editor.meta ├── Editor ├── GBG.PlayableGraphMonitor.Editor.asmdef ├── GBG.PlayableGraphMonitor.Editor.asmdef.meta ├── Scripts.meta └── Scripts │ ├── Element.meta │ ├── Element │ ├── EditorGUILayoutHelper.cs │ ├── EditorGUILayoutHelper.cs.meta │ ├── SearchablePopupField.cs │ ├── SearchablePopupField.cs.meta │ ├── SearchablePopupWindowContent.cs │ ├── SearchablePopupWindowContent.cs.meta │ ├── ToolbarDropdownToggle.cs │ └── ToolbarDropdownToggle.cs.meta │ ├── GraphView.meta │ ├── GraphView │ ├── NoRootPlayableException.cs │ ├── NoRootPlayableException.cs.meta │ ├── PlayableGraphView.cs │ ├── PlayableGraphView.cs.meta │ ├── PlayableOutputGroup.cs │ └── PlayableOutputGroup.cs.meta │ ├── Node.meta │ ├── Node │ ├── AnimationClipPlayableNode.cs │ ├── AnimationClipPlayableNode.cs.meta │ ├── AnimationLayerMixerPlayableNode.cs │ ├── AnimationLayerMixerPlayableNode.cs.meta │ ├── AnimationPlayableOutputNode.cs │ ├── AnimationPlayableOutputNode.cs.meta │ ├── AnimationScriptPlayableNode.cs │ ├── AnimationScriptPlayableNode.cs.meta │ ├── AudioClipPlayableNode.cs │ ├── AudioClipPlayableNode.cs.meta │ ├── AudioPlayableOutputNode.cs │ ├── AudioPlayableOutputNode.cs.meta │ ├── GraphViewNode.cs │ ├── GraphViewNode.cs.meta │ ├── PlayableNode.cs │ ├── PlayableNode.cs.meta │ ├── PlayableOutputNode.cs │ ├── PlayableOutputNode.cs.meta │ ├── TexturePlayableOutputNode.cs │ └── TexturePlayableOutputNode.cs.meta │ ├── Pool.meta │ ├── Pool │ ├── EdgePool.cs │ ├── EdgePool.cs.meta │ ├── IPlayableNodePool.cs │ ├── IPlayableNodePool.cs.meta │ ├── IPlayableOutputNodePool.cs │ ├── IPlayableOutputNodePool.cs.meta │ ├── PlayableNodePool.cs │ ├── PlayableNodePool.cs.meta │ ├── PlayableNodePoolFactory.cs │ ├── PlayableNodePoolFactory.cs.meta │ ├── PlayableOutputNodePool.cs │ ├── PlayableOutputNodePool.cs.meta │ ├── PlayableOutputNodePoolFactory.cs │ └── PlayableOutputNodePoolFactory.cs.meta │ ├── Utility.meta │ ├── Utility │ ├── GraphTool.cs │ └── GraphTool.cs.meta │ ├── Window.meta │ └── Window │ ├── PlayableGraphMonitorWindow.cs │ ├── PlayableGraphMonitorWindow.cs.meta │ ├── PlayableGraphMonitorWindow_GraphView.cs │ ├── PlayableGraphMonitorWindow_GraphView.cs.meta │ ├── PlayableGraphMonitorWindow_Sidebar.cs │ ├── PlayableGraphMonitorWindow_Sidebar.cs.meta │ ├── PlayableGraphMonitorWindow_Toolbar.cs │ └── PlayableGraphMonitorWindow_Toolbar.cs.meta ├── LICENSE ├── LICENSE.meta ├── README.md ├── README.md.meta ├── README_CN.md ├── README_CN.md.meta ├── Tests.meta ├── Tests ├── Runtime.meta └── Runtime │ ├── AC_Test.controller │ ├── AC_Test.controller.meta │ ├── Anim_Empty_0.anim │ ├── Anim_Empty_0.anim.meta │ ├── Anim_Empty_1.anim │ ├── Anim_Empty_1.anim.meta │ ├── Anim_Empty_2.anim │ ├── Anim_Empty_2.anim.meta │ ├── AnimatorController.unity │ ├── AnimatorController.unity.meta │ ├── CircularReference.cs │ ├── CircularReference.cs.meta │ ├── CircularReference.unity │ ├── CircularReference.unity.meta │ ├── GBG.PlayableGraphMonitor.Tests.asmdef │ ├── GBG.PlayableGraphMonitor.Tests.asmdef.meta │ ├── LargeAnimationGraph.cs │ ├── LargeAnimationGraph.cs.meta │ ├── LargeAnimationGraph.unity │ ├── LargeAnimationGraph.unity.meta │ ├── MultiPlayableOutput.cs │ ├── MultiPlayableOutput.cs.meta │ ├── MultiPlayableOutput.playable │ ├── MultiPlayableOutput.playable.meta │ ├── MultiPlayableOutput.unity │ ├── MultiPlayableOutput.unity.meta │ ├── PlayableWithMultiOutputs.cs │ ├── PlayableWithMultiOutputs.cs.meta │ ├── PlayableWithMultiOutputs.unity │ ├── PlayableWithMultiOutputs.unity.meta │ ├── RootPlayables.cs │ ├── RootPlayables.cs.meta │ ├── RootPlayables.unity │ └── RootPlayables.unity.meta ├── package.json └── package.json.meta /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | -------------------------------------------------------------------------------- /Documents~/Screenshots.md: -------------------------------------------------------------------------------- 1 | Screenshots 2 | === 3 | 4 | ![](imgs/img_sample_large_graph.png) 5 | 6 | ![](imgs/img_sample_multi_playable_outputs.png) 7 | 8 | ![](imgs/img_sample_playout_with_multi_outputs.png) 9 | 10 | ![](imgs/img_sample_circular_references.png) 11 | -------------------------------------------------------------------------------- /Documents~/imgs/icon_PlayableGraphMonitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolarianZ/UnityPlayableGraphMonitorTool/79a77b0c18ac605ad3dbf9b7b72e27696beafb75/Documents~/imgs/icon_PlayableGraphMonitor.png -------------------------------------------------------------------------------- /Documents~/imgs/img_sample_circular_references.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolarianZ/UnityPlayableGraphMonitorTool/79a77b0c18ac605ad3dbf9b7b72e27696beafb75/Documents~/imgs/img_sample_circular_references.png -------------------------------------------------------------------------------- /Documents~/imgs/img_sample_large_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolarianZ/UnityPlayableGraphMonitorTool/79a77b0c18ac605ad3dbf9b7b72e27696beafb75/Documents~/imgs/img_sample_large_graph.png -------------------------------------------------------------------------------- /Documents~/imgs/img_sample_multi_playable_outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolarianZ/UnityPlayableGraphMonitorTool/79a77b0c18ac605ad3dbf9b7b72e27696beafb75/Documents~/imgs/img_sample_multi_playable_outputs.png -------------------------------------------------------------------------------- /Documents~/imgs/img_sample_playablegraph_monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolarianZ/UnityPlayableGraphMonitorTool/79a77b0c18ac605ad3dbf9b7b72e27696beafb75/Documents~/imgs/img_sample_playablegraph_monitor.png -------------------------------------------------------------------------------- /Documents~/imgs/img_sample_playablegraph_monitor_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolarianZ/UnityPlayableGraphMonitorTool/79a77b0c18ac605ad3dbf9b7b72e27696beafb75/Documents~/imgs/img_sample_playablegraph_monitor_old.png -------------------------------------------------------------------------------- /Documents~/imgs/img_sample_playout_with_multi_outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolarianZ/UnityPlayableGraphMonitorTool/79a77b0c18ac605ad3dbf9b7b72e27696beafb75/Documents~/imgs/img_sample_playout_with_multi_outputs.png -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4f0018ca0524e624288ce5bbafa5b654 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/GBG.PlayableGraphMonitor.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GBG.PlayableGraphMonitor.Editor", 3 | "rootNamespace": "GBG.PlayableGraphMonitor.Editor", 4 | "references": [], 5 | "includePlatforms": [ 6 | "Editor" 7 | ], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": false, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [], 14 | "versionDefines": [], 15 | "noEngineReferences": false 16 | } -------------------------------------------------------------------------------- /Editor/GBG.PlayableGraphMonitor.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 95eb2fe3966c3484ba67b229d5d62820 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0d75ebe11e8d97f4581030777802b403 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Scripts/Element.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d7e9d742b236d154fa0d5d876d1d4401 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Scripts/Element/EditorGUILayoutHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using UnityEditor; 4 | using UnityEngine; 5 | using UnityEngine.Assertions; 6 | 7 | namespace GBG.PlayableGraphMonitor.Editor 8 | { 9 | public static class EditorGUILayoutHelper 10 | { 11 | #region Reflection 12 | 13 | private static Func _toolbarSearchFieldCache; 14 | 15 | 16 | public static string ToolbarSearchField(string searchText) 17 | { 18 | // string EditorGUILayout.ToolbarSearchField(string); 19 | if (_toolbarSearchFieldCache == null) 20 | { 21 | MethodInfo toolbarSearchFieldMethod = typeof(EditorGUILayout).GetMethod("ToolbarSearchField", BindingFlags.Static | BindingFlags.NonPublic, 22 | null, new Type[] { typeof(string), typeof(GUILayoutOption[]) }, null); 23 | Assert.IsNotNull(toolbarSearchFieldMethod); 24 | _toolbarSearchFieldCache = (Func)Delegate.CreateDelegate(typeof(Func), toolbarSearchFieldMethod); 25 | } 26 | 27 | searchText = _toolbarSearchFieldCache(searchText, null); 28 | return searchText; 29 | } 30 | 31 | #endregion 32 | } 33 | } -------------------------------------------------------------------------------- /Editor/Scripts/Element/EditorGUILayoutHelper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 291d1fa2911e44d2bb9fd25ded5f5f51 3 | timeCreated: 1746099533 -------------------------------------------------------------------------------- /Editor/Scripts/Element/SearchablePopupField.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using UnityEditor; 5 | using UnityEditor.UIElements; 6 | using UnityEditorInternal; 7 | using UnityEngine; 8 | using UnityEngine.Assertions; 9 | using UnityEngine.UIElements; 10 | using PopupWindow = UnityEditor.PopupWindow; 11 | 12 | namespace GBG.PlayableGraphMonitor.Editor 13 | { 14 | public class SearchablePopupField : PopupField 15 | { 16 | #region ctor 17 | 18 | public SearchablePopupField(string label = null) : base(label) 19 | { 20 | } 21 | 22 | public SearchablePopupField(string label, List choices, T defaultValue, 23 | Func formatSelectedValueCallback = null, 24 | Func formatListItemCallback = null) 25 | : base(label, choices, defaultValue, formatSelectedValueCallback, formatListItemCallback) 26 | { 27 | } 28 | 29 | public SearchablePopupField(List choices, T defaultValue, 30 | Func formatSelectedValueCallback = null, 31 | Func formatListItemCallback = null) 32 | : base(choices, defaultValue, formatSelectedValueCallback, formatListItemCallback) 33 | { 34 | } 35 | 36 | public SearchablePopupField(List choices, int defaultIndex, 37 | Func formatSelectedValueCallback = null, 38 | Func formatListItemCallback = null) 39 | : base(choices, defaultIndex, formatSelectedValueCallback, formatListItemCallback) 40 | { 41 | } 42 | 43 | #endregion 44 | 45 | 46 | #region Reflection 47 | 48 | private List _choicesCache; 49 | private VisualElement _visualInputCache; 50 | 51 | 52 | private List GetChoices() 53 | { 54 | #if UNITY_2020_3_OR_NEWER 55 | return choices; 56 | #else 57 | if (_choicesCache == null) 58 | { 59 | PropertyInfo choicesProp = typeof(BasePopupField).GetProperty("choices", BindingFlags.Instance | BindingFlags.NonPublic); 60 | Assert.IsNotNull(choicesProp); 61 | _choicesCache = (List)choicesProp.GetValue(this); 62 | } 63 | 64 | return _choicesCache; 65 | #endif 66 | } 67 | 68 | private VisualElement GetVisualInput() 69 | { 70 | if (_visualInputCache == null) 71 | { 72 | PropertyInfo visualInputProp = typeof(BaseField).GetProperty("visualInput", BindingFlags.Instance | BindingFlags.NonPublic); 73 | Assert.IsNotNull(visualInputProp); 74 | _visualInputCache = (VisualElement)visualInputProp.GetValue(this); 75 | } 76 | 77 | return _visualInputCache; 78 | } 79 | 80 | #endregion 81 | 82 | 83 | #region Block the default GenericMenu 84 | 85 | #if !UNITY_2021_1_OR_NEWER 86 | protected override void ExecuteDefaultActionAtTarget(EventBase evt) 87 | { 88 | } 89 | #endif 90 | 91 | #if UNITY_2023_1_OR_NEWER 92 | [Obsolete] 93 | protected override void ExecuteDefaultAction(EventBase evt) 94 | { 95 | if (TryHandleEventInternal(evt)) 96 | return; 97 | 98 | base.ExecuteDefaultAction(evt); 99 | } 100 | #endif 101 | 102 | #if !UNITY_2023_1_OR_NEWER 103 | #if UNITY_2022_1_OR_NEWER 104 | [Obsolete] 105 | #endif 106 | public override void HandleEvent(EventBase evt) 107 | { 108 | if (TryHandleEventInternal(evt)) 109 | return; 110 | 111 | base.HandleEvent(evt); 112 | } 113 | #endif 114 | 115 | private bool TryHandleEventInternal(EventBase evt) 116 | { 117 | if (evt is PointerDownEvent pointerDownEvent) 118 | { 119 | if (pointerDownEvent.button == 0 && GetVisualInput().ContainsPoint(GetVisualInput().WorldToLocal(pointerDownEvent.originalMousePosition))) 120 | { 121 | pointerDownEvent.StopImmediatePropagation(); 122 | Rect popupRect = GetVisualInput().worldBound; 123 | PopupWindow.Show(popupRect, new Content(this)); 124 | return true; 125 | } 126 | } 127 | 128 | return false; 129 | } 130 | 131 | #endregion 132 | 133 | 134 | class Content : PopupWindowContent 135 | { 136 | private readonly SearchablePopupField _popup; 137 | private string _searchContent; 138 | private Vector2 _scrollPosition; 139 | private List _filteredChoices; 140 | private ReorderableList _list; 141 | 142 | 143 | public Content(SearchablePopupField popup) 144 | { 145 | _popup = popup; 146 | } 147 | 148 | public override void OnOpen() 149 | { 150 | base.OnOpen(); 151 | 152 | _filteredChoices = new List(_popup.GetChoices()); 153 | _list = new ReorderableList(_filteredChoices, typeof(T)) 154 | { 155 | index = _popup.index, 156 | displayAdd = false, 157 | displayRemove = false, 158 | headerHeight = 0, 159 | footerHeight = 0, 160 | draggable = false, 161 | onMouseUpCallback = OnMouseUp, 162 | drawElementCallback = DrawElement, 163 | drawElementBackgroundCallback = DrawElementBackground, 164 | }; 165 | } 166 | 167 | public override void OnGUI(Rect rect) 168 | { 169 | const string SEARCH_CONTROL = "SearchablePopupField.PopupWindowContent.ToolbarSearchField"; 170 | 171 | EditorGUI.BeginChangeCheck(); 172 | { 173 | GUI.SetNextControlName(SEARCH_CONTROL); 174 | _searchContent = EditorGUILayoutHelper.ToolbarSearchField(_searchContent); 175 | EditorGUI.FocusTextInControl(SEARCH_CONTROL); 176 | } 177 | if (EditorGUI.EndChangeCheck()) 178 | { 179 | _filteredChoices.Clear(); 180 | for (int i = 0; i < _popup.GetChoices().Count; i++) 181 | { 182 | string elemDisplayName = GetElementDisplayName(_popup.GetChoices(), i, _popup.index == i); 183 | if (elemDisplayName.IndexOf(_searchContent, StringComparison.OrdinalIgnoreCase) >= 0) 184 | _filteredChoices.Add(_popup.GetChoices()[i]); 185 | } 186 | 187 | _list.list = _filteredChoices; 188 | } 189 | 190 | _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); 191 | _list.DoLayoutList(); 192 | EditorGUILayout.EndScrollView(); 193 | 194 | editorWindow.Repaint(); 195 | } 196 | 197 | public override Vector2 GetWindowSize() 198 | { 199 | GUIContent tempLabelContent = new GUIContent(); 200 | float maxWidth = 0; 201 | foreach (T item in _popup.GetChoices()) 202 | { 203 | string label = item?.GetHashCode() == _popup.value?.GetHashCode() 204 | ? _popup.formatSelectedValueCallback?.Invoke(item) ?? item?.ToString() ?? string.Empty 205 | : _popup.formatListItemCallback?.Invoke(item) ?? item?.ToString() ?? string.Empty; 206 | tempLabelContent.text = label; 207 | float width = EditorStyles.toolbarPopup.CalcSize(tempLabelContent).x; 208 | if (width > maxWidth) 209 | maxWidth = width; 210 | } 211 | 212 | Vector2 size = new Vector2 213 | { 214 | x = Mathf.Max(300, _popup.GetVisualInput().resolvedStyle.width, maxWidth), 215 | y = Mathf.Min(400, Mathf.Max(_list.elementHeight * _list.count + 36, 48)), 216 | }; 217 | 218 | return size; 219 | } 220 | 221 | 222 | private string GetElementDisplayName(IList elements, int index, bool isActive) 223 | { 224 | T item = elements[index]; 225 | string text; 226 | if (isActive) 227 | text = _popup.formatSelectedValueCallback?.Invoke(item) ?? item.ToString(); 228 | else 229 | text = _popup.formatListItemCallback?.Invoke(item) ?? item.ToString(); 230 | 231 | return text; 232 | } 233 | 234 | private void DrawElementBackground(Rect rect, int index, bool isActive, bool isFocused) 235 | { 236 | if (isActive) 237 | { 238 | EditorGUI.DrawRect(rect, new Color(0.24f, 0.48f, 0.90f, 0.5f)); 239 | } 240 | else if (isFocused) 241 | { 242 | EditorGUI.DrawRect(rect, new Color(0.24f, 0.48f, 0.90f, 0.2f)); 243 | } 244 | else if (rect.Contains(Event.current.mousePosition)) 245 | { 246 | EditorGUI.DrawRect(rect, new Color(0.24f, 0.48f, 0.90f, 0.1f)); 247 | } 248 | } 249 | 250 | private void DrawElement(Rect rect, int index, bool isActive, bool isFocused) 251 | { 252 | string text = GetElementDisplayName(_filteredChoices, index, isActive); 253 | GUI.Label(rect, text); 254 | } 255 | 256 | private void OnMouseUp(ReorderableList list) 257 | { 258 | _popup.value = _filteredChoices[list.index]; 259 | editorWindow.Close(); 260 | } 261 | } 262 | } 263 | } -------------------------------------------------------------------------------- /Editor/Scripts/Element/SearchablePopupField.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b0920fa025bf4feb82f259cb13268289 3 | timeCreated: 1746077469 -------------------------------------------------------------------------------- /Editor/Scripts/Element/SearchablePopupWindowContent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEditorInternal; 5 | using UnityEngine; 6 | using PopupWindow = UnityEditor.PopupWindow; 7 | 8 | namespace GBG.PlayableGraphMonitor.Editor 9 | { 10 | public class SearchablePopupWindowContent : PopupWindowContent 11 | { 12 | public static void Show(Rect activatorRect, GetChoices choicesProvider, Action itemSelected, 13 | Func formatListItemCallback = null, Func formatSelectedValueCallback = null) 14 | { 15 | PopupWindow.Show(activatorRect, new SearchablePopupWindowContent(choicesProvider, itemSelected, 16 | formatListItemCallback, formatSelectedValueCallback)); 17 | } 18 | 19 | 20 | public delegate void GetChoices(out IList choices, out int selectionIndex); 21 | 22 | private readonly GetChoices _choicesProvider; 23 | private readonly Action _itemSelected; 24 | private readonly Func _formatListItemCallback; 25 | private readonly Func _formatSelectedValueCallback; 26 | 27 | private string _searchContent; 28 | private Vector2 _scrollPosition; 29 | private List _filteredChoices; 30 | private ReorderableList _list; 31 | 32 | public Vector2 minSize { get; set; } = new Vector2(300, 200); 33 | public Vector2 maxSize { get; set; } = new Vector2(900, 600); 34 | 35 | 36 | public SearchablePopupWindowContent(GetChoices choicesProvider, Action itemSelected, 37 | Func formatListItemCallback = null, Func formatSelectedValueCallback = null) 38 | { 39 | _choicesProvider = choicesProvider ?? throw new ArgumentNullException(nameof(choicesProvider)); 40 | _itemSelected = itemSelected; 41 | _formatListItemCallback = formatListItemCallback; 42 | _formatSelectedValueCallback = formatSelectedValueCallback ?? formatListItemCallback; 43 | } 44 | 45 | public override void OnOpen() 46 | { 47 | base.OnOpen(); 48 | 49 | IList allChoices = null; 50 | int currentSelection = -1; 51 | _choicesProvider?.Invoke(out allChoices, out currentSelection); 52 | 53 | _filteredChoices = new List(allChoices ?? Array.Empty()); 54 | _list = new ReorderableList(_filteredChoices, typeof(T)) 55 | { 56 | index = currentSelection, 57 | displayAdd = false, 58 | displayRemove = false, 59 | headerHeight = 0, 60 | footerHeight = 0, 61 | draggable = false, 62 | onMouseUpCallback = OnMouseUp, 63 | drawElementCallback = DrawElement, 64 | drawElementBackgroundCallback = DrawElementBackground, 65 | }; 66 | } 67 | 68 | public override void OnGUI(Rect rect) 69 | { 70 | const string SEARCH_CONTROL = "SearchablePopupWindowContent.ToolbarSearchField"; 71 | 72 | EditorGUI.BeginChangeCheck(); 73 | { 74 | GUI.SetNextControlName(SEARCH_CONTROL); 75 | _searchContent = EditorGUILayoutHelper.ToolbarSearchField(_searchContent); 76 | EditorGUI.FocusTextInControl(SEARCH_CONTROL); 77 | } 78 | if (EditorGUI.EndChangeCheck()) 79 | { 80 | IList allChoices = null; 81 | int currentSelection = -1; 82 | _choicesProvider?.Invoke(out allChoices, out currentSelection); 83 | allChoices = allChoices ?? Array.Empty(); 84 | 85 | _filteredChoices.Clear(); 86 | for (int i = 0; i < allChoices.Count; i++) 87 | { 88 | string elemDisplayName = GetElementDisplayName(allChoices, i, currentSelection == i); 89 | if (elemDisplayName.IndexOf(_searchContent, StringComparison.OrdinalIgnoreCase) >= 0) 90 | _filteredChoices.Add(allChoices[i]); 91 | } 92 | 93 | _list.list = _filteredChoices; 94 | } 95 | 96 | _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); 97 | _list.DoLayoutList(); 98 | EditorGUILayout.EndScrollView(); 99 | 100 | editorWindow.Repaint(); 101 | } 102 | 103 | public override Vector2 GetWindowSize() 104 | { 105 | IList allChoices = null; 106 | int currentSelection = -1; 107 | _choicesProvider?.Invoke(out allChoices, out currentSelection); 108 | allChoices = allChoices ?? Array.Empty(); 109 | T currentValue = currentSelection == -1 ? default : allChoices[currentSelection]; 110 | 111 | GUIContent tempLabelContent = new GUIContent(); 112 | float maxItemWidth = 0; 113 | foreach (T item in allChoices) 114 | { 115 | string label = item?.GetHashCode() == currentValue?.GetHashCode() 116 | ? _formatSelectedValueCallback?.Invoke(item) ?? item?.ToString() ?? string.Empty 117 | : _formatListItemCallback?.Invoke(item) ?? item?.ToString() ?? string.Empty; 118 | tempLabelContent.text = label; 119 | float width = EditorStyles.toolbarPopup.CalcSize(tempLabelContent).x + 12; 120 | if (width > maxItemWidth) 121 | maxItemWidth = width; 122 | } 123 | 124 | float listHeight = _list.elementHeight * _list.count + 36; 125 | Vector2 size = new Vector2 126 | { 127 | x = Mathf.Clamp(maxItemWidth, minSize.x, maxSize.x), 128 | y = Mathf.Clamp(listHeight, minSize.y, maxSize.y), 129 | }; 130 | 131 | return size; 132 | } 133 | 134 | 135 | private string GetElementDisplayName(IList elements, int index, bool isActive) 136 | { 137 | T item = elements[index]; 138 | string text; 139 | if (isActive) 140 | text = _formatSelectedValueCallback?.Invoke(item) ?? item.ToString(); 141 | else 142 | text = _formatListItemCallback?.Invoke(item) ?? item.ToString(); 143 | 144 | return text; 145 | } 146 | 147 | private void DrawElementBackground(Rect rect, int index, bool isActive, bool isFocused) 148 | { 149 | if (isActive) 150 | { 151 | EditorGUI.DrawRect(rect, new Color(0.24f, 0.48f, 0.90f, 0.5f)); 152 | } 153 | else if (isFocused) 154 | { 155 | EditorGUI.DrawRect(rect, new Color(0.24f, 0.48f, 0.90f, 0.2f)); 156 | } 157 | else if (rect.Contains(Event.current.mousePosition)) 158 | { 159 | EditorGUI.DrawRect(rect, new Color(0.24f, 0.48f, 0.90f, 0.1f)); 160 | } 161 | } 162 | 163 | private void DrawElement(Rect rect, int index, bool isActive, bool isFocused) 164 | { 165 | string text = GetElementDisplayName(_filteredChoices, index, isActive); 166 | GUI.Label(rect, text); 167 | } 168 | 169 | private void OnMouseUp(ReorderableList list) 170 | { 171 | T newSelection = _filteredChoices[list.index]; 172 | editorWindow.Close(); 173 | _itemSelected?.Invoke(newSelection); 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /Editor/Scripts/Element/SearchablePopupWindowContent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 92049103cf764882b4853c05d2910399 3 | timeCreated: 1746094614 -------------------------------------------------------------------------------- /Editor/Scripts/Element/ToolbarDropdownToggle.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor.UIElements; 2 | using UnityEngine.UIElements; 3 | 4 | namespace GBG.PlayableGraphMonitor.Editor 5 | { 6 | public class ToolbarDropdownToggle : VisualElement, INotifyValueChanged 7 | { 8 | private readonly ToolbarToggle _toggle; 9 | private readonly ToolbarMenu _menu; 10 | 11 | public string text 12 | { 13 | get => _toggle.text; 14 | set => _toggle.text = value; 15 | } 16 | public string label 17 | { 18 | get => _toggle.label; 19 | set => _toggle.label = value; 20 | } 21 | public Label labelElement => _toggle.labelElement; 22 | public new string tooltip 23 | { 24 | get => _toggle.tooltip; 25 | set => _toggle.tooltip = value; 26 | } 27 | public bool value 28 | { 29 | get => _toggle.value; 30 | set => _toggle.value = value; 31 | } 32 | public string menuTooltip 33 | { 34 | get => _menu.tooltip; 35 | set => _menu.tooltip = value; 36 | } 37 | public DropdownMenu menu => _menu.menu; 38 | public ToolbarMenu.Variant menuVariant 39 | { 40 | get => _menu.variant; 41 | set => _menu.variant = value; 42 | } 43 | 44 | 45 | public ToolbarDropdownToggle() 46 | { 47 | style.flexDirection = FlexDirection.Row; 48 | style.flexShrink = 0; 49 | 50 | _toggle = new ToolbarToggle(); 51 | _toggle.style.borderRightWidth = 0; 52 | _toggle.style.minWidth = 0; 53 | Add(_toggle); 54 | 55 | _menu = new ToolbarMenu(); 56 | _menu.style.borderLeftWidth = 0; 57 | Add(_menu); 58 | } 59 | 60 | public void SetValueWithoutNotify(bool newValue) 61 | { 62 | _toggle.SetValueWithoutNotify(newValue); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Editor/Scripts/Element/ToolbarDropdownToggle.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 71350c6849af6c845bf8031854ca8e71 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Scripts/GraphView.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 123882fe411c40db84c41edccba0ab82 3 | timeCreated: 1660131581 -------------------------------------------------------------------------------- /Editor/Scripts/GraphView/NoRootPlayableException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GBG.PlayableGraphMonitor.Editor.GraphView 4 | { 5 | /// 6 | /// All Playable has a parent Playable, that means there must be at least one cycle in the PlayableGraph. 7 | /// 8 | public class NoRootPlayableException : Exception 9 | { 10 | public NoRootPlayableException() 11 | { 12 | } 13 | 14 | public NoRootPlayableException(string message) : base(message) 15 | { 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Editor/Scripts/GraphView/NoRootPlayableException.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5c7d51a1ad044bff80854a7ea06ef9de 3 | timeCreated: 1677579087 -------------------------------------------------------------------------------- /Editor/Scripts/GraphView/PlayableGraphView.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0e8f961c93814b8db9da06354becd264 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Scripts/GraphView/PlayableOutputGroup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using GBG.PlayableGraphMonitor.Editor.Node; 4 | using UnityEngine; 5 | using UnityEngine.Playables; 6 | 7 | 8 | namespace GBG.PlayableGraphMonitor.Editor.GraphView 9 | { 10 | public class PlayableOutputGroup 11 | { 12 | public PlayableNode SourceNode { get; set; } 13 | public List OutputNodes { get; } = new List(); 14 | public List ChildOutputGroups { get; } = new List(); 15 | 16 | 17 | public float GetOutputsVerticalSize(IReadOnlyDictionary outputGroups) 18 | { 19 | var verticalSize = 0f; 20 | foreach (var outputNode in OutputNodes) 21 | { 22 | verticalSize += outputNode.GetNodeSize().y + GraphViewNode.VERTICAL_SPACE; 23 | } 24 | 25 | foreach (var childOutputGroup in ChildOutputGroups) 26 | { 27 | var childGroup = outputGroups[childOutputGroup.SourceNode.Playable.GetHandle()]; 28 | verticalSize += childGroup.GetOutputsVerticalSize(outputGroups); 29 | } 30 | 31 | return verticalSize; 32 | } 33 | 34 | public void LayoutOutputNodes(Vector2 outputAnchor, out float bottom) 35 | { 36 | var outputCount = OutputNodes.Count; 37 | var mid = outputCount / 2; 38 | var top = outputAnchor.y; 39 | 40 | // Direct outputs 41 | if ((outputCount & 1) == 1) // Odd 42 | { 43 | OutputNodes[mid].SetPosition(new Rect(outputAnchor, Vector2.zero)); 44 | bottom = outputAnchor.y + OutputNodes[mid].GetNodeSize().y; 45 | 46 | for (int i = 1; i <= mid; i++) 47 | { 48 | // Upward 49 | var outputNodeA = OutputNodes[mid - i]; 50 | var outputPositionA = new Vector2( 51 | outputAnchor.x, 52 | outputAnchor.y - (GraphViewNode.VERTICAL_SPACE + outputNodeA.GetNodeSize().y) * i 53 | ); 54 | outputNodeA.SetPosition(new Rect(outputPositionA, Vector2.zero)); 55 | top = outputPositionA.y; 56 | 57 | // Downward 58 | var outputNodeB = OutputNodes[mid + i]; 59 | var outputPositionB = new Vector2( 60 | outputAnchor.x, 61 | outputAnchor.y + (GraphViewNode.VERTICAL_SPACE + outputNodeB.GetNodeSize().y) * i 62 | ); 63 | outputNodeB.SetPosition(new Rect(outputPositionB, Vector2.zero)); 64 | bottom = outputPositionB.y + outputNodeB.GetNodeSize().y; 65 | } 66 | } 67 | else // Even 68 | { 69 | bottom = outputAnchor.y; 70 | 71 | for (int i = 0; i < mid; i++) 72 | { 73 | // Upward 74 | var outputNodeA = OutputNodes[mid - i - 1]; 75 | var outputPositionA = new Vector2( 76 | outputAnchor.x, 77 | outputAnchor.y - GraphViewNode.VERTICAL_SPACE * (i + 1.5f) - outputNodeA.GetNodeSize().y * i 78 | ); 79 | outputNodeA.SetPosition(new Rect(outputPositionA, Vector2.zero)); 80 | top = outputPositionA.y; 81 | 82 | // Downward 83 | var outputNodeB = OutputNodes[mid + i]; 84 | var outputPositionB = new Vector2( 85 | outputAnchor.x, 86 | outputAnchor.y + GraphViewNode.VERTICAL_SPACE * (i + 1.5f) + outputNodeB.GetNodeSize().y * i 87 | ); 88 | outputNodeB.SetPosition(new Rect(outputPositionB, Vector2.zero)); 89 | bottom = outputPositionB.y + outputNodeB.GetNodeSize().y; 90 | } 91 | } 92 | 93 | // Child outputs 94 | var lastUpperChildIndex = SortAndFindLastUpperChildSourcePlayableNodeIndex(); 95 | // Upper children 96 | for (int i = lastUpperChildIndex; i >= 0; i--) 97 | { 98 | var childOutputGroup = ChildOutputGroups[i]; 99 | for (int j = childOutputGroup.OutputNodes.Count - 1; j >= 0; j--) 100 | { 101 | var outputNode = childOutputGroup.OutputNodes[j]; 102 | var outputPosition = new Vector2( 103 | outputAnchor.x, 104 | top - GraphViewNode.VERTICAL_SPACE - outputNode.GetNodeSize().y 105 | ); 106 | outputNode.SetPosition(new Rect(outputPosition, Vector2.zero)); 107 | top = outputPosition.y; 108 | } 109 | } 110 | 111 | // Lower children 112 | for (int i = lastUpperChildIndex + 1; i < ChildOutputGroups.Count; i++) 113 | { 114 | var childOutputGroup = ChildOutputGroups[i]; 115 | for (int j = 0; j < childOutputGroup.OutputNodes.Count; j++) 116 | { 117 | var outputNode = childOutputGroup.OutputNodes[j]; 118 | var outputPosition = new Vector2( 119 | outputAnchor.x, 120 | bottom + GraphViewNode.VERTICAL_SPACE + outputNode.GetNodeSize().y 121 | ); 122 | outputNode.SetPosition(new Rect(outputPosition, Vector2.zero)); 123 | bottom = outputPosition.y; 124 | } 125 | } 126 | } 127 | 128 | private int SortAndFindLastUpperChildSourcePlayableNodeIndex() 129 | { 130 | if (ChildOutputGroups.Count == 0) 131 | { 132 | return -1; 133 | } 134 | 135 | ChildOutputGroups.Sort(SortOutputGroupBySourceNodePositionAsc); 136 | 137 | if (ChildOutputGroups[0].SourceNode.Position.y >= SourceNode.Position.y) 138 | { 139 | return -1; 140 | } 141 | 142 | if (ChildOutputGroups[ChildOutputGroups.Count - 1].SourceNode.Position.y <= SourceNode.Position.y) 143 | { 144 | return ChildOutputGroups.Count - 1; 145 | } 146 | 147 | for (int i = 0; i < ChildOutputGroups.Count - 1; i++) 148 | { 149 | if (ChildOutputGroups[i].SourceNode.Position.y <= SourceNode.Position.y && 150 | ChildOutputGroups[i + 1].SourceNode.Position.y >= SourceNode.Position.y) 151 | { 152 | return i; 153 | } 154 | } 155 | 156 | throw new Exception("This should not happen!"); 157 | } 158 | 159 | private static int SortOutputGroupBySourceNodePositionAsc(PlayableOutputGroup a, PlayableOutputGroup b) 160 | { 161 | if (a.SourceNode.Position.y < b.SourceNode.Position.y) return -1; 162 | if (a.SourceNode.Position.y > b.SourceNode.Position.y) return 1; 163 | return 0; 164 | } 165 | 166 | 167 | public void Clear() 168 | { 169 | SourceNode = null; 170 | OutputNodes.Clear(); 171 | ChildOutputGroups.Clear(); 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /Editor/Scripts/GraphView/PlayableOutputGroup.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1bb9a19d4b6e441c936cc7efe4998ad2 3 | timeCreated: 1677589406 -------------------------------------------------------------------------------- /Editor/Scripts/Node.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2a2a649fafdba594eb606a877cc3c4fa 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Scripts/Node/AnimationClipPlayableNode.cs: -------------------------------------------------------------------------------- 1 | using GBG.PlayableGraphMonitor.Editor.GraphView; 2 | using GBG.PlayableGraphMonitor.Editor.Utility; 3 | using UnityEditor; 4 | using UnityEditor.UIElements; 5 | using UnityEngine; 6 | using UnityEngine.Animations; 7 | using UnityEngine.Playables; 8 | using UnityEngine.UIElements; 9 | 10 | namespace GBG.PlayableGraphMonitor.Editor.Node 11 | { 12 | public sealed class AnimationClipPlayableNode : PlayableNode 13 | { 14 | private readonly ObjectField _clipField; 15 | 16 | private readonly ProgressBar _progressBar; 17 | 18 | 19 | public AnimationClipPlayableNode() 20 | { 21 | var banner = mainContainer.Q("divider"); 22 | banner.style.height = StyleKeyword.Auto; 23 | 24 | _progressBar = new ProgressBar(); 25 | #if !UNITY_2021_1_OR_NEWER 26 | var progressBarBg = _progressBar.Q(className: "unity-progress-bar__background"); 27 | progressBarBg.style.height = 17; 28 | #endif 29 | banner.Add(_progressBar); 30 | 31 | _clipField = new ObjectField 32 | { 33 | objectType = typeof(Motion), 34 | }; 35 | var clipFieldSelector = _clipField.Q(className: "unity-object-field__selector"); 36 | clipFieldSelector.style.display = DisplayStyle.None; 37 | clipFieldSelector.SetEnabled(false); 38 | banner.Add(_clipField); 39 | } 40 | 41 | protected override void OnUpdate(PlayableGraphViewUpdateContext updateContext, bool playableChanged) 42 | { 43 | base.OnUpdate(updateContext, playableChanged); 44 | 45 | if (!Playable.IsValid()) 46 | { 47 | return; 48 | } 49 | 50 | 51 | var clipPlayable = (AnimationClipPlayable)Playable; 52 | var clip = clipPlayable.GetAnimationClip(); 53 | _clipField.SetValueWithoutNotify(clip); 54 | 55 | // MEMO KEYWORD: CLIP_PROGRESS 56 | if (updateContext.ShowClipProgressBar) 57 | { 58 | _progressBar.style.display = DisplayStyle.Flex; 59 | 60 | // ReSharper disable once TooWideLocalVariableScope 61 | // ReSharper disable once RedundantAssignment 62 | var rawProgress01 = 0.0; // used in UNITY_2021_1_OR_NEWER 63 | double progress01; 64 | if (clip) 65 | { 66 | var time = Playable.GetTime(); 67 | rawProgress01 = time / clip.length; 68 | 69 | if (clip.isLooping) 70 | { 71 | progress01 = GraphTool.Wrap01(rawProgress01); 72 | } 73 | else 74 | { 75 | var speed = Playable.GetSpeed(); 76 | if (speed > 0 && time >= clip.length) 77 | { 78 | progress01 = 1; 79 | } 80 | else if (speed < 0 && time <= 0) 81 | { 82 | progress01 = 0; 83 | } 84 | else 85 | { 86 | progress01 = GraphTool.Wrap01(rawProgress01); 87 | } 88 | } 89 | } 90 | else 91 | { 92 | progress01 = 0; 93 | } 94 | 95 | // Expensive operations 96 | _progressBar.SetValueWithoutNotify((float)progress01 * 100); 97 | 98 | #if UNITY_2021_1_OR_NEWER 99 | // Expensive operations 100 | _progressBar.title = updateContext.ShowClipProgressBarTitle 101 | ? (rawProgress01 * 100).ToString("F2") 102 | : null; 103 | #endif 104 | } 105 | else 106 | { 107 | _progressBar.style.display = DisplayStyle.None; 108 | } 109 | } 110 | 111 | // public override void Release() 112 | // { 113 | // base.Release(); 114 | // // Change the value of the ObjectField is expensive, so we don't clear referenced clip asset to save performance 115 | // // _clipField.SetValueWithoutNotify(null); 116 | // } 117 | 118 | protected override void DrawNodeDescriptionInternal() 119 | { 120 | base.DrawNodeDescriptionInternal(); 121 | 122 | if (!Playable.IsValid()) 123 | { 124 | return; 125 | } 126 | 127 | var animClipPlayable = (AnimationClipPlayable)Playable; 128 | 129 | // IK 130 | GUILayout.Label(LINE); 131 | EditorGUILayout.Toggle("ApplyFootIK:", animClipPlayable.GetApplyFootIK()); 132 | EditorGUILayout.Toggle("ApplyPlayableIK:", animClipPlayable.GetApplyPlayableIK()); 133 | 134 | // Clip 135 | GUILayout.Label(LINE); 136 | var clip = animClipPlayable.GetAnimationClip(); 137 | if (!clip) 138 | { 139 | GUILayout.Label("Clip: None"); 140 | return; 141 | } 142 | 143 | EditorGUILayout.ObjectField("Clip:", clip, typeof(AnimationClip), true); 144 | GUILayout.Label($"Length: {clip.length.ToString("F3")}(s)"); 145 | GUILayout.Label($"Looped: {clip.isLooping}"); 146 | GUILayout.Label($"WrapMode: {clip.wrapMode}"); 147 | GUILayout.Label($"FrameRate: {clip.frameRate.ToString("F3")}"); 148 | GUILayout.Label($"Empty: {clip.empty}"); 149 | GUILayout.Label($"Legacy: {clip.legacy}"); 150 | GUILayout.Label($"HumanMotion: {clip.humanMotion}"); 151 | GUILayout.Label($"HasMotionCurves: {clip.hasMotionCurves}"); 152 | GUILayout.Label($"HasRootCurves: {clip.hasRootCurves}"); 153 | GUILayout.Label($"HasGenericRootTransform: {clip.hasGenericRootTransform}"); 154 | GUILayout.Label($"HasMotionFloatCurves: {clip.hasMotionFloatCurves}"); 155 | GUILayout.Label($"LocalBounds: "); 156 | GUILayout.Label($" Center: {clip.localBounds.center}"); 157 | GUILayout.Label($" Extends: {clip.localBounds.extents}"); 158 | GUILayout.Label($"ApparentSpeed: {clip.apparentSpeed.ToString("F3")}"); // Motion.cs 159 | GUILayout.Label($"AverageSpeed: {clip.averageSpeed}"); 160 | GUILayout.Label($"AverageAngularSpeed: {clip.averageAngularSpeed.ToString("F3")}"); 161 | GUILayout.Label($"AverageDuration: {clip.averageDuration.ToString("F3")}"); 162 | GUILayout.Label($"IsHumanMotion: {clip.isHumanMotion}"); 163 | 164 | // Event 165 | GUILayout.Label(LINE); 166 | var events = clip.events; 167 | GUILayout.Label( 168 | events.Length == 0 169 | ? "No Event" 170 | : (events.Length == 1 ? "1 Event:" : $"{events.Length.ToString()} Events:") 171 | ); 172 | for (int i = 0; i < events.Length; i++) 173 | { 174 | var evt = events[i]; 175 | var evtPosition = evt.time / clip.length * 100; 176 | GUILayout.Label($" #{(i + 1)} {evtPosition:F2}% {evt.functionName}"); 177 | } 178 | } 179 | 180 | public AnimationClip GetAnimationClip() 181 | { 182 | var clipPlayable = (AnimationClipPlayable)Playable; 183 | var clip = clipPlayable.GetAnimationClip(); 184 | return clip; 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /Editor/Scripts/Node/AnimationClipPlayableNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 28bc709421ff719488dbe306faf77716 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Scripts/Node/AnimationLayerMixerPlayableNode.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine.Animations; 3 | using UnityEngine.Playables; 4 | 5 | namespace GBG.PlayableGraphMonitor.Editor.Node 6 | { 7 | public class AnimationLayerMixerPlayableNode : PlayableNode 8 | { 9 | protected override void AppendInputPortDescription() 10 | { 11 | var layerMixer = (AnimationLayerMixerPlayable)Playable; 12 | var inputCount = layerMixer.GetInputCount(); 13 | for (int i = 0; i < inputCount; i++) 14 | { 15 | EditorGUI.BeginChangeCheck(); 16 | var weight = EditorGUILayout.Slider($" #{i} Weight:", Playable.GetInputWeight(i), 0, 1); 17 | if (EditorGUI.EndChangeCheck()) 18 | Playable.SetInputWeight(i, weight); 19 | EditorGUI.BeginChangeCheck(); 20 | var isLayerAdditive = EditorGUILayout.Toggle($" #{i} Additive:", layerMixer.IsLayerAdditive((uint)i)); 21 | if (EditorGUI.EndChangeCheck()) 22 | layerMixer.SetLayerAdditive((uint)i, isLayerAdditive); 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Editor/Scripts/Node/AnimationLayerMixerPlayableNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b61868d3855301042903a9e0e4fce99b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Scripts/Node/AnimationPlayableOutputNode.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEditor.UIElements; 3 | using UnityEngine; 4 | using UnityEngine.Animations; 5 | using UnityEngine.Experimental.Animations; 6 | using UnityEngine.Playables; 7 | using UnityEngine.UIElements; 8 | 9 | namespace GBG.PlayableGraphMonitor.Editor.Node 10 | { 11 | public class AnimationPlayableOutputNode : PlayableOutputNode 12 | { 13 | private readonly ObjectField _targetField; 14 | 15 | 16 | public AnimationPlayableOutputNode() 17 | { 18 | var banner = mainContainer.Q("divider"); 19 | banner.style.height = StyleKeyword.Auto; 20 | 21 | _targetField = new ObjectField 22 | { 23 | objectType = typeof(Animator), 24 | tooltip = "Target", 25 | }; 26 | var targetFieldSelector = _targetField.Q(className: "unity-object-field__selector"); 27 | targetFieldSelector.style.display = DisplayStyle.None; 28 | targetFieldSelector.SetEnabled(false); 29 | banner.Add(_targetField); 30 | } 31 | 32 | protected override void OnUpdate(bool playableOutputChanged) 33 | { 34 | base.OnUpdate(playableOutputChanged); 35 | 36 | if (!PlayableOutput.IsOutputValid()) 37 | { 38 | return; 39 | } 40 | 41 | var animationPlayableOutput = (AnimationPlayableOutput)PlayableOutput; 42 | var target = animationPlayableOutput.GetTarget(); 43 | _targetField.style.display = target ? DisplayStyle.Flex : DisplayStyle.None; 44 | _targetField.SetValueWithoutNotify(target); 45 | } 46 | 47 | protected override void DrawNodeDescriptionInternal() 48 | { 49 | base.DrawNodeDescriptionInternal(); 50 | 51 | if (!PlayableOutput.IsOutputValid()) 52 | { 53 | return; 54 | } 55 | 56 | var animationPlayableOutput = (AnimationPlayableOutput)PlayableOutput; 57 | var target = animationPlayableOutput.GetTarget(); 58 | GUILayout.Label(LINE); 59 | EditorGUILayout.ObjectField("Target:", target, typeof(Animator), true); 60 | EditorGUI.BeginChangeCheck(); 61 | var animationStreamSource = (AnimationStreamSource)EditorGUILayout.EnumPopup("AnimationStreamSource:", animationPlayableOutput.GetAnimationStreamSource()); 62 | if (EditorGUI.EndChangeCheck()) 63 | animationPlayableOutput.SetAnimationStreamSource(animationStreamSource); 64 | EditorGUI.BeginChangeCheck(); 65 | var sortingOrder = (ushort)EditorGUILayout.IntField("SortingOrder:", animationPlayableOutput.GetSortingOrder()); 66 | if (EditorGUI.EndChangeCheck()) 67 | animationPlayableOutput.SetSortingOrder(sortingOrder); 68 | } 69 | 70 | public Animator GetAnimatorTarget() 71 | { 72 | var animationPlayableOutput = (AnimationPlayableOutput)PlayableOutput; 73 | var target = animationPlayableOutput.GetTarget(); 74 | return target; 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Editor/Scripts/Node/AnimationPlayableOutputNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 47661685d6ed4fa8a3043f864261773f 3 | timeCreated: 1678788263 -------------------------------------------------------------------------------- /Editor/Scripts/Node/AnimationScriptPlayableNode.cs: -------------------------------------------------------------------------------- 1 | using GBG.PlayableGraphMonitor.Editor.GraphView; 2 | using System; 3 | using System.Reflection; 4 | using UnityEngine; 5 | using UnityEngine.Animations; 6 | using UnityEngine.Playables; 7 | using UnityEngine.UIElements; 8 | 9 | namespace GBG.PlayableGraphMonitor.Editor.Node 10 | { 11 | public class AnimationScriptPlayableNode : PlayableNode 12 | { 13 | private readonly Label _jobTypeLabel; 14 | 15 | private MethodInfo _getJobTypeMethod; 16 | 17 | private Func _getJobTypeFunc; 18 | 19 | 20 | public AnimationScriptPlayableNode() 21 | { 22 | _jobTypeLabel = new Label 23 | { 24 | style = 25 | { 26 | marginTop = 1, 27 | marginBottom = 1, 28 | marginLeft = 6, 29 | color = Color.white, 30 | fontSize = 12, 31 | unityTextAlign = TextAnchor.MiddleLeft, 32 | unityFontStyleAndWeight = FontStyle.Bold, 33 | } 34 | }; 35 | var banner = mainContainer.Q("divider"); 36 | banner.style.height = StyleKeyword.Auto; 37 | banner.Add(_jobTypeLabel); 38 | } 39 | 40 | 41 | protected override void OnUpdate(PlayableGraphViewUpdateContext updateContext, bool playableChanged) 42 | { 43 | base.OnUpdate(updateContext, playableChanged); 44 | 45 | if (playableChanged) 46 | { 47 | _getJobTypeFunc = null; 48 | _jobTypeLabel.text = GetJobType()?.Name; 49 | } 50 | } 51 | 52 | protected override void AppendPlayableTypeDescription() 53 | { 54 | base.AppendPlayableTypeDescription(); 55 | 56 | // Job 57 | var jobType = GetJobType(); 58 | GUILayout.Label($"Job: {jobType?.Name ?? "?"}"); 59 | } 60 | 61 | protected override void DrawNodeDescriptionInternal() 62 | { 63 | base.DrawNodeDescriptionInternal(); 64 | 65 | if (!Playable.IsValid()) 66 | { 67 | return; 68 | } 69 | 70 | var animScriptPlayable = (AnimationScriptPlayable)Playable; 71 | GUILayout.Label(LINE); 72 | GUILayout.Label($"ProcessInputs: {animScriptPlayable.GetProcessInputs()}"); 73 | } 74 | 75 | public Type GetJobType() 76 | { 77 | if (_getJobTypeFunc != null) 78 | { 79 | return _getJobTypeFunc(); 80 | } 81 | 82 | var playableHandle = Playable.GetHandle(); 83 | if (_getJobTypeMethod == null) 84 | { 85 | _getJobTypeMethod = playableHandle.GetType().GetMethod("GetJobType", 86 | BindingFlags.Instance | BindingFlags.NonPublic); 87 | if (_getJobTypeMethod == null) 88 | { 89 | Debug.LogError("Failed to get method 'PlayableHandle.GetJobType()'."); 90 | return null; 91 | } 92 | } 93 | 94 | _getJobTypeFunc = (Func)_getJobTypeMethod.CreateDelegate(typeof(Func), playableHandle); 95 | 96 | return _getJobTypeFunc(); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /Editor/Scripts/Node/AnimationScriptPlayableNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aa11848552c248d5aaf37370c9c51e57 3 | timeCreated: 1677221982 -------------------------------------------------------------------------------- /Editor/Scripts/Node/AudioClipPlayableNode.cs: -------------------------------------------------------------------------------- 1 | using GBG.PlayableGraphMonitor.Editor.GraphView; 2 | using GBG.PlayableGraphMonitor.Editor.Utility; 3 | using UnityEditor; 4 | using UnityEditor.UIElements; 5 | using UnityEngine; 6 | using UnityEngine.Audio; 7 | using UnityEngine.Playables; 8 | using UnityEngine.UIElements; 9 | 10 | namespace GBG.PlayableGraphMonitor.Editor.Node 11 | { 12 | public sealed class AudioClipPlayableNode : PlayableNode 13 | { 14 | private readonly ObjectField _clipField; 15 | 16 | private readonly ProgressBar _progressBar; 17 | 18 | 19 | public AudioClipPlayableNode() 20 | { 21 | var banner = mainContainer.Q("divider"); 22 | banner.style.height = StyleKeyword.Auto; 23 | 24 | _progressBar = new ProgressBar(); 25 | #if !UNITY_2021_1_OR_NEWER 26 | var progressBarBg = _progressBar.Q(className: "unity-progress-bar__background"); 27 | progressBarBg.style.height = 17; 28 | #endif 29 | banner.Add(_progressBar); 30 | 31 | _clipField = new ObjectField 32 | { 33 | objectType = typeof(AudioClip), 34 | }; 35 | var clipFieldSelector = _clipField.Q(className: "unity-object-field__selector"); 36 | clipFieldSelector.style.display = DisplayStyle.None; 37 | clipFieldSelector.SetEnabled(false); 38 | banner.Add(_clipField); 39 | } 40 | 41 | protected override void OnUpdate(PlayableGraphViewUpdateContext updateContext, bool playableChanged) 42 | { 43 | base.OnUpdate(updateContext, playableChanged); 44 | 45 | if (!Playable.IsValid()) 46 | { 47 | return; 48 | } 49 | 50 | var clipPlayable = (AudioClipPlayable)Playable; 51 | var clip = clipPlayable.GetClip(); 52 | _clipField.SetValueWithoutNotify(clip); 53 | 54 | // MEMO KEYWORD: CLIP_PROGRESS 55 | if (updateContext.ShowClipProgressBar) 56 | { 57 | _progressBar.style.display = DisplayStyle.Flex; 58 | 59 | // ReSharper disable once TooWideLocalVariableScope 60 | // ReSharper disable once RedundantAssignment 61 | var rawProgress01 = 0.0; // used in UNITY_2021_1_OR_NEWER 62 | double progress01; 63 | if (clip) 64 | { 65 | var time = Playable.GetTime(); 66 | rawProgress01 = time / clip.length; 67 | 68 | if (clipPlayable.GetLooped()) 69 | { 70 | progress01 = GraphTool.Wrap01(rawProgress01); 71 | } 72 | else 73 | { 74 | var speed = Playable.GetSpeed(); 75 | if (speed > 0 && time >= clip.length) 76 | { 77 | progress01 = 1; 78 | } 79 | else if (speed < 0 && time <= 0) 80 | { 81 | progress01 = 0; 82 | } 83 | else 84 | { 85 | progress01 = GraphTool.Wrap01(rawProgress01); 86 | } 87 | } 88 | } 89 | else 90 | { 91 | progress01 = 0; 92 | } 93 | 94 | // Expensive operations 95 | _progressBar.SetValueWithoutNotify((float)progress01 * 100); 96 | 97 | #if UNITY_2021_1_OR_NEWER 98 | // Expensive operations 99 | _progressBar.title = updateContext.ShowClipProgressBarTitle 100 | ? (rawProgress01 * 100).ToString("F2") 101 | : null; 102 | #endif 103 | } 104 | else 105 | { 106 | _progressBar.style.display = DisplayStyle.None; 107 | } 108 | } 109 | 110 | // public override void Release() 111 | // { 112 | // base.Release(); 113 | // // Change the value of the ObjectField is expensive, so we don't clear referenced clip asset to save performance 114 | // // _clipField.SetValueWithoutNotify(null); 115 | // } 116 | 117 | 118 | protected override void DrawNodeDescriptionInternal() 119 | { 120 | base.DrawNodeDescriptionInternal(); 121 | 122 | if (!Playable.IsValid()) 123 | { 124 | return; 125 | } 126 | 127 | var clipPlayable = (AudioClipPlayable)Playable; 128 | GUILayout.Label(LINE); 129 | var clip = clipPlayable.GetClip(); 130 | if (!clip) 131 | { 132 | GUILayout.Label("Clip: None"); 133 | return; 134 | } 135 | 136 | EditorGUILayout.ObjectField("Clip:", clip, typeof(AudioClip), true); 137 | GUILayout.Label($"Length: {clip.length:F3}(s)"); 138 | EditorGUILayout.Toggle("Looped:", clipPlayable.GetLooped()); 139 | GUILayout.Label($"Channels: {clip.channels}"); 140 | GUILayout.Label($"Ambisonic: {clip.ambisonic}"); 141 | GUILayout.Label($"Frequency: {clip.frequency}"); 142 | GUILayout.Label($"Samples: {clip.samples}"); 143 | GUILayout.Label($"LoadState: {clip.loadState}"); 144 | GUILayout.Label($"LoadType: {clip.loadType}"); 145 | GUILayout.Label($"LoadInBackground: {clip.loadInBackground}"); 146 | GUILayout.Label($"PreloadAudioData: {clip.preloadAudioData}"); 147 | } 148 | 149 | public AudioClip GetAudioClip() 150 | { 151 | var clipPlayable = (AudioClipPlayable)Playable; 152 | GUILayout.Label(LINE); 153 | var clip = clipPlayable.GetClip(); 154 | return clip; 155 | } 156 | } 157 | } -------------------------------------------------------------------------------- /Editor/Scripts/Node/AudioClipPlayableNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d517f391894849a99d81e84fe6a241b9 3 | timeCreated: 1667907109 -------------------------------------------------------------------------------- /Editor/Scripts/Node/AudioPlayableOutputNode.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEditor.UIElements; 3 | using UnityEngine; 4 | using UnityEngine.Audio; 5 | using UnityEngine.Playables; 6 | using UnityEngine.UIElements; 7 | 8 | namespace GBG.PlayableGraphMonitor.Editor.Node 9 | { 10 | public class AudioPlayableOutputNode : PlayableOutputNode 11 | { 12 | private readonly ObjectField _targetField; 13 | 14 | 15 | public AudioPlayableOutputNode() 16 | { 17 | var banner = mainContainer.Q("divider"); 18 | banner.style.height = StyleKeyword.Auto; 19 | 20 | _targetField = new ObjectField 21 | { 22 | objectType = typeof(AudioSource), 23 | tooltip = "Target", 24 | }; 25 | var targetFieldSelector = _targetField.Q(className: "unity-object-field__selector"); 26 | targetFieldSelector.style.display = DisplayStyle.None; 27 | targetFieldSelector.SetEnabled(false); 28 | banner.Add(_targetField); 29 | } 30 | 31 | protected override void OnUpdate(bool playableOutputChanged) 32 | { 33 | base.OnUpdate(playableOutputChanged); 34 | 35 | if (!PlayableOutput.IsOutputValid()) 36 | { 37 | return; 38 | } 39 | 40 | var audioPlayableOutput = (AudioPlayableOutput)PlayableOutput; 41 | var target = audioPlayableOutput.GetTarget(); 42 | _targetField.style.display = target ? DisplayStyle.Flex : DisplayStyle.None; 43 | _targetField.SetValueWithoutNotify(target); 44 | } 45 | 46 | protected override void DrawNodeDescriptionInternal() 47 | { 48 | base.DrawNodeDescriptionInternal(); 49 | 50 | if (!PlayableOutput.IsOutputValid()) 51 | { 52 | return; 53 | } 54 | 55 | var audioPlayableOutput = (AudioPlayableOutput)PlayableOutput; 56 | var target = audioPlayableOutput.GetTarget(); 57 | var evaluateOnSeek = audioPlayableOutput.GetEvaluateOnSeek(); 58 | GUILayout.Label(LINE); 59 | EditorGUILayout.ObjectField("Target:", target, typeof(AudioSource), true); 60 | EditorGUILayout.Toggle("EvaluateOnSeek:", evaluateOnSeek); 61 | } 62 | 63 | public AudioSource GetAudioSourceTarget() 64 | { 65 | var audioPlayableOutput = (AudioPlayableOutput)PlayableOutput; 66 | var target = audioPlayableOutput.GetTarget(); 67 | return target; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /Editor/Scripts/Node/AudioPlayableOutputNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 99af645e0b1d470c9d32521d3d720591 3 | timeCreated: 1678788595 -------------------------------------------------------------------------------- /Editor/Scripts/Node/GraphViewNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | using UnityEditor; 5 | using UnityEditor.Experimental.GraphView; 6 | using UnityEngine; 7 | using UnityEngine.Assertions; 8 | using UnityEngine.UIElements; 9 | using UNode = UnityEditor.Experimental.GraphView.Node; 10 | 11 | namespace GBG.PlayableGraphMonitor.Editor.Node 12 | { 13 | public abstract class GraphViewNode : UNode 14 | { 15 | protected GraphViewNode() 16 | { 17 | capabilities &= ~Capabilities.Collapsible; 18 | capabilities &= ~Capabilities.Movable; 19 | capabilities &= ~Capabilities.Deletable; 20 | capabilities &= ~Capabilities.Droppable; 21 | capabilities &= ~Capabilities.Renamable; 22 | #if UNITY_2021_1_OR_NEWER 23 | capabilities &= ~Capabilities.Copiable; 24 | #endif 25 | 26 | style.maxWidth = MaxNodeSize.x - 100; 27 | 28 | // Hide collapse button 29 | titleButtonContainer.Clear(); 30 | var titleLabel = titleContainer.Q