using UnityEngine; using UnityEditor; using System.Collections.Generic; public class HumanBoneNameCopier : EditorWindow { private GameObject sourceAvatar; private GameObject targetAvatar; private Vector2 scrollPosition; private bool showAdvancedOptions = false; private bool copyAllBones = true; private Dictionary boneSelection = new Dictionary(); [MenuItem("Tools/Human Bone Name Copier")] public static void ShowWindow() { GetWindow("Human Bone Name Copier"); } private void OnEnable() { InitializeBoneSelection(); } private void InitializeBoneSelection() { boneSelection.Clear(); foreach (HumanBodyBones bone in System.Enum.GetValues(typeof(HumanBodyBones))) { if (bone != HumanBodyBones.LastBone) { boneSelection[bone] = true; } } } private void OnGUI() { GUILayout.Label("Human Bone Name Copier", EditorStyles.boldLabel); EditorGUILayout.Space(); // 소스 아바타 선택 EditorGUILayout.BeginHorizontal(); GUILayout.Label("Source Avatar:", GUILayout.Width(100)); sourceAvatar = (GameObject)EditorGUILayout.ObjectField(sourceAvatar, typeof(GameObject), true); EditorGUILayout.EndHorizontal(); // 타겟 아바타 선택 EditorGUILayout.BeginHorizontal(); GUILayout.Label("Target Avatar:", GUILayout.Width(100)); targetAvatar = (GameObject)EditorGUILayout.ObjectField(targetAvatar, typeof(GameObject), true); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); // 고급 옵션 토글 showAdvancedOptions = EditorGUILayout.Foldout(showAdvancedOptions, "Advanced Options"); if (showAdvancedOptions) { EditorGUI.indentLevel++; copyAllBones = EditorGUILayout.Toggle("Copy All Bones", copyAllBones); if (!copyAllBones) { EditorGUILayout.Space(); GUILayout.Label("Select Bones to Copy:", EditorStyles.boldLabel); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(200)); foreach (var bone in boneSelection) { boneSelection[bone.Key] = EditorGUILayout.Toggle(bone.Key.ToString(), bone.Value); } EditorGUILayout.EndScrollView(); } EditorGUI.indentLevel--; } EditorGUILayout.Space(); // 정보 표시 if (sourceAvatar != null) { var sourceAnimator = sourceAvatar.GetComponent(); if (sourceAnimator != null && sourceAnimator.avatar != null) { EditorGUILayout.HelpBox($"Source Avatar: {sourceAvatar.name}\n" + $"Human Bone Count: {GetHumanBoneCount(sourceAnimator)}", MessageType.Info); } } if (targetAvatar != null) { var targetAnimator = targetAvatar.GetComponent(); if (targetAnimator != null && targetAnimator.avatar != null) { EditorGUILayout.HelpBox($"Target Avatar: {targetAvatar.name}\n" + $"Human Bone Count: {GetHumanBoneCount(targetAnimator)}", MessageType.Info); } } EditorGUILayout.Space(); // 버튼들 EditorGUILayout.BeginHorizontal(); GUI.enabled = sourceAvatar != null && targetAvatar != null; if (GUILayout.Button("Copy Bone Names")) { CopyBoneNames(); } if (GUILayout.Button("Preview Changes")) { PreviewChanges(); } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); // 추가 기능 버튼 EditorGUILayout.BeginHorizontal(); GUI.enabled = targetAvatar != null; if (GUILayout.Button("Add 'zindnick : ' to Non-Human Bones")) { AddPrefixToNonHumanBones(); } GUI.enabled = true; EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); if (GUILayout.Button("Clear Selection")) { sourceAvatar = null; targetAvatar = null; } } private int GetHumanBoneCount(Animator animator) { if (animator == null || animator.avatar == null) return 0; int count = 0; for (int i = 0; i < (int)HumanBodyBones.LastBone; i++) { if (animator.GetBoneTransform((HumanBodyBones)i) != null) { count++; } } return count; } private void CopyBoneNames() { if (sourceAvatar == null || targetAvatar == null) { EditorUtility.DisplayDialog("Error", "Please select both source and target avatars.", "OK"); return; } var sourceAnimator = sourceAvatar.GetComponent(); var targetAnimator = targetAvatar.GetComponent(); if (sourceAnimator == null || sourceAnimator.avatar == null) { EditorUtility.DisplayDialog("Error", "Source avatar must have an Animator component with a valid Avatar.", "OK"); return; } if (targetAnimator == null || targetAnimator.avatar == null) { EditorUtility.DisplayDialog("Error", "Target avatar must have an Animator component with a valid Avatar.", "OK"); return; } Undo.RecordObject(targetAvatar, "Copy Human Bone Names"); int copiedCount = 0; for (int i = 0; i < (int)HumanBodyBones.LastBone; i++) { HumanBodyBones bone = (HumanBodyBones)i; if (!copyAllBones && boneSelection.ContainsKey(bone) && !boneSelection[bone]) { continue; } Transform sourceBone = sourceAnimator.GetBoneTransform(bone); Transform targetBone = targetAnimator.GetBoneTransform(bone); if (sourceBone != null && targetBone != null) { targetBone.name = sourceBone.name; copiedCount++; } } EditorUtility.SetDirty(targetAvatar); AssetDatabase.SaveAssets(); EditorUtility.DisplayDialog("Success", $"Successfully copied {copiedCount} bone names from {this.sourceAvatar.name} to {this.targetAvatar.name}.", "OK"); } private void PreviewChanges() { if (sourceAvatar == null || targetAvatar == null) { EditorUtility.DisplayDialog("Error", "Please select both source and target avatars.", "OK"); return; } var sourceAnimator = sourceAvatar.GetComponent(); var targetAnimator = targetAvatar.GetComponent(); if (sourceAnimator == null || sourceAnimator.avatar == null) { EditorUtility.DisplayDialog("Error", "Source avatar must have an Animator component with a valid Avatar.", "OK"); return; } if (targetAnimator == null || targetAnimator.avatar == null) { EditorUtility.DisplayDialog("Error", "Target avatar must have an Animator component with a valid Avatar.", "OK"); return; } string previewText = "Preview of bone name changes:\n\n"; for (int i = 0; i < (int)HumanBodyBones.LastBone; i++) { HumanBodyBones bone = (HumanBodyBones)i; if (!copyAllBones && boneSelection.ContainsKey(bone) && !boneSelection[bone]) { continue; } Transform sourceBone = sourceAnimator.GetBoneTransform(bone); Transform targetBone = targetAnimator.GetBoneTransform(bone); if (sourceBone != null && targetBone != null) { previewText += $"{bone}: {targetBone.name} → {sourceBone.name}\n"; } } EditorUtility.DisplayDialog("Preview", previewText, "OK"); } private void AddPrefixToNonHumanBones() { if (targetAvatar == null) { EditorUtility.DisplayDialog("Error", "Please select a target avatar.", "OK"); return; } var targetAnimator = targetAvatar.GetComponent(); if (targetAnimator == null || targetAnimator.avatar == null) { EditorUtility.DisplayDialog("Error", "Target avatar must have an Animator component with a valid Avatar.", "OK"); return; } // 휴먼본 목록 수집 HashSet humanBones = new HashSet(); for (int i = 0; i < (int)HumanBodyBones.LastBone; i++) { Transform humanBone = targetAnimator.GetBoneTransform((HumanBodyBones)i); if (humanBone != null) { humanBones.Add(humanBone); } } Undo.RecordObject(targetAvatar, "Add Prefix to Non-Human Bones"); int modifiedCount = 0; Transform[] allTransforms = targetAvatar.GetComponentsInChildren(); foreach (Transform transform in allTransforms) { // 휴먼본이 아니고, 이미 접두사가 없는 경우에만 추가 if (!humanBones.Contains(transform) && !transform.name.StartsWith("zindnick : ")) { transform.name = "zindnick : " + transform.name; modifiedCount++; } } EditorUtility.SetDirty(targetAvatar); AssetDatabase.SaveAssets(); EditorUtility.DisplayDialog("Success", $"Successfully added 'zindnick : ' prefix to {modifiedCount} non-human bones in {targetAvatar.name}.", "OK"); } }