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.
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 supports building for multiple platforms: Windows, macOS, and Android. You can run these builds by selecting the platform from the Build menu in the Unity Editor, or by invoking the script from the command line.
using UnityEditor;
using UnityEditor.Build.Reporting;
public static class BuildScripts
{
// Helper to get all enabled scenes in Build Settings
static string[] GetEnabledScenes()
{
// Get all enabled scenes from Build Settings
var scenesInSettings = EditorBuildSettings.scenes;
var enabledScenes = new System.Collections.Generic.List<string>();
// Iterate through all scenes and add the enabled ones
for (int i = 0; i < scenesInSettings.Length; i++)
{
if (scenesInSettings[i].enabled)
enabledScenes.Add(scenesInSettings[i].path);
}
return enabledScenes.ToArray();
}
// General build method
static void BuildForTarget(BuildTarget target, string outputPath)
{
string[] scenes = GetEnabledScenes();
// Platform-specific settings
if (target == BuildTarget.Android)
{
// Basic Android PlayerSettings (optional)
PlayerSettings.applicationIdentifier = "com.company.mygame";
PlayerSettings.bundleVersion = "1.0.0";
PlayerSettings.Android.bundleVersionCode = 1;
EditorUserBuildSettings.buildAppBundle = true; // Use AAB for Play Store
}
// Build player options
BuildPlayerOptions options = new BuildPlayerOptions
{
scenes = scenes,
locationPathName = outputPath,
target = target,
options = BuildOptions.None
};
// Execute the build
BuildReport report = BuildPipeline.BuildPlayer(options);
CheckBuildResult(report, outputPath);
}
[MenuItem("Build/Windows")]
public static void BuildWindows()
{
BuildForTarget(BuildTarget.StandaloneWindows64, "Builds/Windows/MyGame.exe");
}
[MenuItem("Build/macOS")]
public static void BuildMacOS()
{
BuildForTarget(BuildTarget.StandaloneOSX, "Builds/MacOS/MyGame.app");
}
[MenuItem("Build/Android (AAB)")]
public static void BuildAndroidAAB()
{
BuildForTarget(BuildTarget.Android, "Builds/Android/MyGame.aab");
}
// Helper to validate and log the build result
static void CheckBuildResult(BuildReport report, string outputPath)
{
// Log the build summary
var 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());
}
}
}
You can invoke this script from the command line using one of the following commands:
| Platform | Command |
|---|---|
| Windows | -executeMethod BuildScripts.BuildWindows -buildTarget StandaloneWindows64 -quit -batchmode |
| macOS | -executeMethod BuildScripts.BuildMacOS -buildTarget StandaloneOSX -quit -batchmode |
| Android | -executeMethod BuildScripts.BuildAndroidAAB -buildTarget Android -quit -batchmode |
This example is an improvement on the previous example in the following ways:
EditorBuildSettings.PlayerSettings and EditorUserBuildSettings objects.BuildForTarget method.However, it has limitations because it only supports three platforms, and the output paths are hard coded.
This example demonstrates a custom build script that introduces several advanced concepts:
BuildPlayerProcessor build callback to inject the AssetBundle build into the StreamingAssets folder of the Player build.#if UNITY_EDITOR
using UnityEngine;
using System.IO;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.Build.Profile;
// Example BuildScript that supports building the current build profile.
//
//
// It builds AssetBundles and includes them in the player build.
// Each time it runs it builds into a new directory derived from the
// current build profile and timestamp.
public class BuildScript
{
public const string kBuildRootPath = "Build"; // All builds are found inside this top level project folder
public const string kAssetBundleDirectory = "AssetBundles";
public const string kPlayerDirectory = "Player";
public const string kAppName = "MyGame";
public const string kTextureSourceDirectory = "Assets/Textures";
public const string kTextureSearchPattern = "*.png";
// Global variable so that RegisterContentForPlayer can find the correct AssetBundles to include in the player build
public static string gCurrentBuildRootPath = null;
[MenuItem("Build/Build Active Profile")]
public static void BuildPlayerAndBundles()
{
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");
gCurrentBuildRootPath = $"{kBuildRootPath}/{profile.name}/{timeStamp}";
// Build AssetBundles so that they can be shipped inside the player
var assetBundleBuildPath = BuildAssetBundles(gCurrentBuildRootPath);
// To preserve types used by the AssetBundles
var assetBundleManifestPath = assetBundleBuildPath + "/AssetBundles.manifest";
// Build the player
var playerBuildOptions = new BuildPlayerWithProfileOptions()
{
buildProfile = profile,
locationPathName = CreatePlayerOutputPath(gCurrentBuildRootPath),
assetBundleManifestPath = assetBundleManifestPath,
// 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;
var report = BuildPipeline.BuildPlayer(playerBuildOptions);
gCurrentBuildRootPath = 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}");
}
private static string BuildAssetBundles(string buildRootDirectory)
{
var assetBundlePath = buildRootDirectory + "/" + kAssetBundleDirectory;
if (!Directory.Exists(assetBundlePath))
Directory.CreateDirectory(assetBundlePath);
// For simplicity in this example, define a single AssetBundle,
// containing all the textures found inside a hard-coded directory in the project
string[] texturePaths = Directory.GetFiles(kTextureSourceDirectory, kTextureSearchPattern, SearchOption.AllDirectories);
var assetBundleContents = new AssetBundleBuild()
{
assetBundleName = "textures.bundle",
assetNames = texturePaths
};
// The target platform will be automatically set based on the active build profile
var assetBundleBuildOptions = new BuildAssetBundlesParameters()
{
outputPath = assetBundlePath,
bundleDefinitions = new AssetBundleBuild[] { assetBundleContents }
};
var manifest = BuildPipeline.BuildAssetBundles(assetBundleBuildOptions);
if (manifest == null)
throw new BuildFailedException("AssetBundle build failed, see Editor log for details");
return assetBundlePath;
}
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;
}
}
// Put the AssetBundle build directory into the StreamingAssets folder of the player output.
// This approach keeps built content separate from the source project, avoiding clutter in "Assets/StreamingAssets".
public class RegisterContentForPlayer : BuildPlayerProcessor
{
public override void PrepareForBuild(BuildPlayerContext buildPlayerContext)
{
var currentBuildPath = BuildScript.gCurrentBuildRootPath;
if (string.IsNullOrEmpty(currentBuildPath))
// Do not do anything if we are not in a build initiated by BuildScript
return;
buildPlayerContext.AddAdditionalPathToStreamingAssets(currentBuildPath + "/" + BuildScript.kAssetBundleDirectory);
}
public int callbackOrder => 1;
}
#endif
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 BuildScript.BuildPlayerAndBundles -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 BuildScript.BuildPlayerAndBundles -logFile "~/logs/buildlog.txt" -quit
Note: Adjust the paths in the command lines in the previous examples to match the configuration of your device and path to your Unity project. For more information about command line arguments, refer to Build a player from the command line.