- BackgroundSceneLoaderWindow: OnGUI → CreateGUI (Toolbar + ToolbarSearchField) - PropBrowserWindow: OnGUI → CreateGUI (Toolbar + ToolbarSearchField) - StreamingleCommon.uss: 브라우저 공통 스타일 추가 (그리드/리스트/뷰토글/액션바/상태바) - excludeFromWeb 상태 새로고침 시 보존 수정 - 삭제된 배경 리소스 정리 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
249 lines
9.3 KiB
C#
249 lines
9.3 KiB
C#
using UnityEngine;
|
|
using UnityEditor;
|
|
using UnityEngine.UIElements;
|
|
using UnityEditor.UIElements;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
public class StreamingleAvatarExporter : EditorWindow
|
|
{
|
|
private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss";
|
|
|
|
private ObjectField targetField;
|
|
private VisualElement meshListContainer;
|
|
private Button exportBtn;
|
|
|
|
private GameObject targetAvatar;
|
|
private Dictionary<SkinnedMeshRenderer, string> meshTags = new Dictionary<SkinnedMeshRenderer, string>();
|
|
private string[] tagOptions = { "None", "Clothes", "Hair", "Face", "Body", "Other" };
|
|
|
|
[MenuItem("Tools/Streamingle/아바타 내보내기")]
|
|
public static void ShowWindow()
|
|
{
|
|
GetWindow<StreamingleAvatarExporter>("Streamingle 내보내기");
|
|
}
|
|
|
|
public void CreateGUI()
|
|
{
|
|
var root = rootVisualElement;
|
|
root.AddToClassList("tool-root");
|
|
|
|
var commonUss = AssetDatabase.LoadAssetAtPath<StyleSheet>(CommonUssPath);
|
|
if (commonUss != null) root.styleSheets.Add(commonUss);
|
|
|
|
root.Add(new Label("Streamingle 아바타 내보내기") { name = "title" });
|
|
root.Q<Label>("title").AddToClassList("tool-title");
|
|
|
|
targetField = new ObjectField("아바타 오브젝트") { objectType = typeof(GameObject), allowSceneObjects = true };
|
|
targetField.RegisterValueChangedCallback(evt =>
|
|
{
|
|
var newTarget = evt.newValue as GameObject;
|
|
if (newTarget != targetAvatar)
|
|
{
|
|
targetAvatar = newTarget;
|
|
if (targetAvatar != null)
|
|
InitializeMeshList();
|
|
else
|
|
meshTags.Clear();
|
|
RebuildMeshList();
|
|
}
|
|
});
|
|
root.Add(targetField);
|
|
|
|
root.Add(new Label("메쉬 태그 설정") { style = { unityFontStyleAndWeight = FontStyle.Bold, marginTop = 8, marginBottom = 4 } });
|
|
|
|
var meshScroll = new ScrollView { style = { flexGrow = 1, minHeight = 100 } };
|
|
meshListContainer = new VisualElement();
|
|
meshScroll.Add(meshListContainer);
|
|
root.Add(meshScroll);
|
|
|
|
exportBtn = new Button(ExportAvatar) { text = "아바타 내보내기" };
|
|
exportBtn.style.height = 30;
|
|
exportBtn.style.marginTop = 8;
|
|
exportBtn.AddToClassList("btn-primary");
|
|
root.Add(exportBtn);
|
|
|
|
root.schedule.Execute(() => exportBtn.SetEnabled(targetAvatar != null)).Every(200);
|
|
}
|
|
|
|
private void InitializeMeshList()
|
|
{
|
|
meshTags.Clear();
|
|
var renderers = targetAvatar.GetComponentsInChildren<SkinnedMeshRenderer>(true);
|
|
foreach (var renderer in renderers)
|
|
meshTags[renderer] = "None";
|
|
}
|
|
|
|
private void RebuildMeshList()
|
|
{
|
|
if (meshListContainer == null) return;
|
|
meshListContainer.Clear();
|
|
|
|
if (meshTags.Count == 0)
|
|
{
|
|
meshListContainer.Add(new Label("아바타를 선택하면 메쉬 목록이 표시됩니다.")
|
|
{
|
|
style = { color = new Color(0.6f, 0.6f, 0.6f), unityFontStyleAndWeight = FontStyle.Italic }
|
|
});
|
|
return;
|
|
}
|
|
|
|
foreach (var pair in meshTags.ToList())
|
|
{
|
|
var renderer = pair.Key;
|
|
if (renderer == null) continue;
|
|
|
|
var box = new VisualElement();
|
|
box.style.backgroundColor = new Color(0, 0, 0, 0.1f);
|
|
box.style.borderTopLeftRadius = box.style.borderTopRightRadius =
|
|
box.style.borderBottomLeftRadius = box.style.borderBottomRightRadius = 4;
|
|
box.style.paddingTop = box.style.paddingBottom = box.style.paddingLeft = box.style.paddingRight = 6;
|
|
box.style.marginBottom = 4;
|
|
|
|
box.Add(new Label($"메쉬: {renderer.name}") { style = { unityFontStyleAndWeight = FontStyle.Bold, fontSize = 12 } });
|
|
|
|
var infoRow = new VisualElement { style = { flexDirection = FlexDirection.Row } };
|
|
infoRow.Add(new Label($"버텍스: {renderer.sharedMesh.vertexCount}") { style = { fontSize = 11, minWidth = 120 } });
|
|
infoRow.Add(new Label($"블렌드쉐입: {renderer.sharedMesh.blendShapeCount}") { style = { fontSize = 11 } });
|
|
box.Add(infoRow);
|
|
|
|
int currentIndex = System.Array.IndexOf(tagOptions, pair.Value);
|
|
var tagField = new PopupField<string>("태그", tagOptions.ToList(), currentIndex);
|
|
var capturedRenderer = renderer;
|
|
tagField.RegisterValueChangedCallback(evt => meshTags[capturedRenderer] = evt.newValue);
|
|
box.Add(tagField);
|
|
|
|
meshListContainer.Add(box);
|
|
}
|
|
}
|
|
|
|
private void ExportAvatar()
|
|
{
|
|
if (targetAvatar == null)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "아바타를 먼저 선택해주세요.", "확인");
|
|
return;
|
|
}
|
|
|
|
string fullpath = EditorUtility.SaveFilePanel("Streamingle 아바타 내보내기", ".", 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.Refresh(ImportAssetOptions.ForceUpdate);
|
|
System.Threading.Thread.Sleep(1000);
|
|
|
|
AssetBundleBuild bundleBuild = new AssetBundleBuild
|
|
{
|
|
assetBundleName = filename,
|
|
assetNames = new string[] { prefabPath }
|
|
};
|
|
|
|
BuildPipeline.BuildAssetBundles(tempPath,
|
|
new AssetBundleBuild[] { bundleBuild },
|
|
BuildAssetBundleOptions.None,
|
|
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("완료", "아바타 내보내기 완료!", "확인");
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
Debug.LogError($"내보내기 실패: {e.Message}");
|
|
EditorUtility.DisplayDialog("오류", "아바타 내보내기 실패! 콘솔을 확인해주세요.", "확인");
|
|
}
|
|
finally
|
|
{
|
|
AssetDatabase.DeleteAsset(prefabPath);
|
|
if (File.Exists(prefabPath)) File.Delete(prefabPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class TagSetupWindow : EditorWindow
|
|
{
|
|
private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss";
|
|
|
|
private GameObject targetAvatar;
|
|
private ScrollView scrollView;
|
|
private string[] availableTags = { "Clothes", "Hair", "Face", "Body", "Other" };
|
|
|
|
public void Initialize(GameObject avatar)
|
|
{
|
|
targetAvatar = avatar;
|
|
titleContent = new GUIContent("태그 설정");
|
|
}
|
|
|
|
public void CreateGUI()
|
|
{
|
|
var root = rootVisualElement;
|
|
root.AddToClassList("tool-root");
|
|
|
|
var commonUss = AssetDatabase.LoadAssetAtPath<StyleSheet>(CommonUssPath);
|
|
if (commonUss != null) root.styleSheets.Add(commonUss);
|
|
|
|
root.Add(new Label("아바타 파츠 태그 설정") { name = "title" });
|
|
root.Q<Label>("title").AddToClassList("tool-title");
|
|
|
|
scrollView = new ScrollView();
|
|
root.Add(scrollView);
|
|
|
|
RebuildTagList();
|
|
}
|
|
|
|
private void RebuildTagList()
|
|
{
|
|
if (scrollView == null || targetAvatar == null) return;
|
|
scrollView.Clear();
|
|
|
|
Transform[] children = targetAvatar.GetComponentsInChildren<Transform>(true);
|
|
foreach (Transform child in children)
|
|
{
|
|
if (child == targetAvatar.transform) continue;
|
|
|
|
var row = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center, marginBottom = 2 } };
|
|
row.Add(new Label(child.name) { style = { flexGrow = 1 } });
|
|
|
|
string currentTag = child.tag;
|
|
int tagIndex = System.Array.IndexOf(availableTags, currentTag);
|
|
if (tagIndex < 0) tagIndex = 0;
|
|
var popup = new PopupField<string>(availableTags.ToList(), tagIndex);
|
|
popup.style.width = 100;
|
|
var capturedChild = child;
|
|
popup.RegisterValueChangedCallback(evt =>
|
|
{
|
|
capturedChild.tag = evt.newValue;
|
|
EditorUtility.SetDirty(capturedChild.gameObject);
|
|
});
|
|
row.Add(popup);
|
|
|
|
scrollView.Add(row);
|
|
}
|
|
}
|
|
}
|