ProjectDDD/Assets/_DDD/_Scripts/AssetPostprocessors/AssetPostprocessorSprite.cs
2025-07-17 11:15:19 +09:00

282 lines
10 KiB
C#

#if UNITY_EDITOR
using System.Collections.Generic;
using System.IO;
using Superlazy;
using UnityEngine;
using UnityEngine.U2D;
using UnityEditor;
using UnityEditor.U2D;
namespace DDD
{
public static class AssetPostprocessorSprite
{
private static readonly HashSet<string> TargetPaths = new HashSet<string>();
public static void OnPreprocessTexture(TextureImporter importer)
{
importer.textureType = TextureImporterType.Sprite;
importer.spriteImportMode = SpriteImportMode.Single;
importer.GetSourceTextureWidthAndHeight(out var width, out var height);
importer.spritePixelsPerUnit = width <= height ? width : height;
importer.sRGBTexture = true;
importer.isReadable = true;
importer.mipmapEnabled = false;
importer.streamingMipmaps = false;
importer.wrapMode = TextureWrapMode.Clamp;
importer.filterMode = FilterMode.Bilinear;
importer.textureCompression = TextureImporterCompression.Uncompressed;
importer.crunchedCompression = false;
var textureSettings = new TextureImporterSettings();
importer.ReadTextureSettings(textureSettings);
textureSettings.spriteMeshType = SpriteMeshType.FullRect;
textureSettings.spriteExtrude = 2;
importer.SetTextureSettings(textureSettings);
string path = importer.assetPath;
EditorApplication.delayCall += () => { TryApplyPivotAfterImport(path); };
}
private static void TryApplyPivotAfterImport(string path)
{
if (string.IsNullOrEmpty(path)) return;
// ✅ 무한 루프 방지 플래그 확인
string sessionKey = $"__SPRITE_PIVOT_SET__{path}";
if (SessionState.GetBool(sessionKey, false))
{
SessionState.EraseBool(sessionKey);
return; // 이미 한 번 처리한 경우 재진입 금지
}
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
if (texture == null || !texture.isReadable) return;
int height = texture.height;
int width = texture.width;
int bottomY = height;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (texture.GetPixel(x, y).a > 0.01f)
{
bottomY = y;
goto FOUND_ALPHA;
}
}
}
FOUND_ALPHA:
if (bottomY == height)
{
Debug.LogWarning($"[SpritePivot] 모든 픽셀이 투명하여 pivot 설정 생략: {path}");
return;
}
float pivotY = (float)bottomY / height;
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer == null) return;
var settings = new TextureImporterSettings();
importer.ReadTextureSettings(settings);
settings.spriteAlignment = (int)SpriteAlignment.Custom;
settings.spritePivot = new Vector2(0.5f, pivotY);
importer.SetTextureSettings(settings);
// ✅ 재임포트 플래그 설정 후 실행 (한 번만)
SessionState.SetBool(sessionKey, true);
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
}
public static void OnRemove(string path, string movePath)
{
var upperPath = path.ToUpper();
if (upperPath.Contains(PathConstants.RawSpritesPathUpper) == false ||
upperPath.Contains(ExtenstionConstants.PngExtensionUpper) == false) return;
if (TargetPaths.Contains(path) == false)
{
TargetPaths.Add(path);
}
}
public static void OnAdd(string path)
{
var upperPath = path.ToUpper();
if (upperPath.Contains(PathConstants.RawSpritesPathUpper) == false ||
upperPath.Contains(ExtenstionConstants.PngExtensionUpper) == false) return;
if (TargetPaths.Contains(path) == false)
{
TargetPaths.Add(path);
}
}
public static void CreateAtlas(string path, string destPath)
{
var oldAtlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(destPath);
if (oldAtlas != null)
{
AssetDatabase.DeleteAsset(destPath);
}
var di = new DirectoryInfo(path);
if (di.Exists == false) return;
var objects = new List<Object>();
foreach (var file in di.GetFiles())
{
if (file.Name.ToUpper().IsRight(ExtenstionConstants.PngExtensionUpper) == false) continue;
var filePath = path + "/" + file.Name;
var fileName = file.Name.Substring(0, file.Name.Length - ExtenstionConstants.PngExtensionLower.Length);
var sprite = AssetDatabase.LoadAssetAtPath<Sprite>(filePath);
var maxSize = sprite.rect.size.x > sprite.rect.size.y ? sprite.rect.size.x : sprite.rect.size.y;
if (maxSize > 1024)
{
CreateSingleAtlas(filePath, path.Replace(PathConstants.RawFolderPath, PathConstants.AddressablesFolderPath) + $"_{fileName}{ExtenstionConstants.SpriteAtlasExtenstionLower}");
continue;
}
objects.Add(sprite);
}
if (objects.Count == 0) return;
Utils.MakeFolderFromFilePath(destPath);
var atlas = new SpriteAtlasAsset();
var spriteAtlasComponents = new List<IPostProcessorSpriteAtlas>();
spriteAtlasComponents.CreateInstanceList();
foreach (var component in spriteAtlasComponents)
{
component.OnAddSprite(atlas);
}
atlas.Add(objects.ToArray());
SpriteAtlasAsset.Save(atlas, destPath);
AssetDatabase.Refresh();
var sai = (SpriteAtlasImporter)AssetImporter.GetAtPath(destPath);
sai.packingSettings = new SpriteAtlasPackingSettings
{
enableRotation = false,
enableTightPacking = false,
enableAlphaDilation = false,
padding = 4,
blockOffset = 0
};
sai.textureSettings = new SpriteAtlasTextureSettings
{
filterMode = FilterMode.Bilinear,
sRGB = true,
generateMipMaps = false
};
}
public static void CreateSingleAtlas(string path, string destPath)
{
var oldAtlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(destPath);
if (oldAtlas != null)
{
AssetDatabase.DeleteAsset(destPath);
}
Utils.MakeFolderFromFilePath(destPath);
var atlas = new SpriteAtlasAsset();
var sprite = AssetDatabase.LoadAssetAtPath<Sprite>(path);
atlas.Add(new Object[] { sprite });
var spriteAtlasComponents = new List<IPostProcessorSpriteAtlas>();
spriteAtlasComponents.CreateInstanceList();
foreach (var component in spriteAtlasComponents)
{
component.OnAddSprite(atlas);
}
SpriteAtlasAsset.Save(atlas, destPath);
AssetDatabase.Refresh();
var sai = (SpriteAtlasImporter)AssetImporter.GetAtPath(destPath);
sai.packingSettings = new SpriteAtlasPackingSettings
{
enableRotation = false,
enableTightPacking = false,
enableAlphaDilation = false,
padding = 4,
blockOffset = 0
};
sai.textureSettings = new SpriteAtlasTextureSettings
{
filterMode = FilterMode.Bilinear,
sRGB = true,
generateMipMaps = false
};
}
public static void CreatePrefab(string path, string destPath)
{
try
{
var spriteCreateComponents = new List<IPostProcessorSpriteAtlas>();
spriteCreateComponents.CreateInstanceList();
var sprite = AssetDatabase.LoadAssetAtPath<Sprite>(path);
if (sprite == null) return;
GameObject prefab = new GameObject(sprite.name);
prefab.transform.localPosition = Vector3.zero;
prefab.transform.localRotation = Quaternion.identity;
prefab.name = sprite.name;
GameObject visualLook = new GameObject(CommonConstants.VisualLook);
visualLook.transform.SetParent(prefab.transform);
visualLook.transform.localPosition = Vector3.zero;
visualLook.transform.localRotation = Quaternion.Euler(new Vector3(40f, 0f, 0f));
SpriteRenderer spriteRenderer = visualLook.AddComponent<SpriteRenderer>();
spriteRenderer.sprite = sprite;
spriteRenderer.sortingOrder = 5;
Utils.MakeFolderFromFilePath(destPath);
AssetDatabase.DeleteAsset(destPath);
var newPrefab =
PrefabUtility.SaveAsPrefabAssetAndConnect(prefab, destPath, InteractionMode.AutomatedAction);
Object.DestroyImmediate(prefab, false);
Debug.Log("Build : " + destPath, newPrefab);
}
catch (System.Exception e)
{
Debug.LogError("Cant build " + destPath + "\n" + e);
}
}
public static void BuildTarget()
{
foreach (var path in TargetPaths)
{
CreateAtlas(Utils.FolderPath(path), Utils.FolderPath(path).Replace(PathConstants.RawFolderPath, PathConstants.AddressablesFolderPath) + ExtenstionConstants.SpriteAtlasExtenstionLower);
}
TargetPaths.Clear();
}
}
}
#endif