#nullable enable using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using UnityEditor; using UnityEngine; namespace UVC.UIToolkit.Editor { /// /// 이미지 아이콘 폴더를 스캔하여 아이콘 경로 상수 클래스를 자동 생성하는 에디터 도구입니다. /// public class UTKImageIconsGenerator : EditorWindow { #region EditorPrefs Keys private const string PrefKeyIconsFolder = "UTK_ImageIcons_IconsFolder"; private const string PrefKeyOutputPath = "UTK_ImageIcons_OutputPath"; private const string PrefKeyClassName = "UTK_ImageIcons_ClassName"; private const string PrefKeyNamespace = "UTK_ImageIcons_Namespace"; private const string PrefKeyIncludeSubfolders = "UTK_ImageIcons_IncludeSubfolders"; private const string PrefKeyGeneratePreview = "UTK_ImageIcons_GeneratePreview"; #endregion #region Default Values private const string DefaultIconsFolder = "Assets/Resources/Icons"; private const string DefaultOutputPath = "Assets/Scripts/UVC/UIToolkit/Common/UTKImageIcons.cs"; private const string DefaultClassName = "UTKImageIcons"; private const string DefaultNamespace = "UVC.UIToolkit"; #endregion #region Supported Extensions private static readonly string[] SupportedExtensions = { ".png", ".jpg", ".jpeg", ".tga", ".psd", ".gif", ".bmp" }; #endregion #region Fields private string _iconsFolder = DefaultIconsFolder; private string _outputPath = DefaultOutputPath; private string _className = DefaultClassName; private string _namespace = DefaultNamespace; private bool _includeSubfolders = true; private bool _generatePreview = true; private Vector2 _scrollPosition; private List? _previewIcons; #endregion private class IconInfo { public string FilePath { get; set; } = ""; public string FileName { get; set; } = ""; public string FieldName { get; set; } = ""; public string ResourcePath { get; set; } = ""; public string RelativePath { get; set; } = ""; public Texture2D? Preview { get; set; } } [MenuItem("Tools/UTK/Image Icons Generator")] public static void ShowWindow() { var window = GetWindow("Image Icons Generator"); window.minSize = new Vector2(500, 400); window.LoadSettings(); } [MenuItem("Tools/UTK/Generate Image Icons Class (Quick)")] public static void GenerateQuick() { var iconsFolder = EditorPrefs.GetString(PrefKeyIconsFolder, DefaultIconsFolder); var outputPath = EditorPrefs.GetString(PrefKeyOutputPath, DefaultOutputPath); var className = EditorPrefs.GetString(PrefKeyClassName, DefaultClassName); var namespaceName = EditorPrefs.GetString(PrefKeyNamespace, DefaultNamespace); var includeSubfolders = EditorPrefs.GetBool(PrefKeyIncludeSubfolders, true); Generate(iconsFolder, outputPath, className, namespaceName, includeSubfolders); } private void OnEnable() { LoadSettings(); } private void OnDisable() { ClearPreviewTextures(); } private void LoadSettings() { _iconsFolder = EditorPrefs.GetString(PrefKeyIconsFolder, DefaultIconsFolder); _outputPath = EditorPrefs.GetString(PrefKeyOutputPath, DefaultOutputPath); _className = EditorPrefs.GetString(PrefKeyClassName, DefaultClassName); _namespace = EditorPrefs.GetString(PrefKeyNamespace, DefaultNamespace); _includeSubfolders = EditorPrefs.GetBool(PrefKeyIncludeSubfolders, true); _generatePreview = EditorPrefs.GetBool(PrefKeyGeneratePreview, true); } private void SaveSettings() { EditorPrefs.SetString(PrefKeyIconsFolder, _iconsFolder); EditorPrefs.SetString(PrefKeyOutputPath, _outputPath); EditorPrefs.SetString(PrefKeyClassName, _className); EditorPrefs.SetString(PrefKeyNamespace, _namespace); EditorPrefs.SetBool(PrefKeyIncludeSubfolders, _includeSubfolders); EditorPrefs.SetBool(PrefKeyGeneratePreview, _generatePreview); } private void ClearPreviewTextures() { _previewIcons = null; } private void OnGUI() { EditorGUILayout.Space(10); EditorGUILayout.LabelField("Image Icons Class Generator", EditorStyles.boldLabel); EditorGUILayout.Space(5); EditorGUILayout.HelpBox("이미지 아이콘 폴더를 스캔하여 Resources.Load로 사용할 수 있는 C# 정적 클래스를 생성합니다.", MessageType.Info); EditorGUILayout.Space(10); // 아이콘 폴더 경로 EditorGUILayout.BeginHorizontal(); _iconsFolder = EditorGUILayout.TextField("Icons Folder", _iconsFolder); if (GUILayout.Button("...", GUILayout.Width(30))) { var path = EditorUtility.OpenFolderPanel("Select Icons Folder", "Assets", ""); if (!string.IsNullOrEmpty(path)) { // 상대 경로로 변환 if (path.StartsWith(Application.dataPath)) { path = "Assets" + path.Substring(Application.dataPath.Length); } _iconsFolder = path; ClearPreviewTextures(); } } EditorGUILayout.EndHorizontal(); // 출력 파일 경로 EditorGUILayout.BeginHorizontal(); _outputPath = EditorGUILayout.TextField("Output File", _outputPath); if (GUILayout.Button("...", GUILayout.Width(30))) { var directory = Path.GetDirectoryName(_outputPath) ?? "Assets"; var filename = Path.GetFileName(_outputPath); var path = EditorUtility.SaveFilePanel("Save Generated Class", directory, filename, "cs"); if (!string.IsNullOrEmpty(path)) { // 상대 경로로 변환 if (path.StartsWith(Application.dataPath)) { path = "Assets" + path.Substring(Application.dataPath.Length); } _outputPath = path; } } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(5); // 클래스명 _className = EditorGUILayout.TextField("Class Name", _className); // 네임스페이스 _namespace = EditorGUILayout.TextField("Namespace", _namespace); EditorGUILayout.Space(5); // 옵션 _includeSubfolders = EditorGUILayout.Toggle("Include Subfolders", _includeSubfolders); _generatePreview = EditorGUILayout.Toggle("Show Preview", _generatePreview); EditorGUILayout.Space(10); // 폴더 존재 여부 및 아이콘 수 표시 var folderExists = Directory.Exists(_iconsFolder); var outputExists = File.Exists(_outputPath); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Icons Folder:", GUILayout.Width(100)); EditorGUILayout.LabelField(folderExists ? "Found" : "Not Found", folderExists ? EditorStyles.label : EditorStyles.boldLabel); EditorGUILayout.EndHorizontal(); if (folderExists) { var iconCount = CountIcons(_iconsFolder, _includeSubfolders); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Icons Found:", GUILayout.Width(100)); EditorGUILayout.LabelField($"{iconCount} images"); EditorGUILayout.EndHorizontal(); } EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Output File:", GUILayout.Width(100)); EditorGUILayout.LabelField(outputExists ? "Exists (will overwrite)" : "New file"); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(10); // 버튼들 EditorGUILayout.BeginHorizontal(); GUI.enabled = folderExists; if (GUILayout.Button("Scan & Preview", GUILayout.Height(25))) { SaveSettings(); ScanIcons(); } if (GUILayout.Button("Generate", GUILayout.Height(25))) { SaveSettings(); Generate(_iconsFolder, _outputPath, _className, _namespace, _includeSubfolders); } GUI.enabled = true; if (GUILayout.Button("Reset", GUILayout.Height(25))) { _iconsFolder = DefaultIconsFolder; _outputPath = DefaultOutputPath; _className = DefaultClassName; _namespace = DefaultNamespace; _includeSubfolders = true; _generatePreview = true; SaveSettings(); ClearPreviewTextures(); } EditorGUILayout.EndHorizontal(); // 미리보기 영역 if (_generatePreview && _previewIcons != null && _previewIcons.Count > 0) { EditorGUILayout.Space(10); EditorGUILayout.LabelField($"Preview ({_previewIcons.Count} icons)", EditorStyles.boldLabel); _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, GUILayout.ExpandHeight(true)); var iconSize = 48; var padding = 4; var labelHeight = 16; var itemWidth = iconSize + padding * 2; var itemHeight = iconSize + labelHeight + padding * 2; var windowWidth = position.width - 20; var columns = Mathf.Max(1, (int)(windowWidth / itemWidth)); EditorGUILayout.BeginHorizontal(); var currentColumn = 0; foreach (var icon in _previewIcons) { if (currentColumn >= columns) { EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); currentColumn = 0; } EditorGUILayout.BeginVertical(GUILayout.Width(itemWidth), GUILayout.Height(itemHeight)); // 아이콘 이미지 var rect = GUILayoutUtility.GetRect(iconSize, iconSize); if (icon.Preview != null) { GUI.DrawTexture(rect, icon.Preview, ScaleMode.ScaleToFit); } else { EditorGUI.DrawRect(rect, new Color(0.2f, 0.2f, 0.2f)); } // 필드명 var style = new GUIStyle(EditorStyles.miniLabel) { alignment = TextAnchor.MiddleCenter, clipping = TextClipping.Clip }; EditorGUILayout.LabelField(icon.FieldName, style, GUILayout.Width(itemWidth), GUILayout.Height(labelHeight)); EditorGUILayout.EndVertical(); currentColumn++; } EditorGUILayout.EndHorizontal(); EditorGUILayout.EndScrollView(); } } private void ScanIcons() { ClearPreviewTextures(); _previewIcons = GetIconInfos(_iconsFolder, _includeSubfolders); // 프리뷰 텍스처 로드 foreach (var icon in _previewIcons) { icon.Preview = AssetDatabase.LoadAssetAtPath(icon.FilePath); } } private static int CountIcons(string folder, bool includeSubfolders) { var searchOption = includeSubfolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var count = 0; foreach (var ext in SupportedExtensions) { count += Directory.GetFiles(folder, $"*{ext}", searchOption).Length; } return count; } private static List GetIconInfos(string folder, bool includeSubfolders) { var result = new List(); var searchOption = includeSubfolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var usedNames = new HashSet(); // Resources 폴더 기준 경로 계산 var resourcesIndex = folder.IndexOf("Resources/", StringComparison.OrdinalIgnoreCase); var isInResources = resourcesIndex >= 0; var resourcesBasePath = isInResources ? folder.Substring(resourcesIndex + "Resources/".Length) : ""; foreach (var ext in SupportedExtensions) { var files = Directory.GetFiles(folder, $"*{ext}", searchOption); foreach (var file in files) { var normalizedPath = file.Replace("\\", "/"); var fileName = Path.GetFileNameWithoutExtension(normalizedPath); var relativePath = normalizedPath.Substring(folder.Length).TrimStart('/'); var relativeDir = Path.GetDirectoryName(relativePath)?.Replace("\\", "/") ?? ""; // Resources.Load용 경로 (확장자 제외) string resourcePath; if (isInResources) { var fullRelative = string.IsNullOrEmpty(resourcesBasePath) ? relativePath : $"{resourcesBasePath}/{relativePath}"; resourcePath = Path.ChangeExtension(fullRelative, null); } else { resourcePath = normalizedPath; // Resources 외부면 전체 경로 사용 } var fieldName = ConvertToFieldName(fileName, relativeDir, usedNames); result.Add(new IconInfo { FilePath = normalizedPath, FileName = fileName, FieldName = fieldName, ResourcePath = resourcePath, RelativePath = relativePath }); } } return result.OrderBy(i => i.FieldName).ToList(); } public static void Generate(string iconsFolder, string outputPath, string className, string namespaceName, bool includeSubfolders) { if (!Directory.Exists(iconsFolder)) { Debug.LogError($"아이콘 폴더를 찾을 수 없습니다: {iconsFolder}"); return; } var icons = GetIconInfos(iconsFolder, includeSubfolders); if (icons.Count == 0) { Debug.LogError("아이콘을 찾을 수 없습니다."); return; } var code = GenerateCode(icons, className, namespaceName, iconsFolder); // 디렉토리 생성 var directory = Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } File.WriteAllText(outputPath, code, Encoding.UTF8); AssetDatabase.Refresh(); Debug.Log($"{className}.cs 생성 완료: {icons.Count}개 아이콘 ({outputPath})"); } private static string GenerateCode(List icons, string className, string namespaceName, string sourceFolder) { var sb = new StringBuilder(); // Resources 폴더 여부 확인 var isInResources = sourceFolder.Contains("Resources"); sb.AppendLine("// "); sb.AppendLine("// 이 파일은 UTKImageIconsGenerator에 의해 자동 생성되었습니다."); sb.AppendLine("// 직접 수정하지 마세요. Tools > UTK > Image Icons Generator 메뉴로 재생성하세요."); sb.AppendLine($"// Source: {sourceFolder}"); sb.AppendLine("// "); sb.AppendLine(); sb.AppendLine("#nullable enable"); sb.AppendLine("using System.Collections.Generic;"); sb.AppendLine("using System.Threading;"); sb.AppendLine("using Cysharp.Threading.Tasks;"); sb.AppendLine("using UnityEngine;"); sb.AppendLine(); sb.AppendLine($"namespace {namespaceName}"); sb.AppendLine("{"); sb.AppendLine(" /// "); sb.AppendLine(" /// 이미지 아이콘 리소스 경로 상수 클래스입니다."); sb.AppendLine($" /// 총 {icons.Count}개의 아이콘을 포함합니다."); sb.AppendLine(" /// "); sb.AppendLine(" /// "); sb.AppendLine(" /// "); sb.AppendLine($" /// // 상수로 리소스 경로 사용"); sb.AppendLine($" /// string path = {className}.Home;"); sb.AppendLine(" /// "); sb.AppendLine($" /// // 동기 Sprite 로드 (캐싱됨)"); sb.AppendLine($" /// Sprite sprite = {className}.LoadSprite({className}.Home);"); sb.AppendLine(" /// "); sb.AppendLine($" /// // 비동기 Sprite 로드 (UniTask, 캐싱됨)"); sb.AppendLine($" /// Sprite? sprite = await {className}.LoadSpriteAsync({className}.Home, cancellationToken);"); sb.AppendLine(" /// "); sb.AppendLine($" /// // 동기 Texture2D 로드 (캐싱됨)"); sb.AppendLine($" /// Texture2D texture = {className}.LoadTexture({className}.Settings);"); sb.AppendLine(" /// "); sb.AppendLine($" /// // 비동기 Texture2D 로드 (UniTask, 캐싱됨)"); sb.AppendLine($" /// Texture2D? texture = await {className}.LoadTextureAsync({className}.Settings, cancellationToken);"); sb.AppendLine(" /// "); sb.AppendLine($" /// // 이름으로 Sprite 로드"); sb.AppendLine($" /// Sprite icon = {className}.LoadSpriteByName(\"home\");"); sb.AppendLine(" /// "); sb.AppendLine($" /// // 이름으로 비동기 Sprite 로드"); sb.AppendLine($" /// Sprite? icon = await {className}.LoadSpriteByNameAsync(\"home\", cancellationToken);"); sb.AppendLine(" /// "); sb.AppendLine($" /// // 이름으로 경로 조회"); sb.AppendLine($" /// string iconPath = {className}.GetPath(\"settings\");"); sb.AppendLine(" /// "); sb.AppendLine($" /// // 아이콘 존재 여부 확인"); sb.AppendLine($" /// if ({className}.HasIcon(\"search\")) {{ }}"); sb.AppendLine(" /// "); sb.AppendLine($" /// // 전체 아이콘 이름 순회"); sb.AppendLine($" /// foreach (var name in {className}.GetAllIconNames()) {{ }}"); sb.AppendLine(" /// "); sb.AppendLine($" /// // 캐시 클리어"); sb.AppendLine($" /// {className}.ClearCache();"); sb.AppendLine(" /// "); sb.AppendLine(" /// "); sb.AppendLine(" /// "); sb.AppendLine(" /// UXML에서 사용하기:"); sb.AppendLine(" /// UXML에서 이미지 아이콘을 사용하려면 USS에서 background-image를 설정합니다."); sb.AppendLine(" /// "); sb.AppendLine(" /// /* USS 파일 */"); sb.AppendLine(" /// .my-icon {"); sb.AppendLine(" /// width: 24px;"); sb.AppendLine(" /// height: 24px;"); sb.AppendLine(" /// background-image: resource('UIToolkit/Images/icon_setting_22');"); sb.AppendLine(" /// }"); sb.AppendLine(" /// "); sb.AppendLine(" /// "); sb.AppendLine(" /// "); sb.AppendLine(" /// "); sb.AppendLine(" /// "); sb.AppendLine(" /// C# 코드에서 UXML 요소에 이미지 적용:"); sb.AppendLine(" /// "); sb.AppendLine($" /// var iconElement = root.Q(\"my-icon\");"); sb.AppendLine($" /// var texture = {className}.LoadTextureByName(\"icon_setting_22\");"); sb.AppendLine(" /// iconElement.style.backgroundImage = new StyleBackground(texture);"); sb.AppendLine(" /// "); sb.AppendLine(" /// Image 요소에서 사용:"); sb.AppendLine(" /// "); sb.AppendLine($" /// var image = root.Q(\"my-image\");"); sb.AppendLine($" /// image.sprite = {className}.LoadSpriteByName(\"btn_close_16\");"); sb.AppendLine(" /// "); sb.AppendLine(" /// "); sb.AppendLine($" public static class {className}"); sb.AppendLine(" {"); // 아이콘 경로 상수 생성 foreach (var icon in icons) { sb.AppendLine($" /// {icon.RelativePath}"); sb.AppendLine($" public const string {icon.FieldName} = \"{icon.ResourcePath}\";"); } sb.AppendLine(); sb.AppendLine(" #region Lookup & Load"); sb.AppendLine(); // 이름으로 경로 조회 딕셔너리 sb.AppendLine(" private static readonly Dictionary _pathsByName = new()"); sb.AppendLine(" {"); foreach (var icon in icons) { sb.AppendLine($" [\"{icon.FileName}\"] = \"{icon.ResourcePath}\","); } sb.AppendLine(" };"); sb.AppendLine(); // 캐시 딕셔너리 sb.AppendLine(" private static readonly Dictionary _spriteCache = new();"); sb.AppendLine(" private static readonly Dictionary _textureCache = new();"); sb.AppendLine(); // 조회 메서드 sb.AppendLine(" /// "); sb.AppendLine(" /// 아이콘 이름으로 리소스 경로를 조회합니다."); sb.AppendLine(" /// "); sb.AppendLine(" /// 아이콘 파일명 (확장자 제외)"); sb.AppendLine(" /// 리소스 경로, 없으면 빈 문자열"); sb.AppendLine(" public static string GetPath(string iconName)"); sb.AppendLine(" {"); sb.AppendLine(" return _pathsByName.TryGetValue(iconName, out var path) ? path : string.Empty;"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// 아이콘이 존재하는지 확인합니다."); sb.AppendLine(" /// "); sb.AppendLine(" public static bool HasIcon(string iconName) => _pathsByName.ContainsKey(iconName);"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// 모든 아이콘 이름 목록을 반환합니다."); sb.AppendLine(" /// "); sb.AppendLine(" public static IEnumerable GetAllIconNames() => _pathsByName.Keys;"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// 전체 아이콘 수를 반환합니다."); sb.AppendLine(" /// "); sb.AppendLine($" public static int Count => {icons.Count};"); sb.AppendLine(); // 동기 로드 메서드들 sb.AppendLine(" /// "); sb.AppendLine(" /// 리소스 경로로 Sprite를 동기로 로드합니다. (캐싱됨)"); sb.AppendLine(" /// "); sb.AppendLine(" /// 리소스 경로 (예: Icons/Home)"); sb.AppendLine(" public static Sprite? LoadSprite(string resourcePath)"); sb.AppendLine(" {"); sb.AppendLine(" if (_spriteCache.TryGetValue(resourcePath, out var cached))"); sb.AppendLine(" return cached;"); sb.AppendLine(); sb.AppendLine(" var sprite = Resources.Load(resourcePath);"); sb.AppendLine(" _spriteCache[resourcePath] = sprite;"); sb.AppendLine(" return sprite;"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// 리소스 경로로 Sprite를 비동기로 로드합니다. (캐싱됨)"); sb.AppendLine(" /// "); sb.AppendLine(" /// 리소스 경로 (예: Icons/Home)"); sb.AppendLine(" /// 취소 토큰"); sb.AppendLine(" public static async UniTask LoadSpriteAsync(string resourcePath, CancellationToken ct = default)"); sb.AppendLine(" {"); sb.AppendLine(" if (_spriteCache.TryGetValue(resourcePath, out var cached))"); sb.AppendLine(" return cached;"); sb.AppendLine(); sb.AppendLine(" var request = Resources.LoadAsync(resourcePath);"); sb.AppendLine(" await request.ToUniTask(cancellationToken: ct);"); sb.AppendLine(); sb.AppendLine(" var sprite = request.asset as Sprite;"); sb.AppendLine(" _spriteCache[resourcePath] = sprite;"); sb.AppendLine(" return sprite;"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// 리소스 경로로 Texture2D를 동기로 로드합니다. (캐싱됨)"); sb.AppendLine(" /// "); sb.AppendLine(" /// 리소스 경로 (예: Icons/Home)"); sb.AppendLine(" public static Texture2D? LoadTexture(string resourcePath)"); sb.AppendLine(" {"); sb.AppendLine(" if (_textureCache.TryGetValue(resourcePath, out var cached))"); sb.AppendLine(" return cached;"); sb.AppendLine(); sb.AppendLine(" var texture = Resources.Load(resourcePath);"); sb.AppendLine(" _textureCache[resourcePath] = texture;"); sb.AppendLine(" return texture;"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// 리소스 경로로 Texture2D를 비동기로 로드합니다. (캐싱됨)"); sb.AppendLine(" /// "); sb.AppendLine(" /// 리소스 경로 (예: Icons/Home)"); sb.AppendLine(" /// 취소 토큰"); sb.AppendLine(" public static async UniTask LoadTextureAsync(string resourcePath, CancellationToken ct = default)"); sb.AppendLine(" {"); sb.AppendLine(" if (_textureCache.TryGetValue(resourcePath, out var cached))"); sb.AppendLine(" return cached;"); sb.AppendLine(); sb.AppendLine(" var request = Resources.LoadAsync(resourcePath);"); sb.AppendLine(" await request.ToUniTask(cancellationToken: ct);"); sb.AppendLine(); sb.AppendLine(" var texture = request.asset as Texture2D;"); sb.AppendLine(" _textureCache[resourcePath] = texture;"); sb.AppendLine(" return texture;"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// 아이콘 이름으로 Sprite를 동기로 로드합니다. (캐싱됨)"); sb.AppendLine(" /// "); sb.AppendLine(" /// 아이콘 파일명 (확장자 제외)"); sb.AppendLine(" public static Sprite? LoadSpriteByName(string iconName)"); sb.AppendLine(" {"); sb.AppendLine(" var path = GetPath(iconName);"); sb.AppendLine(" return string.IsNullOrEmpty(path) ? null : LoadSprite(path);"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// 아이콘 이름으로 Sprite를 비동기로 로드합니다. (캐싱됨)"); sb.AppendLine(" /// "); sb.AppendLine(" /// 아이콘 파일명 (확장자 제외)"); sb.AppendLine(" /// 취소 토큰"); sb.AppendLine(" public static async UniTask LoadSpriteByNameAsync(string iconName, CancellationToken ct = default)"); sb.AppendLine(" {"); sb.AppendLine(" var path = GetPath(iconName);"); sb.AppendLine(" return string.IsNullOrEmpty(path) ? null : await LoadSpriteAsync(path, ct);"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// 아이콘 이름으로 Texture2D를 동기로 로드합니다. (캐싱됨)"); sb.AppendLine(" /// "); sb.AppendLine(" /// 아이콘 파일명 (확장자 제외)"); sb.AppendLine(" public static Texture2D? LoadTextureByName(string iconName)"); sb.AppendLine(" {"); sb.AppendLine(" var path = GetPath(iconName);"); sb.AppendLine(" return string.IsNullOrEmpty(path) ? null : LoadTexture(path);"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// 아이콘 이름으로 Texture2D를 비동기로 로드합니다. (캐싱됨)"); sb.AppendLine(" /// "); sb.AppendLine(" /// 아이콘 파일명 (확장자 제외)"); sb.AppendLine(" /// 취소 토큰"); sb.AppendLine(" public static async UniTask LoadTextureByNameAsync(string iconName, CancellationToken ct = default)"); sb.AppendLine(" {"); sb.AppendLine(" var path = GetPath(iconName);"); sb.AppendLine(" return string.IsNullOrEmpty(path) ? null : await LoadTextureAsync(path, ct);"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); sb.AppendLine(" /// 캐시를 클리어합니다."); sb.AppendLine(" /// "); sb.AppendLine(" public static void ClearCache()"); sb.AppendLine(" {"); sb.AppendLine(" _spriteCache.Clear();"); sb.AppendLine(" _textureCache.Clear();"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" #endregion"); sb.AppendLine(" }"); sb.AppendLine("}"); return sb.ToString(); } private static string ConvertToFieldName(string fileName, string relativeDir, HashSet usedNames) { var sb = new StringBuilder(); // 서브폴더가 있으면 접두사로 추가 if (!string.IsNullOrEmpty(relativeDir)) { var dirParts = relativeDir.Split('/'); foreach (var part in dirParts) { AppendPascalCase(sb, part); } sb.Append('_'); } // 파일명 변환 AppendPascalCase(sb, fileName); var result = sb.ToString(); // 숫자로 시작하면 앞에 _ 추가 if (result.Length > 0 && char.IsDigit(result[0])) { result = "_" + result; } // 빈 문자열이면 기본값 if (string.IsNullOrEmpty(result)) { result = "_Icon"; } // 중복 처리 var originalResult = result; var counter = 2; while (usedNames.Contains(result)) { result = $"{originalResult}_{counter}"; counter++; } usedNames.Add(result); return result; } private static void AppendPascalCase(StringBuilder sb, string input) { // 특수문자를 구분자로 사용 var words = input.Split(new[] { '_', '-', ' ', '.' }, StringSplitOptions.RemoveEmptyEntries); foreach (var word in words) { if (word.Length == 0) continue; // 첫 글자 대문자 sb.Append(char.ToUpper(word[0])); if (word.Length > 1) { // 나머지는 소문자로 (단, 연속 대문자면 유지) for (int i = 1; i < word.Length; i++) { if (char.IsUpper(word[i]) && i + 1 < word.Length && char.IsUpper(word[i + 1])) { sb.Append(word[i]); } else { sb.Append(char.ToLower(word[i])); } } } } } } }