StyleGuide Sample 완료

This commit is contained in:
logonkhi
2026-01-13 20:37:57 +09:00
parent 24e7f038fe
commit 538c8a081e
2 changed files with 768 additions and 0 deletions

View File

@@ -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
{
/// <summary>
/// 이미지 아이콘 폴더를 스캔하여 아이콘 경로 상수 클래스를 자동 생성하는 에디터 도구입니다.
/// </summary>
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<IconInfo>? _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<UTKImageIconsGenerator>("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<Texture2D>(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<IconInfo> GetIconInfos(string folder, bool includeSubfolders)
{
var result = new List<IconInfo>();
var searchOption = includeSubfolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
var usedNames = new HashSet<string>();
// 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<IconInfo> icons, string className, string namespaceName, string sourceFolder)
{
var sb = new StringBuilder();
// Resources 폴더 여부 확인
var isInResources = sourceFolder.Contains("Resources");
sb.AppendLine("// <auto-generated>");
sb.AppendLine("// 이 파일은 UTKImageIconsGenerator에 의해 자동 생성되었습니다.");
sb.AppendLine("// 직접 수정하지 마세요. Tools > UTK > Image Icons Generator 메뉴로 재생성하세요.");
sb.AppendLine($"// Source: {sourceFolder}");
sb.AppendLine("// </auto-generated>");
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(" /// <summary>");
sb.AppendLine(" /// 이미지 아이콘 리소스 경로 상수 클래스입니다.");
sb.AppendLine($" /// 총 {icons.Count}개의 아이콘을 포함합니다.");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <example>");
sb.AppendLine(" /// <code>");
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(" /// </code>");
sb.AppendLine(" /// </example>");
sb.AppendLine(" /// <remarks>");
sb.AppendLine(" /// <para><b>UXML에서 사용하기:</b></para>");
sb.AppendLine(" /// <para>UXML에서 이미지 아이콘을 사용하려면 USS에서 background-image를 설정합니다.</para>");
sb.AppendLine(" /// <code>");
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(" /// </code>");
sb.AppendLine(" /// <code>");
sb.AppendLine(" /// <!-- UXML 파일 -->");
sb.AppendLine(" /// <ui:VisualElement class=\"my-icon\" />");
sb.AppendLine(" /// </code>");
sb.AppendLine(" /// <para><b>C# 코드에서 UXML 요소에 이미지 적용:</b></para>");
sb.AppendLine(" /// <code>");
sb.AppendLine($" /// var iconElement = root.Q<VisualElement>(\"my-icon\");");
sb.AppendLine($" /// var texture = {className}.LoadTextureByName(\"icon_setting_22\");");
sb.AppendLine(" /// iconElement.style.backgroundImage = new StyleBackground(texture);");
sb.AppendLine(" /// </code>");
sb.AppendLine(" /// <para><b>Image 요소에서 사용:</b></para>");
sb.AppendLine(" /// <code>");
sb.AppendLine($" /// var image = root.Q<Image>(\"my-image\");");
sb.AppendLine($" /// image.sprite = {className}.LoadSpriteByName(\"btn_close_16\");");
sb.AppendLine(" /// </code>");
sb.AppendLine(" /// </remarks>");
sb.AppendLine($" public static class {className}");
sb.AppendLine(" {");
// 아이콘 경로 상수 생성
foreach (var icon in icons)
{
sb.AppendLine($" /// <summary>{icon.RelativePath}</summary>");
sb.AppendLine($" public const string {icon.FieldName} = \"{icon.ResourcePath}\";");
}
sb.AppendLine();
sb.AppendLine(" #region Lookup & Load");
sb.AppendLine();
// 이름으로 경로 조회 딕셔너리
sb.AppendLine(" private static readonly Dictionary<string, string> _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<string, Sprite?> _spriteCache = new();");
sb.AppendLine(" private static readonly Dictionary<string, Texture2D?> _textureCache = new();");
sb.AppendLine();
// 조회 메서드
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 아이콘 이름으로 리소스 경로를 조회합니다.");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"iconName\">아이콘 파일명 (확장자 제외)</param>");
sb.AppendLine(" /// <returns>리소스 경로, 없으면 빈 문자열</returns>");
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(" /// <summary>");
sb.AppendLine(" /// 아이콘이 존재하는지 확인합니다.");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" public static bool HasIcon(string iconName) => _pathsByName.ContainsKey(iconName);");
sb.AppendLine();
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 모든 아이콘 이름 목록을 반환합니다.");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" public static IEnumerable<string> GetAllIconNames() => _pathsByName.Keys;");
sb.AppendLine();
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 전체 아이콘 수를 반환합니다.");
sb.AppendLine(" /// </summary>");
sb.AppendLine($" public static int Count => {icons.Count};");
sb.AppendLine();
// 동기 로드 메서드들
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 리소스 경로로 Sprite를 동기로 로드합니다. (캐싱됨)");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"resourcePath\">리소스 경로 (예: Icons/Home)</param>");
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<Sprite>(resourcePath);");
sb.AppendLine(" _spriteCache[resourcePath] = sprite;");
sb.AppendLine(" return sprite;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 리소스 경로로 Sprite를 비동기로 로드합니다. (캐싱됨)");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"resourcePath\">리소스 경로 (예: Icons/Home)</param>");
sb.AppendLine(" /// <param name=\"ct\">취소 토큰</param>");
sb.AppendLine(" public static async UniTask<Sprite?> 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<Sprite>(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(" /// <summary>");
sb.AppendLine(" /// 리소스 경로로 Texture2D를 동기로 로드합니다. (캐싱됨)");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"resourcePath\">리소스 경로 (예: Icons/Home)</param>");
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<Texture2D>(resourcePath);");
sb.AppendLine(" _textureCache[resourcePath] = texture;");
sb.AppendLine(" return texture;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 리소스 경로로 Texture2D를 비동기로 로드합니다. (캐싱됨)");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"resourcePath\">리소스 경로 (예: Icons/Home)</param>");
sb.AppendLine(" /// <param name=\"ct\">취소 토큰</param>");
sb.AppendLine(" public static async UniTask<Texture2D?> 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<Texture2D>(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(" /// <summary>");
sb.AppendLine(" /// 아이콘 이름으로 Sprite를 동기로 로드합니다. (캐싱됨)");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"iconName\">아이콘 파일명 (확장자 제외)</param>");
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(" /// <summary>");
sb.AppendLine(" /// 아이콘 이름으로 Sprite를 비동기로 로드합니다. (캐싱됨)");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"iconName\">아이콘 파일명 (확장자 제외)</param>");
sb.AppendLine(" /// <param name=\"ct\">취소 토큰</param>");
sb.AppendLine(" public static async UniTask<Sprite?> 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(" /// <summary>");
sb.AppendLine(" /// 아이콘 이름으로 Texture2D를 동기로 로드합니다. (캐싱됨)");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"iconName\">아이콘 파일명 (확장자 제외)</param>");
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(" /// <summary>");
sb.AppendLine(" /// 아이콘 이름으로 Texture2D를 비동기로 로드합니다. (캐싱됨)");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"iconName\">아이콘 파일명 (확장자 제외)</param>");
sb.AppendLine(" /// <param name=\"ct\">취소 토큰</param>");
sb.AppendLine(" public static async UniTask<Texture2D?> 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(" /// <summary>");
sb.AppendLine(" /// 캐시를 클리어합니다.");
sb.AppendLine(" /// </summary>");
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<string> 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]));
}
}
}
}
}
}
}