using System.Collections; using System.Collections.Generic; using System.Linq; using System.IO; using UnityEditor; using UnityEngine; using System; namespace UniHumanoid { [CustomEditor(typeof(Humanoid))] public class HumanoidEditor : Editor { const int LABEL_WIDTH = 100; Humanoid m_target; SerializedProperty m_Hips; #region leg SerializedProperty m_LeftUpperLeg; SerializedProperty m_RightUpperLeg; SerializedProperty m_LeftLowerLeg; SerializedProperty m_RightLowerLeg; SerializedProperty m_LeftFoot; SerializedProperty m_RightFoot; SerializedProperty m_LeftToes; SerializedProperty m_RightToes; #endregion #region spine SerializedProperty m_Spine; SerializedProperty m_Chest; SerializedProperty m_UpperChest; SerializedProperty m_Neck; SerializedProperty m_Head; SerializedProperty m_LeftEye; SerializedProperty m_RightEye; SerializedProperty m_Jaw; #endregion #region arm SerializedProperty m_LeftShoulder; SerializedProperty m_RightShoulder; SerializedProperty m_LeftUpperArm; SerializedProperty m_RightUpperArm; SerializedProperty m_LeftLowerArm; SerializedProperty m_RightLowerArm; SerializedProperty m_LeftHand; SerializedProperty m_RightHand; #endregion #region fingers SerializedProperty m_LeftThumbProximal; SerializedProperty m_LeftThumbIntermediate; SerializedProperty m_LeftThumbDistal; SerializedProperty m_LeftIndexProximal; SerializedProperty m_LeftIndexIntermediate; SerializedProperty m_LeftIndexDistal; SerializedProperty m_LeftMiddleProximal; SerializedProperty m_LeftMiddleIntermediate; SerializedProperty m_LeftMiddleDistal; SerializedProperty m_LeftRingProximal; SerializedProperty m_LeftRingIntermediate; SerializedProperty m_LeftRingDistal; SerializedProperty m_LeftLittleProximal; SerializedProperty m_LeftLittleIntermediate; SerializedProperty m_LeftLittleDistal; SerializedProperty m_RightThumbProximal; SerializedProperty m_RightThumbIntermediate; SerializedProperty m_RightThumbDistal; SerializedProperty m_RightIndexProximal; SerializedProperty m_RightIndexIntermediate; SerializedProperty m_RightIndexDistal; SerializedProperty m_RightMiddleProximal; SerializedProperty m_RightMiddleIntermediate; SerializedProperty m_RightMiddleDistal; SerializedProperty m_RightRingProximal; SerializedProperty m_RightRingIntermediate; SerializedProperty m_RightRingDistal; SerializedProperty m_RightLittleProximal; SerializedProperty m_RightLittleIntermediate; SerializedProperty m_RightLittleDistal; #endregion void OnEnable() { m_target = target as Humanoid; m_Hips = serializedObject.FindProperty($"m_{nameof(Humanoid.Hips)}"); #region legs m_LeftUpperLeg = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftUpperLeg)}"); m_RightUpperLeg = serializedObject.FindProperty($"m_{nameof(Humanoid.RightUpperLeg)}"); m_LeftLowerLeg = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftLowerLeg)}"); m_RightLowerLeg = serializedObject.FindProperty($"m_{nameof(Humanoid.RightLowerLeg)}"); m_LeftFoot = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftFoot)}"); m_RightFoot = serializedObject.FindProperty($"m_{nameof(Humanoid.RightFoot)}"); m_LeftToes = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftToes)}"); m_RightToes = serializedObject.FindProperty($"m_{nameof(Humanoid.RightToes)}"); #endregion #region spine m_Spine = serializedObject.FindProperty($"m_{nameof(Humanoid.Spine)}"); m_Chest = serializedObject.FindProperty($"m_{nameof(Humanoid.Chest)}"); m_UpperChest = serializedObject.FindProperty($"m_{nameof(Humanoid.UpperChest)}"); m_Neck = serializedObject.FindProperty($"m_{nameof(Humanoid.Neck)}"); m_Head = serializedObject.FindProperty($"m_{nameof(Humanoid.Head)}"); m_LeftEye = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftEye)}"); m_RightEye = serializedObject.FindProperty($"m_{nameof(Humanoid.RightEye)}"); m_Jaw = serializedObject.FindProperty($"m_{nameof(Humanoid.Jaw)}"); #endregion #region arm m_LeftShoulder = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftShoulder)}"); m_RightShoulder = serializedObject.FindProperty($"m_{nameof(Humanoid.RightShoulder)}"); m_LeftUpperArm = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftUpperArm)}"); m_RightUpperArm = serializedObject.FindProperty($"m_{nameof(Humanoid.RightUpperArm)}"); m_LeftLowerArm = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftLowerArm)}"); m_RightLowerArm = serializedObject.FindProperty($"m_{nameof(Humanoid.RightLowerArm)}"); m_LeftHand = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftHand)}"); m_RightHand = serializedObject.FindProperty($"m_{nameof(Humanoid.RightHand)}"); #endregion #region fingers m_LeftThumbProximal = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftThumbProximal)}"); m_LeftThumbIntermediate = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftThumbIntermediate)}"); m_LeftThumbDistal = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftThumbDistal)}"); m_LeftIndexProximal = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftIndexProximal)}"); m_LeftIndexIntermediate = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftIndexIntermediate)}"); m_LeftIndexDistal = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftIndexDistal)}"); m_LeftMiddleProximal = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftMiddleProximal)}"); m_LeftMiddleIntermediate = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftMiddleIntermediate)}"); m_LeftMiddleDistal = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftMiddleDistal)}"); m_LeftRingProximal = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftRingProximal)}"); m_LeftRingIntermediate = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftRingIntermediate)}"); m_LeftRingDistal = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftRingDistal)}"); m_LeftLittleProximal = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftLittleProximal)}"); m_LeftLittleIntermediate = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftLittleIntermediate)}"); m_LeftLittleDistal = serializedObject.FindProperty($"m_{nameof(Humanoid.LeftLittleDistal)}"); m_RightThumbProximal = serializedObject.FindProperty($"m_{nameof(Humanoid.RightThumbProximal)}"); m_RightThumbIntermediate = serializedObject.FindProperty($"m_{nameof(Humanoid.RightThumbIntermediate)}"); m_RightThumbDistal = serializedObject.FindProperty($"m_{nameof(Humanoid.RightThumbDistal)}"); m_RightIndexProximal = serializedObject.FindProperty($"m_{nameof(Humanoid.RightIndexProximal)}"); m_RightIndexIntermediate = serializedObject.FindProperty($"m_{nameof(Humanoid.RightIndexIntermediate)}"); m_RightIndexDistal = serializedObject.FindProperty($"m_{nameof(Humanoid.RightIndexDistal)}"); m_RightMiddleProximal = serializedObject.FindProperty($"m_{nameof(Humanoid.RightMiddleProximal)}"); m_RightMiddleIntermediate = serializedObject.FindProperty($"m_{nameof(Humanoid.RightMiddleIntermediate)}"); m_RightMiddleDistal = serializedObject.FindProperty($"m_{nameof(Humanoid.RightMiddleDistal)}"); m_RightRingProximal = serializedObject.FindProperty($"m_{nameof(Humanoid.RightRingProximal)}"); m_RightRingIntermediate = serializedObject.FindProperty($"m_{nameof(Humanoid.RightRingIntermediate)}"); m_RightRingDistal = serializedObject.FindProperty($"m_{nameof(Humanoid.RightRingDistal)}"); m_RightLittleProximal = serializedObject.FindProperty($"m_{nameof(Humanoid.RightLittleProximal)}"); m_RightLittleIntermediate = serializedObject.FindProperty($"m_{nameof(Humanoid.RightLittleIntermediate)}"); m_RightLittleDistal = serializedObject.FindProperty($"m_{nameof(Humanoid.RightLittleDistal)}"); #endregion } struct Horizontal : IDisposable { public static Horizontal Using() { EditorGUILayout.BeginHorizontal(); return default; } public void Dispose() { EditorGUILayout.EndHorizontal(); } } static int? HorizontalFields(string label, params SerializedProperty[] props) { int? changed = default; try { EditorGUILayout.BeginHorizontal(); GUILayout.Label(label, GUILayout.Width(LABEL_WIDTH)); GUILayout.FlexibleSpace(); for (int i = 0; i < props.Length; ++i) { var prop = props[i]; var prev = prop.objectReferenceValue; EditorGUILayout.PropertyField(prop, GUIContent.none, true, GUILayout.MinWidth(100)); if (prev != prop.objectReferenceValue) { changed = i; } } } finally { EditorGUILayout.EndHorizontal(); } return changed; } static void FingerFields(string label, params SerializedProperty[] props) { try { EditorGUILayout.BeginHorizontal(); GUILayout.Label(label, GUILayout.Width(LABEL_WIDTH)); GUILayout.FlexibleSpace(); for (int i = 0; i < props.Length; ++i) { var prop = props[i]; var prev = prop.objectReferenceValue; EditorGUILayout.PropertyField(prop, GUIContent.none, true, GUILayout.MinWidth(100)); if (prev != prop.objectReferenceValue) { SetFirstChildrenIfNull(prop, props.Skip(1).ToArray()); } } } finally { EditorGUILayout.EndHorizontal(); } } static bool s_spineFold; static bool s_legFold; static bool s_armFold; static bool s_fingerFold; static string GetDialogDir(GameObject obj) { var prefab = PrefabUtility.GetCorrespondingObjectFromSource(obj); if (prefab == null) { return "Assets"; } return Path.GetDirectoryName(AssetDatabase.GetAssetPath(prefab)); } static bool TryGetAssetPath(string fullpath, out string AssetPath) { fullpath = fullpath.Replace("\\", "/"); var basePath = Application.dataPath + "/"; if (!fullpath.StartsWith(basePath)) { AssetPath = default; return false; } AssetPath = "Assets/" + fullpath.Substring(basePath.Length); return true; } static void SetFirstChildrenIfNull(SerializedProperty start, params SerializedProperty[] children) { var parent = start.objectReferenceValue as Transform; if (parent == null) { return; } var current = parent; foreach (var prop in children) { if (prop.objectReferenceValue != null) { // already assigned. exit break; } if (current.childCount == 0) { // no child. exit break; } current = current.GetChild(0); prop.objectReferenceValue = current; } } static bool PropFieldIsUpdated(SerializedProperty prop) { var prev = prop.objectReferenceValue; EditorGUILayout.PropertyField(prop); return prop.objectReferenceValue != prev; } static void LRProps(params (string Name, SerializedProperty L, SerializedProperty R)[] fields) { for (int i = 0; i < fields.Length; ++i) { var field = fields[i]; var changed = HorizontalFields(field.Name, field.L, field.R); if (i == 0) { if (changed == 0) { // left SetFirstChildrenIfNull(field.L, fields.Skip(1).Select(x => x.L).ToArray()); } else if (changed == 1) { // right SetFirstChildrenIfNull(field.R, fields.Skip(1).Select(x => x.R).ToArray()); } } } } public override void OnInspectorGUI() { foreach (var validation in m_target.Validate()) { EditorGUILayout.HelpBox(validation.Message, validation.IsError ? MessageType.Error : MessageType.Warning); } // prefer serializedObject.Update(); EditorGUILayout.PropertyField(m_Hips); s_spineFold = EditorGUILayout.Foldout(s_spineFold, "Body"); if (s_spineFold) { if (PropFieldIsUpdated(m_Spine)) { SetFirstChildrenIfNull(m_Spine, m_Chest, m_UpperChest); } EditorGUILayout.PropertyField(m_Chest); EditorGUILayout.PropertyField(m_UpperChest); if (PropFieldIsUpdated(m_Neck)) { SetFirstChildrenIfNull(m_Neck, m_Head); } EditorGUILayout.PropertyField(m_Head); EditorGUILayout.PropertyField(m_Jaw); HorizontalFields("Eye", m_LeftEye, m_RightEye); } s_legFold = EditorGUILayout.Foldout(s_legFold, "Leg"); if (s_legFold) { LRProps( ("UpperLeg", m_LeftUpperLeg, m_RightUpperLeg), ("LowerLeg", m_LeftLowerLeg, m_RightLowerLeg), ("Foot", m_LeftFoot, m_RightFoot), ("Toes", m_LeftToes, m_RightToes) ); } s_armFold = EditorGUILayout.Foldout(s_armFold, "Arm"); if (s_armFold) { LRProps( ("Shoulder", m_LeftShoulder, m_RightShoulder), ("UpperArm", m_LeftUpperArm, m_RightUpperArm), ("LowerArm", m_LeftLowerArm, m_RightLowerArm), ("Hand", m_LeftHand, m_RightHand) ); } s_fingerFold = EditorGUILayout.Foldout(s_fingerFold, "Finger"); if (s_fingerFold) { FingerFields("LeftThumb", m_LeftThumbProximal, m_LeftThumbIntermediate, m_LeftThumbDistal); FingerFields("LeftIndex", m_LeftIndexProximal, m_LeftIndexIntermediate, m_LeftIndexDistal); FingerFields("LeftMiddle", m_LeftMiddleProximal, m_LeftMiddleIntermediate, m_LeftMiddleDistal); FingerFields("LeftRing", m_LeftRingProximal, m_LeftRingIntermediate, m_LeftRingDistal); FingerFields("LeftLittle", m_LeftLittleProximal, m_LeftLittleIntermediate, m_LeftLittleDistal); FingerFields("RightThumb", m_RightThumbProximal, m_RightThumbIntermediate, m_RightThumbDistal); FingerFields("RightIndex", m_RightIndexProximal, m_RightIndexIntermediate, m_RightIndexDistal); FingerFields("RightMiddle", m_RightMiddleProximal, m_RightMiddleIntermediate, m_RightMiddleDistal); FingerFields("RightRing", m_RightRingProximal, m_RightRingIntermediate, m_RightRingDistal); FingerFields("RightLittle", m_RightLittleProximal, m_RightLittleIntermediate, m_RightLittleDistal); } serializedObject.ApplyModifiedProperties(); // create avatar if (GUILayout.Button("Create UnityEngine.Avatar")) { var path = EditorUtility.SaveFilePanel( "Save avatar", GetDialogDir(m_target.gameObject), string.Format("{0}.avatar.asset", serializedObject.targetObject.name), "asset"); if (TryGetAssetPath(path, out string unityPath)) { var avatar = m_target.CreateAvatar(); if (avatar != null) { avatar.name = "avatar"; Debug.LogFormat("Create avatar {0}", unityPath); AssetDatabase.CreateAsset(avatar, unityPath); AssetDatabase.ImportAsset(unityPath); // replace var animator = m_target.GetComponent(); if (animator == null) { animator = m_target.gameObject.AddComponent(); } animator.avatar = avatar; Selection.activeObject = avatar; } } else { Debug.LogWarning($"cannot create {path}"); } } } void OnSceneGUI() { // var bones = m_target.Bones; // if (bones != null) // { // for (int i = 0; i < bones.Length; ++i) // { // DrawBone((HumanBodyBones)i, bones[i]); // } // foreach (var x in m_bones) // { // x.Draw(); // } // } var forward = m_target.GetForward(); var begin = m_target.transform.position; var end = begin + forward; Handles.DrawLine(begin, end); } } }