今天做工具的时候遇到一个神奇的问题,具体就是在使用transform.eulerAngles拿到的数据是经过计算转换的,简单的说就是你面板上的数据是vector3(-100, 0, 0),而访问transform对象拿到的eulerAngles属性是vector3(260, 0, 0),并且会把大于360的值限制在360内,我做的工具是用animationCurve进行曲线旋转,3个轴分别各自插值,但是拿到的这个eulerAngles属性就没法做反向插值旋转,比如我要从x=10插值到x=-10,结果成了x=10到x=350的插值了,并且没办法做到旋转很多圈。
拿到这个问题,我以为是我的工具在其他地方有设置去限定这个的值,因为我看到transform的检视面板上是明明可以看到负值的。后来才发现监视面板上面是负值,但是调用transform对象拿到的eulerAngles的值就已经是转换后的了。那不用说,肯定是Unity在背后做了工作,于是果断反编译了unity的transformInspactor类查看。
transformInspactor源码:
1 namespace UnityEditor 2 { 3 using System; 4 using UnityEngine; 5 6 [CustomEditor(typeof(Transform)), CanEditMultipleObjects] 7 internal class TransformInspector : Editor 8 { 9 private SerializedProperty m_Position; 10 private TransformRotationGUI m_RotationGUI; 11 private SerializedProperty m_Scale; 12 private static Contents s_Contents; 13 14 private void Inspector3D() 15 { 16 EditorGUILayout.PropertyField(this.m_Position, s_Contents.positionContent, new GUILayoutOption[0]); 17 this.m_RotationGUI.RotationField(); 18 EditorGUILayout.PropertyField(this.m_Scale, s_Contents.scaleContent, new GUILayoutOption[0]); 19 } 20 21 public void OnEnable() 22 { 23 this.m_Position = base.serializedObject.FindProperty("m_LocalPosition"); 24 this.m_Scale = base.serializedObject.FindProperty("m_LocalScale"); 25 if (this.m_RotationGUI == null) 26 { 27 this.m_RotationGUI = new TransformRotationGUI(); 28 } 29 this.m_RotationGUI.OnEnable(base.serializedObject.FindProperty("m_LocalRotation"), new GUIContent(LocalizationDatabase.GetLocalizedString("Rotation"))); 30 } 31 32 public override void OnInspectorGUI() 33 { 34 if (s_Contents == null) 35 { 36 s_Contents = new Contents(); 37 } 38 if (!EditorGUIUtility.wideMode) 39 { 40 EditorGUIUtility.wideMode = true; 41 EditorGUIUtility.labelWidth = EditorGUIUtility.currentViewWidth - 212f; 42 } 43 base.serializedObject.Update(); 44 this.Inspector3D(); 45 Transform target = base.target as Transform; 46 Vector3 position = target.position; 47 if (((Mathf.Abs(position.x) > 100000f) || (Mathf.Abs(position.y) > 100000f)) || (Mathf.Abs(position.z) > 100000f)) 48 { 49 EditorGUILayout.HelpBox(s_Contents.floatingPointWarning, MessageType.Warning); 50 } 51 base.serializedObject.ApplyModifiedProperties(); 52 } 53 54 private class Contents 55 { 56 public string floatingPointWarning = LocalizationDatabase.GetLocalizedString("Due to floating-point precision limitations, it is recommended to bring the world coordinates of the GameObject within a smaller range."); 57 public GUIContent positionContent = new GUIContent(LocalizationDatabase.GetLocalizedString("Position"), LocalizationDatabase.GetLocalizedString("The local position of this Game Object relative to the parent.")); 58 public GUIContent scaleContent = new GUIContent(LocalizationDatabase.GetLocalizedString("Scale"), LocalizationDatabase.GetLocalizedString("The local scaling of this Game Object relative to the parent.")); 59 } 60 } 61 }
可以看到里面的旋转属性在检视面板的显示是由TransformRotationGUI这个类处理的
TransformRotationGUI源码:
1 namespace UnityEditor 2 { 3 using System; 4 using UnityEngine; 5 6 [Serializable] 7 internal class TransformRotationGUI 8 { 9 private Vector3 m_EulerAngles; 10 private Vector3 m_OldEulerAngles = new Vector3(1000000f, 1E+07f, 1000000f); 11 private RotationOrder m_OldRotationOrder = RotationOrder.OrderZXY; 12 private SerializedProperty m_Rotation; 13 private GUIContent rotationContent = new GUIContent("Rotation", "The local rotation of this Game Object relative to the parent."); 14 private static int s_FoldoutHash = "Foldout".GetHashCode(); 15 private Object[] targets; 16 17 public void OnEnable(SerializedProperty m_Rotation, GUIContent label) 18 { 19 this.m_Rotation = m_Rotation; 20 this.targets = m_Rotation.serializedObject.targetObjects; 21 this.m_OldRotationOrder = (this.targets[0] as Transform).rotationOrder; 22 this.rotationContent = label; 23 } 24 25 public void RotationField() 26 { 27 this.RotationField(false); 28 } 29 30 public void RotationField(bool disabled) 31 { 32 Transform transform = this.targets[0] as Transform; 33 Vector3 localEulerAngles = transform.GetLocalEulerAngles(transform.rotationOrder); 34 if (((this.m_OldEulerAngles.x != localEulerAngles.x) || (this.m_OldEulerAngles.y != localEulerAngles.y)) || ((this.m_OldEulerAngles.z != localEulerAngles.z) || (this.m_OldRotationOrder != transform.rotationOrder))) 35 { 36 this.m_EulerAngles = transform.GetLocalEulerAngles(transform.rotationOrder); 37 this.m_OldRotationOrder = transform.rotationOrder; 38 } 39 bool flag = false; 40 bool flag2 = false; 41 for (int i = 1; i < this.targets.Length; i++) 42 { 43 Transform transform2 = this.targets[i] as Transform; 44 Vector3 vector2 = transform2.GetLocalEulerAngles(transform2.rotationOrder); 45 flag |= ((vector2.x != localEulerAngles.x) || (vector2.y != localEulerAngles.y)) || !(vector2.z == localEulerAngles.z); 46 flag2 |= transform2.rotationOrder != transform.rotationOrder; 47 } 48 Rect totalPosition = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight * (!EditorGUIUtility.wideMode ? ((float) 2) : ((float) 1)), new GUILayoutOption[0]); 49 GUIContent label = EditorGUI.BeginProperty(totalPosition, this.rotationContent, this.m_Rotation); 50 EditorGUI.showMixedValue = flag; 51 EditorGUI.BeginChangeCheck(); 52 int id = GUIUtility.GetControlID(s_FoldoutHash, FocusType.Keyboard, totalPosition); 53 string str = ""; 54 if (AnimationMode.InAnimationMode() && (transform.rotationOrder != RotationOrder.OrderZXY)) 55 { 56 if (flag2) 57 { 58 str = "Mixed"; 59 } 60 else 61 { 62 str = transform.rotationOrder.ToString(); 63 str = str.Substring(str.Length - 3); 64 } 65 label.text = label.text + " (" + str + ")"; 66 } 67 totalPosition = EditorGUI.MultiFieldPrefixLabel(totalPosition, id, label, 3); 68 totalPosition.height = EditorGUIUtility.singleLineHeight; 69 using (new EditorGUI.DisabledScope(disabled)) 70 { 71 this.m_EulerAngles = EditorGUI.Vector3Field(totalPosition, GUIContent.none, this.m_EulerAngles); 72 } 73 if (EditorGUI.EndChangeCheck()) 74 { 75 Undo.RecordObjects(this.targets, "Inspector"); 76 foreach (Transform transform3 in this.targets) 77 { 78 transform3.SetLocalEulerAngles(this.m_EulerAngles, transform3.rotationOrder); 79 if (transform3.parent != null) 80 { 81 transform3.SendTransformChangedScale(); 82 } 83 } 84 this.m_Rotation.serializedObject.SetIsDifferentCacheDirty(); 85 } 86 EditorGUI.showMixedValue = false; 87 if (flag2) 88 { 89 EditorGUILayout.HelpBox("Transforms have different rotation orders, keyframes saved will have the same value but not the same local rotation", MessageType.Warning); 90 } 91 EditorGUI.EndProperty(); 92 } 93 } 94 }
再仔细看里面得到eulerAngles的方法:
1 // 拿到transform对象 2 Transform transform = this.targets[0] as Transform; 3 4 // 调用transform的GetLocalEulerAngles方法获得原生值(此方法是直接调用到unity的native层) 5 Vector3 localEulerAngles = transform.GetLocalEulerAngles(transform.rotationOrder);
再看反编译的Transform类的GetLocalEulerAngles方法和eulerAngles属性:
public Vector3 eulerAngles { get { return this.rotation.eulerAngles; } set { this.rotation = Quaternion.Euler(value); } }
eulerAngles是用的缓存的四元素,GetLocalEulerAngles是直接访问去了底层。
于是用反射去调用GetLocalEulerAngles,最终结果果然就是检视面板上面的真实值。
请看结果:
最后上调用该方法的代码:
1 // 获取原生值 2 System.Type transformType = transform.GetType(); 3 PropertyInfo m_propertyInfo_rotationOrder = transformType.GetProperty("rotationOrder", BindingFlags.Instance | BindingFlags.NonPublic); 4 object m_OldRotationOrder = m_propertyInfo_rotationOrder.GetValue(transform, null); 5 MethodInfo m_methodInfo_GetLocalEulerAngles = transformType.GetMethod("GetLocalEulerAngles", BindingFlags.Instance | BindingFlags.NonPublic); 6 object value = m_methodInfo_GetLocalEulerAngles.Invoke(transform, new object[] { m_OldRotationOrder }); 7 8 9 Debug.LogError("反射调用GetLocalEulerAngles方法获得的值:" + value.ToString()); 10 Debug.LogError("transform.localEulerAngles获取的值:" + transform.localEulerAngles.ToString());