How to execute a DOTS build from command line?

I want to execute a build for my DOTS project from command line but I’m unsure how to do it correctly.
Anyone know how to invoke a DOTS build from command line?
I don’t want to accidentally invoke the regular build pipeline since it is not supported.

UPDATE: I found this post [RESOLVED] Standalone builds (from command line) have incorrect lighting
Instructions are there.

For completeness, here is the build script I stole from that post and tweaked.

There are 2 ways to invoke it,

  1. Through the Editor top level menu that is created via the [MenuItem] attributes below
  2. Through command line, just by invoking the Unity Editor as you normally would from command line and passing a ‘-executeMethod’ argument, e.g. I have a little Python script

import os
cmd = “”"
“C:\Program Files\Unity\Hub\Editor\2020.3.13f1\Editor\Unity.exe”
-batchmode
-quit
-logFile build.log
-projectPath ./
-executeMethod Krooq.Framework.Editor.Builder.Build
-platform Windows
“”"
os.system(cmd)

The important parts are

  • You need Unity.Build package referenced in your assembly
  • This is an Editor script, i.e. it wont be included in your build
  • I’m using Netcode, so it creates 2 instances for me to test client and server, tweak this as you wish in the menu code
  • I’m using Addressables so the build does that as well, again, tweak as you wish
  • I also handle window position using a custom command line arg passed to the game as you would for any other program (code for that was ripped from the DOTS Sample)
  • You will need to replace the constants with things specific to your game e.g. _buildAssetsDirectory is the directory for your “Build Configuration” assets.

Hope this helps someone, it’s always a pain setting up build tooling.

#if UNITY_EDITOR
using System;
using System.Diagnostics;
using System.Linq;
using Unity.Build;
using UnityEditor;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
using Debug = UnityEngine.Debug;

namespace Krooq.Framework.Editor
{

    public static class Builder
    {
        private const string _buildAssetsDirectory = "Assets/Build";

        [MenuItem("Build/[Windows] Build ")]
        public static void BuildForWindows() => BuildForPlatform(Platform.Windows);
    
        [MenuItem("Build/[Windows] Run")]
        public static void Run()
        {
            // We run 2 instances of the game, one for a host (who is also a "local" client) and one for a "remote" client.
            var exe = $"{Environment.CurrentDirectory}/Builds/DOTS Game/DOTS Game.exe";
            var hostLog = $"{Application.persistentDataPath}/Host.log";
            var clientLog = $"{Application.persistentDataPath}/Client.log";
            Process.Start(exe, $"-windowpos 0,0 -logFile \"{hostLog}\"");
            Process.Start(exe, $"-windowpos 1920,0 -logFile \"{clientLog}\"");
        
            Process.Start("Code", $"\"{Application.persistentDataPath}/{Application.productName}.code-workspace\"");
        }
    
        [MenuItem("Build/[Windows] Build and Run")]
        public static void BuildAndRunForWindows()
        {
            BuildForWindows();
            Run();
        }

        public static void Build()
        {
            var platformArg = CommandLineUtil.GetArg("-platform");
            var platform = Platform.GetPlatformByName(platformArg);
            if (platform != null) BuildForPlatform(platform);
        }
    
        private static void BuildForPlatform(Platform platform)
        {
            try
            {
                var buildConfiguration = GetBuildConfiguration(platform);
                BuildAddressableAssets();
                BuildPlayer(buildConfiguration);
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
            }
        }

        private static void BuildAddressableAssets()
        {
            AddressableAssetSettings.BuildPlayerContent();
        }

        private static BuildConfiguration GetBuildConfiguration(Platform platform)
        {
            // Check whether the target platform can be build on this machine.
            var compatible = false;
            switch (platform.Name)
            {
                case KnownPlatforms.Linux.Name:
                    if (Application.platform == RuntimePlatform.LinuxEditor || Application.platform == RuntimePlatform.WindowsEditor)
                        compatible = true;
                    break;
                case KnownPlatforms.macOS.Name:
                    if (Application.platform == RuntimePlatform.OSXEditor)
                        compatible = true;
                    break;
                case KnownPlatforms.Windows.Name:
                case KnownPlatforms.Switch.Name:
                    if (Application.platform == RuntimePlatform.WindowsEditor)
                        compatible = true;
                    break;
            }
            if (!compatible)
                throw new Exception($"Cannot build the player for platform {platform} on this machine");
            // Find the platform-specific build configuration asset.
            var assetGuids = AssetDatabase.FindAssets($"t:BuildConfiguration {platform.DisplayName}" , new [] { _buildAssetsDirectory});
            if (assetGuids.Length < 1) throw new Exception($"Platform {platform} BuildConfiguration not found.");
            if (assetGuids.Length > 1) throw new Exception($"Multiple BuildConfigurations found for platform {platform} {assetGuids.Select(AssetDatabase.GUIDToAssetPath).ToList().ToString()}");
            // Load the build configuration asset.
            var assetPath = AssetDatabase.GUIDToAssetPath(assetGuids[0]);
            var buildConfiguration = AssetDatabase.LoadAssetAtPath<BuildConfiguration>(assetPath);
            if (!buildConfiguration.CanBuild()) throw new Exception($"Cannot build the player for platform {platform} on this machine");
            return buildConfiguration;
        }
        private static void BuildPlayer(BuildConfiguration buildConfiguration)
        {
            var result = buildConfiguration.Build();
            result.LogResult();
        }
    }
}
#endif

@krooq thank you for this script. You’ve helped me solve a problem I was quite stuck on :slight_smile: