在項目中有時會遇到批量生成Prefab的需求。於是寫了一個編輯器,用來實現此功能。
在Hierarchy面板中選中多個GameObject,點擊生成Prefab即可。
如果所選物體中包含自定義Mesh,需要先在指定目錄生成Obj,再將Obj包含的網格賦值給新生成的Prefab。
編輯器腳本如下:

using UnityEngine; using UnityEditor; using System.Collections; using System.IO; /// <summary> /// CreatePrefabs類為批量創建Prefab的窗口類,選擇Hierarchy窗口的物體,點擊創建Prefab即可在指定目錄生成Prefab /// 如果所選物體含有動態創建的Mesh,必須先在指定目錄先生成OBJ文件 /// </summary> public class CreatePrefabs : EditorWindow { [MenuItem("AssetsManager/批量生成Prefab")] static void AddWindow() { //創建窗口 CreatePrefabs window = (CreatePrefabs)EditorWindow.GetWindow(typeof(CreatePrefabs), false, "批量生成Prefab"); window.Show(); } //輸入文字的內容 private string PrefabPath = "Assets/Resources/"; private string ObjPath = @"Assets/Obj/"; GameObject[] selectedGameObjects; [InitializeOnLoadMethod] public void Awake() { OnSelectionChange(); } void OnGUI() { GUIStyle text_style = new GUIStyle(); text_style.fontSize = 15; text_style.alignment = TextAnchor.MiddleCenter; EditorGUILayout.BeginHorizontal(); GUILayout.Label("Prefab導出路徑:"); PrefabPath = EditorGUILayout.TextField(PrefabPath); if (GUILayout.Button("瀏覽")) { EditorApplication.delayCall += OpenPrefabFolder; } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); GUILayout.Label(" Obj導出路徑:"); ObjPath = EditorGUILayout.TextField(ObjPath); if (GUILayout.Button("瀏覽")) { EditorApplication.delayCall += OpenObjFolder; } EditorGUILayout.EndHorizontal(); GUILayout.Label("當前選中了" + selectedGameObjects.Length + "個物體", text_style); if (GUILayout.Button("如果包含動態創建的Mesh,請先點擊生成Obj", GUILayout.MinHeight(20))) { foreach (GameObject m in selectedGameObjects) { CreateObj(m); } AssetDatabase.Refresh(); } if (GUILayout.Button("生成當前選中物體的Prefab", GUILayout.MinHeight(20))) { if (selectedGameObjects.Length <= 0) { //打開一個通知欄 this.ShowNotification(new GUIContent("未選擇所要導出的物體")); return; } if (!Directory.Exists(PrefabPath)) { Directory.CreateDirectory(PrefabPath); } foreach (GameObject m in selectedGameObjects) { CreatePrefab(m, m.name); } AssetDatabase.Refresh(); } } void OpenPrefabFolder() { string path = EditorUtility.OpenFolderPanel("選擇要導出的路徑", "", ""); if (!path.Contains(Application.dataPath)) { Debug.LogError("導出路徑應在當前工程目錄下"); return; } if (path.Length != 0) { int firstindex = path.IndexOf("Assets"); PrefabPath = path.Substring(firstindex) + "/"; EditorUtility.FocusProjectWindow(); } } void OpenObjFolder() { string path = EditorUtility.OpenFolderPanel("選擇要導出的路徑", "", ""); if (!path.Contains(Application.dataPath)) { Debug.LogError("導出路徑應在當前工程目錄下"); return; } if (path.Length != 0) { int firstindex = path.IndexOf("Assets"); ObjPath = path.Substring(firstindex) + "/"; EditorUtility.FocusProjectWindow(); } } void CreateObj(GameObject go) { if (!Directory.Exists(ObjPath)) { Directory.CreateDirectory(ObjPath); } MeshFilter[] meshfilters = go.GetComponentsInChildren<MeshFilter>(); if (meshfilters.Length > 0) { for (int i = 0; i < meshfilters.Length; i++) { ObjExporter.MeshToFile(meshfilters[i], ObjPath + meshfilters[i].gameObject.name + ".obj"); } } } /// <summary> /// 此函數用來根據某物體創建指定名字的Prefab /// </summary> /// <param name="go">選定的某物體</param> /// <param name="name">物體名</param> /// <returns>void</returns> void CreatePrefab(GameObject go, string name) { //先創建一個空的預制物體 //預制物體保存在工程中路徑,可以修改("Assets/" + name + ".prefab"); GameObject tempPrefab = PrefabUtility.CreatePrefab(PrefabPath + name + ".prefab", go); MeshFilter []meshfilters= go.GetComponentsInChildren<MeshFilter>(); if (meshfilters.Length > 0) { MeshFilter[] prefabmeshfilters = tempPrefab.GetComponentsInChildren<MeshFilter>(); for (int i = 0; i < meshfilters.Length; i++) { Mesh m_mesh = AssetDatabase.LoadAssetAtPath<Mesh>(ObjPath + meshfilters[i].gameObject.name + ".obj"); prefabmeshfilters[i].sharedMesh = m_mesh; } } //返回創建后的預制物體 } void OnInspectorUpdate() { //這里開啟窗口的重繪,不然窗口信息不會刷新 this.Repaint(); } void OnSelectionChange() { //當窗口出去開啟狀態,並且在Hierarchy視圖中選擇某游戲對象時調用 selectedGameObjects = Selection.gameObjects; } }

1 using UnityEngine; 2 using System.Collections; 3 using System.IO; 4 using System.Text; 5 6 public class ObjExporter 7 { 8 9 public static string MeshToString(MeshFilter mf) 10 { 11 Mesh m = mf.sharedMesh; 12 // Material[] mats = mf.GetComponent<MeshRenderer>().sharedMaterials; 13 14 StringBuilder sb = new StringBuilder(); 15 16 sb.Append("g ").Append(mf.name).Append("\n"); 17 for(int i=0;i<m.vertices.Length;i++) 18 sb.Append(string.Format("v {0} {1} {2}\n", -m.vertices[i].x, m.vertices[i].y, m.vertices[i].z)); 19 sb.Append("\n"); 20 for (int i = 0; i < m.normals.Length; i++) 21 sb.Append(string.Format("vn {0} {1} {2}\n", -m.normals[i].x, m.normals[i].y, m.normals[i].z)); 22 sb.Append("\n"); 23 for (int i = 0; i < m.uv.Length; i++) 24 sb.Append(string.Format("vt {0} {1}\n",m.uv[i].x, m.uv[i].y)); 25 26 for (int material = 0; material < m.subMeshCount; material++) 27 { 28 sb.Append("\n"); 29 30 int[] triangles = m.GetTriangles(material); 31 for (int i = 0; i < triangles.Length; i += 3) 32 { 33 //Because we inverted the x-component, we also needed to alter the triangle winding. 34 sb.Append(string.Format("f {1}/{1}/{1} {0}/{0}/{0} {2}/{2}/{2}\n", 35 triangles[i] + 1, triangles[i + 1] + 1, triangles[i + 2] + 1)); 36 } 37 } 38 return sb.ToString(); 39 } 40 41 public static void MeshToFile(MeshFilter mf, string filename) 42 { 43 using (StreamWriter sw = new StreamWriter(filename)) 44 { 45 sw.Write(MeshToString(mf)); 46 } 47 } 48 }