浏览代码

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 3 年前
当前提交
a54b34ad
共有 27 个文件被更改,包括 1151 次插入38 次删除
  1. 7
      CHANGELOG.md
  2. 2
      Editor/CheckWindow/CheckWindow.cs
  3. 3
      Editor/GameplayIngredients-Editor.asmdef
  4. 2
      Editor/GlobalsDebugWindow/GlobalsDebugWindow.cs
  5. 4
      Editor/PropertyDrawers/CallablePropertyDrawer.cs
  6. 14
      Runtime/Ingredients/Rigs/DirectorControlRig.cs
  7. 32
      Runtime/Ingredients/Rigs/FloatingRig.cs
  8. 9
      Runtime/Ingredients/Rigs/FollowPathRig.cs
  9. 7
      Runtime/Ingredients/Rigs/LookAtRig.cs
  10. 9
      Runtime/Ingredients/Rigs/ReachPositionRig.cs
  11. 12
      Runtime/Ingredients/Rigs/RigidBodyForceRig.cs
  12. 10
      Runtime/Ingredients/Rigs/RotationRig.cs
  13. 94
      Editor/CustomInspectors/PingableEditor.cs
  14. 11
      Editor/CustomInspectors/PingableEditor.cs.meta
  15. 8
      Editor/Rigs.meta
  16. 69
      Runtime/Ingredients/Rigs/Rig.cs
  17. 11
      Runtime/Ingredients/Rigs/Rig.cs.meta
  18. 111
      Runtime/Ingredients/Rigs/RigManager.cs
  19. 11
      Runtime/Ingredients/Rigs/RigManager.cs.meta
  20. 644
      Editor/IngredientsExplorer/IngredientsExplorerWindow.cs
  21. 108
      Editor/Rigs/RigEditor.cs
  22. 11
      Editor/Rigs/RigEditor.cs.meta
  23. 0
      /Editor/IngredientsExplorer.meta
  24. 0
      /Editor/IngredientsExplorer/IngredientsExplorerWindow.cs.meta

7
CHANGELOG.md


## 2020.2.3
####
#### Added
* **Refactor**: Rigs Single Update. Rigs are now behaviors that are ticked at various rates and priorities. While this refactor is supposed to match as closely as possible the order of execution of rigs, the order can change slightly (for rigs of the same priority).
* Renamed **Call Tree Explorer** to **Ingredients Explorer** as it is more general purpose now.
* Added Rigs to **Ingredients Explorer** : that view summarizes currently loaded rigs, and groups them in update groups.
* Added abstract `PingableEditor` class for MonoBehaviours that can be Pinged to stand out in the inspector.
## 2020.2.2

2
Editor/CheckWindow/CheckWindow.cs


{
public class CheckWindow : EditorWindow
{
[MenuItem("Window/Gameplay Ingredients/Check and Resolve")]
[MenuItem("Window/Gameplay Ingredients/Check and Resolve", priority = MenuItems.kWindowMenuPriority+30)]
static void OpenWindow()
{
GetWindow<CheckWindow>(false);

3
Editor/GameplayIngredients-Editor.asmdef


{
"name": "GameplayIngredients-Editor",
"rootNamespace": "",
"NaughtyAttributes>Editor",
"NaughtyAttributes.Editor",
"GameplayIngredients",
"Unity.ugui",
"Unity.Timeline",

2
Editor/GlobalsDebugWindow/GlobalsDebugWindow.cs


{
public class GlobalsDebugWindow : EditorWindow
{
[MenuItem("Window/Gameplay Ingredients/Globals Debug")]
[MenuItem("Window/Gameplay Ingredients/Globals Debug", priority = MenuItems.kWindowMenuPriority + 30)]
static void Open()
{
GetWindow<GlobalsDebugWindow>();

4
Editor/PropertyDrawers/CallablePropertyDrawer.cs


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

14
Runtime/Ingredients/Rigs/DirectorControlRig.cs


using UnityEngine.Playables;
using UnityEngine.Timeline;
using NaughtyAttributes;
using UnityEngine.PlayerLoop;
public class DirectorControlRig : MonoBehaviour
public class DirectorControlRig : Rig
public override int defaultPriority => 0;
public override UpdateMode defaultUpdateMode => UpdateMode.Update;
public enum PlayMode
{
Stop,

float m_StopTime = -1.0f;
PlayMode m_PlayMode;
private void OnEnable()
protected override void OnEnable()
base.OnEnable();
if (director != null)
{
m_PlayMode = InitialPlayMode;

}
public void Update()
public override void UpdateRig(float deltaTime)
{
if(m_PlayMode != PlayMode.Stop)
{

break;
}
}
}
}
}

32
Runtime/Ingredients/Rigs/FloatingRig.cs


using System.Collections.Generic;
using UnityEngine;
public class FloatingRig : MonoBehaviour
namespace GameplayIngredients.Rigs
public Vector3 Frequency = new Vector3(4,5,6);
public Vector3 Amplitude = new Vector3(0.0f, 0.2f, 0.0f);
public class FloatingRig : Rig
{
public override int defaultPriority => 0;
public override UpdateMode defaultUpdateMode => UpdateMode.Update;
Vector3 m_InitialPosition;
public Vector3 Frequency = new Vector3(4, 5, 6);
public Vector3 Amplitude = new Vector3(0.0f, 0.2f, 0.0f);
private void Awake()
{
m_InitialPosition = transform.position;
}
Vector3 m_InitialPosition;
private void Update()
{
float t = Time.time;
transform.position = m_InitialPosition + new Vector3(Mathf.Sin(t * Frequency.x) * Amplitude.x, Mathf.Sin(t * Frequency.y) * Amplitude.y, Mathf.Sin(t * Frequency.z) * Amplitude.z);
private void Awake()
{
m_InitialPosition = transform.position;
}
public override void UpdateRig(float deltaTime)
{
float t = (updateMode == UpdateMode.FixedUpdate)? Time.fixedTime : Time.time;
transform.position = m_InitialPosition + new Vector3(Mathf.Sin(t * Frequency.x) * Amplitude.x, Mathf.Sin(t * Frequency.y) * Amplitude.y, Mathf.Sin(t * Frequency.z) * Amplitude.z);
}

9
Runtime/Ingredients/Rigs/FollowPathRig.cs


namespace GameplayIngredients.Rigs
{
public class FollowPathRig : MonoBehaviour
public class FollowPathRig : Rig
public override int defaultPriority => 0;
public override UpdateMode defaultUpdateMode => UpdateMode.LateUpdate;
public enum PlayMode
{
Playing,

m_Progress = 0.0f;
}
private void LateUpdate()
public override void UpdateRig(float deltaTime)
{
if(m_PlayMode != PlayMode.Stopped)
{

Vector3 dir = ( outPos - inPos ).normalized;
Vector3 pos = Vector3.Lerp( sign > 0? inPos : outPos, sign > 0? outPos : inPos, m_Progress % 1.0f);
Vector3 move = dir * Speed * Time.deltaTime;
Vector3 move = dir * Speed * deltaTime;
float moveT = move.magnitude / (outPos - inPos).magnitude * sign;
m_Progress = Mathf.Clamp(m_Progress + moveT, (sign > 0)? idx : nextidx, (sign > 0) ? nextidx : idx);

7
Runtime/Ingredients/Rigs/LookAtRig.cs


namespace GameplayIngredients.Rigs
{
public class LookAtRig : MonoBehaviour
public class LookAtRig : Rig
public override int defaultPriority => 0;
public override UpdateMode defaultUpdateMode => UpdateMode.LateUpdate;
void Update()
public override void UpdateRig(float deltaTime)
{
if (LookAtTarget != null)
{

9
Runtime/Ingredients/Rigs/ReachPositionRig.cs


namespace GameplayIngredients.Rigs
{
public class ReachPositionRig : MonoBehaviour
public class ReachPositionRig : Rig
public override int defaultPriority => 0;
public override UpdateMode defaultUpdateMode => UpdateMode.LateUpdate;
public Transform target => m_Target;
[Header("Target")]

return m_Target != null && m_Target.transform.parent != transform.parent;
}
void LateUpdate()
public override void UpdateRig(float deltaTime)
{
if(m_Target != null)
{

else
{
var delta = m_Target.position - transform.position;
var speed = Time.deltaTime * Mathf.Min((Dampen * delta.magnitude), MaximumVelocity);
var speed = deltaTime * Mathf.Min((Dampen * delta.magnitude), MaximumVelocity);
gameObject.transform.position += delta.normalized * speed;
m_PositionReached = false;
}

12
Runtime/Ingredients/Rigs/RigidBodyForceRig.cs


namespace GameplayIngredients.Rigs
{
[RequireComponent(typeof(Rigidbody))]
public class RigidBodyForceRig : MonoBehaviour
public class RigidBodyForceRig : Rig
public override int defaultPriority => 0;
public override UpdateMode defaultUpdateMode => UpdateMode.Update;
private Rigidbody m_RigidBody;
public enum EffectorType

return !isExplosion();
}
private void OnEnable()
protected override void OnEnable()
base.OnEnable();
m_RigidBody = GetComponent<Rigidbody>();
m_Time = 0.0f;
}

return noise;
}
public void Update()
public override void UpdateRig(float deltaTime)
m_Time += Time.deltaTime;
m_Time += deltaTime;
Vector3 force = vector;
float attenuation = ForceOverTime.Evaluate(m_Time);

10
Runtime/Ingredients/Rigs/RotationRig.cs


namespace GameplayIngredients.Rigs
{
public class RotationRig : MonoBehaviour
public class RotationRig : Rig
public override int defaultPriority => 0;
public override UpdateMode defaultUpdateMode => UpdateMode.Update;
public override bool canChangeUpdateMode => false;
void Update()
public override void UpdateRig(float deltaTime)
transform.Rotate(RotationAxis.normalized, RotationSpeed * Time.deltaTime, Space);
transform.Rotate(RotationAxis.normalized, RotationSpeed * deltaTime, Space);
}
}
}

94
Editor/CustomInspectors/PingableEditor.cs


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;
}
}
}

11
Editor/CustomInspectors/PingableEditor.cs.meta


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

8
Editor/Rigs.meta


fileFormatVersion: 2
guid: 3b5218c80b9574b438d16bb9e5eb12d1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

69
Runtime/Ingredients/Rigs/Rig.cs


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);
}
}

11
Runtime/Ingredients/Rigs/Rig.cs.meta


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

111
Runtime/Ingredients/Rigs/RigManager.cs


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);
}
}
}

11
Runtime/Ingredients/Rigs/RigManager.cs.meta


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

644
Editor/IngredientsExplorer/IngredientsExplorerWindow.cs


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}");
}
}
}
}
}

108
Editor/Rigs/RigEditor.cs


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();
}
}
}

11
Editor/Rigs/RigEditor.cs.meta


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

/Editor/CallTree.meta → /Editor/IngredientsExplorer.meta

/Editor/CallTree/CallTreeWindow.cs.meta → /Editor/IngredientsExplorer/IngredientsExplorerWindow.cs.meta

正在加载...
取消
保存