From 538c8a081efbe8a9c0769895c6b6cb2b754a91cf Mon Sep 17 00:00:00 2001 From: logonkhi Date: Tue, 13 Jan 2026 20:37:57 +0900 Subject: [PATCH] =?UTF-8?q?StyleGuide=20Sample=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UIToolkit/UTKImageIconsGenerator.cs | 760 ++++++++++++++++++ Assets/Resources/Fonts/Icons.meta | 8 + 2 files changed, 768 insertions(+) create mode 100644 Assets/Editor/UIToolkit/UTKImageIconsGenerator.cs create mode 100644 Assets/Resources/Fonts/Icons.meta diff --git a/Assets/Editor/UIToolkit/UTKImageIconsGenerator.cs b/Assets/Editor/UIToolkit/UTKImageIconsGenerator.cs new file mode 100644 index 00000000..1512cf1f --- /dev/null +++ b/Assets/Editor/UIToolkit/UTKImageIconsGenerator.cs @@ -0,0 +1,760 @@ +#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])); + } + } + } + } + } + } +} diff --git a/Assets/Resources/Fonts/Icons.meta b/Assets/Resources/Fonts/Icons.meta new file mode 100644 index 00000000..f04f140e --- /dev/null +++ b/Assets/Resources/Fonts/Icons.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1f3edc5b32ba94941ac6f2897a9cbf45 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: