Version: Unity 6.7 Alpha (6000.7)
Language : English
Introduction to customizing the build pipeline
Build a player from the command line

Create a custom build script

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.

Create a basic build script with menu item

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:

  • It only works for one platform.
  • The list of scenesA Scene contains the environments and menus of your game. Think of each unique Scene file as a unique level. In each Scene, you place your environments, obstacles, and decorations, essentially designing and building your game in pieces. More info
    See in Glossary
    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:

  1. Select the target platform in the Build Profiles window.
  2. Select Build > Build MyGame from the menu.

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:

  • 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 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:

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:

  • In the Editor, set the active platform in the Build Profiles window.
  • On the command line, use the -activeBuildProfile or -buildTarget argument so that Unity opens the project with the target already active.

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 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;
}

Use the script in the Editor

To use this script in the Unity Editor:

  1. Select the build profile in the Build Profiles window.
  2. 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

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.

Additional resources

Introduction to customizing the build pipeline
Build a player from the command line