This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.2D.IK.Tests.EditorTests")]

View File

@@ -0,0 +1,527 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.U2D.Common;
using UnityEngine.U2D.IK;
using UnityEngine.Profiling;
namespace UnityEditor.U2D.IK
{
internal class IKEditorManager : ScriptableSingleton<IKEditorManager>
{
private readonly HashSet<IKManager2D> m_DirtyManagers = new HashSet<IKManager2D>();
private readonly HashSet<Solver2D> m_IKSolvers = new HashSet<Solver2D>();
private readonly List<IKManager2D> m_IKManagers = new List<IKManager2D>();
private readonly Dictionary<IKChain2D, Vector3> m_ChainPositionOverrides = new Dictionary<IKChain2D, Vector3>();
private readonly List<Vector3> m_TargetPositions = new List<Vector3>();
private GameObject m_Helper;
private GameObject[] m_SelectedGameobjects;
private bool m_IgnorePostProcessModifications = false;
private HashSet<Transform> m_IgnoreTransformsOnUndo = new HashSet<Transform>();
internal bool isDraggingATool { get; private set; }
internal bool isDragging { get { return IKGizmos.instance.isDragging || isDraggingATool; } }
[InitializeOnLoadMethod]
private static void Setup()
{
instance.Create();
}
private void Create() {}
private void OnEnable()
{
SetupLateUpdateHelper();
RegisterCallbacks();
Initialize();
}
private void OnDisable()
{
UnregisterCallbacks();
DestroyLateUpdateHelper();
}
private void RegisterCallbacks()
{
EditorApplication.hierarchyChanged += Initialize;
Undo.postprocessModifications += OnPostProcessModifications;
#if UNITY_2019_1_OR_NEWER
SceneView.duringSceneGui += OnSceneGUI;
#else
SceneView.onSceneGUIDelegate += OnSceneGUI;
#endif
Selection.selectionChanged += OnSelectionChanged;
}
private void UnregisterCallbacks()
{
EditorApplication.hierarchyChanged -= Initialize;
Undo.postprocessModifications -= OnPostProcessModifications;
#if UNITY_2019_1_OR_NEWER
SceneView.duringSceneGui -= OnSceneGUI;
#else
SceneView.onSceneGUIDelegate -= OnSceneGUI;
#endif
Selection.selectionChanged -= OnSelectionChanged;
}
private bool m_EnableGizmos;
private bool m_CurrentEnableGizmoState;
void OnDrawGizmos()
{
m_EnableGizmos = true;
IKManager2D.onDrawGizmos.RemoveListener(OnDrawGizmos);
}
public void CheckGizmoToggle()
{
//Ignore events other than Repaint
if (Event.current.type != EventType.Repaint)
return;
if (m_CurrentEnableGizmoState != m_EnableGizmos)
SceneView.RepaintAll();
m_CurrentEnableGizmoState = m_EnableGizmos;
//Assume the Gizmo toggle is disabled and listen to the event again
m_EnableGizmos = false;
IKManager2D.onDrawGizmos.RemoveListener(OnDrawGizmos);
IKManager2D.onDrawGizmos.AddListener(OnDrawGizmos);
}
private void OnSelectionChanged()
{
m_SelectedGameobjects = null;
}
private void SetupLateUpdateHelper()
{
if (m_Helper != null)
return;
m_Helper = new GameObject("IKEditorManagerHelper");
m_Helper.hideFlags = HideFlags.HideAndDontSave;
var helper = m_Helper.AddComponent<IKEditorManagerHelper>();
helper.onLateUpdate.AddListener(OnLateUpdate);
}
private void DestroyLateUpdateHelper()
{
if (m_Helper != null)
GameObject.DestroyImmediate(m_Helper);
}
public void Initialize()
{
m_IKManagers.Clear();
m_IKSolvers.Clear();
m_DirtyManagers.Clear();
m_ChainPositionOverrides.Clear();
var currentStage = StageUtility.GetCurrentStageHandle();
var managers = currentStage.FindComponentsOfType<IKManager2D>().Where(x => x.gameObject.scene.isLoaded).ToArray();
m_IKManagers.AddRange(managers);
foreach (IKManager2D manager in m_IKManagers)
{
foreach (Solver2D solver in manager.solvers)
{
if (solver)
m_IKSolvers.Add(solver);
}
}
}
public IKManager2D FindManager(Solver2D solver)
{
foreach (IKManager2D manager in m_IKManagers)
{
if (manager == null)
continue;
foreach (Solver2D s in manager.solvers)
{
if (s == null)
continue;
if (s == solver)
return manager;
}
}
return null;
}
public void Record(Solver2D solver, string undoName)
{
var manager = FindManager(solver);
DoUndo(manager, undoName, true);
}
public void RegisterUndo(Solver2D solver, string undoName)
{
var manager = FindManager(solver);
DoUndo(manager, undoName, false);
}
public void Record(IKManager2D manager, string undoName)
{
DoUndo(manager, undoName, true);
}
public void RegisterUndo(IKManager2D manager, string undoName)
{
DoUndo(manager, undoName, false);
}
private void DoUndo(IKManager2D manager, string undoName, bool record)
{
if (manager == null)
return;
foreach (var solver in manager.solvers)
{
if (solver == null || !solver.isActiveAndEnabled)
continue;
if (!solver.isValid)
solver.Initialize();
if (!solver.isValid)
continue;
for (int i = 0; i < solver.chainCount; ++i)
{
var chain = solver.GetChain(i);
if (record)
{
foreach(var t in chain.transforms)
{
if(m_IgnoreTransformsOnUndo.Contains(t))
continue;
Undo.RecordObject(t, undoName);
}
if(chain.target && !m_IgnoreTransformsOnUndo.Contains(chain.target))
Undo.RecordObject(chain.target, undoName);
}
else
{
foreach(var t in chain.transforms)
{
if(m_IgnoreTransformsOnUndo.Contains(t))
continue;
Undo.RegisterCompleteObjectUndo(t, undoName);
}
if(chain.target && !m_IgnoreTransformsOnUndo.Contains(chain.target))
Undo.RegisterCompleteObjectUndo(chain.target, undoName);
}
}
m_IgnorePostProcessModifications = true;
}
}
public void UpdateManagerImmediate(IKManager2D manager, bool recordRootLoops)
{
SetManagerDirty(manager);
UpdateDirtyManagers(recordRootLoops);
}
public void UpdateSolverImmediate(Solver2D solver, bool recordRootLoops)
{
SetSolverDirty(solver);
UpdateDirtyManagers(recordRootLoops);
}
public void UpdateHierarchyImmediate(Transform hierarchyRoot, bool recordRootLoops)
{
SetDirtyUnderHierarchy(hierarchyRoot);
UpdateDirtyManagers(recordRootLoops);
}
public void SetChainPositionOverride(IKChain2D chain, Vector3 position)
{
m_ChainPositionOverrides[chain] = position;
}
private bool IsViewToolActive()
{
int button = Event.current.button;
return Tools.current == Tool.View || Event.current.alt || (button == 1) || (button == 2);
}
private bool IsDraggingATool()
{
//If a tool has used EventType.MouseDrag, we won't be able to detect it. Instead we check for delta magnitude
return GUIUtility.hotControl != 0 && Event.current.button == 0 && Event.current.delta.sqrMagnitude > 0f && !IsViewToolActive();
}
private void OnSceneGUI(SceneView sceneView)
{
CheckGizmoToggle();
if (!m_CurrentEnableGizmoState)
return;
if (m_SelectedGameobjects == null)
m_SelectedGameobjects = Selection.gameObjects;
foreach (Solver2D solver in m_IKSolvers)
IKGizmos.instance.DoSolverGUI(solver);
if (!IKGizmos.instance.isDragging && IsDraggingATool())
{
//We expect the object to be selected while dragged
foreach (var gameObject in m_SelectedGameobjects)
{
if (gameObject != null && gameObject.transform != null)
SetDirtySolversAffectedByTransform(gameObject.transform);
}
if(m_DirtyManagers.Count > 0 && !isDraggingATool)
{
isDraggingATool = true;
Undo.SetCurrentGroupName("IK Update");
RegisterUndoForDirtyManagers();
}
}
if(GUIUtility.hotControl == 0)
isDraggingATool = false;
}
internal void OnLateUpdate()
{
if (Application.isPlaying)
return;
Profiler.BeginSample("IKEditorManager.OnLateUpdate");
SetAllManagersDirty();
UpdateDirtyManagers(false);
Profiler.EndSample();
}
private bool ProcessTransformPropertyModification(UndoPropertyModification modification, out Transform transform)
{
transform = null;
var targetType = modification.currentValue.target.GetType();
if ((targetType == typeof(Transform) || targetType.IsSubclassOf(typeof(Transform))))
{
transform = (Transform)modification.currentValue.target;
return true;
}
return false;
}
private UndoPropertyModification[] OnPostProcessModifications(UndoPropertyModification[] modifications)
{
if(!m_IgnorePostProcessModifications && !isDragging)
{
//Prepare transforms that already have an undo modification
foreach (var modification in modifications)
{
if (modification.currentValue == null)
continue;
Transform transform;
if (ProcessTransformPropertyModification(modification, out transform))
m_IgnoreTransformsOnUndo.Add(transform);
}
var processedObjectList = new HashSet<Object>();
foreach (var modification in modifications)
{
if (modification.currentValue == null)
continue;
var target = modification.currentValue.target;
if(processedObjectList.Contains(target))
continue;
processedObjectList.Add(target);
var targetType = target.GetType();
Transform transform;
if (ProcessTransformPropertyModification(modification, out transform))
{
SetDirtySolversAffectedByTransform(transform);
RegisterUndoForDirtyManagers();
}
if (targetType == typeof(Solver2D) || targetType.IsSubclassOf(typeof(Solver2D)))
{
var solver = (Solver2D)modification.currentValue.target;
SetSolverDirty(solver);
RegisterUndoForDirtyManagers();
}
if (targetType == typeof(IKManager2D))
{
var dirtyManager = (IKManager2D)modification.currentValue.target;
SetManagerDirty(dirtyManager);
RegisterUndoForDirtyManagers();
}
}
m_IgnoreTransformsOnUndo.Clear();
}
m_IgnorePostProcessModifications = false;
return modifications;
}
private void SetSolverDirty(Solver2D solver)
{
if (solver && solver.isValid && solver.isActiveAndEnabled)
SetManagerDirty(FindManager(solver));
}
private void SetManagerDirty(IKManager2D manager)
{
if (manager && manager.isActiveAndEnabled)
m_DirtyManagers.Add(manager);
}
private void SetAllManagersDirty()
{
m_DirtyManagers.Clear();
foreach (IKManager2D manager in m_IKManagers)
SetManagerDirty(manager);
}
private void SetDirtyUnderHierarchy(Transform hierarchyRoot)
{
if (hierarchyRoot == null)
return;
foreach (Solver2D solver in m_IKSolvers)
{
if (solver.isValid)
{
for (int i = 0; i < solver.chainCount; ++i)
{
var chain = solver.GetChain(i);
if(chain.target == null)
continue;
if (hierarchyRoot == chain.target ||
IKUtility.IsDescendentOf(chain.target, hierarchyRoot) ||
IKUtility.IsDescendentOf(chain.effector, hierarchyRoot))
{
SetSolverDirty(solver);
break;
}
}
}
}
}
private void SetDirtySolversAffectedByTransform(Transform transform)
{
foreach (Solver2D solver in m_IKSolvers)
{
if (solver.isValid)
{
for (int i = 0; i < solver.chainCount; ++i)
{
var chain = solver.GetChain(i);
if(chain.target == null)
continue;
if (!(IKUtility.IsDescendentOf(chain.target, transform) && IKUtility.IsDescendentOf(chain.rootTransform, transform)) &&
(chain.target == transform || IKUtility.IsDescendentOf(chain.target, transform) || IKUtility.IsDescendentOf(chain.effector, transform)))
{
SetSolverDirty(solver);
break;
}
}
}
}
}
private void RegisterUndoForDirtyManagers()
{
foreach (var manager in m_DirtyManagers)
RegisterUndo(manager, Undo.GetCurrentGroupName());
}
private void UpdateDirtyManagers(bool recordRootLoops)
{
foreach (var manager in m_DirtyManagers)
{
if (manager == null || !manager.isActiveAndEnabled)
continue;
foreach (var solver in manager.solvers)
{
if (solver == null || !solver.isActiveAndEnabled)
continue;
if (!solver.isValid)
solver.Initialize();
if (!solver.isValid)
continue;
if(solver.allChainsHaveTargets)
solver.UpdateIK(manager.weight);
else if(PrepareTargetOverrides(solver))
solver.UpdateIK(m_TargetPositions, manager.weight);
for (int i = 0; i < solver.chainCount; ++i)
{
var chain = solver.GetChain(i);
if (recordRootLoops)
InternalEngineBridge.SetLocalEulerHint(chain.rootTransform);
if(solver.constrainRotation && chain.target != null)
InternalEngineBridge.SetLocalEulerHint(chain.effector);
}
}
}
m_DirtyManagers.Clear();
m_ChainPositionOverrides.Clear();
}
private bool PrepareTargetOverrides(Solver2D solver)
{
m_TargetPositions.Clear();
for (int i = 0; i < solver.chainCount; ++i)
{
var chain = solver.GetChain(i);
Vector3 positionOverride;
if (!m_ChainPositionOverrides.TryGetValue(chain, out positionOverride))
{
m_TargetPositions.Clear();
return false;
}
m_TargetPositions.Add(positionOverride);
}
return true;
}
}
}

View File

@@ -0,0 +1,238 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.U2D.IK;
namespace UnityEditor.U2D.IK
{
internal class IKGizmos : ScriptableSingleton<IKGizmos>
{
private static readonly int kTargetHashCode = "IkTarget".GetHashCode();
private Color enabledColor = Color.green;
private Color disabledColor = Color.grey;
private const float kCircleHandleRadius = 0.1f;
private const float kNodeRadius = 0.05f;
private const float kDottedLineLength = 5f;
private const float kFadeStart = 0.75f;
private const float kFadeEnd = 1.75f;
private Dictionary<IKChain2D, Vector3> m_ChainPositionOverrides = new Dictionary<IKChain2D, Vector3>();
public bool isDragging { get; private set; }
public void DoSolverGUI(Solver2D solver)
{
if (solver == null || !solver.isValid)
return;
IKManager2D manager = IKEditorManager.instance.FindManager(solver);
if (!solver.isActiveAndEnabled || manager == null || !manager.isActiveAndEnabled)
return;
var solverData = manager.GetSolverEditorData(solver);
if (!solverData.showGizmo)
return;
DrawSolver(solver, solverData);
var allChainsHaveTargets = solver.allChainsHaveTargets;
for (int i = 0; i < solver.chainCount; ++i)
{
var chain = solver.GetChain(i);
if (chain == null)
continue;
if (allChainsHaveTargets)
{
if (!IsTargetTransformSelected(chain))
DoTargetGUI(solver, chain);
}
else if(chain.target == null)
DoIkPoseGUI(solver, chain);
}
if(GUIUtility.hotControl == 0)
isDragging = false;
}
private void DoTargetGUI(Solver2D solver, IKChain2D chain)
{
int controlId = GUIUtility.GetControlID(kTargetHashCode, FocusType.Passive);
var color = FadeFromChain(Color.white, chain);
if (!isDragging && (color.a == 0f || !IsVisible(chain.target.position)))
return;
EditorGUI.BeginChangeCheck();
Handles.color = color;
var newPosition = Handles.Slider2D(controlId, chain.target.position, chain.target.forward, chain.target.up, chain.target.right, HandleUtility.GetHandleSize(chain.effector.position) * kCircleHandleRadius, Handles.CircleHandleCap, Vector2.zero);
if (EditorGUI.EndChangeCheck())
{
if(!isDragging)
{
isDragging = true;
IKEditorManager.instance.RegisterUndo(solver, "Move Target");
}
Undo.RecordObject(chain.target, "Move Target");
chain.target.position = newPosition;
}
}
private void DoIkPoseGUI(Solver2D solver, IKChain2D chain)
{
int controlId = GUIUtility.GetControlID(kTargetHashCode, FocusType.Passive);
var color = FadeFromChain(Color.white, chain);
if (!isDragging && (color.a == 0f || !IsVisible(chain.effector.position)))
return;
if (HandleUtility.nearestControl == controlId && Event.current.type == EventType.MouseDown && Event.current.button == 0)
StoreSolverPositionOverrides(solver);
EditorGUI.BeginChangeCheck();
Handles.color = color;
Vector3 newPosition = Handles.Slider2D(controlId, chain.effector.position, chain.effector.forward, chain.effector.up, chain.effector.right, HandleUtility.GetHandleSize(chain.effector.position) * kCircleHandleRadius, Handles.CircleHandleCap, Vector2.zero);
if (EditorGUI.EndChangeCheck())
{
if(!isDragging)
isDragging = true;
IKEditorManager.instance.Record(solver, "IK Pose");
SetSolverPositionOverrides();
IKEditorManager.instance.SetChainPositionOverride(chain, newPosition);
IKEditorManager.instance.UpdateSolverImmediate(solver, true);
}
}
private void StoreSolverPositionOverrides(Solver2D solver)
{
Debug.Assert(solver.allChainsHaveTargets == false);
m_ChainPositionOverrides.Clear();
IKManager2D manager = IKEditorManager.instance.FindManager(solver);
foreach (Solver2D l_solver in manager.solvers)
{
if(l_solver == null || l_solver.allChainsHaveTargets)
continue;
for (int i = 0; i < l_solver.chainCount; ++i)
{
var chain = l_solver.GetChain(i);
if (chain.effector != null)
m_ChainPositionOverrides[chain] = chain.effector.position;
}
}
}
private void SetSolverPositionOverrides()
{
foreach (var pair in m_ChainPositionOverrides)
IKEditorManager.instance.SetChainPositionOverride(pair.Key, pair.Value);
}
private bool IsTargetTransformSelected(IKChain2D chain)
{
Debug.Assert(chain.target != null);
return Selection.Contains(chain.target.gameObject);
}
private void DrawSolver(Solver2D solver, IKManager2D.SolverEditorData editorData)
{
if (Event.current.type != EventType.Repaint)
return;
for (int i = 0; i < solver.chainCount; ++i)
{
var chain = solver.GetChain(i);
if (chain != null)
DrawChain(chain, editorData.color, solver.allChainsHaveTargets);
}
}
private void DrawChain(IKChain2D chain, Color solverColor, bool solverHasTargets)
{
Handles.matrix = Matrix4x4.identity;
Color color = FadeFromChain(solverColor, chain);
if (color.a == 0f)
return;
Transform currentTransform = chain.effector;
for (int i = 0; i < chain.transformCount - 1; ++i)
{
var parentPosition = currentTransform.parent.position;
var position = currentTransform.position;
Vector3 projectedLocalPosition = Vector3.Project(currentTransform.localPosition, Vector3.right);
Vector3 projectedEndPoint = currentTransform.parent.position + currentTransform.parent.TransformVector(projectedLocalPosition);
var visible = IsVisible(projectedEndPoint) || IsVisible(position);
if (visible && currentTransform.localPosition.sqrMagnitude != projectedLocalPosition.sqrMagnitude)
{
Color red = Color.red;
red.a = color.a;
Handles.color = red;
Handles.DrawDottedLine(projectedEndPoint, position, kDottedLineLength);
}
visible = IsVisible(parentPosition) || IsVisible(projectedEndPoint);
Handles.color = color;
if (visible)
Handles.DrawDottedLine(parentPosition, projectedEndPoint, kDottedLineLength);
currentTransform = currentTransform.parent;
}
Handles.color = color;
currentTransform = chain.effector;
for (int i = 0; i < chain.transformCount; ++i)
{
var position = currentTransform.position;
var size = HandleUtility.GetHandleSize(position);
if (IsVisible(position))
Handles.DrawSolidDisc(position, currentTransform.forward, kNodeRadius * size);
currentTransform = currentTransform.parent;
}
Handles.color = Color.white;
}
private Color FadeFromChain(Color color, IKChain2D chain)
{
var size = HandleUtility.GetHandleSize(chain.effector.position);
var scaleFactor = 1f;
var lengths = chain.lengths;
foreach (var length in lengths)
scaleFactor = Mathf.Max(scaleFactor, length);
return FadeFromSize(color, size, kFadeStart * scaleFactor, kFadeEnd * scaleFactor);
}
private Color FadeFromSize(Color color, float size, float fadeStart, float fadeEnd)
{
float alpha = Mathf.Lerp(1f, 0f, (size - fadeStart) / (fadeEnd - fadeStart));
color.a = alpha;
return color;
}
private bool IsVisible(Vector3 position)
{
var screenPos = HandleUtility.GUIPointToScreenPixelCoordinate(HandleUtility.WorldToGUIPoint(position));
if (screenPos.x < 0f || screenPos.x > Camera.current.pixelWidth || screenPos.y < 0f || screenPos.y > Camera.current.pixelHeight)
return false;
return true;
}
}
}

View File

@@ -0,0 +1,63 @@
using UnityEngine;
using UnityEngine.U2D.IK;
namespace UnityEditor.U2D.IK
{
/// <summary>
/// Custom Inspector for CCDSolver2D.
/// </summary>
[CustomEditor(typeof(CCDSolver2D))]
[CanEditMultipleObjects]
public class CCDSolver2DEditor : Solver2DEditor
{
private static class Contents
{
public static readonly GUIContent effectorLabel = new GUIContent("Effector", "The last Transform of a hierarchy constrained by the target");
public static readonly GUIContent targetLabel = new GUIContent("Target", "Transfrom which the effector will follow");
public static readonly GUIContent chainLengthLabel = new GUIContent("Chain Length", "Number of Transforms handled by the IK");
public static readonly GUIContent iterationsLabel = new GUIContent("Iterations", "Number of iterations the IK solver is run per frame");
public static readonly GUIContent toleranceLabel = new GUIContent("Tolerance", "How close the target is to the goal to be considered as successful");
public static readonly GUIContent velocityLabel = new GUIContent("Velocity", "How fast the chain elements rotate to the effector per iteration");
}
private SerializedProperty m_TargetProperty;
private SerializedProperty m_EffectorProperty;
private SerializedProperty m_TransformCountProperty;
private SerializedProperty m_IterationsProperty;
private SerializedProperty m_ToleranceProperty;
private SerializedProperty m_VelocityProperty;
private CCDSolver2D m_Solver;
private void OnEnable()
{
m_Solver = target as CCDSolver2D;
var chainProperty = serializedObject.FindProperty("m_Chain");
m_TargetProperty = chainProperty.FindPropertyRelative("m_TargetTransform");
m_EffectorProperty = chainProperty.FindPropertyRelative("m_EffectorTransform");
m_TransformCountProperty = chainProperty.FindPropertyRelative("m_TransformCount");
m_IterationsProperty = serializedObject.FindProperty("m_Iterations");
m_ToleranceProperty = serializedObject.FindProperty("m_Tolerance");
m_VelocityProperty = serializedObject.FindProperty("m_Velocity");
}
/// <summary>
/// Custom Inspector OnInspectorGUI override.
/// </summary>
public override void OnInspectorGUI()
{
IKChain2D chain = m_Solver.GetChain(0);
serializedObject.Update();
EditorGUILayout.PropertyField(m_EffectorProperty, Contents.effectorLabel);
EditorGUILayout.PropertyField(m_TargetProperty, Contents.targetLabel);
EditorGUILayout.IntSlider(m_TransformCountProperty, 0, IKUtility.GetMaxChainCount(chain), Contents.chainLengthLabel);
EditorGUILayout.PropertyField(m_IterationsProperty, Contents.iterationsLabel);
EditorGUILayout.PropertyField(m_ToleranceProperty, Contents.toleranceLabel);
EditorGUILayout.PropertyField(m_VelocityProperty, Contents.velocityLabel);
DrawCommonSolverInspector();
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,59 @@
using UnityEngine;
using UnityEngine.U2D.IK;
namespace UnityEditor.U2D.IK
{
/// <summary>
/// Custom Inspector for FabrikSolver2D.
/// </summary>
[CustomEditor(typeof(FabrikSolver2D))]
[CanEditMultipleObjects]
public class FabrikSolver2DEditor : Solver2DEditor
{
private static class Contents
{
public static readonly GUIContent effectorLabel = new GUIContent("Effector", "The last Transform of a hierarchy constrained by the target");
public static readonly GUIContent targetLabel = new GUIContent("Target", "Transfrom which the effector will follow");
public static readonly GUIContent chainLengthLabel = new GUIContent("Chain Length", "Number of Transforms handled by the IK");
public static readonly GUIContent iterationsLabel = new GUIContent("Iterations", "Number of iterations the IK solver is run per frame");
public static readonly GUIContent toleranceLabel = new GUIContent("Tolerance", "How close the target is to the goal to be considered as successful");
}
private SerializedProperty m_TargetProperty;
private SerializedProperty m_EffectorProperty;
private SerializedProperty m_TransformCountProperty;
private SerializedProperty m_IterationsProperty;
private SerializedProperty m_ToleranceProperty;
private FabrikSolver2D m_Solver;
private void OnEnable()
{
m_Solver = target as FabrikSolver2D;
var chainProperty = serializedObject.FindProperty("m_Chain");
m_TargetProperty = chainProperty.FindPropertyRelative("m_TargetTransform");
m_EffectorProperty = chainProperty.FindPropertyRelative("m_EffectorTransform");
m_TransformCountProperty = chainProperty.FindPropertyRelative("m_TransformCount");
m_IterationsProperty = serializedObject.FindProperty("m_Iterations");
m_ToleranceProperty = serializedObject.FindProperty("m_Tolerance");
}
/// <summary>
/// Custom Inspector OnInspectorGUI override.
/// </summary>
public override void OnInspectorGUI()
{
IKChain2D chain = m_Solver.GetChain(0);
serializedObject.Update();
EditorGUILayout.PropertyField(m_EffectorProperty, Contents.effectorLabel);
EditorGUILayout.PropertyField(m_TargetProperty, Contents.targetLabel);
EditorGUILayout.IntSlider(m_TransformCountProperty, 0, IKUtility.GetMaxChainCount(chain), Contents.chainLengthLabel);
EditorGUILayout.PropertyField(m_IterationsProperty, Contents.iterationsLabel);
EditorGUILayout.PropertyField(m_ToleranceProperty, Contents.toleranceLabel);
DrawCommonSolverInspector();
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,219 @@
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.U2D.IK;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using UnityEditor.U2D.Animation;
namespace UnityEditor.U2D.IK
{
/// <summary>
/// Custom Inspector for IKManager2D.
/// </summary>
[CustomEditor(typeof(IKManager2D))]
[CanEditMultipleObjects]
class IKManager2DEditor : Editor
{
private class Contents
{
public static readonly GUIContent findAllSolversLabel = new GUIContent("Find Solvers", "Find all applicable solvers handled by this manager");
public static readonly GUIContent weightLabel = new GUIContent("Weight", "Blend between Forward and Inverse Kinematics");
public static readonly string listHeaderLabel = "IK Solvers";
public static readonly string createSolverString = "Create Solver";
public static readonly string restoreDefaultPoseString = "Restore Default Pose";
public static readonly GUIContent gizmoColorTooltip = new GUIContent("","Customizes the IK Chain's Gizmo color");
public static int showGizmoPropertyWidth = 20;
public static int solverPropertyWidth = 100;
public static int solverColorPropertyWidth = 40;
public static readonly GUIContent gizmoVisibilityToolTip = new GUIContent("",L10n.Tr("Show/Hide Gizmo"));
public readonly GUIStyle visibilityToggleStyle;
public Contents()
{
visibilityToggleStyle = new GUIStyle();
visibilityToggleStyle.fixedWidth = EditorGUIUtility.singleLineHeight;
visibilityToggleStyle.onNormal.background = IconUtility.LoadIconResource("Visibility_Tool", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath);
visibilityToggleStyle.normal.background = IconUtility.LoadIconResource("Visibility_Hidded", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath);
}
}
static Contents k_Contents;
private ReorderableList m_ReorderableList;
private Solver2D m_SelectedSolver;
private Editor m_SelectedSolverEditor;
SerializedProperty m_SolversProperty;
SerializedProperty m_SolverEditorDataProperty;
SerializedProperty m_WeightProperty;
List<Type> m_SolverTypes;
IKManager2D m_Manager;
private void OnEnable()
{
m_Manager = target as IKManager2D;
m_SolverTypes = GetDerivedTypes<Solver2D>();
m_SolversProperty = serializedObject.FindProperty("m_Solvers");
m_SolverEditorDataProperty = serializedObject.FindProperty("m_SolverEditorData");
m_WeightProperty = serializedObject.FindProperty("m_Weight");
SetupReordeableList();
}
void SetupReordeableList()
{
m_ReorderableList = new ReorderableList(serializedObject, m_SolversProperty, true, true, true, true);
m_ReorderableList.drawHeaderCallback = (Rect rect) =>
{
GUI.Label(rect, Contents.listHeaderLabel);
};
m_ReorderableList.elementHeightCallback = (int index) =>
{
return EditorGUIUtility.singleLineHeight + 6;
};
m_ReorderableList.drawElementCallback = (Rect rect, int index, bool isactive, bool isfocused) =>
{
rect.y += 2f;
rect.height = EditorGUIUtility.singleLineHeight;
SerializedProperty element = m_SolversProperty.GetArrayElementAtIndex(index);
SerializedProperty elementData = m_SolverEditorDataProperty.GetArrayElementAtIndex(index);
var width = rect.width;
rect.width = width > Contents.showGizmoPropertyWidth ? Contents.showGizmoPropertyWidth : width;
var showGizmoProperty = elementData.FindPropertyRelative("showGizmo");
showGizmoProperty.boolValue = GUI.Toggle(rect, showGizmoProperty.boolValue, Contents.gizmoVisibilityToolTip, k_Contents.visibilityToggleStyle);
rect.x += rect.width;
width -= rect.width;
rect.width = width > Contents.solverPropertyWidth ? width - Contents.solverColorPropertyWidth : Contents.solverPropertyWidth;
EditorGUI.PropertyField(rect, element, GUIContent.none);
rect.x += rect.width;
width -= 100;
rect.width = width > Contents.solverColorPropertyWidth ? Contents.solverColorPropertyWidth : width;
EditorGUI.PropertyField(rect, elementData.FindPropertyRelative("color"), Contents.gizmoColorTooltip);
};
m_ReorderableList.onAddCallback = (ReorderableList list) =>
{
var menu = new GenericMenu();
foreach (Type type in m_SolverTypes)
{
Solver2DMenuAttribute attribute = Attribute.GetCustomAttribute(type, typeof(Solver2DMenuAttribute)) as Solver2DMenuAttribute;
if (attribute != null)
menu.AddItem(new GUIContent(attribute.menuPath), false, OnSelectMenu, type);
else
menu.AddItem(new GUIContent(type.Name), false, OnSelectMenu, type);
}
menu.ShowAsContext();
};
m_ReorderableList.onRemoveCallback = (ReorderableList list) =>
{
Solver2D solver = m_Manager.solvers[list.index];
if (solver)
{
Undo.RegisterCompleteObjectUndo(m_Manager, Undo.GetCurrentGroupName());
m_Manager.RemoveSolver(solver);
GameObject solverGO = solver.gameObject;
if (solverGO.transform.childCount == 0)
Undo.DestroyObjectImmediate(solverGO);
else
Undo.DestroyObjectImmediate(solver);
EditorUtility.SetDirty(m_Manager);
}
else
{
ReorderableList.defaultBehaviours.DoRemoveButton(list);
}
};
}
private void OnSelectMenu(object param)
{
Type solverType = param as Type;
GameObject solverGO = new GameObject(GameObjectUtility.GetUniqueNameForSibling(m_Manager.transform, "New " + solverType.Name));
solverGO.transform.SetParent(m_Manager.transform);
solverGO.transform.localPosition = Vector3.zero;
solverGO.transform.rotation = Quaternion.identity;
solverGO.transform.localScale = Vector3.one;
Solver2D solver = solverGO.AddComponent(solverType) as Solver2D;
Undo.RegisterCreatedObjectUndo(solverGO, Contents.createSolverString);
Undo.RegisterCompleteObjectUndo(m_Manager, Contents.createSolverString);
m_Manager.AddSolver(solver);
EditorUtility.SetDirty(m_Manager);
Selection.activeGameObject = solverGO;
}
/// <summary>
/// Custom Inspector OnInspectorGUI override.
/// </summary>
public override void OnInspectorGUI()
{
if(k_Contents == null)
k_Contents = new Contents();
serializedObject.Update();
EditorGUILayout.Space();
m_ReorderableList.DoLayoutList();
EditorGUILayout.Space();
EditorGUILayout.PropertyField(m_WeightProperty, Contents.weightLabel);
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
DoRestoreDefaultPoseButton();
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
serializedObject.ApplyModifiedProperties();
}
private void DoRestoreDefaultPoseButton()
{
if (GUILayout.Button(Contents.restoreDefaultPoseString, GUILayout.MaxWidth(150f)))
{
foreach (var l_target in targets)
{
var manager = l_target as IKManager2D;
IKEditorManager.instance.Record(manager, Contents.restoreDefaultPoseString);
foreach(var solver in manager.solvers)
{
for(int i = 0; i < solver.chainCount; ++i)
{
var chain = solver.GetChain(i);
chain.RestoreDefaultPose(solver.constrainRotation);
if(chain.target)
{
chain.target.position = chain.effector.position;
chain.target.rotation = chain.effector.rotation;
}
}
}
IKEditorManager.instance.UpdateManagerImmediate(manager, true);
}
}
}
List<Type> GetDerivedTypes<T>() where T : class
{
List<Type> types = Assembly.GetAssembly(typeof(T)).GetTypes().Where(myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf(typeof(T))).ToList();
return types;
}
}
}

View File

@@ -0,0 +1,44 @@
using UnityEngine;
using UnityEngine.U2D.IK;
namespace UnityEditor.U2D.IK
{
/// <summary>
/// Custom Inspector for LimbSolver2D.
/// </summary>
[CustomEditor(typeof(LimbSolver2D))]
[CanEditMultipleObjects]
public class LimbSolver2DEditor : Solver2DEditor
{
private static class Contents
{
public static readonly GUIContent effectorLabel = new GUIContent("Effector", "The last Transform of a hierarchy constrained by the target");
public static readonly GUIContent targetLabel = new GUIContent("Target", "Transfrom which the effector will follow");
public static readonly GUIContent flipLabel = new GUIContent("Flip", "Select between the two possible solutions of the solver");
}
private SerializedProperty m_ChainProperty;
private SerializedProperty m_FlipProperty;
private void OnEnable()
{
m_ChainProperty = serializedObject.FindProperty("m_Chain");
m_FlipProperty = serializedObject.FindProperty("m_Flip");
}
/// <summary>
/// Custom Inspector OnInspectorGUI override.
/// </summary>
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_ChainProperty.FindPropertyRelative("m_EffectorTransform"), Contents.effectorLabel);
EditorGUILayout.PropertyField(m_ChainProperty.FindPropertyRelative("m_TargetTransform"), Contents.targetLabel);
EditorGUILayout.PropertyField(m_FlipProperty, Contents.flipLabel);
DrawCommonSolverInspector();
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,164 @@
using UnityEngine;
using UnityEngine.U2D.IK;
namespace UnityEditor.U2D.IK
{
/// <summary>
/// Custom Inspector for Solver2D.
/// </summary>
[CustomEditor(typeof(Solver2D))]
[CanEditMultipleObjects]
public class Solver2DEditor : Editor
{
private static class Contents
{
public static readonly GUIContent constrainRotationLabel = new GUIContent("Constrain Rotation", "Set Effector's rotation to Target");
public static readonly GUIContent solveFromDefaultPoseLabel = new GUIContent("Solve from Default Pose", "Restore transform's rotation to default value before solving the IK");
public static readonly GUIContent weightLabel = new GUIContent("Weight", "Blend between Forward and Inverse Kinematics");
public static readonly string restoreDefaultPoseString = "Restore Default Pose";
public static readonly string createTargetString = "Create Target";
}
private SerializedProperty m_ConstrainRotationProperty;
private SerializedProperty m_SolveFromDefaultPoseProperty;
private SerializedProperty m_WeightProperty;
private SerializedProperty m_SolverColorProperty;
private void SetupProperties()
{
if(m_ConstrainRotationProperty == null || m_SolveFromDefaultPoseProperty == null || m_WeightProperty == null)
{
m_ConstrainRotationProperty = serializedObject.FindProperty("m_ConstrainRotation");
m_SolveFromDefaultPoseProperty = serializedObject.FindProperty("m_SolveFromDefaultPose");
m_WeightProperty = serializedObject.FindProperty("m_Weight");
}
}
/// <summary>
/// Custom Inspector GUI for Solver2D.
/// </summary>
protected void DrawCommonSolverInspector()
{
SetupProperties();
EditorGUILayout.PropertyField(m_ConstrainRotationProperty, Contents.constrainRotationLabel);
EditorGUILayout.PropertyField(m_SolveFromDefaultPoseProperty, Contents.solveFromDefaultPoseLabel);
EditorGUILayout.PropertyField(m_WeightProperty, Contents.weightLabel);
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
EditorGUI.BeginDisabledGroup(!EnableCreateTarget());
DoCreateTargetButton();
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(!EnableRestoreDefaultPose());
DoRestoreDefaultPoseButton();
EditorGUI.EndDisabledGroup();
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
}
private bool EnableRestoreDefaultPose()
{
foreach (var l_target in targets)
{
var solver = l_target as Solver2D;
if (!solver.isValid || IKEditorManager.instance.FindManager(solver) == null)
continue;
return true;
}
return false;
}
private bool EnableCreateTarget()
{
foreach (var l_target in targets)
{
var solver = l_target as Solver2D;
if (!solver.isValid)
continue;
for(int i = 0; i < solver.chainCount; ++i)
{
var chain = solver.GetChain(i);
if(chain.target == null)
return true;
}
}
return false;
}
private void DoRestoreDefaultPoseButton()
{
if (GUILayout.Button(Contents.restoreDefaultPoseString, GUILayout.MaxWidth(150f)))
{
foreach (var l_target in targets)
{
var solver = l_target as Solver2D;
if (!solver.isValid)
continue;
IKEditorManager.instance.Record(solver, Contents.restoreDefaultPoseString);
for(int i = 0; i < solver.chainCount; ++i)
{
var chain = solver.GetChain(i);
chain.RestoreDefaultPose(solver.constrainRotation);
if(chain.target)
{
chain.target.position = chain.effector.position;
chain.target.rotation = chain.effector.rotation;
}
}
IKEditorManager.instance.UpdateSolverImmediate(solver, true);
}
}
}
private void DoCreateTargetButton()
{
if (GUILayout.Button(Contents.createTargetString, GUILayout.MaxWidth(125f)))
{
foreach (var l_target in targets)
{
var solver = l_target as Solver2D;
if (!solver.isValid)
continue;
for(int i = 0; i < solver.chainCount; ++i)
{
var chain = solver.GetChain(i);
if(chain.target == null)
{
Undo.RegisterCompleteObjectUndo(solver, Contents.createTargetString);
chain.target = new GameObject(GameObjectUtility.GetUniqueNameForSibling(solver.transform, solver.name + "_Target")).transform;
chain.target.SetParent(solver.transform);
chain.target.position = chain.effector.position;
chain.target.rotation = chain.effector.rotation;
Undo.RegisterCreatedObjectUndo(chain.target.gameObject, Contents.createTargetString);
}
}
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
{
"name": "Unity.2D.IK.Editor",
"references": [
"Unity.InternalAPIEditorBridge.001",
"Unity.InternalAPIEngineBridge.001",
"Unity.2D.Common.Editor",
"Unity.2D.Common.Runtime",
"Unity.2D.IK.Runtime",
"Unity.2D.Animation.Editor"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}