浏览代码
Single update for Rigs (#22)
Single update for Rigs (#22)
* Base Commit * Added option to lock update modes for some rigs * Custom Editor for Rigs * Fixed Gameplay Ingredients windows menu / Added Rig Debug Window * Added Filter string / Reload in toolbar * Fix potential null * Trigger Reload on Scene Change + Changelog * Small Fixes * Fix potential null on scene change / object delete * Factorized Rig view in Ingredients Explorer Window * Made Rig Editor a Pingable Editor (Genericized the Ping) * Updated Changelog * Error Messages when Rig Manager is excluded / not present./main
GitHub
4 年前
当前提交
a54b34ad
共有 27 个文件被更改,包括 1151 次插入 和 38 次删除
-
7CHANGELOG.md
-
2Editor/CheckWindow/CheckWindow.cs
-
3Editor/GameplayIngredients-Editor.asmdef
-
2Editor/GlobalsDebugWindow/GlobalsDebugWindow.cs
-
4Editor/PropertyDrawers/CallablePropertyDrawer.cs
-
14Runtime/Ingredients/Rigs/DirectorControlRig.cs
-
32Runtime/Ingredients/Rigs/FloatingRig.cs
-
9Runtime/Ingredients/Rigs/FollowPathRig.cs
-
7Runtime/Ingredients/Rigs/LookAtRig.cs
-
9Runtime/Ingredients/Rigs/ReachPositionRig.cs
-
12Runtime/Ingredients/Rigs/RigidBodyForceRig.cs
-
10Runtime/Ingredients/Rigs/RotationRig.cs
-
94Editor/CustomInspectors/PingableEditor.cs
-
11Editor/CustomInspectors/PingableEditor.cs.meta
-
8Editor/Rigs.meta
-
69Runtime/Ingredients/Rigs/Rig.cs
-
11Runtime/Ingredients/Rigs/Rig.cs.meta
-
111Runtime/Ingredients/Rigs/RigManager.cs
-
11Runtime/Ingredients/Rigs/RigManager.cs.meta
-
644Editor/IngredientsExplorer/IngredientsExplorerWindow.cs
-
108Editor/Rigs/RigEditor.cs
-
11Editor/Rigs/RigEditor.cs.meta
-
0/Editor/IngredientsExplorer.meta
-
0/Editor/IngredientsExplorer/IngredientsExplorerWindow.cs.meta
|
|||
using NaughtyAttributes.Editor; |
|||
using System.Collections.Generic; |
|||
using UnityEditor; |
|||
using UnityEngine; |
|||
|
|||
namespace GameplayIngredients.Editor |
|||
{ |
|||
public abstract class PingableEditor : NaughtyInspector |
|||
{ |
|||
float m_pingValue; |
|||
static MonoBehaviour m_NextToPing; |
|||
|
|||
static Dictionary<MonoBehaviour, PingableEditor> trackedEditors; |
|||
|
|||
protected override void OnEnable() |
|||
{ |
|||
base.OnEnable(); |
|||
|
|||
if (trackedEditors == null) |
|||
trackedEditors = new Dictionary<MonoBehaviour, PingableEditor>(); |
|||
|
|||
if (!trackedEditors.ContainsKey(serializedObject.targetObject as MonoBehaviour)) |
|||
trackedEditors.Add(serializedObject.targetObject as MonoBehaviour, this); |
|||
} |
|||
|
|||
protected override void OnDisable() |
|||
{ |
|||
if (serializedObject != null && serializedObject.targetObject != null) |
|||
{ |
|||
if (trackedEditors.ContainsKey(serializedObject.targetObject as MonoBehaviour)) |
|||
trackedEditors.Remove(serializedObject.targetObject as MonoBehaviour); |
|||
} |
|||
else // Delete or scene change
|
|||
{ |
|||
trackedEditors.Clear(); |
|||
} |
|||
base.OnDisable(); |
|||
} |
|||
|
|||
public abstract void OnInspectorGUI_PingArea(); |
|||
|
|||
public override void OnInspectorGUI() |
|||
{ |
|||
Rect r = EditorGUILayout.BeginVertical(); |
|||
bool needRepaint = UpdatePing(r); |
|||
|
|||
OnInspectorGUI_PingArea(); |
|||
|
|||
EditorGUILayout.EndVertical(); |
|||
|
|||
if (needRepaint) |
|||
Repaint(); |
|||
} |
|||
|
|||
public static void PingObject(MonoBehaviour r) |
|||
{ |
|||
m_NextToPing = r; |
|||
lastEditorTime = EditorApplication.timeSinceStartup; |
|||
|
|||
// Trigger a repaint if the editor is currently visible
|
|||
if (trackedEditors != null && trackedEditors.ContainsKey(r)) |
|||
trackedEditors[r].Repaint(); |
|||
} |
|||
|
|||
static double lastEditorTime; |
|||
|
|||
protected bool UpdatePing(Rect r) |
|||
{ |
|||
if (m_NextToPing == serializedObject.targetObject as MonoBehaviour) |
|||
{ |
|||
m_pingValue = 1; |
|||
m_NextToPing = null; |
|||
} |
|||
|
|||
if (m_pingValue <= 0) |
|||
return false; |
|||
|
|||
r.yMin -= 2; |
|||
r.xMin -= 14; |
|||
r.width += 14; |
|||
r.height += 6; |
|||
EditorGUI.DrawRect(r, new Color(15f / 256, 128f / 256, 190f / 256, m_pingValue)); |
|||
|
|||
double time = EditorApplication.timeSinceStartup; |
|||
float dt = (float)(time - lastEditorTime); |
|||
|
|||
m_pingValue -= 2 * dt; // 2 is hardcoded, TODO: Make a preference out of it
|
|||
lastEditorTime = time; |
|||
return true; |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|
|||
fileFormatVersion: 2 |
|||
guid: 5719dec7120f0674993fab3f034f2237 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 3b5218c80b9574b438d16bb9e5eb12d1 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using ICSharpCode.NRefactory.Ast; |
|||
using NaughtyAttributes; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
|
|||
|
|||
namespace GameplayIngredients.Rigs |
|||
{ |
|||
public abstract class Rig : MonoBehaviour |
|||
{ |
|||
public UpdateMode updateMode |
|||
{ |
|||
get { return m_UpdateMode; } |
|||
} |
|||
|
|||
public int rigPriority |
|||
{ |
|||
get { return m_RigPriority; } |
|||
} |
|||
|
|||
public enum UpdateMode |
|||
{ |
|||
Update, |
|||
LateUpdate, |
|||
FixedUpdate, |
|||
} |
|||
|
|||
public abstract UpdateMode defaultUpdateMode { get; } |
|||
public abstract int defaultPriority { get; } |
|||
public virtual bool canChangeUpdateMode { get { return false; } } |
|||
|
|||
protected bool CanChangeUpdateMode() { return canChangeUpdateMode; } |
|||
|
|||
[SerializeField, EnableIf("CanChangeUpdateMode")] |
|||
private UpdateMode m_UpdateMode; |
|||
[SerializeField] |
|||
private int m_RigPriority = 0; |
|||
|
|||
|
|||
private void Reset() |
|||
{ |
|||
if(!canChangeUpdateMode) |
|||
m_UpdateMode = defaultUpdateMode; |
|||
m_RigPriority = defaultPriority; |
|||
} |
|||
|
|||
protected virtual void OnEnable() |
|||
{ |
|||
if (Manager.TryGet(out RigManager rigManager)) |
|||
rigManager.RegistedRig(this); |
|||
else |
|||
Debug.LogWarning($"{gameObject.name} : Could not register the Rig of type {GetType().Name}. Rig Manager is not present or has been excluded. Please check your Assets/GameplayIngredientsSettings asset"); |
|||
} |
|||
|
|||
protected virtual void OnDisable() |
|||
{ |
|||
if (Manager.TryGet(out RigManager rigManager)) |
|||
rigManager.RemoveRig(this); |
|||
else |
|||
Debug.LogWarning($"{gameObject.name} : Could not remove the Rig of type {GetType().Name}. Rig Manager is not present or has been excluded. Please check your Assets/GameplayIngredientsSettings asset"); |
|||
} |
|||
|
|||
public abstract void UpdateRig(float deltaTime); |
|||
|
|||
} |
|||
} |
|||
|
|||
|
|
|||
fileFormatVersion: 2 |
|||
guid: bd4a5336b93b61b4a9c2045594783100 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Xml.Serialization; |
|||
using UnityEngine; |
|||
|
|||
namespace GameplayIngredients.Rigs |
|||
{ |
|||
public class RigManager : Manager |
|||
{ |
|||
Dictionary<int, List<Rig>> m_UpdateRigs; |
|||
Dictionary<int, List<Rig>> m_LateUpdateRigs; |
|||
Dictionary<int, List<Rig>> m_FixedUpdateRigs; |
|||
|
|||
private void OnEnable() |
|||
{ |
|||
m_UpdateRigs = new Dictionary<int, List<Rig>>(); |
|||
m_LateUpdateRigs = new Dictionary<int, List<Rig>>(); |
|||
m_FixedUpdateRigs = new Dictionary<int, List<Rig>>(); |
|||
} |
|||
|
|||
|
|||
public void RegistedRig(Rig rig) |
|||
{ |
|||
Rig.UpdateMode updateMode; |
|||
if (rig.canChangeUpdateMode) |
|||
updateMode = rig.updateMode; |
|||
else |
|||
updateMode = rig.defaultUpdateMode; |
|||
|
|||
Dictionary<int, List<Rig>> dict; |
|||
switch (updateMode) |
|||
{ |
|||
default: |
|||
case Rig.UpdateMode.Update: |
|||
dict = m_UpdateRigs; |
|||
break; |
|||
case Rig.UpdateMode.LateUpdate: |
|||
dict = m_LateUpdateRigs; |
|||
break; |
|||
case Rig.UpdateMode.FixedUpdate: |
|||
dict = m_FixedUpdateRigs; |
|||
break; |
|||
} |
|||
|
|||
if(!dict.ContainsKey(rig.rigPriority)) |
|||
{ |
|||
dict.Add(rig.rigPriority, new List<Rig>()); |
|||
} |
|||
|
|||
dict[rig.rigPriority].Add(rig); |
|||
} |
|||
|
|||
public void RemoveRig(Rig rig) |
|||
{ |
|||
Dictionary<int, List<Rig>> dict; |
|||
switch (rig.updateMode) |
|||
{ |
|||
default: |
|||
case Rig.UpdateMode.Update: |
|||
dict = m_UpdateRigs; |
|||
break; |
|||
case Rig.UpdateMode.LateUpdate: |
|||
dict = m_LateUpdateRigs; |
|||
break; |
|||
case Rig.UpdateMode.FixedUpdate: |
|||
dict = m_FixedUpdateRigs; |
|||
break; |
|||
} |
|||
|
|||
int priority = rig.rigPriority; |
|||
if (dict.ContainsKey(priority) && dict[priority].Contains(rig)) |
|||
{ |
|||
dict[priority].Remove(rig); |
|||
} |
|||
|
|||
if(dict[priority].Count == 0) |
|||
{ |
|||
dict.Remove(priority); |
|||
} |
|||
} |
|||
|
|||
void UpdateRigDictionary(Dictionary<int, List<Rig>> dict, float deltaTime) |
|||
{ |
|||
var priorities = dict.Keys.OrderBy(i => i); |
|||
foreach(int priority in priorities) |
|||
{ |
|||
foreach(var rig in dict[priority]) |
|||
{ |
|||
rig.UpdateRig(deltaTime); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Update() |
|||
{ |
|||
UpdateRigDictionary(m_UpdateRigs, Time.deltaTime); |
|||
} |
|||
|
|||
public void FixedUpdate() |
|||
{ |
|||
UpdateRigDictionary(m_FixedUpdateRigs, Time.fixedDeltaTime); |
|||
} |
|||
|
|||
public void LateUpdate() |
|||
{ |
|||
UpdateRigDictionary(m_LateUpdateRigs, Time.deltaTime); |
|||
} |
|||
} |
|||
} |
|||
|
|
|||
fileFormatVersion: 2 |
|||
guid: 081682afa3b23474e90dfb80d748a9ad |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Linq; |
|||
using System.Collections.Generic; |
|||
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; |
|||
using GameplayIngredients.Rigs; |
|||
|
|||
namespace GameplayIngredients.Editor |
|||
{ |
|||
public class IngredientsExplorerWindow : EditorWindow |
|||
{ |
|||
CallTreeView m_TreeView; |
|||
[MenuItem("Window/Gameplay Ingredients/Ingredients Explorer", priority = MenuItems.kWindowMenuPriority + 30)] |
|||
internal static void OpenWindow() |
|||
{ |
|||
s_Instance = GetWindow<IngredientsExplorerWindow>(); |
|||
} |
|||
|
|||
internal static void OpenWindow(MonoBehaviour selected) |
|||
{ |
|||
OpenWindow(); |
|||
s_Instance.Repaint(); |
|||
s_Instance.SelectItem(selected); |
|||
} |
|||
|
|||
public static bool visible = false; |
|||
static IngredientsExplorerWindow 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("Ingredients Explorer", CallTreeView.Styles.Callable); |
|||
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(); |
|||
EditorGUI.BeginChangeCheck(); |
|||
string filter = EditorGUILayout.DelayedTextField(m_TreeView.stringFilter, EditorStyles.toolbarSearchField); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
{ |
|||
m_TreeView.SetStringFilter(filter); |
|||
} |
|||
|
|||
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.SetObjectFilter(Selection.activeGameObject); |
|||
}); |
|||
menu.AddItem(new GUIContent("Clear Filter"), false, () => { |
|||
m_TreeView.SetAutoFilter(false); |
|||
m_TreeView.SetObjectFilter(null); |
|||
m_TreeView.SetStringFilter(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); |
|||
} |
|||
|
|||
void SelectItem(MonoBehaviour target) |
|||
{ |
|||
int selected = m_TreeView.FindID(target); |
|||
m_TreeView.SetSelection(new[] { selected }, TreeViewSelectionOptions.RevealAndFrame); |
|||
} |
|||
|
|||
Dictionary<string, List<CallTreeNode>> nodeRoots; |
|||
|
|||
List<MonoBehaviour> erroneous; |
|||
|
|||
void ReloadCallHierarchy() |
|||
{ |
|||
if (nodeRoots == null) |
|||
nodeRoots = new Dictionary<string, List<CallTreeNode>>(); |
|||
else |
|||
nodeRoots.Clear(); |
|||
|
|||
erroneous = new List<MonoBehaviour>(); |
|||
|
|||
AddToCategory<EventBase>("Events"); |
|||
AddToCategory<StateMachine>("State Machines"); |
|||
AddToCategory<Factory>("Factories"); |
|||
AddToCategory<SendMessageAction>("Messages"); |
|||
AddRigs(); |
|||
CollectErroneousCallables(); |
|||
m_TreeView.Reload(); |
|||
} |
|||
|
|||
void CollectErroneousCallables() |
|||
{ |
|||
if (erroneous == null || erroneous.Count == 0) |
|||
return; |
|||
var root = new List<CallTreeNode>(); |
|||
nodeRoots.Add("Erroneous Callables", root); |
|||
|
|||
foreach(var callable in erroneous) |
|||
{ |
|||
root.Add(new CallTreeNode(callable, CallTreeNodeType.Callable, callable.name)); |
|||
} |
|||
} |
|||
|
|||
void AddErroneous(MonoBehaviour bhv) |
|||
{ |
|||
if (!erroneous.Contains(bhv)) |
|||
erroneous.Add(bhv); |
|||
} |
|||
|
|||
void AddRigs() |
|||
{ |
|||
// Populate rigs
|
|||
Dictionary<Rig.UpdateMode, Dictionary<int, List<Rig>>> allRigs = new Dictionary<Rig.UpdateMode, Dictionary<int, List<Rig>>>(); |
|||
|
|||
var list = FindObjectsOfType<Rig>().ToList(); |
|||
if (list.Count == 0) |
|||
return; |
|||
|
|||
foreach(var rig in list) |
|||
{ |
|||
if (!allRigs.ContainsKey(rig.updateMode)) |
|||
allRigs.Add(rig.updateMode, new Dictionary<int, List<Rig>>()); |
|||
|
|||
if (!allRigs[rig.updateMode].ContainsKey(rig.rigPriority)) |
|||
allRigs[rig.updateMode].Add(rig.rigPriority, new List<Rig>()); |
|||
|
|||
allRigs[rig.updateMode][rig.rigPriority].Add(rig); |
|||
} |
|||
|
|||
// Construct tree
|
|||
nodeRoots.Add("Rigs", new List<CallTreeNode>()); |
|||
var listRoot = nodeRoots["Rigs"]; |
|||
foreach(var updateMode in allRigs.Keys) |
|||
{ |
|||
var group = GetGroupNode($"Update Mode: {updateMode}"); |
|||
foreach(var index in allRigs[updateMode].Keys.OrderBy(o => o)) |
|||
{ |
|||
var indexGroup = GetGroupNode($"Priority : #{index}"); |
|||
foreach(var rig in allRigs[updateMode][index]) |
|||
{ |
|||
indexGroup.Children.Add(new CallTreeNode(rig, CallTreeNodeType.Rig, $"{rig.gameObject.name} ({rig.GetType().Name})")); |
|||
} |
|||
group.Children.Add(indexGroup); |
|||
} |
|||
listRoot.Add(group); |
|||
} |
|||
} |
|||
|
|||
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) |
|||
{ |
|||
if (call != null) |
|||
node.Children.Add(GetCallableNode(call, stack)); |
|||
else |
|||
AddErroneous(node.Target); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return rootNode; |
|||
} |
|||
else |
|||
{ |
|||
return new CallTreeNode(bhv, GetType(bhv), $"RECURSED : {bhv.gameObject.name} ({bhv.GetType().Name})"); |
|||
} |
|||
} |
|||
|
|||
CallTreeNode GetCallableNode(Callable c, Stack<object> stack) |
|||
{ |
|||
if (!stack.Contains(c)) |
|||
{ |
|||
stack.Push(c); |
|||
var rootNode = new CallTreeNode(c, GetType(c), $"{c.Name} ({c.gameObject.name} : {c.GetType().Name})"); |
|||
var type = c.GetType(); |
|||
foreach (var field in type.GetFields()) |
|||
{ |
|||
// Find Fields that are Callable[]
|
|||
if (field.FieldType.IsAssignableFrom(typeof(Callable[]))) |
|||
{ |
|||
var node = new CallTreeNode(c, CallTreeNodeType.Callable, field.Name); |
|||
var value = (Callable[])field.GetValue(c); |
|||
|
|||
if (value != null && value.Length > 0) |
|||
{ |
|||
rootNode.Children.Add(node); |
|||
// Add Callables from this Callable[] array
|
|||
foreach (var call in value) |
|||
{ |
|||
if (call != null) |
|||
node.Children.Add(GetCallableNode(call, stack)); |
|||
else |
|||
AddErroneous(node.Target); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return rootNode; |
|||
} |
|||
else |
|||
{ |
|||
return new CallTreeNode(c, GetType(c), $"RECURSED : {c.Name} ({c.gameObject.name} : {c.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 GetGroupNode(string name) |
|||
{ |
|||
return new CallTreeNode(null, CallTreeNodeType.Group, 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) |
|||
{ |
|||
if (state != null) |
|||
rootNode.Children.Add(GetStateNode(state, stack)); |
|||
else |
|||
AddErroneous(rootNode.Target); |
|||
} |
|||
} |
|||
} |
|||
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) |
|||
{ |
|||
if (call != null) |
|||
node.Children.Add(GetNode(call, stack)); |
|||
else |
|||
AddErroneous(rootNode.Target); |
|||
} |
|||
} |
|||
} |
|||
return rootNode; |
|||
} |
|||
else |
|||
{ |
|||
return new CallTreeNode(st, GetType(st), $"RECURSED :{st.gameObject.name}"); |
|||
} |
|||
} |
|||
|
|||
CallTreeNodeType GetType(MonoBehaviour bhv) |
|||
{ |
|||
if (bhv == null) |
|||
return CallTreeNodeType.Group; |
|||
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 Factory) |
|||
return CallTreeNodeType.Factory; |
|||
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) |
|||
{ |
|||
bool keep = (go == null || this.Target.gameObject == go) |
|||
&& (string.IsNullOrEmpty(filter) ? true : this.Name.Contains(filter)); |
|||
|
|||
if(!keep) |
|||
{ |
|||
foreach (var node in Children) |
|||
keep = keep || node.Filter(go, filter); |
|||
} |
|||
|
|||
return keep; |
|||
} |
|||
} |
|||
|
|||
public enum CallTreeNodeType |
|||
{ |
|||
Callable, |
|||
Event, |
|||
Logic, |
|||
Action, |
|||
Message, |
|||
StateMachine, |
|||
State, |
|||
Factory, |
|||
Rig, |
|||
Group, |
|||
} |
|||
|
|||
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; } } |
|||
|
|||
[SerializeField] |
|||
GameObject m_filter = null; |
|||
[SerializeField] |
|||
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()) |
|||
{ |
|||
SetObjectFilter(m_Bindings[this.GetSelection()[0]].Target.gameObject); |
|||
} |
|||
} |
|||
else |
|||
Selection.selectionChanged -= UpdateAutoFilter; |
|||
} |
|||
|
|||
void UpdateAutoFilter() |
|||
{ |
|||
if (Selection.activeGameObject != null) |
|||
SetObjectFilter(Selection.activeGameObject); |
|||
} |
|||
|
|||
public void SetObjectFilter(GameObject filter = null) |
|||
{ |
|||
m_filter = filter; |
|||
Reload(); |
|||
} |
|||
|
|||
public void SetStringFilter(string stringFilter) |
|||
{ |
|||
m_StringFilter = stringFilter; |
|||
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.Type != CallTreeNodeType.Group && !node.Filter(m_filter, m_StringFilter)) |
|||
continue; |
|||
|
|||
currentRoot.AddChild(GetNode(node, ref id, 1)); |
|||
} |
|||
} |
|||
if (treeRoot.children == null) |
|||
{ |
|||
treeRoot.AddChild(new TreeViewItem(1, 0, "(No Results)")); |
|||
} |
|||
|
|||
return treeRoot; |
|||
} |
|||
|
|||
public int FindID(MonoBehaviour target) |
|||
{ |
|||
if (m_Bindings.Any(o => o.Value.Target == target)) |
|||
return m_Bindings.Where(o => o.Value.Target == target).First().Key; |
|||
else |
|||
return int.MinValue; |
|||
} |
|||
|
|||
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) |
|||
{ |
|||
// If this is a group, filter all its direct children
|
|||
if (node.Type == CallTreeNodeType.Group && !child.Filter(m_filter, m_StringFilter)) |
|||
continue; |
|||
|
|||
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.Group: |
|||
return Styles.Group; |
|||
case CallTreeNodeType.Rig: |
|||
return Styles.Rig; |
|||
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.Factory: |
|||
return Styles.Factory; |
|||
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])) |
|||
if(m_Bindings[selectedIds[0]].Target != null) |
|||
{ |
|||
var node = m_Bindings[selectedIds[0]]; |
|||
Selection.activeObject = node.Target; |
|||
|
|||
if (node.Type == CallTreeNodeType.Rig) |
|||
RigEditor.PingObject(node.Target as Rig); |
|||
} |
|||
} |
|||
|
|||
public static class Styles |
|||
{ |
|||
public static Texture2D Group = null; |
|||
public static Texture2D Callable = Icon("Misc/ic-callable.png"); |
|||
public static Texture2D Rig = 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"); |
|||
public static Texture2D Factory = Icon("Misc/ic-Factory.png"); |
|||
|
|||
static Texture2D Icon(string path) |
|||
{ |
|||
return AssetDatabase.LoadAssetAtPath<Texture2D>($"Packages/net.peeweek.gameplay-ingredients/Icons/{path}"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|
|||
using UnityEngine; |
|||
using UnityEditor; |
|||
using System; |
|||
using System.Reflection; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using NaughtyAttributes.Editor; |
|||
using GameplayIngredients.Rigs; |
|||
|
|||
namespace GameplayIngredients.Editor |
|||
{ |
|||
[CustomEditor(typeof(Rig), true)] |
|||
public class RigEditor : PingableEditor |
|||
{ |
|||
SerializedProperty m_UpdateMode; |
|||
SerializedProperty m_RigPriority; |
|||
|
|||
List<SerializedProperty> rigProperties; |
|||
|
|||
static GUIContent callableIcon; |
|||
|
|||
protected override void OnEnable() |
|||
{ |
|||
base.OnEnable(); |
|||
|
|||
callableIcon = new GUIContent(AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/net.peeweek.gameplay-ingredients/Icons/Misc/ic-callable.png")); |
|||
|
|||
m_UpdateMode = serializedObject.FindProperty("m_UpdateMode"); |
|||
m_RigPriority = serializedObject.FindProperty("m_RigPriority"); |
|||
|
|||
if (rigProperties == null) |
|||
rigProperties = new List<SerializedProperty>(); |
|||
else |
|||
rigProperties.Clear(); |
|||
|
|||
Type inspectedType = this.serializedObject.targetObject.GetType(); |
|||
foreach(FieldInfo info in inspectedType.FindMembers(MemberTypes.Field, |
|||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, |
|||
null, null)) |
|||
{ |
|||
if (info.IsNotSerialized) |
|||
continue; |
|||
|
|||
var property = serializedObject.FindProperty(info.Name); |
|||
|
|||
if (property != null) |
|||
rigProperties.Add(property); |
|||
} |
|||
} |
|||
|
|||
public override void OnInspectorGUI_PingArea() |
|||
{ |
|||
serializedObject.Update(); |
|||
|
|||
bool excludedRigManager = GameplayIngredientsSettings.currentSettings.excludedeManagers.Any(s => s == "RigManager"); |
|||
|
|||
if (excludedRigManager) |
|||
{ |
|||
EditorGUILayout.HelpBox("This Rig depends on the Rig Manager which is excluded in your Gameplay Ingredients Settings. This rig component will be inactive unless the manager is not excluded.", MessageType.Error, true); |
|||
if (GUILayout.Button("Open Settings")) |
|||
Selection.activeObject = GameplayIngredientsSettings.currentSettings; |
|||
} |
|||
|
|||
EditorGUI.BeginDisabledGroup(excludedRigManager); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
GUILayout.Label("Rig : Update Properties", EditorStyles.boldLabel); |
|||
|
|||
using (new GUILayout.HorizontalScope()) |
|||
{ |
|||
using (new GUILayout.VerticalScope(GUILayout.ExpandWidth(true))) |
|||
{ |
|||
using (new EditorGUI.IndentLevelScope(1)) |
|||
{ |
|||
NaughtyEditorGUI.PropertyField_Layout(m_UpdateMode, true); |
|||
NaughtyEditorGUI.PropertyField_Layout(m_RigPriority, true); |
|||
} |
|||
} |
|||
|
|||
GUILayout.Space(8); |
|||
|
|||
if (GUILayout.Button(callableIcon, GUILayout.Width(48), GUILayout.ExpandHeight(true))) |
|||
{ |
|||
// Open Debug Window
|
|||
IngredientsExplorerWindow.OpenWindow(this.serializedObject.targetObject as Rig); |
|||
} |
|||
} |
|||
|
|||
|
|||
EditorGUILayout.Space(); |
|||
GUILayout.Label("Rig Properties", EditorStyles.boldLabel); |
|||
using (new EditorGUI.IndentLevelScope(1)) |
|||
{ |
|||
foreach (var prop in rigProperties) |
|||
NaughtyEditorGUI.PropertyField_Layout(prop, true); |
|||
|
|||
} |
|||
if (EditorGUI.EndChangeCheck()) |
|||
{ |
|||
serializedObject.ApplyModifiedProperties(); |
|||
} |
|||
|
|||
EditorGUI.EndDisabledGroup(); |
|||
|
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 1b748c575e0cce149af9192eafda77a0 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
撰写
预览
正在加载...
取消
保存
Reference in new issue