using UnityEngine; using UnityEditor; using System.IO; using System.Collections.Generic; using System.Linq; /// /// Unity 프로젝트 폴더 자동 정리 스크립트 V3 (최종 완성판) /// - 공백이 있는 파일명 지원 /// - 5.TestScript 폴더 구조 완벽 인식 /// - 모든 변형 파일명 검색 /// public class ProjectOrganizerV3 : EditorWindow { private const string TARGET_FOLDER = "Assets/Scripts"; private bool _createBackup = true; private bool _showDetailedLog = true; private Vector2 _scrollPosition; private List _manualMoveList = new List(); [MenuItem("Tools/Project Organizer V3 (Final)")] public static void ShowWindow() { GetWindow("프로젝트 정리 V3"); } private void OnGUI() { _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); EditorGUILayout.Space(10); EditorGUILayout.LabelField("🗂️ 프로젝트 폴더 자동 정리 V3 (최종판)", EditorStyles.boldLabel); EditorGUILayout.Space(5); EditorGUILayout.HelpBox( "✨ V3 개선사항:\n" + "- 공백이 있는 파일명 지원 (예: 'HP UI bar.cs')\n" + "- 5.TestScript 폴더 구조 완벽 인식\n" + "- Heal/, Level_Scripts/, Optimizaion/ 등 모든 폴더 검색\n" + "- 수동 이동 목록 제공\n\n" + "⚠️ 실행 전 백업 필수!", MessageType.Info ); EditorGUILayout.Space(10); _createBackup = EditorGUILayout.Toggle("백업 자동 생성", _createBackup); _showDetailedLog = EditorGUILayout.Toggle("상세 로그 출력", _showDetailedLog); EditorGUILayout.Space(10); if (GUILayout.Button("🔍 파일 스캔 + 수동 이동 목록 생성", GUILayout.Height(40))) { ScanFilesWithManualList(); } EditorGUILayout.Space(5); if (GUILayout.Button("🚀 폴더 구조 생성하기", GUILayout.Height(35))) { if (EditorUtility.DisplayDialog( "폴더 구조 생성", "새로운 폴더 구조를 생성하시겠습니까?", "실행", "취소")) { CreateFolderStructure(); } } EditorGUILayout.Space(5); if (GUILayout.Button("📦 찾은 파일 자동 이동하기", GUILayout.Height(40))) { if (EditorUtility.DisplayDialog( "파일 자동 이동", "⚠️ 찾은 파일들을 새 위치로 이동합니다.\n\n계속하시겠습니까?", "실행", "취소")) { if (_createBackup) { CreateBackup(); } OrganizeFilesRecursive(); } } EditorGUILayout.Space(5); if (GUILayout.Button("🔙 백업 생성만 하기", GUILayout.Height(30))) { CreateBackup(); } EditorGUILayout.Space(10); // 수동 이동 목록 표시 if (_manualMoveList.Count > 0) { EditorGUILayout.HelpBox( $"⚠️ 수동 이동 필요: {_manualMoveList.Count}개\n아래 Console 로그 참고", MessageType.Warning ); } EditorGUILayout.HelpBox( "💡 권장 순서:\n" + "1. '파일 스캔' 클릭 → Console에서 결과 확인\n" + "2. '백업 생성'\n" + "3. '폴더 구조 생성'\n" + "4. '찾은 파일 자동 이동'\n" + "5. Console에 나온 수동 이동 목록 처리\n" + "6. Unity 컴파일 대기 후 테스트", MessageType.Info ); EditorGUILayout.EndScrollView(); } private void ScanFilesWithManualList() { Log("🔍 파일 스캔 시작..."); _manualMoveList.Clear(); var fileMap = GetFileMappings(); int foundCount = 0; int notFoundCount = 0; List notFoundFiles = new List(); foreach (var kvp in fileMap) { string fileName = kvp.Key; string targetFolder = kvp.Value; string[] foundFiles = FindScriptAdvanced(fileName); if (foundFiles.Length > 0) { foundCount++; Log($" ✅ 찾음: {fileName} → {foundFiles[0]}"); } else { notFoundCount++; notFoundFiles.Add(fileName); Log($" ❌ 못 찾음: {fileName}"); } } // 수동 이동 가이드 생성 Log("\n" + new string('=', 60)); Log("📋 수동 이동 가이드"); Log(new string('=', 60)); if (notFoundFiles.Count > 0) { Log("\n⚠️ 다음 파일들을 수동으로 이동해주세요:\n"); foreach (string fileName in notFoundFiles) { if (fileMap.ContainsKey(fileName)) { string targetPath = fileMap[fileName]; Log($"[ ] {fileName}.cs"); Log($" → Assets/Scripts/{targetPath}/"); Log(""); _manualMoveList.Add($"{fileName} → Scripts/{targetPath}/"); } } Log("\n💡 수동 이동 방법:"); Log("1. Project 창에서 't:Script [파일명]' 으로 검색"); Log("2. 찾은 파일을 드래그해서 위 경로로 이동"); Log("3. Unity가 자동으로 참조 업데이트"); } string result = $"스캔 완료!\n✅ 찾음: {foundCount}개\n❌ 못 찾음: {notFoundCount}개"; if (notFoundCount > 0) { result += $"\n\n⚠️ Console 로그에서 수동 이동 가이드 확인!"; } Log(result); EditorUtility.DisplayDialog("스캔 완료", result, "확인"); } private void CreateBackup() { Log("🔙 백업 생성 시작..."); string timestamp = System.DateTime.Now.ToString("yyyyMMdd_HHmmss"); string backupPath = $"Assets/Scripts_Backup_{timestamp}"; // 모든 스크립트 폴더 찾기 string[] scriptFolders = new string[] { "Assets/5.TestScript", "Assets/Scripts", "Assets/Code" // 혹시 있을 수 있는 다른 폴더 }; AssetDatabase.CreateFolder("Assets", $"Scripts_Backup_{timestamp}"); foreach (string folder in scriptFolders) { if (Directory.Exists(folder)) { string folderName = Path.GetFileName(folder); string destFolder = Path.Combine(backupPath, folderName); CopyDirectorySafe(folder, destFolder); Log($" 백업: {folder}"); } } AssetDatabase.Refresh(); Log($"✅ 백업 완료: {backupPath}"); EditorUtility.DisplayDialog("백업 완료", $"백업이 생성되었습니다:\n{backupPath}", "확인"); } private void CopyDirectorySafe(string sourceDir, string destDir) { try { Directory.CreateDirectory(destDir); foreach (string file in Directory.GetFiles(sourceDir)) { string fileName = Path.GetFileName(file); if (fileName.EndsWith(".meta")) continue; string destFile = Path.Combine(destDir, fileName); File.Copy(file, destFile, true); } foreach (string subDir in Directory.GetDirectories(sourceDir)) { string dirName = Path.GetFileName(subDir); string destSubDir = Path.Combine(destDir, dirName); CopyDirectorySafe(subDir, destSubDir); } } catch (System.Exception ex) { Log($"❌ 백업 실패: {sourceDir} - {ex.Message}"); } } private void CreateFolderStructure() { Log("📁 폴더 구조 생성 시작..."); string[] folders = new string[] { "Core", "Player/Controller", "Player/Animation", "Player/Combat", "Player/Stats", "Player/Upgrade/Data", "Player/Upgrade/UI", "Player/Equipment", "Player/Interaction", "Combat/Interfaces", "Combat/Components", "Combat/Debug", "Enemy/Spawner", "Enemy/Types", "Items/Pickups", "Items/Interactive", "Items/VFX", "UI/HUD", "UI/Enemy", "UI/Level", "Camera/Effects", "Systems/ObjectPool", "Systems/Scene", "Systems/Settings", "Systems/Optimization/Editor", "Data/Stats", "Utilities" }; int createdCount = 0; foreach (string folder in folders) { string fullPath = $"{TARGET_FOLDER}/{folder}"; if (CreateFolderRecursive(fullPath)) { createdCount++; } } AssetDatabase.Refresh(); Log($"✅ 폴더 구조 생성 완료! ({createdCount}개 폴더)"); EditorUtility.DisplayDialog("완료", $"{createdCount}개의 폴더가 생성되었습니다.", "확인"); } private bool CreateFolderRecursive(string path) { string[] parts = path.Replace("Assets/", "").Split('/'); string currentPath = "Assets"; bool created = false; for (int i = 0; i < parts.Length; i++) { string nextPath = currentPath + "/" + parts[i]; if (!AssetDatabase.IsValidFolder(nextPath)) { AssetDatabase.CreateFolder(currentPath, parts[i]); Log($" 생성됨: {nextPath}"); created = true; } currentPath = nextPath; } return created; } private void OrganizeFilesRecursive() { Log("📦 파일 이동 시작..."); var fileMap = GetFileMappings(); int movedCount = 0; int failedCount = 0; int skippedCount = 0; List notFoundFiles = new List(); foreach (var kvp in fileMap) { string fileName = kvp.Key; string targetFolder = kvp.Value; string[] foundFiles = FindScriptAdvanced(fileName); if (foundFiles.Length == 0) { notFoundFiles.Add(fileName); continue; } string oldPath = foundFiles[0]; string newPath = $"{TARGET_FOLDER}/{targetFolder}/{Path.GetFileName(oldPath)}"; // 이미 목적지에 있으면 스킵 if (oldPath == newPath) { Log($" ⏭️ 스킵: {fileName} (이미 올바른 위치)"); skippedCount++; continue; } // 파일 이동 string result = AssetDatabase.MoveAsset(oldPath, newPath); if (string.IsNullOrEmpty(result)) { Log($" ✅ 이동: {Path.GetFileName(oldPath)}"); Log($" {oldPath} → {newPath}"); movedCount++; } else { Log($" ❌ 실패: {fileName} - {result}"); failedCount++; } } AssetDatabase.Refresh(); // 결과 출력 string resultMessage = $"✅ 이동 완료: {movedCount}개\n"; if (skippedCount > 0) { resultMessage += $"⏭️ 스킵: {skippedCount}개 (이미 올바른 위치)\n"; } if (failedCount > 0) { resultMessage += $"❌ 실패: {failedCount}개\n"; } if (notFoundFiles.Count > 0) { resultMessage += $"\n⚠️ 못 찾음: {notFoundFiles.Count}개\n"; resultMessage += "→ Console에서 수동 이동 가이드 확인\n"; } Log($"\n✅ 파일 정리 완료!\n{resultMessage}"); EditorUtility.DisplayDialog("완료", resultMessage, "확인"); } /// /// 고급 스크립트 검색 (공백, 언더스코어, 변형 모두 지원) /// private string[] FindScriptAdvanced(string fileName) { List results = new List(); // 1단계: 정확한 이름으로 검색 string[] guids = AssetDatabase.FindAssets($"{fileName} t:Script"); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); string scriptName = Path.GetFileNameWithoutExtension(path); if (scriptName == fileName) { results.Add(path); } } if (results.Count > 0) return results.ToArray(); // 2단계: 변형 검색 List variations = new List { fileName, fileName.Replace("_", " "), // HP_UI_bar → HP UI bar fileName.Replace("_", ""), // HP_UI_bar → HPUIbar fileName.Replace(" ", "_"), // 반대 fileName.Replace(" ", ""), // 공백 제거 fileName + "UI", // 끝에 UI 붙은 버전 fileName.Replace("UI", ""), // UI 제거 }; // 부분 검색 string[] allGuids = AssetDatabase.FindAssets("t:Script"); foreach (string guid in allGuids) { string path = AssetDatabase.GUIDToAssetPath(guid); // 5.TestScript 폴더 우선 if (!path.Contains("5.TestScript") && !path.Contains("Scripts")) continue; string scriptName = Path.GetFileNameWithoutExtension(path); foreach (string variation in variations) { if (scriptName.Equals(variation, System.StringComparison.OrdinalIgnoreCase)) { results.Add(path); return results.ToArray(); } } } return results.ToArray(); } private Dictionary GetFileMappings() { return new Dictionary() { // Player {"PlayerMovement", "Player/Controller"}, {"PlayerInput", "Player/Controller"}, {"Animation", "Player/Animation"}, {"Attack", "Player/Combat"}, {"Health", "Combat/Components"}, {"Stats", "Player/Stats"}, {"PlayerLevelSystem", "Player/Stats"}, {"StatType", "Player/Stats"}, {"CardData", "Player/Upgrade/Data"}, {"RandomStatCardData", "Player/Upgrade/Data"}, {"RandomStatCardUIstar", "Player/Upgrade/UI"}, {"RandomStatCardUIStar", "Player/Upgrade/UI"}, {"CardUI", "Player/Upgrade/UI"}, {"EquipItem", "Player/Equipment"}, {"PlayerInteraction", "Player/Interaction"}, // Combat {"IDamageable", "Combat/Interfaces"}, {"WeaponHitBox", "Combat/Components"}, {"WePonHitBox", "Combat/Components"}, {"DamageBot", "Combat/Debug"}, // Enemy {"MonsterSpawner", "Enemy/Spawner"}, {"Monster Spawner", "Enemy/Spawner"}, {"MonsterUpdateManager", "Enemy/Spawner"}, {"DummyBot", "Enemy/Types"}, // Items {"HealthPotion", "Items/Pickups"}, {"HealthAltar", "Items/Interactive"}, {"ItemHighlight", "Items/VFX"}, // UI {"CrossHairUI", "UI/HUD"}, {"HP_UI_bar", "UI/HUD"}, {"HP UI bar", "UI/HUD"}, {"HPUIbar", "UI/HUD"}, {"PlayerStat_UI", "UI/HUD"}, {"PlayerStat UI", "UI/HUD"}, {"PlayerStatUI", "UI/HUD"}, {"EnemyHP_UI", "UI/Enemy"}, {"EnemyHP UI", "UI/Enemy"}, {"EnemyHPUI", "UI/Enemy"}, {"LevelUIManager", "UI/Level"}, // Camera {"CamShake", "Camera/Effects"}, // Systems {"GenericObjectPool", "Systems/ObjectPool"}, {"LoadScene", "Systems/Scene"}, {"SettingResolution", "Systems/Settings"}, // Data {"StatsConfig", "Data/Stats"}, // Optimization {"PlayerRangeManager", "Systems/Optimization"}, {"RenderGroup", "Systems/Optimization"}, {"RenderOptimizationConfig", "Systems/Optimization"}, {"PlayerRangeManagerEditor", "Systems/Optimization/Editor"} }; } private void Log(string message) { if (_showDetailedLog) { Debug.Log($"[ProjectOrganizerV3] {message}"); } } }