Álvaro Bermejo

Posts

Automating unity builds ⅠⅠ, calling unity from the command line

2021-02-02

{: .scale_media} Building like it's 1988. {: .scale_media}

If you haven´t read the previous post, do it in order to get familiar with taskfile!

An essential part of automating our builds is using the command line interface. Instead of clicking a button on the Unity Editor, we will call it from the shell.

Finding the executable

In order to call unity, you need to find where the executable is.

The location will depend on your system.

In windows (from WSL) it´s at /mnt/c/Program Files/Unity/Hub/Editor/2019.4.1f1/Editor/Unity.exe, where the specific version will depend on your installed unity.

In any case, for the rest of the blog, we will store that location on a variable, so we will work with that.

Building for windows

Let´s get right into it! The Taskfile.yml on your project directory should look like this.


version: 3

env:
  UNITY: /mnt/c/Program\ Files/Unity/Hub/Editor/2020.1.16f1/Editor/Unity.exe

tasks:
  build-windows:
    cmds:
      - >
        {{.UNITY}}
        -batchmode
        -projectPath .
        -buildWindows64Player Builds/Win/game.exe
        -quit

Congratulations! Now typing task build-windows from the shell should automatically kick a build for windows.

If this fails, it can be for a myriad of reasons. Unity doesn't allow opening the project multiple times, so if you are building from the shell you need to close the editor. Also remember to have the build module for the platform you intend on building.

We are simply calling Unity, telling it to run without manual interaction (-batchmode), open the project on the current directory (-projectPath), build for windows (-buildWindows64Player), and quitting when finished (-quit). Read the unity docs for more information on specific arguments and their effects.

What happens if something goes wrong? In order to debug any issues, we need the output log. Unfortunately in windows we can´t simply pipe the output to the console, so use -logFile Logs/Build.log and open it up in your favorite editor.

Pushing to itch

Now you probably have created a bunch of tasks that builds for every single specific platform you need. However we can go even further. We should automate the uploading part. Let's use butler to automatically push our builds to itch.

Remember, your path to Butler and itch url will be different!


version: 3

env:
  UNITY: /mnt/c/Program\ Files/Unity/Hub/Editor/2020.1.16f1/Editor/Unity.exe
  BUTLER: /mnt/c/Users/AlvarBer/AppData/Roaming/itch/apps/butler/butler.exe
  ITCH_URL: user/game-name

tasks:
  build-windows:
    cmds:
      - >
        {{.UNITY}}
        -batchmode
        -buildWindows64Player Builds/Win/game.exe
        -quit
  push-windows:
    deps:
      - task: build-windows
    cmds:
      - {{.BUTLER}} push Builds/Win {{.ITCH_URL}}:Win

And that's it! typing task build-windows will automatically build your game, and if it doesn't fail, will push to itch.

Passing information to the build

Let´s say we want want to more than just building our game. Such as putting the version number on the corner of the game, run tests, or anything we can imagine at build time.

We need to call a custom C# method, from which we will do what we want.

Before that, we need to create a C# file inside an Editor folder. We will call it Build.cs.

using UnityEditor;
using UnityEngine;
using System;
using static System.Environment;
using System.IO;
using System.Linq;

public static class Build {
  public static void BuildWin() {
    PlayerSettings.SetScriptingBackend(BuildTargetGroup.Standalone, ScriptingImplementation.Mono2x);

    BuildPlayerOptions options = new BuildPlayerOptions {
      locationPathName = $"Builds/Win/{PlayerSettings.productName}-win.exe",
      scenes = GetEnabledScenes(),
      options = BuildOptions.StrictMode,
      target = BuildTarget.StandaloneWindows64,
    };

    PlayerSettings.bundleVersion = GetArgument("buildNumber") ?? PlayerSettings.bundleVersion;

    BuildPipeline.BuildPlayer(options);
  }

  private static string GetArgument(string name) {
    string[] args = GetCommandLineArgs();
    for (int i = 0; i < args.Length; i++) {
      if (args[i].Contains(name)) {
        return args[i + 1];
      }
    }
    return null;
  }

  private static string[] GetEnabledScenes() {
    return EditorBuildSettings.scenes
      .Where(scene => scene.enabled && !string.IsNullOrEmpty(scene.path))
      .Select(scene => scene.path)
      .ToArray();
  }
}

This will do the same thing we did previously, only difference is it also takes a -buildNumber argument that will set the bundleVersion.

Here's how we call this method.


version: 3

env:
  UNITY: /mnt/c/Program\ Files/Unity/Hub/Editor/2020.1.16f1/Editor/Unity.exe
  BUTLER: /mnt/c/Users/AlvarBer/AppData/Roaming/itch/apps/butler/butler.exe
  ITCH_URL: user/game-name

tasks:
  build-windows:
    cmds:
      - >
        {{.UNITY}}
        -batchmode
        -executeMethod Build.BuildWin
        -buildNumber 0.0.4
        -quit
  push-windows:
    deps:
      - task: build-windows
    cmds:
      - {{.BUTLER}} push Builds/Win {{.ITCH_URL}}:Win

Taskfile.yaml

It does require a tiny more boilerplate in the form of C# code, but once we have scripting access to the build pipeline, we can do anything imaginable. It's a small price to pay for all the flexibility it gives.

What's next?

This whole thing has been great, but building on your local machine isn't all that different from doing it manually, so we will see how to run it on a CI server.

So stay tuned for the next tutorial, if you have any suggestions, feedback, or commentary, hit me up at @alvarber.