using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.U2D;
using UnityEngine;
using UnityEngine.U2D;
namespace TKS.EditorTools
{
///
/// UI 스프라이트 아틀라스(PD 2026-11-10 「드로우콜 최소화 구조」) — 구성 SOT = Assets/Data/UI/ui_atlas.csv → Assets/UI/Atlases/<이름>.spriteatlasv2.
/// theme 줄 = UI 테마(UITheme.asset)가 쓰는 스프라이트를 match(경로 포함 · | = 또는 · * = 나머지 전부)로 나눔 · folder 줄 = 폴더 전체 ·
/// option single_area = 그보다 넓은 그림은 묶지 않음 · padding.
/// UI 테마 임포트가 끝날 때마다 자동으로 다시 맞춘다(새 납품 스프라이트 = 코드 변경 없이 아틀라스에 들어감) · 구성이 같으면 파일을 건드리지 않는다.
/// 패커 = Sprite Atlas V2(에디터 Play 에서도 묶음 · 프로젝트 설정 EditorSettings).
///
public static class UIAtlasBuilder
{
public const string CsvPath = "Assets/Data/UI/ui_atlas.csv";
public const string OutDir = "Assets/UI/Atlases";
class Group
{
public string kind;
public string name;
public string[] match;
public int maxSize;
public readonly List paths = new List();
}
[MenuItem("TKS/Build UI Atlases (UI 아틀라스 갱신)")]
public static void Menu()
{
Build(AssetDatabase.LoadAssetAtPath(UIThemeImporter.AssetPath), true);
}
/// 표대로 아틀라스를 맞춘다 · 반환 = 오류 없이 끝났는가(오류면 아틀라스 미변경).
public static bool Build(UITheme theme, bool log = false)
{
if (!File.Exists(CsvPath))
{
Debug.LogError("[TKS] UI 아틀라스 표 없음: " + CsvPath);
return false;
}
var groups = new List();
long singleArea = long.MaxValue;
int padding = 4;
var errors = new List();
var lines = File.ReadAllText(CsvPath, Encoding.UTF8).TrimStart('').Replace("\r", "").Split('\n');
for (int n = 1; n < lines.Length; n++)
{
if (string.IsNullOrWhiteSpace(lines[n])) continue;
var f = lines[n].Split(',');
if (f.Length < 4)
{
errors.Add((n + 1) + "행: 열 부족");
continue;
}
string kind = f[0].Trim(), name = f[1].Trim(), match = f[2].Trim(), value = f[3].Trim();
int v;
switch (kind)
{
case "theme":
case "folder":
if (!int.TryParse(value, out v) || v < 128) errors.Add((n + 1) + "행 " + name + ": 최대 크기 " + value);
groups.Add(new Group { kind = kind, name = name, match = match.Split('|'), maxSize = v });
break;
case "option":
if (!int.TryParse(value, out v)) errors.Add((n + 1) + "행 " + name + ": 값 " + value);
else if (name == "single_area") singleArea = v;
else if (name == "padding") padding = v;
else errors.Add((n + 1) + "행: 모르는 옵션 " + name);
break;
default:
errors.Add((n + 1) + "행: 모르는 종류 " + kind);
break;
}
}
// theme 스프라이트 → 첫 번째로 맞는 theme 줄(* = 나머지)
var singles = new List();
foreach (var path in ThemeTextures(theme))
{
var tex = AssetDatabase.LoadAssetAtPath(path);
if (tex == null) continue;
if ((long)tex.width * tex.height > singleArea)
{
singles.Add(path);
continue;
}
Group hit = null;
foreach (var g in groups)
if (g.kind == "theme" && g.match.Any(m => m != "*" && path.Contains(m)))
{
hit = g;
break;
}
if (hit == null) hit = groups.FirstOrDefault(g => g.kind == "theme" && g.match.Contains("*"));
if (hit != null) hit.paths.Add(path);
}
foreach (var g in groups)
if (g.kind == "folder")
{
if (!AssetDatabase.IsValidFolder(g.match[0])) errors.Add(g.name + ": 폴더 없음 " + g.match[0]);
else g.paths.Add(g.match[0]);
}
if (errors.Count > 0)
{
Debug.LogError("[TKS] UI 아틀라스 표 오류 " + errors.Count + "건(아틀라스 미변경):\n" + string.Join("\n", errors));
return false;
}
// 패커 = Sprite Atlas V2(에디터 Play 에서도 묶음)
if (EditorSettings.spritePackerMode != SpritePackerMode.SpriteAtlasV2)
{
EditorSettings.spritePackerMode = SpritePackerMode.SpriteAtlasV2;
AssetDatabase.SaveAssets();
}
if (!AssetDatabase.IsValidFolder(OutDir)) AssetDatabase.CreateFolder(Path.GetDirectoryName(OutDir).Replace('\\', '/'), Path.GetFileName(OutDir));
int changed = 0;
var report = new StringBuilder();
foreach (var g in groups)
{
g.paths.Sort(System.StringComparer.Ordinal);
if (Write(g, padding)) changed++;
report.Append(" · ").Append(g.name).Append(' ').Append(g.paths.Count);
}
if (changed > 0) SpriteAtlasUtility.PackAllAtlases(EditorUserBuildSettings.activeBuildTarget);
if (log || changed > 0)
Debug.Log("[TKS] UI 아틀라스" + report + " · 큰 그림(묶지 않음) " + singles.Count + " · 갱신 " + changed + "장");
return true;
}
/// 아틀라스 1장을 표 구성대로 · 구성(목록·크기·간격) 서명이 같으면 건드리지 않는다.
static bool Write(Group g, int padding)
{
string path = OutDir + "/" + g.name + ".spriteatlasv2";
string sig = Hash128.Compute(g.maxSize + ";" + padding + ";" + string.Join("|", g.paths.Select(AssetDatabase.AssetPathToGUID))).ToString();
var imp = AssetImporter.GetAtPath(path) as SpriteAtlasImporter;
if (imp != null && imp.userData == sig) return false;
var asset = new SpriteAtlasAsset();
var objs = new List