Create a custom build script
Create custom build scripts that perform Player builds with pre-build and post-build steps.
읽는 시간 11분최근 업데이트: 4일 전
To customize how Unity makes a build, use to perform a build, along with any pre-build and post-build steps that you need for your project.
BuildPipelineLaunching a Player build from the Build Profiles window doesn't trigger custom build scripts. You need to provide a way to invoke your script. The most common way to invoke a build script is from the command line, but you can also expose your script as a menu item or invoke it from a custom Unity Editor window.
The following examples use the API to perform a Player build. To use the scripts, place them in the folder of your project, or create an Editor assembly asset.
BuildPipeline.BuildPlayerEditorCreate a basic build script with menu item
This example demonstrates a simple custom build script that builds a Windows player, copies a file to the build folder, and automatically launches the built Player.
READMEIt uses the attribute to add a Build > Build Windows Player With Readme menu item to the Unity Editor, which allows you to start the build from the Editor.
MenuItemusing System.IO; using UnityEditor.Build.Reporting; using UnityEditor; using UnityEngine; public class CustomBuild { [MenuItem("Build/Build Windows Player With Readme")] public static void BuildWindowsPlayer() { // Define build options string path = EditorUtility.SaveFolderPanel("Choose Location of Built Game", "", ""); var buildOptions = new BuildPlayerOptions() { // Adjust scene list based on your project scenes = new string[] { "Assets/Scenes/Scene1.unity", "Assets/Scenes/Scene2.unity" }, locationPathName = path + "/MyGame.exe", target = BuildTarget.StandaloneWindows64, options = BuildOptions.AutoRunPlayer }; // Build the Player var buildReport = BuildPipeline.BuildPlayer(buildOptions); if (buildReport.summary.result != BuildResult.Succeeded) { Debug.Log("Build failed!\n\n" + buildReport.SummarizeErrors()); return; } // Post-process: Copy README file to the build folder File.Copy("Assets/Documentation/README.txt", path + "/README.txt", true); } }
This example has the following limitations:
- It only works for one platform.
- The list of scenes is hard coded directly in the script.
- It bypasses many of the build settings configured in the Build Profiles window.
- You can't use it in an automated build pipeline, because it requires user input to select the output folder each time it runs.
Create a build script for multiple platforms
This example demonstrates a custom build script that builds the active build target. It works for any platform you select in the Build Profiles window, and sets the correct output file extension for that target.
using UnityEditor; using UnityEditor.Build.Reporting; public static class BuildScripts { // Builds the player for the build target selected in the Build Profiles window. [MenuItem("Build/Build MyGame")] public static void BuildMyGame() { string outputPath = CreatePlayerOutputPath("Builds/MyGame"); // The target platform is automatically taken from the active build profile BuildPlayerOptions options = new BuildPlayerOptions { scenes = GetEnabledScenes(), locationPathName = outputPath, options = BuildOptions.None }; BuildReport report = BuildPipeline.BuildPlayer(options); CheckBuildResult(report, outputPath); } // Returns the paths of all enabled scenes in Build Settings. static string[] GetEnabledScenes() { var enabledScenes = new System.Collections.Generic.List<string>(); foreach (var scene in EditorBuildSettings.scenes) { if (scene.enabled) enabledScenes.Add(scene.path); } return enabledScenes.ToArray(); } // Appends the correct file extension for the active build target. static string CreatePlayerOutputPath(string outputPath) { BuildTarget target = EditorUserBuildSettings.activeBuildTarget; if (target == BuildTarget.StandaloneWindows64 || target == BuildTarget.StandaloneWindows) return outputPath + ".exe"; if (target == BuildTarget.StandaloneOSX) return outputPath + ".app"; if (target == BuildTarget.StandaloneLinux64) return outputPath + ".x86_64"; if (target == BuildTarget.Android) return outputPath + ".aab"; return outputPath; } // Validates and logs the build result. static void CheckBuildResult(BuildReport report, string outputPath) { BuildSummary summary = report.summary; if (summary.result == BuildResult.Succeeded) UnityEngine.Debug.Log("Build succeeded at: " + outputPath + " (" + summary.totalSize + " bytes)"); else throw new System.Exception("Build failed: " + report.SummarizeErrors()); } }
To run this script in the Unity Editor:
- Select the target platform in the Build Profiles window.
- Select Build > Build MyGame from the menu.
You can also invoke this script from the command line. Use the argument so that the active target matches the platform you want to build:
-buildTargetPlatform | Command |
|---|---|
| Windows | |
| macOS | |
| Android | |
This example improves on the previous example in the following ways:
- It retrieves the list of enabled scenes from .
EditorBuildSettings - It builds the active build target, instead of being hard-coded to just a single platform.
- It sets the correct output file extension for the active target.
However, it still has limitations: the output path is hard coded, and it builds an explicit scene list rather than using the full active build profile.
Build the active build target
The previous example builds the target that's currently active in the Build Profiles window. This is the recommended approach. Where possible, let the active build target or build profile determine the platform, rather than passing a specific target to the build API:
- In a build that uses , prefer not to set
BuildPlayerOptions. If you do set it, set it to the active build target.BuildPlayerOptions.target - In a build that uses set BuildPlayerWithProfileOptions.buildProfile to the active build profile.
BuildPlayerWithProfileOptions
Building a target that doesn't match the active build target is unreliable. To change the active build target, Unity recompiles Editor scripts for the new platform and performs a domain reload. This can't happen while a build script is running, so build callbacks and platform-dependent code (for example, code inside directives) might compile for the platform that was active when the script started, not the target you requested. Some platforms fail to build entirely in this situation, and packages that add their own platform-specific defines can make the problem worse.
#ifTo select the platform before you build:
- In the Editor, set the active platform in the Build Profiles window.
- On the command line, use the or
-activeBuildProfileargument so that Unity opens the project with the target already active.-buildTarget
Create an advanced build script with build profiles and content directories
This example demonstrates a custom build script that introduces several advanced concepts:
- Using the active build profile: This script sets the scene list, flags, and settings based on the active build profile. Before using this script, create build profiles in the Build Profiles window to configure the builds.
- Dynamic output paths: The script uses a naming convention for the output path based on the profile name and a timestamp, similar to what a basic build server might do.
- Content directory builds: The script performs a content directory build followed by a Player build. A content directory is a separate build of additional content that you distribute alongside the Player. The script builds the textures in a project folder into a content directory with .
BuildPipeline.BuildContentDirectory - Root assets: A content directory build starts from one or more root assets. The script creates a temporary root asset that references the textures, builds it, then deletes it.
- Type preservation: The script passes the content directory's build report to the Player build, so that managed code stripping doesn't remove types used only by the content.
- Build callbacks: The script uses a build callback to include the content directory in the
BuildPlayerProcessorfolder of the Player build.StreamingAssets
The root asset is a that references each texture through a loadable reference. Any asset referenced this way is included in the content directory build. Storing the references in a serialized dictionary lets the runtime look up a texture by name:
ScriptableObjectusing System.Collections.Generic; using Unity.Loading; using UnityEngine; // A root asset that maps a name to an on-demand-loadable texture. // When used as the root of a content directory build, each texture referenced // through its loadable reference is included in the build. At runtime, code can // look up a texture by name and load it on demand. public class TextureContent : ScriptableObject { [SerializeField] Dictionary<string, Loadable<Texture>> m_Textures = new(); public IReadOnlyDictionary<string, Loadable<Texture>> Textures => m_Textures; public void Add(string name, Loadable<Texture> texture) { m_Textures[name] = texture; } }
The build script creates a temporary instance of this root asset, populates it with the textures it finds, and builds it into a content directory. It then builds the Player and includes the content directory:
using UnityEngine; using System.IO; using Unity.Loading; using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Profile; using UnityEditor.Build.Reporting; // Example build script that builds the active build profile. // // It builds the textures found in a project folder into a content directory, // then builds the player and includes the content directory in the player's // StreamingAssets folder. Each time it runs it builds into a new directory derived // from the active build profile and a timestamp. public class ContentBuildScript { public const string kBuildRootPath = "Build"; // All builds are inside this top level project folder public const string kContentDirectory = "Content"; public const string kPlayerDirectory = "Player"; public const string kAppName = "MyGame"; public const string kTextureSourceDirectory = "Assets/Textures"; public const string kTextureSearchPattern = "*.png"; // Temporary root asset that the script creates and then deletes. public const string kRootAssetPath = "Assets/TextureContent.asset"; // Global variables so that RegisterContentDirectoryForPlayer can find the content built for this player. public static string gContentDirectoryPath = null; public static string gContentBuildReportDirectory = null; [MenuItem("Build/Build Active Profile (Content Directory)")] public static void BuildPlayerAndContent() { var profile = BuildProfile.GetActiveBuildProfile(); if (profile == null) throw new BuildFailedException("No active build profile is set." + "Use the Build Profiles window or the `-activeBuildProfile` cli argument"); // Use a timestamp so that each build goes to a unique output folder var timeStamp = System.DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); var buildRootPath = $"{kBuildRootPath}/{profile.name}/{timeStamp}"; // Build the content directory so that it can be shipped inside the player gContentDirectoryPath = BuildTextureContentDirectory(buildRootPath); // Build the player var playerBuildOptions = new BuildPlayerWithProfileOptions() { buildProfile = profile, locationPathName = CreatePlayerOutputPath(buildRootPath), // These options can be adjusted as needed. // Note: the development and compression flags come from the build profile options = BuildOptions.CleanBuildCache | BuildOptions.StrictMode }; // Convenient for manual testing if (!Application.isBatchMode) playerBuildOptions.options |= BuildOptions.AutoRunPlayer; BuildReport report; try { report = BuildPipeline.BuildPlayer(playerBuildOptions); } finally { gContentDirectoryPath = null; gContentBuildReportDirectory = null; } if (report.summary.result != BuildResult.Succeeded) throw new BuildFailedException("Player build failed, see Editor log for details"); Debug.Log($"Completed build to {playerBuildOptions.locationPathName}"); } // Builds the textures found under kTextureSourceDirectory into a content directory. // Returns the path to the built content directory. private static string BuildTextureContentDirectory(string buildRootDirectory) { var contentDirectoryPath = $"{buildRootDirectory}/{kContentDirectory}"; if (!Directory.Exists(contentDirectoryPath)) Directory.CreateDirectory(contentDirectoryPath); // Create a temporary root asset that references each texture through a loadable reference. // Every texture referenced this way is included in the content directory build. var textureContent = ScriptableObject.CreateInstance<TextureContent>(); string[] texturePaths = Directory.GetFiles(kTextureSourceDirectory, kTextureSearchPattern, SearchOption.AllDirectories); foreach (var texturePath in texturePaths) { var texture = AssetDatabase.LoadAssetAtPath<Texture>(texturePath); var loadable = new Loadable<Texture>(LoadableObjectIdEditorUtility.CreateLoadableObjectId(texture)); textureContent.Add(Path.GetFileNameWithoutExtension(texturePath), loadable); } AssetDatabase.CreateAsset(textureContent, kRootAssetPath); try { // The target platform is automatically taken from the active build profile var buildParameters = new BuildContentDirectoryParameters() { outputPath = contentDirectoryPath, rootAssetPaths = new string[] { kRootAssetPath }, options = BuildContentOptions.CleanBuildCache }; var report = BuildPipeline.BuildContentDirectory(buildParameters); if (report.summary.result != BuildResult.Succeeded) throw new BuildFailedException("Content directory build failed, see Editor log for details"); // Remember the build report directory, for use in the callback during the player build. BuildHistory.TryGetBuildReportDirectory(report.summary.buildSessionGuid, out gContentBuildReportDirectory); return contentDirectoryPath; } finally { // Delete the temporary root asset to avoid special cases of updating an existing asset AssetDatabase.DeleteAsset(kRootAssetPath); } } private static string CreatePlayerOutputPath(string buildRootDirectory) { var playerOutputFolder = $"{buildRootDirectory}/{kPlayerDirectory}"; if (!Directory.Exists(playerOutputFolder)) Directory.CreateDirectory(playerOutputFolder); var playerPath = $"{playerOutputFolder}/{kAppName}"; // This property will match the target in the active build profile var target = EditorUserBuildSettings.activeBuildTarget; // See "Build path requirements for target platforms" in the Unity Manual if ((target == BuildTarget.StandaloneWindows64) || (target == BuildTarget.StandaloneWindows)) playerPath += ".exe"; else if (target == BuildTarget.StandaloneOSX) playerPath += ".app"; else if (target == BuildTarget.StandaloneLinux64) playerPath += ".x86_64"; else if (target == BuildTarget.Android) playerPath += ".aab"; return playerPath; } } // Includes the content directory in the StreamingAssets folder of the player output. // This approach keeps built content separate from the source project, avoiding clutter in "Assets/StreamingAssets". public class RegisterContentDirectoryForPlayer : BuildPlayerProcessor { public override void PrepareForBuild(BuildPlayerContext buildPlayerContext) { var contentDirectoryPath = ContentBuildScript.gContentDirectoryPath; if (string.IsNullOrEmpty(contentDirectoryPath)) // Do not do anything if we are not in a build initiated by ContentBuildScript return; buildPlayerContext.AddAdditionalPathToStreamingAssets(contentDirectoryPath); // Preserve the types used by the content so that managed code stripping does not remove them if (!string.IsNullOrEmpty(ContentBuildScript.gContentBuildReportDirectory)) buildPlayerContext.AddPreviousBuildReportDirectory(ContentBuildScript.gContentBuildReportDirectory); } public override int callbackOrder => 1; }
Use the script in the Editor
To use this script in the Unity Editor:
- Select the build profile in the Build Profiles window.
- Select Build > Build Active Profile (Content Directory) from the menu.
Use the script from the command line
You can also invoke this script from the command line. On Windows, the command is as follows:
.\Unity.exe -batchmode -projectPath "C:\UnityProjects\CLIBuildExample" -activeBuildProfile "Assets\Settings\Build Profiles\MyWindowsProfile.asset" -executeMethod ContentBuildScript.BuildPlayerAndContent -logFile C:\logs\buildlog.txt -quit
An equivalent command on macOS is as follows:
Unity -batchmode -projectPath "~/UnityProjects/CLIBuildExample" -activeBuildProfile "Assets/Settings/Build Profiles/MyWeb - Desktop - Development.asset" -executeMethod ContentBuildScript.BuildPlayerAndContent -logFile "~/logs/buildlog.txt" -quit