using UnityEngine; using UnityEditor; using System.IO; using System.Collections.Generic; using System.Linq; public class StreamingleAvatarExporter : EditorWindow { private GameObject targetAvatar; private Vector2 scrollPosition; private Dictionary meshTags = new Dictionary(); private string[] tagOptions = new string[] { "None", "Clothes", "Hair", "Face", "Body", "Other" }; private Color[] tagColors = new Color[] { Color.gray, // None Color.blue, // Clothes Color.yellow, // Hair Color.green, // Face Color.red, // Body Color.magenta // Other }; [MenuItem("Tools/Streamingle/Avatar Exporter")] public static void ShowWindow() { GetWindow("Streamingle Exporter"); } private void OnGUI() { GUILayout.Label("Streamingle Avatar Exporter", EditorStyles.boldLabel); EditorGUI.BeginChangeCheck(); GameObject newTarget = (GameObject)EditorGUILayout.ObjectField("Avatar Object", targetAvatar, typeof(GameObject), true); if (newTarget != targetAvatar) { targetAvatar = newTarget; if (targetAvatar != null) { InitializeMeshList(); } else { meshTags.Clear(); } } if (targetAvatar != null) { GUILayout.Space(10); EditorGUILayout.LabelField("메쉬 태그 설정", EditorStyles.boldLabel); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); foreach (var pair in meshTags.ToList()) { SkinnedMeshRenderer renderer = pair.Key; if (renderer == null) continue; EditorGUILayout.BeginVertical("box"); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField($"메쉬: {renderer.name}", EditorStyles.boldLabel); EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField($"버텍스: {renderer.sharedMesh.vertexCount}", GUILayout.Width(120)); EditorGUILayout.LabelField($"블렌드쉐입: {renderer.sharedMesh.blendShapeCount}", GUILayout.Width(120)); EditorGUILayout.EndHorizontal(); int currentIndex = System.Array.IndexOf(tagOptions, pair.Value); int newIndex = EditorGUILayout.Popup("태그", currentIndex, tagOptions); if (newIndex != currentIndex) { meshTags[renderer] = tagOptions[newIndex]; } EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 3), tagColors[newIndex]); EditorGUILayout.EndVertical(); GUILayout.Space(5); } EditorGUILayout.EndScrollView(); if (GUILayout.Button("Export Avatar")) { ExportAvatar(); } } } private void InitializeMeshList() { meshTags.Clear(); var renderers = targetAvatar.GetComponentsInChildren(true); foreach (var renderer in renderers) { meshTags[renderer] = "None"; } } private void ExportAvatar() { if (targetAvatar == null) { EditorUtility.DisplayDialog("Error", "Please select an avatar first!", "OK"); return; } string fullpath = EditorUtility.SaveFilePanel( "Export Streamingle Avatar", ".", targetAvatar.name, "streamingle" ); if (string.IsNullOrEmpty(fullpath)) return; string filename = Path.GetFileName(fullpath); string prefabPath = "Assets/StreamingleAvatarTemp.prefab"; try { // 컴파일 에러 체크 if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(targetAvatar))) { AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(targetAvatar), ImportAssetOptions.ForceUpdate); } // 프리팹 생성 전 임시 폴더 정리 string tempPath = Path.Combine(Application.dataPath, "../Temp/AssetBundles"); if (Directory.Exists(tempPath)) { Directory.Delete(tempPath, true); } Directory.CreateDirectory(tempPath); // 프리팹 생성 bool succeededPack = false; GameObject prefabAsset = PrefabUtility.SaveAsPrefabAsset(targetAvatar, prefabPath, out succeededPack); if (!succeededPack || prefabAsset == null) { throw new System.Exception("프리팹 생성 실패"); } // AssetDatabase 새로고침 및 대기 AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate); System.Threading.Thread.Sleep(1000); // 새로고침 대기 // AssetBundle 설정 AssetBundleBuild bundleBuild = new AssetBundleBuild { assetBundleName = filename, assetNames = new string[] { prefabPath } }; // 빌드 옵션 설정 BuildAssetBundleOptions options = BuildAssetBundleOptions.None; // AssetBundle 빌드 BuildPipeline.BuildAssetBundles( tempPath, new AssetBundleBuild[] { bundleBuild }, options, EditorUserBuildSettings.activeBuildTarget ); // 결과 파일 이동 string bundlePath = Path.Combine(tempPath, filename); if (File.Exists(bundlePath)) { if (File.Exists(fullpath)) File.Delete(fullpath); File.Copy(bundlePath, fullpath, true); } else { throw new System.Exception($"AssetBundle 파일을 찾을 수 없습니다. 경로: {bundlePath}"); } EditorUtility.DisplayDialog("Export", "아바타 출력 완료!", "OK"); } catch (System.Exception e) { Debug.LogError($"Export failed: {e.Message}"); EditorUtility.DisplayDialog("Error", "아바타 출력 실패! 콘솔을 확인해주세요.", "OK"); } finally { AssetDatabase.DeleteAsset(prefabPath); if (File.Exists(prefabPath)) File.Delete(prefabPath); } } } // 태그 설정을 위한 별도의 윈도우 public class TagSetupWindow : EditorWindow { private GameObject targetAvatar; private Vector2 scrollPosition; private string[] availableTags = new string[] { "Clothes", "Hair", "Face", "Body", "Other" }; public void Initialize(GameObject avatar) { targetAvatar = avatar; titleContent = new GUIContent("태그 설정"); } private void OnGUI() { if (targetAvatar == null) return; GUILayout.Label("아바타 파츠 태그 설정", EditorStyles.boldLabel); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); Transform[] children = targetAvatar.GetComponentsInChildren(true); foreach (Transform child in children) { if (child == targetAvatar.transform) continue; EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(child.name); string currentTag = child.tag; int tagIndex = System.Array.IndexOf(availableTags, currentTag); int newTagIndex = EditorGUILayout.Popup(tagIndex, availableTags); if (newTagIndex != tagIndex) { child.tag = availableTags[newTagIndex]; EditorUtility.SetDirty(child.gameObject); } EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); } }