[ i.am.kon ]

[ i.am.kon ]

Unity Custom Build

I like to have convenience build functions that I can just select from a menu and a build would be generated in a proper location with all the correct settings. Another benefit of having a Builder class is that these build functions can be invoked from command line and can be executed on the CI. This is why I always create my own Unity Builder in the project.

Clean Up

We can start by creating a simple Builder class that will have one helper function, in this case this function deletes the build folder. I find it convenient to have an easy way to delete previous builds and artifacts.

public static class Builder
{
    public const string BuildPath = "Build";


    [MenuItem("Kon Build/Delete Build Folder")]
    public static void DeleteBuildFolder()
    {
        if (Directory.Exists(BuildPath))
        {
            Directory.Delete(BuildPath, true);
        }
    }
}

Build Number

Next let's add a way to update the project version inside the actual Unity project, this is not required, but I like it.

[MenuItem("Kon Build/Increment Build Number")]
public static void IncrementBuildNumber()
{
    string settingAssetPath = "./ProjectSettings/ProjectSettings.asset";

    // Read in file contents
    string fileContents = File.ReadAllText(settingAssetPath);

    // Find version string
    int startIndex = fileContents.IndexOf("bundleVersion:", StringComparison.Ordinal);
    if (startIndex == -1)
    {
        return;
    }
    int endIndex = fileContents.IndexOf("\n", startIndex);
    string versionString = fileContents.Substring(startIndex, endIndex - startIndex);

    // Split this string into an array of 3 numbers
    string[] numbers = versionString.Split(".");

    // Convert last string to a number, this will be the build number
    string buildString = numbers[^1];
    int buildNumber = Int32.Parse(buildString);

    // Increment the build number
    buildNumber++;

    // Replace the last string in the array with our new buildNumber
    numbers[^1] = $"{buildNumber}";

    // Convert array of multiple strings into a single string
    string newVersionString = string.Join(".", numbers);

    // Replace the version string in the file contents with the new version string and save contents back to disk
    fileContents = fileContents.Replace(versionString, newVersionString);
    File.WriteAllText(settingAssetPath, fileContents);
}

Scenes

Next we need a way to select all the scenes that we want to be part of the build. In this case I just copy the list from the build settings.

private static string[] GetListOfScenes()
{
    int sceneCount = SceneManager.sceneCountInBuildSettings;
    string[] scenes = new string[sceneCount];

    for (int i = 0; i < sceneCount; i++)
    {
        scenes[i] = SceneUtility.GetScenePathByBuildIndex(i);
    }

    return scenes;
}

iOS

[MenuItem("Kon Build/Build iOS")]
public static void BuildIOS()
{
    IncrementBuildNumber();

    string buildPath = BuildPath + "/iOS_Device";
    string buildName = "iOS Build";

    BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
    buildPlayerOptions.locationPathName = buildPath;
    buildPlayerOptions.scenes = GetListOfScenes();
    buildPlayerOptions.target = BuildTarget.iOS;

    // Refresh assets
    if (Application.isBatchMode)
    {
        AssetDatabase.Refresh();
    }

    BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
    BuildSummary summary = report.summary;

    if (summary.result == BuildResult.Succeeded)
    {
        Debug.Log($"{buildName} succeeded: {summary.totalSize} bytes");
    }

    if (summary.result == BuildResult.Failed)
    {
        Debug.LogError($"{buildName} failed");
    }
}

Web

[MenuItem("Kon Build/Build Web")]
public static void BuildWeb()
{
    IncrementBuildNumber();

    string buildPath = BuildPath + "/WebGL/game";
    string buildName = "kon";

    BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
    buildPlayerOptions.locationPathName = buildPath;
    buildPlayerOptions.scenes = GetListOfScenes();
    buildPlayerOptions.target = BuildTarget.WebGL;

    // Refresh assets
    if (Application.isBatchMode)
    {
        AssetDatabase.Refresh();
    }

    BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
    BuildSummary summary = report.summary;

    if (summary.result == BuildResult.Succeeded)
    {
        Debug.Log($"{buildName} succeeded: {summary.totalSize} bytes");
    }

    if (summary.result == BuildResult.Failed)
    {
        Debug.LogError($"{buildName} failed");
    }
}
Created by Konstantin Yavichev. Copyright 2025.