diff --git a/Assets/Editor/PrefabVariantGeneratorWindow.cs b/Assets/Editor/PrefabVariantGeneratorWindow.cs index 489fe223..2e47f5ea 100644 --- a/Assets/Editor/PrefabVariantGeneratorWindow.cs +++ b/Assets/Editor/PrefabVariantGeneratorWindow.cs @@ -3,34 +3,74 @@ using UnityEngine; using System.Collections.Generic; using System.IO; using System.Linq; -using UnityEditor.Experimental.SceneManagement; +using System.Text; namespace Editor { public class PrefabVariantGeneratorWindow : EditorWindow { + // Renderer configuration class to track sprite renderers and their assigned sprites + [System.Serializable] + private class RendererConfig + { + public string Path; // Hierarchy path to the renderer + public string Name; // Display name for the renderer + public SpriteRenderer Renderer; // Reference to the actual renderer + public Sprite CurrentSprite; // Current sprite in the renderer + public List AssignedSprites = new List(); // Sprites to use for variants + public bool Enabled = true; // Whether to include in variant generation + public bool Expanded = true; // UI expanded state + public Vector2 ScrollPosition; // Scroll position for sprite list + } + + // Main fields private GameObject sourcePrefab; private GameObject previousSourcePrefab; - private List selectedSprites = new List(); - private Vector2 scrollPosition; + private List detectedRenderers = new List(); + private Vector2 mainScrollPosition; private string variantSaveFolder = "Assets/Prefabs/Variants"; - private string namingPattern = "{0}_{1}"; // {0} = prefab name, {1} = sprite name + private string namingPattern = "{0}_{1}"; // Default: {0} = prefab name, {1} = first renderer sprite private bool userChangedSavePath = false; + private int estimatedVariantCount = 0; + private int maxSafeVariantCount = 100; // Warn above this number + private GUIStyle boldFoldoutStyle; + private bool showDefaultHelp = true; - [MenuItem("Tools/Prefab Variant Generator")] + // Editor window setup + [MenuItem("Tools/Sprite Variant Generator")] public static void ShowWindow() { var window = GetWindow("Prefab Variant Generator"); - window.minSize = new Vector2(400, 500); + window.minSize = new Vector2(500, 600); + } + + private void OnEnable() + { + // Initialize styles on enable to avoid null reference issues + boldFoldoutStyle = new GUIStyle(EditorStyles.foldout) + { + fontStyle = FontStyle.Bold + }; } private void OnGUI() { + // Initialize styles if needed + if (boldFoldoutStyle == null) + { + boldFoldoutStyle = new GUIStyle(EditorStyles.foldout) + { + fontStyle = FontStyle.Bold + }; + } + EditorGUILayout.LabelField("Prefab Variant Generator", EditorStyles.boldLabel); - EditorGUILayout.HelpBox("This tool generates prefab variants with different sprites assigned to a sprite renderer.", MessageType.Info); - EditorGUILayout.Space(); + EditorGUILayout.HelpBox("Create multiple prefab variants with different sprites assigned to renderers.", MessageType.Info); + + mainScrollPosition = EditorGUILayout.BeginScrollView(mainScrollPosition); // Source Prefab Selection + EditorGUILayout.Space(); EditorGUILayout.LabelField("Step 1: Select Source Prefab", EditorStyles.boldLabel); // Store previous selection to detect changes @@ -51,6 +91,15 @@ namespace Editor variantSaveFolder = Path.GetDirectoryName(prefabPath).Replace("\\", "/"); } } + + // Find sprite renderers in the prefab + FindRenderersInPrefab(); + + // Clear default help once a prefab is selected + if (sourcePrefab != null) + { + showDefaultHelp = false; + } } // Warn if not a prefab @@ -59,16 +108,244 @@ namespace Editor EditorGUILayout.HelpBox("Please select a prefab asset.", MessageType.Warning); } - // Sprite Selection - EditorGUILayout.Space(); - EditorGUILayout.LabelField("Step 2: Select Sprites", EditorStyles.boldLabel); + // Display default help if no prefab selected + if (showDefaultHelp && sourcePrefab == null) + { + EditorGUILayout.HelpBox( + "This tool lets you create prefab variants with different sprites.\n\n" + + "1. Select a source prefab\n" + + "2. Assign sprites to each detected sprite renderer\n" + + "3. Generate all combinations as prefab variants", + MessageType.Info + ); + } + + // Only show the rest if a valid prefab is selected + if (sourcePrefab != null) + { + // Renderer sections + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Step 2: Configure Sprite Renderers", EditorStyles.boldLabel); + + if (detectedRenderers.Count == 0) + { + EditorGUILayout.HelpBox("No sprite renderers found in prefab. A new renderer will be created.", MessageType.Info); + } + else + { + EditorGUILayout.HelpBox($"{detectedRenderers.Count} sprite renderer{(detectedRenderers.Count > 1 ? "s" : "")} found in prefab.", MessageType.Info); + } + + // Display each renderer configuration + for (int i = 0; i < detectedRenderers.Count; i++) + { + DrawRendererSection(detectedRenderers[i], i); + } + + // Update estimated variant count + UpdateVariantCount(); + + // Output settings + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Step 3: Output Settings", EditorStyles.boldLabel); + + // Save folder + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel("Save Folder"); + EditorGUILayout.SelectableLabel(variantSaveFolder, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); + if (GUILayout.Button("Select...", GUILayout.Width(80))) + { + string newFolder = PrefabEditorUtility.SelectFolder(variantSaveFolder, "Prefabs/Variants"); + if (newFolder != variantSaveFolder) + { + variantSaveFolder = newFolder; + userChangedSavePath = true; // Mark that user manually changed the path + } + } + EditorGUILayout.EndHorizontal(); + + // Add a reset button if user changed the path and a valid prefab is selected + if (userChangedSavePath && sourcePrefab != null) + { + string prefabPath = AssetDatabase.GetAssetPath(sourcePrefab); + if (!string.IsNullOrEmpty(prefabPath)) + { + if (GUILayout.Button("Reset Path to Prefab Directory")) + { + variantSaveFolder = Path.GetDirectoryName(prefabPath).Replace("\\", "/"); + userChangedSavePath = false; + } + } + } + + // Naming pattern field + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel("Naming Pattern"); + namingPattern = EditorGUILayout.TextField(namingPattern); + EditorGUILayout.EndHorizontal(); + + // Help text for naming pattern + StringBuilder helpText = new StringBuilder("Naming placeholders:\n"); + helpText.AppendLine("{0} = Prefab name"); + for (int i = 0; i < detectedRenderers.Count; i++) + { + helpText.AppendLine($"{{{i+1}}} = {detectedRenderers[i].Name} sprite name"); + } + EditorGUILayout.HelpBox(helpText.ToString(), MessageType.Info); + + // Variant count display + string variantCountText = $"Will generate {estimatedVariantCount} variant{(estimatedVariantCount != 1 ? "s" : "")}"; + if (estimatedVariantCount > maxSafeVariantCount) + { + EditorGUILayout.HelpBox($"Warning: {variantCountText}. This might take some time.", MessageType.Warning); + } + else if (estimatedVariantCount > 0) + { + EditorGUILayout.HelpBox(variantCountText, MessageType.Info); + } + else + { + EditorGUILayout.HelpBox("Please assign at least one sprite to each enabled renderer to generate variants.", MessageType.Warning); + } + + // Generate button + EditorGUILayout.Space(); + GUI.enabled = estimatedVariantCount > 0; + if (GUILayout.Button("Generate Prefab Variants", GUILayout.Height(30))) + { + // Show warning for large numbers of variants + if (estimatedVariantCount > maxSafeVariantCount) + { + bool proceed = EditorUtility.DisplayDialog( + "Generate Many Variants?", + $"You are about to generate {estimatedVariantCount} prefab variants. This might take some time and use significant disk space. Continue?", + "Generate", + "Cancel" + ); + + if (!proceed) return; + } + + GeneratePrefabVariants(); + } + GUI.enabled = true; + } + + EditorGUILayout.EndScrollView(); + } + + private void DrawRendererSection(RendererConfig config, int index) + { + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + EditorGUILayout.BeginHorizontal(); + // Expand/collapse button + config.Expanded = EditorGUILayout.Foldout(config.Expanded, "", boldFoldoutStyle); - // Drag and drop area for sprites - EditorGUILayout.BeginVertical(EditorStyles.helpBox, GUILayout.Height(80)); - EditorGUILayout.LabelField("Drag and drop sprites here", EditorStyles.centeredGreyMiniLabel); + // Enable/disable toggle + bool newEnabled = EditorGUILayout.Toggle(config.Enabled, GUILayout.Width(20)); + if (newEnabled != config.Enabled) + { + config.Enabled = newEnabled; + UpdateVariantCount(); + } - Rect dropArea = GUILayoutUtility.GetRect(0, 50, GUILayout.ExpandWidth(true)); + // Renderer name/title + EditorGUILayout.LabelField(config.Name, EditorStyles.boldLabel); + + // Current sprite preview if available + if (config.CurrentSprite != null) + { + GUILayout.Box( + AssetPreview.GetAssetPreview(config.CurrentSprite), + GUILayout.Width(40), + GUILayout.Height(40) + ); + } + + EditorGUILayout.EndHorizontal(); + + // Only show contents if expanded + if (config.Expanded) + { + // Path display + if (!string.IsNullOrEmpty(config.Path)) + { + EditorGUILayout.LabelField($"Path: {config.Path}", EditorStyles.miniLabel); + } + + EditorGUI.BeginDisabledGroup(!config.Enabled); + + // Sprite selection controls + EditorGUILayout.Space(); + EditorGUILayout.BeginHorizontal(); + + // Drag and drop area for sprites + EditorGUILayout.BeginVertical(EditorStyles.helpBox, GUILayout.Height(60)); + EditorGUILayout.LabelField("Drag and drop sprites here", EditorStyles.centeredGreyMiniLabel); + + Rect dropArea = GUILayoutUtility.GetRect(0, 40, GUILayout.ExpandWidth(true)); + HandleDragAndDrop(dropArea, config); + + EditorGUILayout.EndVertical(); + + if (GUILayout.Button("Add Selected", GUILayout.Width(100), GUILayout.Height(60))) + { + AddSelectedSpritesToConfig(config); + } + + EditorGUILayout.EndHorizontal(); + + if (GUILayout.Button("Clear Sprites")) + { + config.AssignedSprites.Clear(); + UpdateVariantCount(); + } + + // Display selected sprites + EditorGUILayout.Space(); + EditorGUILayout.LabelField($"Selected Sprites ({config.AssignedSprites.Count}):", EditorStyles.miniBoldLabel); + + // Sprite list + config.ScrollPosition = EditorGUILayout.BeginScrollView(config.ScrollPosition, GUILayout.Height(120)); + for (int i = config.AssignedSprites.Count - 1; i >= 0; i--) + { + EditorGUILayout.BeginHorizontal(); + config.AssignedSprites[i] = (Sprite)EditorGUILayout.ObjectField( + config.AssignedSprites[i], + typeof(Sprite), + false, + GUILayout.ExpandWidth(true) + ); + + // Preview sprite + if (config.AssignedSprites[i] != null) + { + GUILayout.Box( + AssetPreview.GetAssetPreview(config.AssignedSprites[i]), + GUILayout.Width(40), + GUILayout.Height(40) + ); + } + + if (GUILayout.Button("Remove", GUILayout.Width(60))) + { + config.AssignedSprites.RemoveAt(i); + UpdateVariantCount(); + } + + EditorGUILayout.EndHorizontal(); + } + EditorGUILayout.EndScrollView(); + + EditorGUI.EndDisabledGroup(); + } + + EditorGUILayout.EndVertical(); + } + + private void HandleDragAndDrop(Rect dropArea, RendererConfig config) + { Event evt = Event.current; switch (evt.type) { @@ -82,13 +359,17 @@ namespace Editor if (evt.type == EventType.DragPerform) { DragAndDrop.AcceptDrag(); + bool added = false; foreach (var draggedObject in DragAndDrop.objectReferences) { if (draggedObject is Sprite sprite) { - if (!selectedSprites.Contains(sprite)) - selectedSprites.Add(sprite); + if (!config.AssignedSprites.Contains(sprite)) + { + config.AssignedSprites.Add(sprite); + added = true; + } } else if (draggedObject is Texture2D texture) { @@ -100,125 +381,200 @@ namespace Editor foreach (var s in sprites) { - if (!selectedSprites.Contains(s)) - selectedSprites.Add(s); + if (!config.AssignedSprites.Contains(s)) + { + config.AssignedSprites.Add(s); + added = true; + } } } } + if (added) + { + UpdateVariantCount(); + } + evt.Use(); } break; } - EditorGUILayout.EndVertical(); + } - if (GUILayout.Button("Add Selected Sprites", GUILayout.Width(120), GUILayout.Height(80))) + private void AddSelectedSpritesToConfig(RendererConfig config) + { + var selectedObjects = Selection.objects; + bool added = false; + + foreach (var obj in selectedObjects) { - var selectedObjects = Selection.objects; - foreach (var obj in selectedObjects) + if (obj is Sprite sprite) { - if (obj is Sprite sprite) + if (!config.AssignedSprites.Contains(sprite)) { - if (!selectedSprites.Contains(sprite)) - selectedSprites.Add(sprite); + config.AssignedSprites.Add(sprite); + added = true; } - else if (obj is Texture2D texture) - { - // Try to get sprites from texture - string texturePath = AssetDatabase.GetAssetPath(texture); - var sprites = AssetDatabase.LoadAllAssetsAtPath(texturePath) - .OfType() - .ToArray(); + } + else if (obj is Texture2D texture) + { + // Try to get sprites from texture + string texturePath = AssetDatabase.GetAssetPath(texture); + var sprites = AssetDatabase.LoadAllAssetsAtPath(texturePath) + .OfType() + .ToArray(); - foreach (var s in sprites) + foreach (var s in sprites) + { + if (!config.AssignedSprites.Contains(s)) { - if (!selectedSprites.Contains(s)) - selectedSprites.Add(s); + config.AssignedSprites.Add(s); + added = true; } } } } - EditorGUILayout.EndHorizontal(); - - if (GUILayout.Button("Clear Sprites")) + if (added) { - selectedSprites.Clear(); + UpdateVariantCount(); } + } - // Display selected sprites - EditorGUILayout.Space(); - EditorGUILayout.LabelField($"Selected Sprites ({selectedSprites.Count}):", EditorStyles.boldLabel); + private void FindRenderersInPrefab() + { + detectedRenderers.Clear(); - scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(150)); - for (int i = selectedSprites.Count - 1; i >= 0; i--) + if (sourcePrefab == null) return; + + // Get all renderers in prefab (including children) + GameObject instance = null; + + try { - EditorGUILayout.BeginHorizontal(); - selectedSprites[i] = (Sprite)EditorGUILayout.ObjectField(selectedSprites[i], typeof(Sprite), false); + instance = (GameObject)PrefabUtility.InstantiatePrefab(sourcePrefab); + SpriteRenderer[] renderers = instance.GetComponentsInChildren(true); - // Preview sprite - if (selectedSprites[i] != null) + for (int i = 0; i < renderers.Length; i++) { - GUILayout.Box(selectedSprites[i].texture, GUILayout.Width(40), GUILayout.Height(40)); - } + var renderer = renderers[i]; + string path = GetRelativePath(instance.transform, renderer.transform); + string name = renderer.gameObject.name; - if (GUILayout.Button("Remove", GUILayout.Width(60))) - { - selectedSprites.RemoveAt(i); - } - EditorGUILayout.EndHorizontal(); - } - EditorGUILayout.EndScrollView(); - - // Output settings - EditorGUILayout.Space(); - EditorGUILayout.LabelField("Step 3: Output Settings", EditorStyles.boldLabel); - - // Save folder - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.PrefixLabel("Save Folder"); - EditorGUILayout.SelectableLabel(variantSaveFolder, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); - if (GUILayout.Button("Select...", GUILayout.Width(80))) - { - string newFolder = PrefabEditorUtility.SelectFolder(variantSaveFolder, "Prefabs/Variants"); - if (newFolder != variantSaveFolder) - { - variantSaveFolder = newFolder; - userChangedSavePath = true; // Mark that user manually changed the path - } - } - EditorGUILayout.EndHorizontal(); - - // Naming pattern - // Add a reset button if user changed the path and a valid prefab is selected - if (userChangedSavePath && sourcePrefab != null) - { - string prefabPath = AssetDatabase.GetAssetPath(sourcePrefab); - if (!string.IsNullOrEmpty(prefabPath)) - { - if (GUILayout.Button("Reset Path to Prefab Directory")) + // For root object, use "Main" + if (string.IsNullOrEmpty(path)) { - variantSaveFolder = Path.GetDirectoryName(prefabPath).Replace("\\", "/"); - userChangedSavePath = false; + name = "Main"; + } + // For objects with the same name, add index + else if (renderers.Count(r => r.gameObject.name == renderer.gameObject.name) > 1) + { + name = $"{name} ({i+1})"; + } + + detectedRenderers.Add(new RendererConfig + { + Path = path, + Name = name, + Renderer = renderer, + CurrentSprite = renderer.sprite, + AssignedSprites = renderer.sprite != null ? + new List { renderer.sprite } : + new List() + }); + } + } + finally + { + if (instance != null) + DestroyImmediate(instance); + } + + // If no renderers found, create a default entry + if (detectedRenderers.Count == 0) + { + detectedRenderers.Add(new RendererConfig + { + Path = "", + Name = "Main", + Renderer = null, + CurrentSprite = null, + AssignedSprites = new List() + }); + } + + UpdateVariantCount(); + } + + private string GetRelativePath(Transform root, Transform target) + { + if (target == root) return ""; + + string path = target.name; + Transform parent = target.parent; + + while (parent != null && parent != root) + { + path = parent.name + "/" + path; + parent = parent.parent; + } + + return path; + } + + private void UpdateVariantCount() + { + // Calculate estimated variants + estimatedVariantCount = 0; + + // Get only enabled renderers with at least one sprite + var enabledConfigs = detectedRenderers + .Where(r => r.Enabled && r.AssignedSprites.Count > 0) + .ToList(); + + if (enabledConfigs.Count > 0) + { + // Start with count of first renderer's sprites + estimatedVariantCount = enabledConfigs[0].AssignedSprites.Count; + + // Multiply by subsequent renderers' sprite counts + for (int i = 1; i < enabledConfigs.Count; i++) + { + estimatedVariantCount *= enabledConfigs[i].AssignedSprites.Count; + } + } + } + + private List> GenerateAllCombinations() + { + var enabledConfigs = detectedRenderers + .Where(r => r.Enabled && r.AssignedSprites.Count > 0) + .ToList(); + + if (enabledConfigs.Count == 0) + return new List>(); + + // Initialize with first renderer's sprites + var combinations = enabledConfigs[0].AssignedSprites + .Select(s => new List { s }) + .ToList(); + + // Add each subsequent renderer's sprites + for (int i = 1; i < enabledConfigs.Count; i++) + { + var newCombinations = new List>(); + foreach (var combo in combinations) + { + foreach (var sprite in enabledConfigs[i].AssignedSprites) + { + var newCombo = new List(combo) { sprite }; + newCombinations.Add(newCombo); } } + combinations = newCombinations; } - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.PrefixLabel("Naming Pattern"); - namingPattern = EditorGUILayout.TextField(namingPattern); - EditorGUILayout.EndHorizontal(); - - EditorGUILayout.HelpBox("Use {0} for prefab name and {1} for sprite name", MessageType.Info); - - // Generate button - EditorGUILayout.Space(); - GUI.enabled = sourcePrefab != null && selectedSprites.Count > 0; - if (GUILayout.Button("Generate Prefab Variants", GUILayout.Height(30))) - { - GeneratePrefabVariants(); - } - GUI.enabled = true; + return combinations; } private void GeneratePrefabVariants() @@ -229,21 +585,180 @@ namespace Editor return; } - if (selectedSprites.Count == 0) + // Get enabled renderer configurations + var enabledConfigs = detectedRenderers + .Where(r => r.Enabled && r.AssignedSprites.Count > 0) + .ToList(); + + if (enabledConfigs.Count == 0) { - EditorUtility.DisplayDialog("Error", "Please select at least one sprite.", "OK"); + EditorUtility.DisplayDialog("Error", "Please assign at least one sprite to a renderer.", "OK"); return; } // Ensure the save folder exists - if (!AssetDatabase.IsValidFolder(variantSaveFolder)) - { - string[] folderPath = variantSaveFolder.Split('/'); - string currentPath = folderPath[0]; + EnsureFolderExists(variantSaveFolder); - for (int i = 1; i < folderPath.Length; i++) + // Generate all sprite combinations + var combinations = GenerateAllCombinations(); + + string sourcePrefabPath = AssetDatabase.GetAssetPath(sourcePrefab); + string prefabName = Path.GetFileNameWithoutExtension(sourcePrefabPath); + int successCount = 0; + + // Show progress bar + EditorUtility.DisplayProgressBar("Generating Prefab Variants", "Preparing...", 0f); + + try + { + // For each combination, create a prefab variant + for (int i = 0; i < combinations.Count; i++) { - string folderName = folderPath[i]; + // Update progress + if (i % 5 == 0 || i == combinations.Count - 1) + { + float progress = (float)i / combinations.Count; + if (EditorUtility.DisplayCancelableProgressBar( + "Generating Prefab Variants", + $"Creating variant {i+1} of {combinations.Count}", + progress)) + { + // User canceled + break; + } + } + + var combination = combinations[i]; + + // Generate variant name + string variantName = prefabName; + string[] spriteNames = new string[combination.Count]; + + for (int j = 0; j < combination.Count; j++) + { + spriteNames[j] = combination[j].name; + } + + // Format with the naming pattern + object[] formatArgs = new object[spriteNames.Length + 1]; + formatArgs[0] = prefabName; + for (int j = 0; j < spriteNames.Length; j++) + { + formatArgs[j + 1] = spriteNames[j]; + } + + try + { + variantName = string.Format(namingPattern, formatArgs); + } + catch (System.FormatException) + { + // Fallback if format fails + variantName = $"{prefabName}_{string.Join("_", spriteNames)}"; + } + + variantName = PrefabEditorUtility.SanitizeFileName(variantName); + string variantPath = Path.Combine(variantSaveFolder, variantName + ".prefab").Replace("\\", "/"); + + // Create the prefab variant + GameObject prefabInstance = (GameObject)PrefabUtility.InstantiatePrefab(sourcePrefab); + + try + { + // Apply sprites to renderers + for (int j = 0; j < enabledConfigs.Count; j++) + { + SpriteRenderer renderer = null; + + // Find the corresponding renderer in the instance + if (string.IsNullOrEmpty(enabledConfigs[j].Path)) + { + // Root object + renderer = prefabInstance.GetComponent(); + if (renderer == null) + { + renderer = prefabInstance.AddComponent(); + } + } + else + { + // Child object + Transform child = prefabInstance.transform.Find(enabledConfigs[j].Path); + if (child != null) + { + renderer = child.GetComponent(); + } + } + + // Apply sprite if renderer was found + if (renderer != null) + { + renderer.sprite = combination[j]; + } + } + + // Create the prefab variant + GameObject prefabVariant = PrefabUtility.SaveAsPrefabAsset(prefabInstance, variantPath); + + if (prefabVariant != null) + { + successCount++; + } + } + catch (System.Exception e) + { + Debug.LogError($"Error creating prefab variant: {e.Message}"); + } + finally + { + // Clean up the instance + DestroyImmediate(prefabInstance); + } + } + } + finally + { + EditorUtility.ClearProgressBar(); + } + + AssetDatabase.Refresh(); + + if (successCount > 0) + { + EditorUtility.DisplayDialog( + "Prefab Variants Created", + $"Successfully created {successCount} prefab variants in {variantSaveFolder}.", + "OK" + ); + + // Open the folder in Project view + var folderObject = AssetDatabase.LoadAssetAtPath(variantSaveFolder); + if (folderObject != null) + { + Selection.activeObject = folderObject; + EditorGUIUtility.PingObject(folderObject); + } + } + else + { + EditorUtility.DisplayDialog( + "Prefab Variants", + "No prefab variants were created. Please check the console for errors.", + "OK" + ); + } + } + + private void EnsureFolderExists(string folderPath) + { + if (!AssetDatabase.IsValidFolder(folderPath)) + { + string[] folderParts = folderPath.Split('/'); + string currentPath = folderParts[0]; + + for (int i = 1; i < folderParts.Length; i++) + { + string folderName = folderParts[i]; string newPath = Path.Combine(currentPath, folderName); if (!AssetDatabase.IsValidFolder(newPath)) @@ -256,57 +771,6 @@ namespace Editor AssetDatabase.Refresh(); } - - string sourcePrefabPath = AssetDatabase.GetAssetPath(sourcePrefab); - string prefabName = Path.GetFileNameWithoutExtension(sourcePrefabPath); - int successCount = 0; - - // For each sprite, create a prefab variant - foreach (var sprite in selectedSprites) - { - if (sprite == null) continue; - - string variantName = string.Format(namingPattern, prefabName, sprite.name); - variantName = PrefabEditorUtility.SanitizeFileName(variantName); - string variantPath = Path.Combine(variantSaveFolder, variantName + ".prefab").Replace("\\", "/"); - - // Create the prefab variant - GameObject prefabInstance = (GameObject)PrefabUtility.InstantiatePrefab(sourcePrefab); - - try - { - // Check if it has a SpriteRenderer - SpriteRenderer spriteRenderer = prefabInstance.GetComponent(); - if (spriteRenderer == null) - { - // Add a SpriteRenderer if it doesn't exist - spriteRenderer = prefabInstance.AddComponent(); - } - - // Assign the sprite - spriteRenderer.sprite = sprite; - - // Create the prefab variant - GameObject prefabVariant = PrefabUtility.SaveAsPrefabAsset(prefabInstance, variantPath); - - if (prefabVariant != null) - { - successCount++; - } - } - catch (System.Exception e) - { - Debug.LogError($"Error creating prefab variant: {e.Message}"); - } - finally - { - // Clean up the instance - DestroyImmediate(prefabInstance); - } - } - - AssetDatabase.Refresh(); - EditorUtility.DisplayDialog("Prefab Variants Created", $"Successfully created {successCount} prefab variants.", "OK"); } } } diff --git a/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head1_1.prefab b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head1_1.prefab new file mode 100644 index 00000000..36323a67 --- /dev/null +++ b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head1_1.prefab @@ -0,0 +1,59 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1001 &7921038058497292108 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 2015720985618639356, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_Name + value: QuarryMonster_monster_head1_1 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalPosition.x + value: 2.46 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalPosition.y + value: -1.79 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 7754d064d743d1b419acb859c2db6121, type: 3} diff --git a/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head1_1.prefab.meta b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head1_1.prefab.meta new file mode 100644 index 00000000..518fc210 --- /dev/null +++ b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head1_1.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cd5c27c38c427524988b8932e7205d81 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head2_0.prefab b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head2_0.prefab new file mode 100644 index 00000000..43ca68d8 --- /dev/null +++ b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head2_0.prefab @@ -0,0 +1,63 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1001 &7839076539202245770 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 2015720985618639356, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_Name + value: QuarryMonster_monster_head2_0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalPosition.x + value: 2.46 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalPosition.y + value: -1.79 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8447572436637192077, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_Sprite + value: + objectReference: {fileID: -7812277834941893986, guid: e9ac6c3e349f9b247a0ea03665da57ce, type: 3} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 7754d064d743d1b419acb859c2db6121, type: 3} diff --git a/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head2_0.prefab.meta b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head2_0.prefab.meta new file mode 100644 index 00000000..be8cc2bb --- /dev/null +++ b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head2_0.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7c45910be7187144b8d2af12ff772353 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head3_0.prefab b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head3_0.prefab new file mode 100644 index 00000000..2716c216 --- /dev/null +++ b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head3_0.prefab @@ -0,0 +1,63 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1001 &4541086625678702007 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 2015720985618639356, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_Name + value: QuarryMonster_monster_head3_0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalPosition.x + value: 2.46 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalPosition.y + value: -1.79 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8447572436637192077, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_Sprite + value: + objectReference: {fileID: 4139348639081821586, guid: 1043ceca06eb2ac48b22c20d281278b0, type: 3} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 7754d064d743d1b419acb859c2db6121, type: 3} diff --git a/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head3_0.prefab.meta b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head3_0.prefab.meta new file mode 100644 index 00000000..b31682be --- /dev/null +++ b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head3_0.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c0d4b6d8a3b9a2447b883a78c568826c +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head4_0.prefab b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head4_0.prefab new file mode 100644 index 00000000..96798e0d --- /dev/null +++ b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head4_0.prefab @@ -0,0 +1,63 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1001 &1190377006705176201 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 2015720985618639356, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_Name + value: QuarryMonster_monster_head4_0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalPosition.x + value: 2.46 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalPosition.y + value: -1.79 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6779310478082390115, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8447572436637192077, guid: 7754d064d743d1b419acb859c2db6121, type: 3} + propertyPath: m_Sprite + value: + objectReference: {fileID: -3471612407960728276, guid: c85ce41979ed896429c62330c546d3ce, type: 3} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 7754d064d743d1b419acb859c2db6121, type: 3} diff --git a/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head4_0.prefab.meta b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head4_0.prefab.meta new file mode 100644 index 00000000..65e0ed08 --- /dev/null +++ b/Assets/Prefabs/Minigames/DivingForPictures/QuarryMonster_monster_head4_0.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5d23561cb618e86409a2b7ccbf08e37d +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: