To customize how Unity makes a build, use BuildPipeline to perform a build, along with any pre-build and post-build steps that you need for your project.
Launching a Player build from the Build Profiles window doesn’t trigger custom build scriptsA piece of code that allows you to create your own Components, trigger game events, modify Component properties over time and respond to user input in any way you like. More info
See in Glossary. 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 BuildPipeline.BuildPlayer API to perform a Player build. To use the scripts, place them in the Editor folder of your project, or create an Editor assembly asset.
This example demonstrates a simple custom build script that builds a Windows player, copies a README file to the build folder, and automatically launches the built Player.
It uses the MenuItem 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.
using 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:
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:
You can also invoke this script from the command line. Use the -buildTarget argument so that the active target matches the platform you want to build:
| Platform | Command |
|---|---|
| Windows | -executeMethod BuildScripts.BuildMyGame -buildTarget StandaloneWindows64 -quit -batchmode |
| macOS | -executeMethod BuildScripts.BuildMyGame -buildTarget StandaloneOSX -quit -batchmode |
| Android | -executeMethod BuildScripts.BuildMyGame -buildTarget Android -quit -batchmode |
This example improves on the previous example in the following ways:
EditorBuildSettings.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.
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 profileA set of customizable configuration settings to use when creating a build for your target platform. More info
See in Glossary determine the platform, rather than passing a specific target to the build API:
BuildPlayerOptions, prefer not to set BuildPlayerOptions.target. If you do set it, set it to the active build target.BuildPlayerWithProfileOptions set BuildPlayerWithProfileOptions.buildProfile to the active build profile.Building a target that doesn’t match the active build target is unreliable. To change the active build target, Unity recompiles Editor scriptsC# source files composed entirely of code that runs in the Unity Editor only and not in the runtime Player build. Keep such scripts in dedicated Editor assemblies either by placing them in a parent folder called Editor or creating an Editor-only assembly definition.
See in Glossary 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 #if 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.
To select the platform before you build:
-activeBuildProfile or -buildTarget argument so that Unity opens the project with the target already active.This example demonstrates a custom build script that introduces several advanced concepts:
BuildPipeline.BuildContentDirectory.BuildPlayerProcessor build callback to include the content directory in the StreamingAssets folder of the Player build.The root asset is a ScriptableObject 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:
using 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;
}
To use this script in the Unity Editor:
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
Note: Adjust the paths in these command lines to match your device configuration and the path to your Unity project. For more information about command line arguments, refer to Build a player from the command line.