using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
///
/// 아바타의 휴먼본 이름을 Unity Human Bone 기준으로 변환하는 에디터 툴
/// 3ds Max Biped이나 다른 본 구조를 Unity 표준 이름으로 변환
///
public class HumanBoneRenamer : EditorWindow
{
private Avatar targetAvatar;
private GameObject targetGameObject;
private bool showMapping = false;
private Vector2 scrollPosition;
// Unity Human Bone 매핑 테이블
private static readonly Dictionary humanBoneNames = new Dictionary()
{
// Body
{ HumanBodyBones.Hips, "Hips" },
{ HumanBodyBones.Spine, "Spine" },
{ HumanBodyBones.Chest, "Chest" },
{ HumanBodyBones.UpperChest, "UpperChest" },
{ HumanBodyBones.Neck, "Neck" },
{ HumanBodyBones.Head, "Head" },
// Left Arm
{ HumanBodyBones.LeftShoulder, "LeftShoulder" },
{ HumanBodyBones.LeftUpperArm, "LeftUpperArm" },
{ HumanBodyBones.LeftLowerArm, "LeftLowerArm" },
{ HumanBodyBones.LeftHand, "LeftHand" },
// Right Arm
{ HumanBodyBones.RightShoulder, "RightShoulder" },
{ HumanBodyBones.RightUpperArm, "RightUpperArm" },
{ HumanBodyBones.RightLowerArm, "RightLowerArm" },
{ HumanBodyBones.RightHand, "RightHand" },
// Left Leg
{ HumanBodyBones.LeftUpperLeg, "LeftUpperLeg" },
{ HumanBodyBones.LeftLowerLeg, "LeftLowerLeg" },
{ HumanBodyBones.LeftFoot, "LeftFoot" },
{ HumanBodyBones.LeftToes, "LeftToes" },
// Right Leg
{ HumanBodyBones.RightUpperLeg, "RightUpperLeg" },
{ HumanBodyBones.RightLowerLeg, "RightLowerLeg" },
{ HumanBodyBones.RightFoot, "RightFoot" },
{ HumanBodyBones.RightToes, "RightToes" },
// Left Hand Fingers
{ HumanBodyBones.LeftThumbProximal, "LeftThumbProximal" },
{ HumanBodyBones.LeftThumbIntermediate, "LeftThumbIntermediate" },
{ HumanBodyBones.LeftThumbDistal, "LeftThumbDistal" },
{ HumanBodyBones.LeftIndexProximal, "LeftIndexProximal" },
{ HumanBodyBones.LeftIndexIntermediate, "LeftIndexIntermediate" },
{ HumanBodyBones.LeftIndexDistal, "LeftIndexDistal" },
{ HumanBodyBones.LeftMiddleProximal, "LeftMiddleProximal" },
{ HumanBodyBones.LeftMiddleIntermediate, "LeftMiddleIntermediate" },
{ HumanBodyBones.LeftMiddleDistal, "LeftMiddleDistal" },
{ HumanBodyBones.LeftRingProximal, "LeftRingProximal" },
{ HumanBodyBones.LeftRingIntermediate, "LeftRingIntermediate" },
{ HumanBodyBones.LeftRingDistal, "LeftRingDistal" },
{ HumanBodyBones.LeftLittleProximal, "LeftLittleProximal" },
{ HumanBodyBones.LeftLittleIntermediate, "LeftLittleIntermediate" },
{ HumanBodyBones.LeftLittleDistal, "LeftLittleDistal" },
// Right Hand Fingers
{ HumanBodyBones.RightThumbProximal, "RightThumbProximal" },
{ HumanBodyBones.RightThumbIntermediate, "RightThumbIntermediate" },
{ HumanBodyBones.RightThumbDistal, "RightThumbDistal" },
{ HumanBodyBones.RightIndexProximal, "RightIndexProximal" },
{ HumanBodyBones.RightIndexIntermediate, "RightIndexIntermediate" },
{ HumanBodyBones.RightIndexDistal, "RightIndexDistal" },
{ HumanBodyBones.RightMiddleProximal, "RightMiddleProximal" },
{ HumanBodyBones.RightMiddleIntermediate, "RightMiddleIntermediate" },
{ HumanBodyBones.RightMiddleDistal, "RightMiddleDistal" },
{ HumanBodyBones.RightRingProximal, "RightRingProximal" },
{ HumanBodyBones.RightRingIntermediate, "RightRingIntermediate" },
{ HumanBodyBones.RightRingDistal, "RightRingDistal" },
{ HumanBodyBones.RightLittleProximal, "RightLittleProximal" },
{ HumanBodyBones.RightLittleIntermediate, "RightLittleIntermediate" },
{ HumanBodyBones.RightLittleDistal, "RightLittleDistal" }
};
[MenuItem("Tools/Human Bone Renamer")]
public static void ShowWindow()
{
GetWindow("Human Bone Renamer");
}
private void OnGUI()
{
GUILayout.Label("Human Bone Renamer", EditorStyles.boldLabel);
GUILayout.Label("아바타의 휴먼본 이름을 Unity 표준으로 변환", EditorStyles.helpBox);
EditorGUILayout.Space();
// 아바타 또는 GameObject 참조
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Target:", GUILayout.Width(60));
targetAvatar = EditorGUILayout.ObjectField(targetAvatar, typeof(Avatar), false) as Avatar;
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("또는:", GUILayout.Width(60));
targetGameObject = EditorGUILayout.ObjectField(targetGameObject, typeof(GameObject), true) as GameObject;
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
// 매핑 정보 표시 토글
showMapping = EditorGUILayout.Foldout(showMapping, "매핑 정보 보기");
if (showMapping)
{
ShowMappingInfo();
}
EditorGUILayout.Space();
// 변환 버튼들
EditorGUILayout.BeginHorizontal();
GUI.enabled = (targetAvatar != null || targetGameObject != null);
if (GUILayout.Button("휴먼본 이름 변환", GUILayout.Height(30)))
{
RenameHumanBones();
}
if (GUILayout.Button("미리보기", GUILayout.Height(30)))
{
PreviewRenaming();
}
GUI.enabled = true;
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
// 도움말
EditorGUILayout.HelpBox(
"사용법:\n" +
"1. Avatar 또는 GameObject를 참조해주세요\n" +
"2. '미리보기'로 변경될 이름을 확인하세요\n" +
"3. '휴먼본 이름 변환'을 클릭하여 실행하세요\n\n" +
"지원하는 본 구조:\n" +
"• 3ds Max Biped (Bip001 Pelvis, etc.)\n" +
"• Mixamo 본 구조\n" +
"• 기타 휴머노이드 본 구조",
MessageType.Info);
}
private void ShowMappingInfo()
{
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(200));
EditorGUILayout.LabelField("Unity Human Bone 목록:", EditorStyles.boldLabel);
foreach (var bone in humanBoneNames)
{
if (bone.Key != HumanBodyBones.LastBone)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(bone.Key.ToString(), GUILayout.Width(200));
EditorGUILayout.LabelField("→", GUILayout.Width(20));
EditorGUILayout.LabelField(bone.Value);
EditorGUILayout.EndHorizontal();
}
}
EditorGUILayout.EndScrollView();
}
private void PreviewRenaming()
{
var bones = GetHumanBones();
if (bones.Count == 0)
{
EditorUtility.DisplayDialog("오류", "휴먼본을 찾을 수 없습니다.", "확인");
return;
}
string preview = "변경될 본 이름:\n\n";
int changeCount = 0;
foreach (var bone in bones)
{
if (humanBoneNames.TryGetValue(bone.Key, out string newName))
{
if (bone.Value.name != newName)
{
preview += $"{bone.Value.name} → {newName}\n";
changeCount++;
}
}
}
if (changeCount == 0)
{
preview += "변경할 본이 없습니다. (이미 Unity 표준 이름)";
}
else
{
preview = $"총 {changeCount}개 본 이름이 변경됩니다:\n\n" + preview;
}
EditorUtility.DisplayDialog("미리보기", preview, "확인");
}
private void RenameHumanBones()
{
var bones = GetHumanBones();
if (bones.Count == 0)
{
EditorUtility.DisplayDialog("오류", "휴먼본을 찾을 수 없습니다.", "확인");
return;
}
// Undo 등록
var transforms = new List();
foreach (var bone in bones)
{
transforms.Add(bone.Value);
}
Undo.RecordObjects(transforms.ToArray(), "Rename Human Bones");
int changeCount = 0;
foreach (var bone in bones)
{
if (humanBoneNames.TryGetValue(bone.Key, out string newName))
{
if (bone.Value.name != newName)
{
Debug.Log($"[Human Bone Renamer] {bone.Value.name} → {newName}");
bone.Value.name = newName;
changeCount++;
}
}
}
if (changeCount > 0)
{
EditorUtility.DisplayDialog("완료", $"{changeCount}개 휴먼본의 이름이 Unity 표준으로 변경되었습니다.", "확인");
// Scene을 Dirty로 마킹
if (targetGameObject != null)
{
EditorUtility.SetDirty(targetGameObject);
}
}
else
{
EditorUtility.DisplayDialog("정보", "변경할 본이 없습니다. 이미 Unity 표준 이름을 사용하고 있습니다.", "확인");
}
}
private Dictionary GetHumanBones()
{
Dictionary bones = new Dictionary();
if (targetAvatar != null)
{
// Avatar에서 휴먼본 가져오기
if (targetAvatar.isHuman)
{
var humanDescription = targetAvatar.humanDescription;
foreach (var humanBone in humanDescription.human)
{
if (System.Enum.TryParse(humanBone.humanName, out HumanBodyBones boneType))
{
// Avatar의 경우 직접 Transform을 가져올 수 없으므로 GameObject를 통해 찾아야 함
// 이 경우 GameObject도 함께 제공해야 함
}
}
}
}
if (targetGameObject != null)
{
// GameObject에서 Animator를 찾아서 휴먼본 가져오기
Animator animator = targetGameObject.GetComponent();
if (animator != null && animator.avatar != null && animator.avatar.isHuman)
{
foreach (HumanBodyBones boneType in System.Enum.GetValues(typeof(HumanBodyBones)))
{
if (boneType != HumanBodyBones.LastBone)
{
Transform boneTransform = animator.GetBoneTransform(boneType);
if (boneTransform != null)
{
bones[boneType] = boneTransform;
}
}
}
}
else
{
// Animator가 없거나 휴먼 아바타가 아닌 경우, 이름으로 추측해서 찾기
bones = FindBonesbyName(targetGameObject.transform);
}
}
return bones;
}
private Dictionary FindBonesbyName(Transform root)
{
Dictionary bones = new Dictionary();
Transform[] allTransforms = root.GetComponentsInChildren();
// 3ds Max Biped 매핑
Dictionary bipedMapping = new Dictionary()
{
// Body
{ "Bip001 Pelvis", HumanBodyBones.Hips },
{ "Bip001 Spine", HumanBodyBones.Spine },
{ "Bip001 Spine1", HumanBodyBones.Chest },
{ "Bip001 Neck", HumanBodyBones.Neck },
{ "Bip001 Head", HumanBodyBones.Head },
// Left Arm
{ "Bip001 L Clavicle", HumanBodyBones.LeftShoulder },
{ "Bip001 L UpperArm", HumanBodyBones.LeftUpperArm },
{ "Bip001 L Forearm", HumanBodyBones.LeftLowerArm },
{ "Bip001 L Hand", HumanBodyBones.LeftHand },
// Right Arm
{ "Bip001 R Clavicle", HumanBodyBones.RightShoulder },
{ "Bip001 R UpperArm", HumanBodyBones.RightUpperArm },
{ "Bip001 R Forearm", HumanBodyBones.RightLowerArm },
{ "Bip001 R Hand", HumanBodyBones.RightHand },
// Left Leg
{ "Bip001 L Thigh", HumanBodyBones.LeftUpperLeg },
{ "Bip001 L Calf", HumanBodyBones.LeftLowerLeg },
{ "Bip001 L Foot", HumanBodyBones.LeftFoot },
{ "Bip001 L Toe0", HumanBodyBones.LeftToes },
// Right Leg
{ "Bip001 R Thigh", HumanBodyBones.RightUpperLeg },
{ "Bip001 R Calf", HumanBodyBones.RightLowerLeg },
{ "Bip001 R Foot", HumanBodyBones.RightFoot },
{ "Bip001 R Toe0", HumanBodyBones.RightToes },
// Left Hand Fingers
{ "Bip001 L Finger0", HumanBodyBones.LeftThumbProximal },
{ "Bip001 L Finger01", HumanBodyBones.LeftThumbIntermediate },
{ "Bip001 L Finger02", HumanBodyBones.LeftThumbDistal },
{ "Bip001 L Finger1", HumanBodyBones.LeftIndexProximal },
{ "Bip001 L Finger11", HumanBodyBones.LeftIndexIntermediate },
{ "Bip001 L Finger12", HumanBodyBones.LeftIndexDistal },
{ "Bip001 L Finger2", HumanBodyBones.LeftMiddleProximal },
{ "Bip001 L Finger21", HumanBodyBones.LeftMiddleIntermediate },
{ "Bip001 L Finger22", HumanBodyBones.LeftMiddleDistal },
{ "Bip001 L Finger3", HumanBodyBones.LeftRingProximal },
{ "Bip001 L Finger31", HumanBodyBones.LeftRingIntermediate },
{ "Bip001 L Finger32", HumanBodyBones.LeftRingDistal },
{ "Bip001 L Finger4", HumanBodyBones.LeftLittleProximal },
{ "Bip001 L Finger41", HumanBodyBones.LeftLittleIntermediate },
{ "Bip001 L Finger42", HumanBodyBones.LeftLittleDistal },
// Right Hand Fingers
{ "Bip001 R Finger0", HumanBodyBones.RightThumbProximal },
{ "Bip001 R Finger01", HumanBodyBones.RightThumbIntermediate },
{ "Bip001 R Finger02", HumanBodyBones.RightThumbDistal },
{ "Bip001 R Finger1", HumanBodyBones.RightIndexProximal },
{ "Bip001 R Finger11", HumanBodyBones.RightIndexIntermediate },
{ "Bip001 R Finger12", HumanBodyBones.RightIndexDistal },
{ "Bip001 R Finger2", HumanBodyBones.RightMiddleProximal },
{ "Bip001 R Finger21", HumanBodyBones.RightMiddleIntermediate },
{ "Bip001 R Finger22", HumanBodyBones.RightMiddleDistal },
{ "Bip001 R Finger3", HumanBodyBones.RightRingProximal },
{ "Bip001 R Finger31", HumanBodyBones.RightRingIntermediate },
{ "Bip001 R Finger32", HumanBodyBones.RightRingDistal },
{ "Bip001 R Finger4", HumanBodyBones.RightLittleProximal },
{ "Bip001 R Finger41", HumanBodyBones.RightLittleIntermediate },
{ "Bip001 R Finger42", HumanBodyBones.RightLittleDistal }
};
// 이름으로 본 찾기
foreach (Transform t in allTransforms)
{
if (bipedMapping.TryGetValue(t.name, out HumanBodyBones boneType))
{
bones[boneType] = t;
}
}
return bones;
}
}