#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 TargetPaths = new HashSet(); 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(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); Debug.Log($"[SpritePivot] {path} → pivotY = {pivotY:F2}"); // ✅ 재임포트 플래그 설정 후 실행 (한 번만) SessionState.SetBool(sessionKey, true); AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate); } public static void OnRemove(string path, string movePath) { var upperPath = path.ToUpper(); if (upperPath.Contains("ASSETS/_DATAS/RAW/SPRITES/") == false || upperPath.Contains(".PNG") == false) return; if (TargetPaths.Contains(path) == false) { TargetPaths.Add(path); } } public static void OnAdd(string path) { var upperPath = path.ToUpper(); if (upperPath.Contains("ASSETS/_DATAS/RAW/SPRITES/") == false || upperPath.Contains(".PNG") == false) return; if (TargetPaths.Contains(path) == false) { TargetPaths.Add(path); } } public static void CreateAtlas(string path, string destPath) { var oldAtlas = AssetDatabase.LoadAssetAtPath(destPath); if (oldAtlas != null) { AssetDatabase.DeleteAsset(destPath); } var di = new DirectoryInfo(path); if (di.Exists == false) return; var objects = new List(); foreach (var file in di.GetFiles()) { if (file.Name.ToUpper().IsRight(".PNG") == false) continue; var filePath = path + "/" + file.Name; var fileName = file.Name.Substring(0, file.Name.Length - ".png".Length); var sprite = AssetDatabase.LoadAssetAtPath(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("/Raw/", "/Addressables/") + $"_{fileName}.spriteatlasv2"); continue; } objects.Add(sprite); } if (objects.Count == 0) return; Utils.MakeFolderFromFilePath(destPath); var atlas = new SpriteAtlasAsset(); var spriteAtlasComponents = new List(); 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(destPath); if (oldAtlas != null) { AssetDatabase.DeleteAsset(destPath); } Utils.MakeFolderFromFilePath(destPath); var atlas = new SpriteAtlasAsset(); var sprite = AssetDatabase.LoadAssetAtPath(path); atlas.Add(new Object[] { sprite }); var spriteAtlasComponents = new List(); 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(); spriteCreateComponents.CreateInstanceList(); var sprite = AssetDatabase.LoadAssetAtPath(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("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.sprite = sprite; spriteRenderer.sortingOrder = 5; // var guids = AssetDatabase.FindAssets("t:Material", new string[] { Utils.FolderPath(path) }); // var materials = guids // .Select(guid => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid))) // .ToDictionary(m => m.name); // // foreach (var renderer in model.GetComponentsInChildren()) // { // var sharedMaterials = // new Material[renderer.sharedMaterials.Length]; // Material을 직접 엘리먼트로 대입하면 안되고 배열을 통으로 넣어야함 // for (var i = 0; i < renderer.sharedMaterials.Length; ++i) // { // if (materials.TryGetValue($"{renderer.name}_{i + 1}", out var numMat)) // { // sharedMaterials[i] = numMat; // } // else if (materials.TryGetValue(renderer.name, out var mat)) // { // sharedMaterials[i] = mat; // } // else if (materials.TryGetValue(go.name, out var defaultMat)) // { // sharedMaterials[i] = defaultMat; // } // else // { // sharedMaterials[i] = renderer.sharedMaterials[i]; // // 에러인가? 잠깐 파일 안갖다놨을뿐인가? // } // } // // renderer.sharedMaterials = sharedMaterials; // } // // var res = prefab.AddComponent(); //res.Init(); Utils.MakeFolderFromFilePath(destPath); // { // // Make Duumy(for test) // var dummyObj = Object.Instantiate(model); // var anim = dummyObj.GetComponentInChildren(); // var controller = // AnimatorController.CreateAnimatorControllerAtPath(GetPrefabPath(path) // .Replace(".prefab", ".controller")); // // anim.runtimeAnimatorController = controller; // var rootStateMachine = controller.layers[0].stateMachine; // // var clipids = AssetDatabase.FindAssets("t:AnimationClip", // new string[] { SLFileUtility.FolderPath(path) }); // foreach (var guid in clipids) // { // var clip = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid)); // if (clip != null) // { // var state = rootStateMachine.AddState(clip.name); // state.motion = clip; // } // } // // var dummy = PrefabUtility.SaveAsPrefabAssetAndConnect(dummyObj, GetPrefabPath(path), // InteractionMode.AutomatedAction); // // Object.DestroyImmediate(dummyObj, false); // } 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("/Raw/", "/Addressables/") + ".spriteatlasv2"); //CreatePrefab(path, (path.Replace("/Raw/Sprites/", "/Addressables/") + ".prefab").Replace(".png", "")); } TargetPaths.Clear(); } } } #endif