using UnityEngine; using TMPro; using UnityEngine.UI; using UnityEngine.EventSystems; using System.Collections.Generic; using System; using System.Linq; using System.Collections; namespace Streamingle.Debug { public class StreamingleDebugWindow : MonoBehaviour { private static StreamingleDebugWindow instance; public static StreamingleDebugWindow Instance { get { if (instance == null) { var go = new GameObject("StreamingleDebugWindow"); DontDestroyOnLoad(go); instance = go.AddComponent(); instance.CreateDebugUI(); } return instance; } } private Dictionary importers = new Dictionary(); private StreamingleAvatarImport selectedImporter; private Canvas debugCanvas; private TextMeshProUGUI debugText; private TMP_Dropdown importerDropdown; private bool showDebugUI = false; // 기본값을 false로 변경 private GameObject debugPanel; private Vector2 dragStartPosition; private bool isDragging = false; private RectTransform containerRect; private TMP_FontAsset nanumGothicFont; private Vector2 savedPosition; private bool isPositionInitialized = false; public static StreamingleDebugWindow Create(StreamingleAvatarImport importer, System.Action loadCallback) { if (instance == null) { var go = new GameObject("StreamingleDebugWindow"); DontDestroyOnLoad(go); instance = go.AddComponent(); instance.CreateDebugUI(); } instance.RegisterImporter(importer, loadCallback); return instance; } private void CreateDebugUI() { // 캔버스 생성 GameObject canvasObj = new GameObject("StreamingleDebugCanvas"); canvasObj.transform.SetParent(this.transform, false); debugCanvas = canvasObj.AddComponent(); debugCanvas.renderMode = RenderMode.ScreenSpaceOverlay; debugCanvas.sortingOrder = 10000; CanvasScaler scaler = canvasObj.AddComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1920, 1080); canvasObj.AddComponent(); // UI 컨테이너 GameObject containerObj = new GameObject("DebugContainer"); containerObj.transform.SetParent(debugCanvas.transform, false); containerRect = containerObj.AddComponent(); containerRect.anchorMin = new Vector2(0.5f, 0.5f); containerRect.anchorMax = new Vector2(0.5f, 0.5f); containerRect.pivot = new Vector2(0.5f, 0.5f); containerRect.anchoredPosition = Vector2.zero; // 디버그 패널 debugPanel = CreateDebugPanel(); debugPanel.transform.SetParent(containerRect, false); // UI 요소들 생성 CreateDragArea(); CreateImporterSelector(); CreateDebugText(); CreateLoadButton(); // 초기 상태를 숨김으로 설정 debugPanel.SetActive(false); // 토글 버튼만 보이도록 설정 CreateToggleButton(); // 초기 위치 설정 (한 번만 설정) if (debugPanel != null) { var rectTransform = debugPanel.GetComponent(); rectTransform.anchorMin = new Vector2(0, 1); rectTransform.anchorMax = new Vector2(0, 1); rectTransform.pivot = new Vector2(0, 1); if (!isPositionInitialized) { savedPosition = new Vector2(10, -10); isPositionInitialized = true; } rectTransform.anchoredPosition = savedPosition; } UpdateDebugStatus(); } private GameObject CreateDebugPanel() { GameObject panel = new GameObject("DebugPanel"); Image panelImage = panel.AddComponent(); panelImage.color = new Color(0, 0, 0, 0.8f); RectTransform panelRect = panel.GetComponent(); panelRect.sizeDelta = new Vector2(300, 250); // 크기 증가 return panel; } private void CreateDebugText() { GameObject textObj = new GameObject("DebugText"); textObj.transform.SetParent(debugPanel.transform, false); debugText = textObj.AddComponent(); debugText.font = nanumGothicFont; debugText.fontSize = 12; debugText.color = Color.white; debugText.alignment = TextAlignmentOptions.Left; RectTransform textRect = textObj.GetComponent(); textRect.anchorMin = new Vector2(0, 0); textRect.anchorMax = new Vector2(1, 1); textRect.offsetMin = new Vector2(10, 40); // 하단 버튼 위 여백 textRect.offsetMax = new Vector2(-10, -65); // 드롭다운 아래 여백 } private void CreateImporterSelector() { GameObject dropdownObj = new GameObject("ImporterSelector"); dropdownObj.transform.SetParent(debugPanel.transform, false); // 배경 이미지 개선 var image = dropdownObj.AddComponent(); image.color = new Color(0.2f, 0.2f, 0.2f, 0.95f); importerDropdown = dropdownObj.AddComponent(); // 드롭다운 위치 및 크기 조정 RectTransform rect = dropdownObj.GetComponent(); rect.anchorMin = new Vector2(0, 1); rect.anchorMax = new Vector2(1, 1); rect.sizeDelta = new Vector2(-20, 35); // 높이 증가 rect.anchoredPosition = new Vector2(0, -35); // 캡션 텍스트 개선 GameObject captionObj = new GameObject("Caption"); captionObj.transform.SetParent(dropdownObj.transform, false); var captionText = captionObj.AddComponent(); captionText.font = nanumGothicFont; captionText.fontSize = 14; captionText.color = Color.white; captionText.alignment = TextAlignmentOptions.Left; RectTransform captionRect = captionObj.GetComponent(); captionRect.anchorMin = Vector2.zero; captionRect.anchorMax = Vector2.one; captionRect.offsetMin = new Vector2(15, 2); captionRect.offsetMax = new Vector2(-35, -2); importerDropdown.captionText = captionText; // 화살표 개선 GameObject arrowObj = new GameObject("Arrow"); arrowObj.transform.SetParent(dropdownObj.transform, false); var arrowText = arrowObj.AddComponent(); arrowText.font = nanumGothicFont; arrowText.text = "▼"; arrowText.fontSize = 12; arrowText.color = new Color(1f, 1f, 1f, 0.8f); arrowText.alignment = TextAlignmentOptions.Center; RectTransform arrowRect = arrowObj.GetComponent(); arrowRect.anchorMin = new Vector2(1, 0); arrowRect.anchorMax = new Vector2(1, 1); arrowRect.pivot = new Vector2(1, 0.5f); arrowRect.sizeDelta = new Vector2(25, 0); arrowRect.anchoredPosition = new Vector2(-10, 0); // 드롭다운 템플릿 생성 CreateDropdownTemplate(importerDropdown); importerDropdown.onValueChanged.RemoveAllListeners(); importerDropdown.onValueChanged.AddListener((index) => { // 위치 저장 if (debugPanel != null) { savedPosition = debugPanel.GetComponent().anchoredPosition; } if (index >= 0 && index < importers.Count) { var selectedKey = importers.Keys.ElementAt(index); if (selectedKey != selectedImporter) { selectedImporter = selectedKey; UpdateDebugStatus(); } } // 위치 복원 if (debugPanel != null) { debugPanel.GetComponent().anchoredPosition = savedPosition; } }); // 호버 효과 추가 var trigger = dropdownObj.AddComponent(); var pointerEnter = new EventTrigger.Entry { eventID = EventTriggerType.PointerEnter }; pointerEnter.callback.AddListener((data) => { image.color = new Color(0.25f, 0.25f, 0.25f, 0.95f); arrowText.color = Color.white; }); trigger.triggers.Add(pointerEnter); var pointerExit = new EventTrigger.Entry { eventID = EventTriggerType.PointerExit }; pointerExit.callback.AddListener((data) => { image.color = new Color(0.2f, 0.2f, 0.2f, 0.95f); arrowText.color = new Color(1f, 1f, 1f, 0.8f); }); trigger.triggers.Add(pointerExit); // 초기 옵션 설정 UpdateDropdownOptions(); } private void CreateDropdownTemplate(TMP_Dropdown dropdown) { if (dropdown == null) return; // 템플릿 생성 var template = new GameObject("Template"); template.SetActive(false); template.transform.SetParent(dropdown.transform, false); var templateRect = template.AddComponent(); templateRect.anchorMin = new Vector2(0, 0); templateRect.anchorMax = new Vector2(1, 0); templateRect.pivot = new Vector2(0.5f, 1); templateRect.anchoredPosition = new Vector2(0, 0); templateRect.sizeDelta = new Vector2(0, 150); // 템플릿 배경 var templateBg = template.AddComponent(); templateBg.color = new Color(0.1f, 0.1f, 0.1f, 0.95f); // 스크롤 뷰 생성 var scrollView = new GameObject("Scroll View"); scrollView.transform.SetParent(template.transform, false); var scrollRect = scrollView.AddComponent(); var scrollRectTransform = scrollView.GetComponent(); scrollRectTransform.anchorMin = Vector2.zero; scrollRectTransform.anchorMax = Vector2.one; scrollRectTransform.sizeDelta = Vector2.zero; // 뷰포트 생성 var viewport = new GameObject("Viewport"); viewport.transform.SetParent(scrollView.transform, false); var viewportRect = viewport.AddComponent(); viewportRect.anchorMin = Vector2.zero; viewportRect.anchorMax = Vector2.one; viewportRect.sizeDelta = Vector2.zero; viewport.AddComponent(); var viewportImage = viewport.AddComponent(); viewportImage.color = new Color(0.15f, 0.15f, 0.15f, 1f); // 컨텐츠 생성 var content = new GameObject("Content"); content.transform.SetParent(viewport.transform, false); var contentRect = content.AddComponent(); contentRect.anchorMin = new Vector2(0, 1); contentRect.anchorMax = new Vector2(1, 1); contentRect.pivot = new Vector2(0.5f, 1); contentRect.sizeDelta = new Vector2(0, 28); // 스크롤렉트 설정 scrollRect.content = contentRect; scrollRect.viewport = viewportRect; scrollRect.horizontal = false; scrollRect.movementType = ScrollRect.MovementType.Clamped; scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHide; // 아이템 생성 var item = new GameObject("Item"); item.transform.SetParent(content.transform, false); var itemRect = item.AddComponent(); itemRect.anchorMin = new Vector2(0, 1); itemRect.anchorMax = new Vector2(1, 1); itemRect.pivot = new Vector2(0.5f, 1); itemRect.sizeDelta = new Vector2(0, 28); // 아이템 토글 var toggle = item.AddComponent(); var itemBg = item.AddComponent(); itemBg.color = Color.clear; toggle.targetGraphic = itemBg; toggle.transition = Selectable.Transition.ColorTint; var colors = toggle.colors; colors.normalColor = Color.clear; colors.highlightedColor = new Color(1, 1, 1, 0.1f); colors.pressedColor = new Color(1, 1, 1, 0.2f); colors.selectedColor = new Color(0.3f, 0.3f, 0.3f, 1f); toggle.colors = colors; // 아이템 텍스트 var itemLabel = new GameObject("Item Label"); itemLabel.transform.SetParent(item.transform, false); var itemLabelRect = itemLabel.AddComponent(); itemLabelRect.anchorMin = Vector2.zero; itemLabelRect.anchorMax = Vector2.one; itemLabelRect.offsetMin = new Vector2(10, 0); itemLabelRect.offsetMax = new Vector2(-10, 0); var itemText = itemLabel.AddComponent(); itemText.font = nanumGothicFont; itemText.fontSize = 14; itemText.color = Color.white; itemText.alignment = TextAlignmentOptions.Left; // 드롭다운 설정 dropdown.template = templateRect; dropdown.itemText = itemText; // 캡션 텍스트 설정 var captionText = dropdown.captionText; if (captionText != null) { captionText.font = nanumGothicFont; captionText.fontSize = 14; captionText.color = Color.white; } } private void UpdateDropdownOptions() { if (importerDropdown == null) return; // 옵션을 오름차순으로 정렬된 리스트로 생성 var sortedOptions = importers.Keys .Where(importer => importer != null) .Select(importer => importer.gameObject.name) .OrderBy(name => name) .Select(name => new TMP_Dropdown.OptionData(name)) .ToList(); // 드롭다운 이벤트 일시 중지 importerDropdown.onValueChanged.RemoveAllListeners(); // 옵션 업데이트 importerDropdown.ClearOptions(); importerDropdown.AddOptions(sortedOptions); // 첫 번째 아이템 선택 if (sortedOptions.Count > 0) { importerDropdown.value = 0; var firstImporterName = sortedOptions[0].text; selectedImporter = importers.Keys.FirstOrDefault( importer => importer != null && importer.gameObject.name == firstImporterName ); if (selectedImporter != null) { UpdateDebugStatus(); } } // 드롭다운 이벤트 재설정 importerDropdown.onValueChanged.AddListener((index) => { if (index >= 0 && index < sortedOptions.Count) { var selectedName = sortedOptions[index].text; var selectedKey = importers.Keys.FirstOrDefault( importer => importer != null && importer.gameObject.name == selectedName ); if (selectedKey != null && selectedKey != selectedImporter) { selectedImporter = selectedKey; UpdateDebugStatus(); } } }); } public void RegisterImporter(StreamingleAvatarImport importer, System.Action loadCallback) { if (!importers.ContainsKey(importer)) { importers.Add(importer, loadCallback); if (selectedImporter == null) { selectedImporter = importer; } UpdateDropdownOptions(); UpdateDebugStatus(); } } public void UnregisterImporter(StreamingleAvatarImport importer) { if (importers.ContainsKey(importer)) { importers.Remove(importer); if (selectedImporter == importer) { selectedImporter = importers.Keys.FirstOrDefault(); } UpdateDropdownOptions(); UpdateDebugStatus(); } } private void Awake() { // Resources 폴더 기준으로 경로 수정 nanumGothicFont = Resources.Load("Fonts/SDF/NanumGothic SDF"); if (nanumGothicFont == null) { UnityEngine.Debug.LogError("나눔고딕 폰트를 찾을 수 없습니다: Resources/Fonts/SDF/NanumGothic SDF"); } } public void UpdateImporterStatus(string status) { UpdateDebugStatus(); } public void UpdateDebugStatus() { if (debugPanel == null) return; // 현재 위치 저장 savedPosition = debugPanel.GetComponent().anchoredPosition; if (debugText != null) { string text = "Streamingle 디버그 정보\n"; text += "-------------------\n"; text += $"등록된 임포터: {importers.Count}개\n\n"; if (selectedImporter != null) { text += $"[{selectedImporter.gameObject.name}]\n"; var avatars = selectedImporter.GetLoadedAvatars(); if (avatars.Count == 0) { text += "- 로드된 아바타 없음\n"; } else { foreach (var avatar in avatars) { text += $"- {avatar.Key}: {(avatar.Value != null ? "로드됨" : "없음")}\n"; if (avatar.Value != null) { var renderers = avatar.Value.GetComponentsInChildren(); int totalVertices = 0; int totalBlendShapes = 0; foreach (var renderer in renderers) { totalVertices += renderer.sharedMesh.vertexCount; totalBlendShapes += renderer.sharedMesh.blendShapeCount; } text += $" 메시 수: {renderers.Length}\n"; text += $" 총 버텍스: {totalVertices:N0}\n"; text += $" 총 블렌드쉐입: {totalBlendShapes:N0}\n"; } } } } else { text += "선택된 임포터 없음\n"; } debugText.text = text; } // 위치 복원 debugPanel.GetComponent().anchoredPosition = savedPosition; } private void CreateDragArea() { GameObject dragAreaObj = new GameObject("DragArea"); dragAreaObj.transform.SetParent(debugPanel.transform, false); Image dragAreaImage = dragAreaObj.AddComponent(); dragAreaImage.color = new Color(0.3f, 0.3f, 0.3f, 0.8f); RectTransform dragAreaRect = dragAreaObj.GetComponent(); dragAreaRect.anchorMin = new Vector2(0, 1); dragAreaRect.anchorMax = new Vector2(1, 1); dragAreaRect.sizeDelta = new Vector2(0, 25); dragAreaRect.anchoredPosition = new Vector2(0, 25); // 드래그 이벤트 EventTrigger trigger = dragAreaObj.AddComponent(); var beginDrag = new EventTrigger.Entry { eventID = EventTriggerType.BeginDrag }; beginDrag.callback.AddListener((data) => { OnBeginDrag((PointerEventData)data); }); trigger.triggers.Add(beginDrag); var drag = new EventTrigger.Entry { eventID = EventTriggerType.Drag }; drag.callback.AddListener((data) => { OnDrag((PointerEventData)data); }); trigger.triggers.Add(drag); var endDrag = new EventTrigger.Entry { eventID = EventTriggerType.EndDrag }; endDrag.callback.AddListener((data) => { OnEndDrag((PointerEventData)data); }); trigger.triggers.Add(endDrag); // 드래그 영역 텍스트 CreateDragAreaText(dragAreaRect); } private void CreateDragAreaText(RectTransform parent) { GameObject textObj = new GameObject("DragText"); textObj.transform.SetParent(parent, false); TextMeshProUGUI text = textObj.AddComponent(); text.font = nanumGothicFont; text.fontSize = 12; text.text = "● Streamingle Debug"; text.alignment = TextAlignmentOptions.Left; text.color = Color.white; RectTransform textRect = text.GetComponent(); textRect.anchorMin = Vector2.zero; textRect.anchorMax = Vector2.one; textRect.offsetMin = new Vector2(5, 0); textRect.offsetMax = new Vector2(-5, 0); } private void CreateLoadButton() { GameObject buttonObj = new GameObject("LoadButton"); buttonObj.transform.SetParent(debugPanel.transform, false); Button loadButton = buttonObj.AddComponent