#if UNITY_EDITOR using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEditor.AddressableAssets; using UnityEditor.AddressableAssets.Settings; using UnityEngine; namespace DDD { public static class AssetPostprocessorEnvironment { private static readonly HashSet TileTargetPaths = new(); private static readonly HashSet PropTargetPaths = new(); private static readonly HashSet FoodTargetPaths = new(); private const string BasePrefabPath_Prop = "Assets/_DDD/_Raw/Environments/Env_Mesh_Prop.prefab"; private const string BasePrefabPath_Tile = "Assets/_DDD/_Raw/Environments/Env_Sprite_Background.prefab"; private const string BasePrefabPath_Food = "Assets/_DDD/_Raw/Environments/Env_Unlit_Food.prefab"; private const string ShaderName = "Universal Render Pipeline/LitEnvironment"; private const string Prop = "Prop_"; private const string BaseUpper = "_BASE"; private const string NormalUpper = "_NORMAL"; public enum EnvPrefabType { Tile, Prop, Food } public static void OnPreprocessTexture(TextureImporter importer) { var path = importer.assetPath; string fileNameUpper = Utils.FileName(path).ToUpper(); if (fileNameUpper.Contains(NormalUpper)) { importer.textureType = TextureImporterType.NormalMap; } else if (fileNameUpper.Contains(BaseUpper)) { importer.textureType = TextureImporterType.Sprite; importer.spriteImportMode = SpriteImportMode.Single; } else { importer.textureType = TextureImporterType.Default; importer.sRGBTexture = true; } importer.mipmapEnabled = true; } public static void OnAdd(string path) { AddTargetPath(path); } public static void OnRemove(string path, string movePath = "") { AddTargetPath(path); } private static void AddTargetPath(string path) { string upperPath = path.ToUpper(); if (upperPath.Contains(PathConstants.RawEnvPathUpper_Tile) && upperPath.Contains(ExtenstionConstants.PngExtensionUpper)) { TileTargetPaths.Add(path); } else if (upperPath.Contains(PathConstants.RawEnvPathUpper_Prop) && upperPath.Contains(ExtenstionConstants.PngExtensionUpper)) { PropTargetPaths.Add(path); } else if (upperPath.Contains(PathConstants.RawEnvPathUpper_Food) && upperPath.Contains(ExtenstionConstants.PngExtensionUpper)) { FoodTargetPaths.Add(path); } } public static void BuildMaterialAndPrefab(string path, EnvPrefabType prefabType) { var di = new DirectoryInfo(path); if (!di.Exists) return; string folderName = di.Name; string rawRoot = PathConstants.RawFolderPath; // "/_Raw" string addrRoot = PathConstants.AddressablesFolderPath; // "/_Addressables" string destDir = path.Replace(rawRoot, addrRoot); string materialPath = $"{destDir}/{folderName}{ExtenstionConstants.MaterialExtenstionLower}"; string prefabPath = $"{destDir}/{prefabType.ToString()}{folderName}{ExtenstionConstants.PrefabExtenstionLower}"; Utils.MakeFolderFromFilePath(materialPath); bool bShouldCreateMaterial = false; bShouldCreateMaterial = prefabType != EnvPrefabType.Food; // Add conditions if needed. if (bShouldCreateMaterial) { // 머티리얼 생성 또는 로드 var mat = CreateOrLoadMaterial(path, materialPath, out var files, out var shader); MatchTexturesToShaderProperties(shader, files, mat); AssetDatabase.ImportAsset(materialPath, ImportAssetOptions.ForceUpdate); AssetDatabase.SaveAssets(); CreateMaterialPrefabVariantIfNotExist(folderName, mat, prefabPath, prefabType); } else // Set sprite to renderer { var files = Directory.GetFiles(path, $"*{ExtenstionConstants.PngExtensionLower}", SearchOption.TopDirectoryOnly); foreach (var file in files) { string texName = Path.GetFileNameWithoutExtension(file).ToUpper(); Sprite tex = AssetDatabase.LoadAssetAtPath(file); if (tex == null) continue; // 셰이더 프로퍼티명과 텍스처 파일명의 접미사 매칭 if (IsTextureMatchingPropertyBySuffix(texName, BaseUpper)) CreateSpritePrefabVariantIfNotExist(folderName, tex, prefabPath, prefabType); } } } private static Material CreateOrLoadMaterial(string path, string materialPath, out string[] files, out Shader shader) { Material mat = AssetDatabase.LoadAssetAtPath(materialPath); if (mat == null) { mat = new Material(Shader.Find(ShaderName)); AssetDatabase.CreateAsset(mat, materialPath); } // PNG 텍스처 매핑 - 셰이더 프로퍼티와 텍스처 접미사 매칭 files = Directory.GetFiles(path, $"*{ExtenstionConstants.PngExtensionLower}", SearchOption.TopDirectoryOnly); shader = mat.shader; return mat; } private static void MatchTexturesToShaderProperties(Shader shader, string[] files, Material mat) { for (int i = 0; i < ShaderUtil.GetPropertyCount(shader); i++) { if (ShaderUtil.GetPropertyType(shader, i) != ShaderUtil.ShaderPropertyType.TexEnv) continue; string propertyName = ShaderUtil.GetPropertyName(shader, i); foreach (var file in files) { string texName = Path.GetFileNameWithoutExtension(file).ToUpper(); var tex = AssetDatabase.LoadAssetAtPath(file); if (tex == null) continue; // 셰이더 프로퍼티명과 텍스처 파일명의 접미사 매칭 if (IsTextureMatchingPropertyBySuffix(texName, propertyName)) { mat.SetTexture(propertyName, tex); break; // 매칭되면 다음 프로퍼티로 } } } } private static bool IsTextureMatchingPropertyBySuffix(string textureName, string propertyName) { // 셰이더 프로퍼티명에서 접미사 추출 (예: _BaseMap -> BASEMAP) string propertySuffix = propertyName.TrimStart('_').ToUpper(); // 텍스처 파일명이 해당 접미사로 끝나는지 확인 return textureName.Contains($"_{propertySuffix}"); } private static void CreateMaterialPrefabVariantIfNotExist(string folderName, Material mat, string prefabPath, EnvPrefabType prefabType) { if (AssetDatabase.LoadAssetAtPath(prefabPath) != null) return; if (InstantiatePrefabByType(folderName, prefabPath, prefabType, out var instancePrefab)) return; if (mat != null) { var renderer = instancePrefab.GetComponentInChildren(); if (renderer != null) renderer.sharedMaterial = mat; } SavePrefabInstance(prefabPath, instancePrefab); } private static void CreateSpritePrefabVariantIfNotExist(string folderName, Sprite sprite, string prefabPath, EnvPrefabType prefabType) { if (AssetDatabase.LoadAssetAtPath(prefabPath) != null) return; if (InstantiatePrefabByType(folderName, prefabPath, prefabType, out var instancePrefab)) return; if (sprite != null) { var renderer = instancePrefab.GetComponentInChildren(); if (renderer != null) renderer.sprite = sprite; } SavePrefabInstance(prefabPath, instancePrefab); } private static void SavePrefabInstance(string prefabPath, GameObject instancePrefab) { Utils.MakeFolderFromFilePath(prefabPath); PrefabUtility.SaveAsPrefabAssetAndConnect(instancePrefab, prefabPath, InteractionMode.AutomatedAction); Object.DestroyImmediate(instancePrefab); AssetDatabase.ImportAsset(prefabPath, ImportAssetOptions.ForceUpdate); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } private static bool InstantiatePrefabByType(string folderName, string prefabPath, EnvPrefabType prefabType, out GameObject instancePrefab) { // EnvPrefabType에 따라 베이스 프리팹 결정 string basePrefabPath = GetBasePrefabPath(prefabType); var basePrefab = AssetDatabase.LoadAssetAtPath(basePrefabPath); if (basePrefab == null) { Debug.LogWarning($"Base prefab not found: {basePrefabPath}"); instancePrefab = null; return true; } instancePrefab = AssetDatabase.LoadAssetAtPath(prefabPath); if (instancePrefab == null) { instancePrefab = (GameObject)PrefabUtility.InstantiatePrefab(basePrefab); instancePrefab.name = $"{Prop}{folderName}"; } return false; } private static string GetBasePrefabPath(EnvPrefabType prefabType) { return prefabType switch { EnvPrefabType.Prop => BasePrefabPath_Prop, EnvPrefabType.Tile => BasePrefabPath_Tile, EnvPrefabType.Food => BasePrefabPath_Food, _ => BasePrefabPath_Prop }; } public static void BuildTarget() { foreach (var path in TileTargetPaths) { BuildMaterialAndPrefab(Utils.FolderPath(path), EnvPrefabType.Tile); } foreach (var path in PropTargetPaths) { BuildMaterialAndPrefab(Utils.FolderPath(path), EnvPrefabType.Prop); } foreach (var path in FoodTargetPaths) { BuildMaterialAndPrefab(Utils.FolderPath(path), EnvPrefabType.Food); } TileTargetPaths.Clear(); PropTargetPaths.Clear(); FoodTargetPaths.Clear(); } } } #endif