// ----------------------------------------------------------------------------- using UnityEngine; #if UNITY_EDITOR // --------- Editor-only code ------------------------ using UnityEditor; using System.Collections.Generic; using UnityEngine.Rendering; using System.IO; namespace BulletHellTemplate { /// /// Creates a wizard window that merges meshes by material, bakes them into /// new Mesh assets and saves a passEntryPrefab with the combined meshes. /// Open via Tools ▸ Mesh Combine Wizard. /// public class MeshCombineWizard : ScriptableWizard { /* ───────────────────── Wizard parameters ───────────────────── */ [Header("Source")] [Tooltip("Root object whose children will be combined.")] public GameObject combineParent; [Header("Output")] [Tooltip("Folder path relative to Assets/ where new meshes & passEntryPrefab go.")] public string resultPath = "CombinedMeshes"; [Tooltip("Use 32-bit index buffer (allows >65K vertices per sub-mesh).")] public bool is32bit = true; [Tooltip("Generate secondary UVs for lightmapping.")] public bool generateSecondaryUVs = false; /* ───────────────────── Menu entry ───────────────────── */ [MenuItem("Tools/Mesh Combine Wizard")] private static void CreateWizard() { var wizard = DisplayWizard("Mesh Combine Wizard"); // Auto-assign selection if exactly one GameObject is selected. if (Selection.objects.Length == 1 && Selection.activeGameObject) wizard.combineParent = Selection.activeGameObject; } /* ───────────────────── Core logic ───────────────────── */ private void OnWizardCreate() { if (combineParent == null) { Debug.LogError("Mesh Combine Wizard: No source object assigned."); return; } string assetFolder = Path.Combine("Assets", resultPath); if (!Directory.Exists(assetFolder)) { Debug.LogError($"Mesh Combine Wizard: Folder '{assetFolder}' not found."); return; } // Save original position, then reset to origin for correct transforms. Vector3 originalPos = combineParent.transform.position; combineParent.transform.position = Vector3.zero; var matToFilters = new Dictionary>(); foreach (MeshFilter mf in combineParent.GetComponentsInChildren()) { MeshRenderer mr = mf.GetComponent(); if (mr == null || mr.sharedMaterials == null || mr.sharedMaterials.Length == 0) continue; if (mr.sharedMaterials.Length > 1) { Debug.LogError($"Mesh '{mf.name}' uses multiple materials. Abort."); combineParent.transform.position = originalPos; return; } Material mat = mr.sharedMaterials[0]; if (!matToFilters.ContainsKey(mat)) matToFilters[mat] = new List(); matToFilters[mat].Add(mf); } var combinedGOs = new List(); foreach (var pair in matToFilters) { Material mat = pair.Key; List filters = pair.Value; var combine = new CombineInstance[filters.Count]; for (int i = 0; i < filters.Count; i++) { combine[i].mesh = filters[i].sharedMesh; combine[i].transform = filters[i].transform.localToWorldMatrix; } Mesh outMesh = new Mesh { indexFormat = is32bit ? IndexFormat.UInt32 : IndexFormat.UInt16 }; outMesh.CombineMeshes(combine); if (generateSecondaryUVs) Unwrapping.GenerateSecondaryUVSet(outMesh); string meshName = $"Combined_{mat.name}_{outMesh.GetInstanceID()}"; AssetDatabase.CreateAsset(outMesh, Path.Combine(assetFolder, $"{meshName}.asset")); GameObject go = new GameObject(meshName); go.AddComponent().sharedMesh = outMesh; go.AddComponent().sharedMaterial = mat; combinedGOs.Add(go); } // Group multiple outputs GameObject rootGO = combinedGOs.Count == 1 ? combinedGOs[0] : new GameObject($"Combined_{combineParent.name}"); if (combinedGOs.Count > 1) foreach (GameObject go in combinedGOs) go.transform.parent = rootGO.transform; string prefabPath = Path.Combine(assetFolder, $"{rootGO.name}.passEntryPrefab"); PrefabUtility.SaveAsPrefabAssetAndConnect(rootGO, prefabPath, InteractionMode.UserAction); combineParent.SetActive(false); combineParent.transform.position = originalPos; rootGO.transform.position = originalPos; Debug.Log($"Mesh Combine Wizard: Success, passEntryPrefab saved at {prefabPath}"); } } } #else // --------- Runtime stub (build-safe) ------------- namespace BulletHellTemplate { /// /// Dummy placeholder so references compile in player builds. /// public class MeshCombineWizard : MonoBehaviour { } } #endif