一般来讲如果项目是PC或Android、IOS端不会有批量Build打包这样的需求,但如果项目是WebGL端可能会遇到这样的需求:不同场景打包成不同的包体,入口是前端在页面中布局的,点击链接打开相应的程序。依次手动打包比较繁琐而且需要等待很长时间,因此写了批量Build这样的功能,下班时点击Build经历漫长的夜晚,第二天上班时包体已经都打好了。
核心API是UnityEditor.BuildPipeline类中的BuildPlayer,调用该方法传入相应参数即可实现打包,我们要做的是做一个配置文件,在其中配置打包不同包体对应的数据,包含打包的场景、名称和平台等。首先构建可序列化类:
/// <summary>/// 打包任务/// </summary>[Serializable] publicsealedclassBuildTask{ /// <summary>/// 名称/// </summary>publicstringProductName; /// <summary>/// 目标平台/// </summary>publicBuildTargetBuildTarget; /// <summary>/// 打包路径/// </summary>publicstringBuildPath; /// <summary>/// 打包场景/// </summary>publicList<SceneAsset>SceneAssets=newList<SceneAsset>(0); }
使用ScriptableObject构建配置:
/// <summary>/// 打包配置表/// </summary>[CreateAssetMenu(fileName="New Build Profile", menuName="Build Profile")] publicsealedclassBuildProfile : ScriptableObject{ /// <summary>/// 打包任务列表/// </summary>publicList<BuildTask>BuildTasks=newList<BuildTask>(0); }
有了BuildProfile后,配置打包列表,批量打包要做的就是遍历该列表依次调用BuildPipeline中的BuildPlayer方法。创建Editor类,重写BuildProfile的Inspector面板,编写打包功能,以及添加、移除打包项等菜单。
[CustomEditor(typeof(BuildProfile))] publicsealedclassBuildProfileInspector : Editor{ privatereadonlyDictionary<BuildTask, bool>foldoutMap=newDictionary<BuildTask, bool>(); privateVector2scroll=Vector2.zero; privateBuildProfileprofile; privatevoidOnEnable() { profile=targetasBuildProfile; } publicoverridevoidOnInspectorGUI() { GUILayout.BeginHorizontal(); { if (GUILayout.Button("新建", "ButtonLeft")) { Undo.RecordObject(profile, "Create"); vartask=newBuildTask() { ProductName="Product Name", BuildTarget=BuildTarget.StandaloneWindows64, BuildPath=Directory.GetParent(Application.dataPath).FullName }; profile.BuildTasks.Add(task); } if (GUILayout.Button("展开", "ButtonMid")) { for (inti=0; i<profile.BuildTasks.Count; i++) { foldoutMap[profile.BuildTasks[i]] =true; } } if (GUILayout.Button("收缩", "ButtonMid")) { for (inti=0; i<profile.BuildTasks.Count; i++) { foldoutMap[profile.BuildTasks[i]] =false; } } GUI.color=Color.yellow; if (GUILayout.Button("清空", "ButtonMid")) { Undo.RecordObject(profile, "Clear"); if (EditorUtility.DisplayDialog("提醒", "是否确定清空列表?", "确定", "取消")) { profile.BuildTasks.Clear(); } } GUI.color=Color.cyan; if (GUILayout.Button("打包", "ButtonRight")) { if (EditorUtility.DisplayDialog("提醒", "打包需要耗费一定时间,是否确定开始?", "确定", "取消")) { StringBuildersb=newStringBuilder(); sb.Append("打包报告:\r\n"); for (inti=0; i<profile.BuildTasks.Count; i++) { EditorUtility.DisplayProgressBar("Build", "Building...", i+1/profile.BuildTasks.Count); vartask=profile.BuildTasks[i]; List<EditorBuildSettingsScene>buildScenes=newList<EditorBuildSettingsScene>(); for (intj=0; j<task.SceneAssets.Count; j++) { varscenePath=AssetDatabase.GetAssetPath(task.SceneAssets[j]); if (!string.IsNullOrEmpty(scenePath)) { buildScenes.Add(newEditorBuildSettingsScene(scenePath, true)); } } stringlocationPathName=$"{task.BuildPath}/{task.ProductName}"; varreport=BuildPipeline.BuildPlayer(buildScenes.ToArray(), locationPathName, task.BuildTarget, BuildOptions.None); sb.Append($"[{task.ProductName}] 打包结果: {report.summary.result}\r\n"); } EditorUtility.ClearProgressBar(); Debug.Log(sb.ToString()); } return; } GUI.color=Color.white; } GUILayout.EndHorizontal(); scroll=GUILayout.BeginScrollView(scroll); { for (inti=0; i<profile.BuildTasks.Count; i++) { vartask=profile.BuildTasks[i]; if (!foldoutMap.ContainsKey(task)) foldoutMap.Add(task, true); GUILayout.BeginHorizontal("Badge"); GUILayout.Space(12); foldoutMap[task] =EditorGUILayout.Foldout(foldoutMap[task], $"{task.ProductName}", true); GUILayout.Label(string.Empty); if (GUILayout.Button(EditorGUIUtility.IconContent("TreeEditor.Trash"), "IconButton", GUILayout.Width(20))) { Undo.RecordObject(profile, "Delete Task"); foldoutMap.Remove(task); profile.BuildTasks.Remove(task); break; } GUILayout.EndHorizontal(); if (foldoutMap[task]) { GUILayout.BeginVertical("Box"); GUILayout.BeginHorizontal(); GUILayout.Label("打包场景:", GUILayout.Width(70)); if (GUILayout.Button(EditorGUIUtility.IconContent("Toolbar Plus More"), GUILayout.Width(28))) { task.SceneAssets.Add(null); } GUILayout.EndHorizontal(); if (task.SceneAssets.Count>0) { GUILayout.BeginHorizontal(); GUILayout.Space(75); GUILayout.BeginVertical("Badge"); for (intj=0; j<task.SceneAssets.Count; j++) { varsceneAsset=task.SceneAssets[j]; GUILayout.BeginHorizontal(); GUILayout.Label($"{j + 1}.", GUILayout.Width(20)); task.SceneAssets[j] =EditorGUILayout.ObjectField(sceneAsset, typeof(SceneAsset), false) asSceneAsset; if (GUILayout.Button("↑", "MiniButtonLeft", GUILayout.Width(20))) { if (j>0) { Undo.RecordObject(profile, "Move Up Scene Assets"); vartemp=task.SceneAssets[j-1]; task.SceneAssets[j-1] =sceneAsset; task.SceneAssets[j] =temp; } } if (GUILayout.Button("↓", "MiniButtonMid", GUILayout.Width(20))) { if (j<task.SceneAssets.Count-1) { Undo.RecordObject(profile, "Move Down Scene Assets"); vartemp=task.SceneAssets[j+1]; task.SceneAssets[j+1] =sceneAsset; task.SceneAssets[j] =temp; } } if (GUILayout.Button(EditorGUIUtility.IconContent("Toolbar Plus"), "MiniButtonMid", GUILayout.Width(20))) { Undo.RecordObject(profile, "Add Scene Assets"); task.SceneAssets.Insert(j+1, null); break; } if (GUILayout.Button(EditorGUIUtility.IconContent("Toolbar Minus"), "MiniButtonMid", GUILayout.Width(20))) { Undo.RecordObject(profile, "Delete Scene Assets"); task.SceneAssets.RemoveAt(j); break; } GUILayout.EndHorizontal(); } GUILayout.EndVertical(); GUILayout.EndHorizontal(); } GUILayout.BeginHorizontal(); GUILayout.Label("产品名称:", GUILayout.Width(70)); varnewPN=GUILayout.TextField(task.ProductName); if (task.ProductName!=newPN) { Undo.RecordObject(profile, "Product Name"); task.ProductName=newPN; } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("打包平台:", GUILayout.Width(70)); varnewBT= (BuildTarget)EditorGUILayout.EnumPopup(task.BuildTarget); if (task.BuildTarget!=newBT) { Undo.RecordObject(profile, "Build Target"); task.BuildTarget=newBT; } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("打包路径:", GUILayout.Width(70)); GUILayout.TextField(task.BuildPath); if (GUILayout.Button("Browse", GUILayout.Width(60))) { task.BuildPath=EditorUtility.SaveFolderPanel("Build Path", task.BuildPath, ""); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); } } } GUILayout.EndScrollView(); serializedObject.ApplyModifiedProperties(); if (GUI.changed) EditorUtility.SetDirty(profile); } }