using System.IO;
using UnityEditor;
using UnityEngine;
namespace TKS.EditorTools
{
///
/// Assets/Resources/Units/<art>/<clip>/f###.png 임포트 설정: 단일 스프라이트 · 피벗 = meta.json pivot(발) · PPU = meta.ppu · 밉맵 + Trilinear(2026-11-11) · 무압축.
/// meta.json 은 skeletal-anim-pipeline `rig_frames_export.py` 가 쓴다.
///
public class UnitArtImporter : AssetPostprocessor
{
public const string Root = "Assets/Resources/Units/";
/// 전장 배경(원화 세션 납품 · 5:8 불투명 · PPU 100 = 전장 20×32 월드).
public const string BoardRoot = "Assets/Resources/Board/";
/// 챕터 월드맵(Maps = 2160×3840 불투명) · 거점 UI 부품(UI = 투명 · pivot·9-slice·PPU = UI/import.json).
public const string ChapterMapRoot = "Assets/Resources/ChapterMap/";
/// Android 유닛 프레임 긴 변 상한(Fable 검토 2026-11-12 · 원화 PNG 는 그대로 · 빌드 때만 축소).
public const int AndroidUnitMaxSize = 512;
/// Android 유닛 프레임 압축(ASTC 5×5 시작값 · 출시 전 4×4·6×6 과 육안 비교로 확정).
public const TextureImporterFormat AndroidUnitFormat = TextureImporterFormat.ASTC_5x5;
[System.Serializable]
class ChapterUiImport
{
public string file;
public float[] pivot;
public float[] border;
public float ppu;
}
[System.Serializable]
class ChapterUiImportList
{
public ChapterUiImport[] items;
}
void OnPreprocessTexture()
{
string path = assetPath.Replace('\\', '/');
if (path.StartsWith(ChapterMapRoot))
{
PreprocessChapterMap(path);
return;
}
if (path.StartsWith(BoardRoot))
{
var bi = (TextureImporter)assetImporter;
bi.textureType = TextureImporterType.Sprite;
bi.spriteImportMode = SpriteImportMode.Single;
bi.mipmapEnabled = false;
bi.filterMode = FilterMode.Bilinear;
bi.alphaIsTransparency = false;
bi.textureCompression = TextureImporterCompression.Compressed;
bi.maxTextureSize = 4096;
bi.spritePixelsPerUnit = 100f;
var bs = new TextureImporterSettings();
bi.ReadTextureSettings(bs);
bs.spriteMeshType = SpriteMeshType.FullRect;
bs.spriteAlignment = (int)SpriteAlignment.Center;
bi.SetTextureSettings(bs);
return;
}
if (!path.StartsWith(Root)) return;
var ti = (TextureImporter)assetImporter;
ti.textureType = TextureImporterType.Sprite;
ti.spriteImportMode = SpriteImportMode.Single;
// 밉맵 + Trilinear(Fable 검토 2026-11-11 A안 · PD 「해상도가 조금 깨진 느낌」) — 로비·전투는 원본을 1/3~1/4 로 줄여 그림 →
// 밉맵 없는 Bilinear 는 4×4 중 2×2 만 섞어 외곽선이 들쭉날쭉(실측 고주파 +18 % · 반투명 가장자리 −20 %) · 대기 프레임이 움직이면 그 깨짐이 반짝임으로 보임. 메모리 +33 %.
ti.mipmapEnabled = true;
ti.filterMode = FilterMode.Trilinear;
ti.alphaIsTransparency = true;
ti.textureCompression = TextureImporterCompression.Uncompressed;
ti.maxTextureSize = 2048;
// Android(구글 플레이 출시 · PD 2026-11-12 「리소스 구성 Fable 검토 후 진행」) = 빌드 때만 긴 변 512(표시 0.12~0.56 배) + ASTC 5×5 · 밉맵 유지
// 장당 ≈ 4.35 → 0.20 MB(계산값) · PC·편집기 화면 불변 · 블록 크기(4×4·5×5·6×6)는 출시 전 Android 전환 육안 비교로 확정(Fable 검토 문서 「출시 전 1」)
var ap = ti.GetPlatformTextureSettings("Android");
ap.overridden = true;
ap.maxTextureSize = AndroidUnitMaxSize;
ap.format = AndroidUnitFormat;
ti.SetPlatformTextureSettings(ap);
var meta = LoadMeta(path);
var s = new TextureImporterSettings();
ti.ReadTextureSettings(s);
s.spriteMeshType = SpriteMeshType.FullRect;
if (meta != null && meta.pivot != null && meta.pivot.Length == 2)
{
s.spriteAlignment = (int)SpriteAlignment.Custom;
s.spritePivot = new Vector2(meta.pivot[0], meta.pivot[1]);
}
if (meta != null && meta.ppu > 0.01f) s.spritePixelsPerUnit = meta.ppu;
ti.SetTextureSettings(s);
}
void PreprocessChapterMap(string path)
{
var ti = (TextureImporter)assetImporter;
ti.textureType = TextureImporterType.Sprite;
ti.spriteImportMode = SpriteImportMode.Single;
ti.mipmapEnabled = false;
ti.filterMode = FilterMode.Bilinear;
var s = new TextureImporterSettings();
if (path.StartsWith(ChapterMapRoot + "Maps/"))
{
ti.alphaIsTransparency = false;
ti.textureCompression = TextureImporterCompression.Compressed;
ti.maxTextureSize = 4096;
ti.spritePixelsPerUnit = 100f;
ti.ReadTextureSettings(s);
s.spriteMeshType = SpriteMeshType.FullRect;
s.spriteAlignment = (int)SpriteAlignment.Center;
ti.SetTextureSettings(s);
return;
}
ti.alphaIsTransparency = true;
ti.textureCompression = TextureImporterCompression.Uncompressed;
ti.maxTextureSize = 1024;
ti.ReadTextureSettings(s);
s.spriteMeshType = SpriteMeshType.FullRect;
var item = FindChapterUi(path);
if (item != null)
{
if (item.pivot != null && item.pivot.Length == 2)
{
s.spriteAlignment = (int)SpriteAlignment.Custom;
s.spritePivot = new Vector2(item.pivot[0], 1f - item.pivot[1]); // import.json = 이미지 좌표(위 → 아래)
}
if (item.border != null && item.border.Length == 4)
s.spriteBorder = new Vector4(item.border[0], item.border[1], item.border[2], item.border[3]);
if (item.ppu > 0.01f) s.spritePixelsPerUnit = item.ppu;
}
ti.SetTextureSettings(s);
}
static ChapterUiImport FindChapterUi(string texPath)
{
string dir = Path.GetDirectoryName(texPath);
if (string.IsNullOrEmpty(dir)) return null;
string json = Path.Combine(dir, "import.json");
if (!File.Exists(json)) return null;
try
{
var list = JsonUtility.FromJson(File.ReadAllText(json));
string name = Path.GetFileName(texPath);
if (list != null && list.items != null)
foreach (var it in list.items)
if (it.file == name) return it;
}
catch
{
// 표가 깨졌으면 기본 설정
}
return null;
}
static UnitArtMeta LoadMeta(string texPath)
{
string clipDir = Path.GetDirectoryName(texPath);
string artDir = clipDir != null ? Path.GetDirectoryName(clipDir) : null;
if (string.IsNullOrEmpty(artDir)) return null;
string metaPath = Path.Combine(artDir, "meta.json");
if (!File.Exists(metaPath)) return null;
try
{
return JsonUtility.FromJson(File.ReadAllText(metaPath));
}
catch
{
return null;
}
}
/// 프레임을 새로 넣거나 meta.json 피벗을 바꾼 뒤 임포트 설정을 다시 적용한다(메뉴 · CLI `-executeMethod TKS.EditorTools.UnitArtImporter.ReimportAll`).
[MenuItem("TKS/Reimport Unit Art (유닛 아트 재임포트)")]
public static void ReimportAll()
{
foreach (var root in new[] { Root, BoardRoot, ChapterMapRoot })
{
string dir = root.TrimEnd('/');
if (!AssetDatabase.IsValidFolder(dir))
{
Debug.Log("[TKS] " + dir + " 없음");
continue;
}
AssetDatabase.ImportAsset(dir, ImportAssetOptions.ImportRecursive | ImportAssetOptions.ForceUpdate);
Debug.Log("[TKS] 아트 재임포트 완료 · " + dir);
}
AssetDatabase.Refresh();
}
}
}