using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using UnityEditor; using UnityEngine; namespace Amanotes.Core { internal partial class AmaDevSettings { [SerializeField] internal PresetGeneratorConfig presetGenerator = new PresetGeneratorConfig(); [Serializable] internal class PresetGeneratorConfig { [SerializeField] internal List entries = new List(); private const string DefaultTemplatePath = "Packages/com.amanotes.amadev/Editor/Preset/Preset-HierarchyTemplate.txt"; private const string InstantiatePrefabMethod = " private static void InstantiatePrefab(string prefabGuid)\n" + " {\n" + " var assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(prefabGuid);\n" + " var prefab = UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath);\n" + " if (prefab == null) { UnityEngine.Debug.LogError($\"Prefab not found for GUID: {prefabGuid}\"); return; }\n" + " var instance = UnityEditor.PrefabUtility.InstantiatePrefab(prefab) as UnityEngine.GameObject;\n" + " if (instance == null) { UnityEngine.Debug.LogError($\"Failed to instantiate prefab: {prefab.name}\"); return; }\n" + " UnityEditor.PrefabUtility.UnpackPrefabInstance(instance, UnityEditor.PrefabUnpackMode.Completely, UnityEditor.InteractionMode.AutomatedAction);\n" + " if (UnityEditor.Selection.activeGameObject != null) instance.transform.SetParent(UnityEditor.Selection.activeGameObject.transform, false);\n" + " UnityEditor.Selection.activeGameObject = instance;\n" + " UnityEditor.Undo.RegisterCreatedObjectUndo(instance, $\"Create {prefab.name}\");\n" + " UnityEditor.EditorGUIUtility.PingObject(instance);\n" + " }"; public void GenerateAll(AmaDevSettings owner) { for (int i = 0; i < entries.Count; i++) GenerateEntryInternal(entries[i], i, owner); } public void GenerateEntry(int index, AmaDevSettings owner) { if (index < 0 || index >= entries.Count) { Debug.LogError($"PresetGenerator: entry index {index} out of range."); return; } GenerateEntryInternal(entries[index], index, owner); } private void GenerateEntryInternal(PresetFolderEntry entry, int index, AmaDevSettings owner) { var templateContent = GetTemplateContent(entry, index); if (templateContent == null) return; var sourceFolderPath = GetSourceFolderPath(entry, index); if (sourceFolderPath == null) return; var prefabs = GetPrefabsInFolder(sourceFolderPath); if (prefabs.Count == 0) { Debug.LogWarning($"PresetGenerator: entry [{index}] — no prefabs found in '{sourceFolderPath}'."); return; } var className = GetClassName(entry, index); if (className == null) return; var outputPath = GetOutputPath(entry, sourceFolderPath, className); var finalContent = templateContent .Replace("{{CLASS_NAME}}", className) .Replace("{{NAME_SPACE}}", entry.nameSpace) .Replace("{{PREFAB_METHODS}}", GenerateMethods(prefabs)) .Replace("{{INSTANTIATE_PREFAB_METHOD}}", InstantiatePrefabMethod); File.WriteAllText(outputPath, finalContent); AssetDatabase.Refresh(); entry.generatedScript = AssetDatabase.LoadAssetAtPath(outputPath); if (owner != null) EditorUtility.SetDirty(owner); } private static string GetTemplateContent(PresetFolderEntry entry, int index) { if (entry.templateAsset != null) return entry.templateAsset.text; // Use default bundled template var defaultTemplate = AssetDatabase.LoadAssetAtPath(DefaultTemplatePath); if (defaultTemplate != null) return defaultTemplate.text; // Fallback: search by filename in case package path differs (e.g. local dev) var guids = AssetDatabase.FindAssets("Preset-HierarchyTemplate t:TextAsset"); foreach (var guid in guids) { var path = AssetDatabase.GUIDToAssetPath(guid); if (path.Contains("AmaDev") || path.Contains("com.amanotes.amadev")) { var asset = AssetDatabase.LoadAssetAtPath(path); if (asset != null) return asset.text; } } Debug.LogError($"PresetGenerator: entry [{index}] — default template not found. Assign one manually."); return null; } internal string GetSourceFolderPath(PresetFolderEntry entry, int index) { if (entry.sourceFolder == null) { Debug.LogError($"PresetGenerator: entry [{index}] has no source folder."); return null; } var path = AssetDatabase.GetAssetPath(entry.sourceFolder); if (!AssetDatabase.IsValidFolder(path)) { Debug.LogError($"PresetGenerator: entry [{index}] source asset '{path}' is not a folder."); return null; } return path; } internal string GetClassName(PresetFolderEntry entry, int index) { if (entry.generatedScript != null) return entry.generatedScript.name; if (!string.IsNullOrEmpty(entry.generatedClassName)) return entry.generatedClassName; Debug.LogError($"PresetGenerator: entry [{index}] has no generated script or class name."); return null; } internal List GetPrefabsInFolder(string folderPath) { var result = new List(); var guids = AssetDatabase.FindAssets("t:GameObject", new[] { folderPath }); foreach (var guid in guids) { var assetPath = AssetDatabase.GUIDToAssetPath(guid); var asset = AssetDatabase.LoadAssetAtPath(assetPath); if (asset == null) continue; var relativePath = Path.GetRelativePath(folderPath, Path.GetDirectoryName(assetPath)); var prefabName = asset.name; var menuPath = relativePath == "." ? "GameObject/" + prefabName : "GameObject/" + relativePath.Replace("\\", "/") + "/" + prefabName; var methodName = "Create_" + menuPath.Replace("GameObject/", "").Replace("/", "_").Replace(" ", "_"); result.Add(new PrefabInfo { guid = guid, prefabName = prefabName, relativePath = relativePath, fullMenuPath = menuPath, methodName = methodName }); } return result; } /// /// Detects namespace from .cs files near the given folder (same folder, parent, or siblings). /// Returns null if no namespace found. /// internal static string DetectNamespaceNearFolder(string folderPath) { if (string.IsNullOrEmpty(folderPath)) return null; // Search in the folder itself first var ns = FindNamespaceInFolder(folderPath); if (ns != null) return ns; // Search in parent folder var parentPath = Path.GetDirectoryName(folderPath)?.Replace("\\", "/"); if (!string.IsNullOrEmpty(parentPath) && AssetDatabase.IsValidFolder(parentPath)) { ns = FindNamespaceInFolder(parentPath); if (ns != null) return ns; } return null; } private static string FindNamespaceInFolder(string folderPath) { var scriptGuids = AssetDatabase.FindAssets("t:MonoScript", new[] { folderPath }); foreach (var guid in scriptGuids) { var scriptPath = AssetDatabase.GUIDToAssetPath(guid); // Only check direct children, not deep subfolders var scriptDir = Path.GetDirectoryName(scriptPath)?.Replace("\\", "/"); if (scriptDir != folderPath) continue; var content = File.ReadAllText(scriptPath); var match = Regex.Match(content, @"^\s*namespace\s+([\w.]+)", RegexOptions.Multiline); if (match.Success) return match.Groups[1].Value; } return null; } private static string GetOutputPath(PresetFolderEntry entry, string sourceFolderPath, string className) { if (entry.generatedScript != null) return AssetDatabase.GetAssetPath(entry.generatedScript); return Path.Combine(sourceFolderPath, className + ".cs"); } private static string GenerateMethods(List prefabs) { var sb = new System.Text.StringBuilder(); foreach (var prefab in prefabs) { sb.AppendLine($" [MenuItem(\"{prefab.fullMenuPath}\")]"); sb.AppendLine($" private static void {prefab.methodName}()"); sb.AppendLine(" {"); sb.AppendLine($" InstantiatePrefab(\"{prefab.guid}\");"); sb.AppendLine(" }"); sb.AppendLine(); } return sb.ToString().TrimEnd('\r', '\n', ' '); } } [Serializable] internal class PresetFolderEntry { public DefaultAsset sourceFolder; public TextAsset templateAsset; public string nameSpace; public MonoScript generatedScript; public string generatedClassName; } [Serializable] internal class PrefabInfo { public string guid; public string prefabName; public string relativePath; public string fullMenuPath; public string methodName; } } }