浏览代码

Merge pull request #4 from peeweek/feature/call-tree

Feature : Callable Tree Explorer
/main
GitHub 5 年前
当前提交
ce21f373
共有 14 个文件被更改,包括 1067 次插入167 次删除
  1. 5
      Editor/MenuItems.cs
  2. 4
      Editor/PropertyDrawers/CallablePropertyDrawer.cs
  3. 332
      Editor/SelectionHistory/SelectionHistoryWindow.cs
  4. 2
      Editor/WelcomeScreen/WelcomeScreen.cs
  5. 8
      Editor/CallTree.meta
  6. 5
      Icons/Actions/ic-action-generic.png
  7. 110
      Icons/Actions/ic-action-generic.png.meta
  8. 4
      Icons/Events/ic-event-generic.png
  9. 115
      Icons/Events/ic-event-generic.png.meta
  10. 47
      Icons/Misc/ic-callable.png
  11. 115
      Icons/Misc/ic-callable.png.meta
  12. 11
      Editor/CallTree/CallTreeWindow.cs.meta
  13. 476
      Editor/CallTree/CallTreeWindow.cs

5
Editor/MenuItems.cs


{
public static class MenuItems
{
const int kPlayMenuPriority = 160;
const int kMenuPriority = 330;
public const int kWindowMenuPriority = 100;
public const int kPlayMenuPriority = 160;
public const int kMenuPriority = 330;
#region PLAY HERE

4
Editor/PropertyDrawers/CallablePropertyDrawer.cs


{
property.objectReferenceValue = setNextObjectValue;
setNextObjectValue = null;
if(CallTreeWindow.visible)
{
CallTreeWindow.Refresh();
}
}
if(property.objectReferenceValue == null)

332
Editor/SelectionHistory/SelectionHistoryWindow.cs


using System.Reflection;
using UnityEngine;
using UnityEditor;
public class SelectionHistoryWindow : EditorWindow
namespace GameplayIngredients.Editor
[MenuItem("Window/Selection History")]
public static void OpenSelectionHistoryWindow()
public class SelectionHistoryWindow : EditorWindow
EditorWindow.GetWindow<SelectionHistoryWindow>();
}
Vector2 scrollPos = Vector2.zero;
void OnGUI()
{
titleContent = Contents.title;
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
[MenuItem("Window/General/Selection History")]
public static void OpenSelectionHistoryWindow()
Selection_OnGUI();
EditorWindow.GetWindow<SelectionHistoryWindow>();
EditorGUILayout.EndScrollView();
}
void OnEnable()
{
lockedObjects = null;
selectionHistory = null;
}
Vector2 scrollPos = Vector2.zero;
void OnDisable()
{
lockedObjects = null;
selectionHistory = null;
}
void OnGUI()
{
titleContent = Contents.title;
List<GameObject> selectionHistory;
List<GameObject> lockedObjects;
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
{
Selection_OnGUI();
}
EditorGUILayout.EndScrollView();
}
int maxHistoryCount = 32;
bool ignoreNextSelection = false;
void OnEnable()
{
lockedObjects = null;
selectionHistory = null;
}
void OnSelectionChange()
{
if (ignoreNextSelection)
void OnDisable()
ignoreNextSelection = false;
return;
lockedObjects = null;
selectionHistory = null;
if (selectionHistory == null) selectionHistory = new List<GameObject>();
if (lockedObjects == null) lockedObjects = new List<GameObject>();
List<GameObject> selectionHistory;
List<GameObject> lockedObjects;
int maxHistoryCount = 32;
bool ignoreNextSelection = false;
if (Selection.activeGameObject != null || Selection.gameObjects.Length > 0)
void OnSelectionChange()
foreach(var go in Selection.gameObjects)
if (ignoreNextSelection)
if (!selectionHistory.Contains(go))
selectionHistory.Add(go);
ignoreNextSelection = false;
return;
if (selectionHistory.Count > maxHistoryCount)
selectionHistory.Take(maxHistoryCount);
if (selectionHistory == null) selectionHistory = new List<GameObject>();
if (lockedObjects == null) lockedObjects = new List<GameObject>();
Repaint();
}
if (Selection.activeGameObject != null || Selection.gameObjects.Length > 0)
{
}
foreach(var go in Selection.gameObjects)
{
if (!selectionHistory.Contains(go))
selectionHistory.Add(go);
}
public bool CompareArray(GameObject[] a, GameObject[] b)
{
return a.SequenceEqual(b);
}
if (selectionHistory.Count > maxHistoryCount)
selectionHistory.Take(maxHistoryCount);
void Selection_OnGUI()
{
if (selectionHistory == null) selectionHistory = new List<GameObject>();
if (lockedObjects == null) lockedObjects = new List<GameObject>();
int i = 0;
int toRemove = -1;
Repaint();
}
if (lockedObjects.Count > 0)
}
public bool CompareArray(GameObject[] a, GameObject[] b)
GUILayout.Label("Favorites", EditorStyles.boldLabel);
i = 0;
toRemove = -1;
foreach (var obj in lockedObjects)
return a.SequenceEqual(b);
}
void Selection_OnGUI()
{
if (selectionHistory == null) selectionHistory = new List<GameObject>();
if (lockedObjects == null) lockedObjects = new List<GameObject>();
int i = 0;
int toRemove = -1;
if (lockedObjects.Count > 0)
if (obj == null)
GUILayout.Label("Favorites", EditorStyles.boldLabel);
i = 0;
toRemove = -1;
foreach (var obj in lockedObjects)
using (new EditorGUILayout.HorizontalScope())
if (obj == null)
GUILayout.Label("(object is either null or has been deleted)");
if (GUILayout.Button("X", GUILayout.Width(24)))
using (new EditorGUILayout.HorizontalScope())
toRemove = i;
GUILayout.Label("(object is either null or has been deleted)");
if (GUILayout.Button("X", GUILayout.Width(24)))
{
toRemove = i;
}
else
{
bool highlight = Selection.gameObjects.Contains(obj);
Color backup = GUI.color;
if (highlight)
GUI.color = Styles.highlightColor;
using (new EditorGUILayout.HorizontalScope())
{
var b = GUI.color;
GUI.color = Color.yellow * 3;
if (GUILayout.Button(Contents.star, Styles.icon, GUILayout.Width(24)))
{
toRemove = i;
}
GUI.color = b;
string label = obj.name;
if (GUILayout.Button(label, EditorStyles.foldout))
{
ignoreNextSelection = true;
Selection.activeObject = obj;
}
if (GUILayout.Button("Focus", EditorStyles.miniButton, GUILayout.Width(40)))
{
ignoreNextSelection = true;
Selection.activeObject = obj;
SceneView.lastActiveSceneView.FrameSelected();
}
}
GUI.color = backup;
}
i++;
else
if (toRemove != -1) lockedObjects.RemoveAt(toRemove);
}
int toAdd = -1;
toRemove = -1;
i = 0;
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.Label("History", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
if (GUILayout.Button("Clear", EditorStyles.miniButton))
{
selectionHistory.Clear();
Repaint();
}
}
GUILayout.Space(8);
var reversedHistory = selectionHistory.Reverse<GameObject>().ToArray();
foreach (var obj in reversedHistory)
{
if (obj != null)
{
bool highlight = Selection.gameObjects.Contains(obj);
Color backup = GUI.color;

using (new EditorGUILayout.HorizontalScope())
{
var b = GUI.color;
GUI.color = Color.yellow * 3;
if (GUILayout.Button(Contents.star, Styles.icon, GUILayout.Width(24)))
if (GUILayout.Button(Contents.starDisabled, Styles.icon, GUILayout.Width(24)))
toRemove = i;
toAdd = i;
GUI.color = b;
if (GUILayout.Button("Focus", EditorStyles.miniButton, GUILayout.Width(40)))
if (GUILayout.Button("Focus", Styles.historyButton, GUILayout.Width(40)))
{
ignoreNextSelection = true;
Selection.activeObject = obj;

var rect = GUILayoutUtility.GetLastRect();
EditorGUI.DrawRect(rect, new Color(0.2f,0.2f,0.2f,0.5f));
if (toRemove != -1) lockedObjects.RemoveAt(toRemove);
}
int toAdd = -1;
toRemove = -1;
i = 0;
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.Label("History", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
if (GUILayout.Button("Clear", EditorStyles.miniButton))
if (toAdd != -1)
{
lockedObjects.Add(reversedHistory[toAdd]);
Repaint();
}
if (toRemove != -1)
selectionHistory.Clear();
selectionHistory.RemoveAt(toRemove);
}
GUILayout.Space(8);
}
var reversedHistory = selectionHistory.Reverse<GameObject>().ToArray();
foreach (var obj in reversedHistory)
static class Styles
if (obj != null)
{
bool highlight = Selection.gameObjects.Contains(obj);
Color backup = GUI.color;
if (highlight)
GUI.color = Styles.highlightColor;
public static GUIStyle historyButton;
public static GUIStyle highlight;
public static Color highlightColor = new Color(2.0f, 2.0f, 2.0f);
using (new EditorGUILayout.HorizontalScope())
{
public static GUIStyle icon;
if (GUILayout.Button(Contents.starDisabled, Styles.icon, GUILayout.Width(24)))
{
toAdd = i;
}
static Styles()
{
historyButton = new GUIStyle(EditorStyles.miniButton);
historyButton.alignment = TextAnchor.MiddleLeft;
highlight = new GUIStyle(EditorStyles.miniLabel);
highlight.onNormal.background = Texture2D.whiteTexture;
highlight.onHover.background = Texture2D.whiteTexture;
highlight.onActive.background = Texture2D.whiteTexture;
highlight.onFocused.background = Texture2D.whiteTexture;
string label = obj.name;
if (GUILayout.Button(label, EditorStyles.foldout))
{
ignoreNextSelection = true;
Selection.activeObject = obj;
}
if (GUILayout.Button("Focus", Styles.historyButton, GUILayout.Width(40)))
{
ignoreNextSelection = true;
Selection.activeObject = obj;
SceneView.lastActiveSceneView.FrameSelected();
}
}
var rect = GUILayoutUtility.GetLastRect();
EditorGUI.DrawRect(rect, new Color(0.2f,0.2f,0.2f,0.5f));
icon = new GUIStyle(EditorStyles.label);
icon.fixedHeight = 16;
icon.padding = new RectOffset(8,2,2,0);
icon.margin = new RectOffset();
GUI.color = backup;
i++;
}
if (toAdd != -1)
{
lockedObjects.Add(reversedHistory[toAdd]);
Repaint();
}
if (toRemove != -1)
{
selectionHistory.RemoveAt(toRemove);
Repaint();
}
static class Styles
{
public static GUIStyle historyButton;
public static GUIStyle highlight;
public static Color highlightColor = new Color(2.0f, 2.0f, 2.0f);
public static GUIStyle icon;
static Styles()
static class Contents
historyButton = new GUIStyle(EditorStyles.miniButton);
historyButton.alignment = TextAnchor.MiddleLeft;
highlight = new GUIStyle(EditorStyles.miniLabel);
highlight.onNormal.background = Texture2D.whiteTexture;
highlight.onHover.background = Texture2D.whiteTexture;
highlight.onActive.background = Texture2D.whiteTexture;
highlight.onFocused.background = Texture2D.whiteTexture;
icon = new GUIStyle(EditorStyles.label);
icon.fixedHeight = 16;
icon.padding = new RectOffset(8,2,2,0);
icon.margin = new RectOffset();
public static GUIContent title = new GUIContent("Selection History");
public static GUIContent star = new GUIContent(EditorGUIUtility.IconContent("Favorite Icon").image);
public static GUIContent starDisabled = new GUIContent(EditorGUIUtility.IconContent("Favorite").image);
}
static class Contents
{
public static GUIContent title = new GUIContent("Selection History");
public static GUIContent star = new GUIContent(EditorGUIUtility.IconContent("Favorite Icon").image);
public static GUIContent starDisabled = new GUIContent(EditorGUIUtility.IconContent("Favorite").image);
}
}

2
Editor/WelcomeScreen/WelcomeScreen.cs


EditorApplication.update -= ShowAtStartup;
}
[MenuItem("Window/Gameplay Ingredients")]
[MenuItem("Window/Gameplay Ingredients/Welcome Screen", priority = MenuItems.kWindowMenuPriority)]
static void ShowFromMenu()
{
GetWindow<WelcomeScreen>(true, "Gameplay Ingredients");

8
Editor/CallTree.meta


fileFormatVersion: 2
guid: 37d3d697167f89145a81aa7bb6c0acc4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

5
Icons/Actions/ic-action-generic.png

之前 之后
宽度: 16  |  高度: 16  |  大小: 669 B

110
Icons/Actions/ic-action-generic.png.meta


fileFormatVersion: 2
guid: 68fdae855e346504b8884bfb6d7756cd
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 9
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- serializedVersion: 2
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- serializedVersion: 2
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

4
Icons/Events/ic-event-generic.png

之前 之后
宽度: 16  |  高度: 16  |  大小: 766 B

115
Icons/Events/ic-event-generic.png.meta


fileFormatVersion: 2
guid: 5beb76c73e9d9f34aa9f1092912301db
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

47
Icons/Misc/ic-callable.png

之前 之后
宽度: 16  |  高度: 16  |  大小: 2.0 KiB

115
Icons/Misc/ic-callable.png.meta


fileFormatVersion: 2
guid: 322030000d8e32240a902f51e3971ef3
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

11
Editor/CallTree/CallTreeWindow.cs.meta


fileFormatVersion: 2
guid: 07aa515d40867f64686276ce5db0e51d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

476
Editor/CallTree/CallTreeWindow.cs


using System.Collections;
using System.Linq;
using System.Collections.Generic;
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEditor.SceneManagement;
using GameplayIngredients.Events;
using GameplayIngredients.Logic;
using GameplayIngredients.Actions;
using GameplayIngredients.StateMachines;
using UnityEngine.SceneManagement;
namespace GameplayIngredients.Editor
{
public class CallTreeWindow : EditorWindow
{
CallTreeView m_TreeView;
[MenuItem("Window/Gameplay Ingredients/Callable Tree Explorer", priority = MenuItems.kWindowMenuPriority)]
static void OpenWindow()
{
s_Instance = GetWindow<CallTreeWindow>();
}
public static bool visible = false;
static CallTreeWindow s_Instance;
private void OnDisable()
{
visible = false;
s_Instance = null;
}
private void OnEnable()
{
nodeRoots = new Dictionary<string, List<CallTreeNode>>();
m_TreeView = new CallTreeView(nodeRoots);
titleContent = new GUIContent("Callable Tree Explorer");
ReloadCallHierarchy();
EditorSceneManager.sceneOpened += Reload;
EditorSceneSetup.onSetupLoaded += ReloadSetup;
visible = true;
}
void Reload(Scene scene, OpenSceneMode mode)
{
ReloadCallHierarchy();
}
void ReloadSetup(EditorSceneSetup setup)
{
ReloadCallHierarchy();
}
public static void Refresh()
{
s_Instance.ReloadCallHierarchy();
s_Instance.Repaint();
}
private void OnGUI()
{
int tbHeight = 24;
using (new GUILayout.HorizontalScope(EditorStyles.toolbar, GUILayout.Height(tbHeight)))
{
if (GUILayout.Button("Reload", EditorStyles.toolbarButton))
{
ReloadCallHierarchy();
}
GUILayout.FlexibleSpace();
m_TreeView.stringFilter = EditorGUILayout.DelayedTextField(m_TreeView.stringFilter, EditorStyles.toolbarSearchField);
Rect buttonRect = GUILayoutUtility.GetRect(52, 16);
if (GUI.Button(buttonRect, "Filter", EditorStyles.toolbarDropDown))
{
GenericMenu menu = new GenericMenu();
menu.AddItem(new GUIContent("Filter Selected"), false, () => {
m_TreeView.SetAutoFilter(false);
m_TreeView.SetFilter(Selection.activeGameObject);
});
menu.AddItem(new GUIContent("Clear Filter"), false, () => {
m_TreeView.SetAutoFilter(false);
m_TreeView.SetFilter(null);
m_TreeView.stringFilter = string.Empty;
});
menu.AddSeparator("");
menu.AddItem(new GUIContent("Automatic Filter"), m_TreeView.AutoFilter, () => {
m_TreeView.ToggleAutoFilter();
});
menu.DropDown(buttonRect);
}
}
Rect r = GUILayoutUtility.GetRect(position.width, position.height - tbHeight);
m_TreeView.OnGUI(r);
}
Dictionary<string, List<CallTreeNode>> nodeRoots;
void ReloadCallHierarchy()
{
if (nodeRoots == null)
nodeRoots = new Dictionary<string, List<CallTreeNode>>();
else
nodeRoots.Clear();
AddToCategory<EventBase>("Events");
AddToCategory<StateMachine>("State Machines");
AddToCategory<Factory>("Factories");
AddToCategory<SendMessageAction>("Messages");
m_TreeView.Reload();
}
void AddToCategory<T>(string name) where T:MonoBehaviour
{
var list = Resources.FindObjectsOfTypeAll<T>().ToList();
if (list.Count > 0)
nodeRoots.Add(name, new List<CallTreeNode>());
else
return;
var listRoot = nodeRoots[name];
foreach (var item in list)
{
if (item.gameObject.scene == null || !item.gameObject.scene.isLoaded)
continue;
var stack = new Stack<object>();
if(typeof(T) == typeof(StateMachine))
{
listRoot.Add(GetStateMachineNode(item as StateMachine, stack));
}
else if(typeof(T) == typeof(SendMessageAction))
{
listRoot.Add(GetMessageNode(item as SendMessageAction, stack));
}
else
{
listRoot.Add(GetNode(item, stack));
}
}
}
CallTreeNode GetNode(MonoBehaviour bhv, Stack<object> stack)
{
if(!stack.Contains(bhv))
{
stack.Push(bhv);
var rootNode = new CallTreeNode(bhv, GetType(bhv), $"{bhv.gameObject.name} ({bhv.GetType().Name})");
var type = bhv.GetType();
foreach (var field in type.GetFields())
{
// Find Fields that are Callable[]
if (field.FieldType.IsAssignableFrom(typeof(Callable[])))
{
var node = new CallTreeNode(bhv, CallTreeNodeType.Callable, field.Name);
var value = (Callable[])field.GetValue(bhv);
if (value != null && value.Length > 0)
{
rootNode.Children.Add(node);
// Add Callables from this Callable[] array
foreach (var call in value)
{
node.Children.Add(GetNode(call, stack));
}
}
}
}
return rootNode;
}
else
{
return new CallTreeNode(bhv, GetType(bhv), $"RECURSED : {bhv.gameObject.name} ({bhv.GetType().Name})");
}
}
CallTreeNode GetMessageNode(SendMessageAction msg, Stack<object> stack)
{
if (!stack.Contains(msg))
{
stack.Push(msg);
var rootNode = new CallTreeNode(msg, CallTreeNodeType.Message, $"{msg.MessageToSend} : ({msg.gameObject.name}.{msg.Name})");
var all = Resources.FindObjectsOfTypeAll<OnMessageEvent>().Where(o=> o.MessageName == msg.MessageToSend).ToList();
foreach(var evt in all)
{
rootNode.Children.Add(GetNode(evt, stack));
}
return rootNode;
}
else
{
return new CallTreeNode(msg, GetType(msg), $"RECURSED :{msg.MessageToSend} : ({msg.gameObject.name}.{msg.Name})");
}
}
CallTreeNode GetStateMachineNode(StateMachine sm, Stack<object> stack)
{
if (!stack.Contains(sm))
{
stack.Push(sm);
var rootNode = new CallTreeNode(sm, CallTreeNodeType.StateMachine, sm.gameObject.name);
var type = sm.GetType();
foreach (var field in type.GetFields())
{
// Find Fields that are State[]
if (field.FieldType.IsAssignableFrom(typeof(State[])))
{
// Add Callables from this Callable[] array
var value = (State[])field.GetValue(sm);
foreach (var state in value)
{
rootNode.Children.Add(GetStateNode(state, stack));
}
}
}
return rootNode;
}
else
{
return new CallTreeNode(sm, GetType(sm), $"RECURSED :{sm.gameObject.name}");
}
}
CallTreeNode GetStateNode(State st, Stack<object> stack)
{
if (!stack.Contains(st))
{
stack.Push(st);
var rootNode = new CallTreeNode(st, CallTreeNodeType.State, st.gameObject.name);
var type = st.GetType();
foreach (var field in type.GetFields())
{
// Find Fields that are Callable[]
if (field.FieldType.IsAssignableFrom(typeof(Callable[])))
{
var node = new CallTreeNode(st, CallTreeNodeType.Callable, field.Name);
rootNode.Children.Add(node);
// Add Callables from this Callable[] array
var value = (Callable[])field.GetValue(st);
foreach (var call in value)
{
node.Children.Add(GetNode(call, stack));
}
}
}
return rootNode;
}
else
{
return new CallTreeNode(st, GetType(st), $"RECURSED :{st.gameObject.name}");
}
}
CallTreeNodeType GetType(MonoBehaviour bhv)
{
if (bhv == null)
return CallTreeNodeType.Callable;
else if (bhv is EventBase)
return CallTreeNodeType.Event;
else if (bhv is LogicBase)
return CallTreeNodeType.Logic;
else if (bhv is ActionBase)
return CallTreeNodeType.Action;
else if (bhv is StateMachine)
return CallTreeNodeType.StateMachine;
else if (bhv is State)
return CallTreeNodeType.State;
else if (bhv is OnMessageEvent || bhv is SendMessageAction)
return CallTreeNodeType.Message;
else
return CallTreeNodeType.Callable;
}
class CallTreeNode
{
public string Name;
public MonoBehaviour Target;
public List<CallTreeNode> Children;
public CallTreeNodeType Type;
public CallTreeNode(MonoBehaviour target, CallTreeNodeType type, string name = "")
{
Name = string.IsNullOrEmpty(name) ? target.GetType().Name : name;
Target = target;
Type = type;
Children = new List<CallTreeNode>();
}
public bool Filter(GameObject go, string filter)
{
if (go == null && string.IsNullOrEmpty(filter))
return true;
else
{
if (this.Target.gameObject == go && string.IsNullOrEmpty(filter)? true: this.Name.Contains(filter))
return true;
bool value = false;
foreach (var node in Children)
value = value || node.Filter(go, filter);
return value;
}
}
}
public enum CallTreeNodeType
{
Callable,
Event,
Logic,
Action,
Message,
StateMachine,
State
}
class CallTreeView : TreeView
{
Dictionary<string, List<CallTreeNode>> m_Roots;
Dictionary<int, CallTreeNode> m_Bindings;
public CallTreeView(Dictionary<string, List<CallTreeNode>> roots) : base(new TreeViewState())
{
m_Roots = roots;
m_Bindings = new Dictionary<int, CallTreeNode>();
}
public string stringFilter { get { return m_StringFilter; } set { m_StringFilter = value; this.Reload(); } }
GameObject m_filter = null;
string m_StringFilter = "";
public bool AutoFilter { get; private set; }
public void ToggleAutoFilter()
{
SetAutoFilter(!AutoFilter);
}
public void SetAutoFilter(bool value)
{
AutoFilter = value;
if (AutoFilter)
{
Selection.selectionChanged += UpdateAutoFilter;
if(this.HasSelection())
{
SetFilter(m_Bindings[this.GetSelection()[0]].Target.gameObject);
}
}
else
Selection.selectionChanged -= UpdateAutoFilter;
}
void UpdateAutoFilter()
{
if (Selection.activeGameObject != null)
SetFilter(Selection.activeGameObject);
}
public void SetFilter(GameObject filter = null)
{
m_filter = filter;
Reload();
}
protected override TreeViewItem BuildRoot()
{
int id = -1;
m_Bindings.Clear();
var treeRoot = new TreeViewItem(++id, -1, "~Root");
foreach(var kvp in m_Roots)
{
if (kvp.Value == null || kvp.Value.Count == 0)
continue;
var currentRoot = new TreeViewItem(++id, 0, kvp.Key);
treeRoot.AddChild(currentRoot);
foreach (var node in kvp.Value)
{
if (node.Filter(m_filter, m_StringFilter))
{
currentRoot.AddChild(GetNode(node, ref id, 1));
}
}
}
if (treeRoot.children == null)
{
treeRoot.AddChild(new TreeViewItem(1, 0, "(No Results)"));
}
return treeRoot;
}
TreeViewItem GetNode(CallTreeNode node, ref int id, int depth)
{
id++;
var item = new TreeViewItem(id, depth, $"{node.Name}");
item.icon = GetIcon(node.Target, node.Type);
m_Bindings.Add(id, node);
foreach(var child in node.Children)
{
item.AddChild(GetNode(child, ref id, depth + 1));
}
return item;
}
Texture2D GetIcon(MonoBehaviour bhv, CallTreeNodeType type)
{
if(bhv != null && type != CallTreeNodeType.Callable)
{
var texture = EditorGUIUtility.ObjectContent(bhv, bhv.GetType()).image;
if (texture != null)
return texture as Texture2D;
}
switch(type)
{
default:
case CallTreeNodeType.Callable:
return Styles.Callable;
case CallTreeNodeType.Action:
return Styles.Action;
case CallTreeNodeType.Logic:
return Styles.Logic;
case CallTreeNodeType.Event:
return Styles.Event;
case CallTreeNodeType.Message:
return Styles.Message;
case CallTreeNodeType.State:
return Styles.State;
case CallTreeNodeType.StateMachine:
return Styles.StateMachine;
}
}
protected override void SelectionChanged(IList<int> selectedIds)
{
if (AutoFilter)
return;
base.SelectionChanged(selectedIds);
if (selectedIds.Count > 0 && m_Bindings.ContainsKey(selectedIds[0]))
Selection.activeObject = m_Bindings[selectedIds[0]].Target;
}
static class Styles
{
public static Texture2D Callable = Icon("Misc/ic-callable.png");
public static Texture2D Action = Icon("Actions/ic-action-generic.png");
public static Texture2D Logic = Icon("Logic/ic-generic-logic.png");
public static Texture2D Event = Icon("Events/ic-event-generic.png");
public static Texture2D Message = Icon("Events/ic-event-message .png");
public static Texture2D StateMachine = Icon("Misc/ic-StateMachine.png");
public static Texture2D State = Icon("Misc/ic-State.png");
static Texture2D Icon(string path)
{
return AssetDatabase.LoadAssetAtPath<Texture2D>($"Packages/net.peeweek.gameplay-ingredients/Icons/{path}");
}
}
}
}
}
正在加载...
取消
保存