How do you manage build settings across multiple platforms?

So this last week we wrapped up our first Steam release!

When learning the process of uploading to Steam I decided I wanted to add one last part to the way I manage my build settings. And that was a one-click deploy that built every version of the game (windows & linux) and then uploaded them to Steam.

Over the last couple years working with Unity one of the biggest things that annoyed me is the way it handles build/input settings. With a lot of data spread across the editor application. I’ve gone and written several tools to stream line it, but it often leaves me wondering…

What do other people do?

Like I’m willing to bet many of you have written your own scripts for this. But there’s so many people that use Unity who may not necessarily be that skilled in that matter, and I find it chilling how they may be left dealing with the vanilla unity editor’s build process.

TLDR; This Post’s Intent
So I wanted to come here and share what I’ve done to streamline my build process. Catch a glimpse of how other people deal with their own build process. And maybe get some feedback on some things I might be missing from my process.

In the spoiler below I’m including how I deal with my build process.

Input Settings
So early on the first thing I ran into that annoyed me was Input Settings. There’s no way to configure input settings on a per platform basis as far as I know! And well controllers on 1 platform may be mapped different then on other platforms.

Now I’ve since written a newer input system that works more like many of the 3rd party options like cInput and Rewired… but I wrote this before doing that and I find it super useful. I still even use it with my new system, just not for platform specific settings, but instead to easily configure my input settings to work with my newer system.

Essentially I have a ScriptableObject (editor only) that houses all the input settings, and buttons to sync them to the global input settings.
spacepuppy-unity-framework-3.0/SpacepuppyUnityFrameworkEditor/Settings/InputSettings.cs at master · lordofduct/spacepuppy-unity-framework-3.0 · GitHub

Build Settings
After that I started wanting easy ways to configure per platform build settings. Something where I can define:
FileName,
BuildDirectory,
Version,
Boot Scene,
Other Scenes,
Build Target,
Build Options,
Define Symbols,
Input Settings

The result is this:
spacepuppy-unity-framework-3.0/SpacepuppyUnityFrameworkEditor/Settings/BuildSettings.cs at master · lordofduct/spacepuppy-unity-framework-3.0 · GitHub
3575196--288634--Build_BuildSettings01.png

Of course, I can expand upon it. For example in my current game I’ve added some stuff specific to my game:
Distribution Platform (the target platform like steam/xbox/generic/etc)
Episode Settings (The game is episodic, so these are settings for that specific episodes configuration)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

using com.spacepuppy;
using com.spacepuppy.Collections;
using com.spacepuppy.Project;
using com.spacepuppyeditor;

using com.mansion;

namespace com.mansioneditor
{

    [CreateAssetMenu(fileName = "EpisodeBuildSettings", menuName = "Spacepuppy Build Pipeline/Episode Build Settings")]
    public class EpisodeBuildSettings : BuildSettings
    {

        #region Fields

        [SerializeField]
        private DistributionPlatform _distributionPlatform;
        [SerializeField]
        private EpisodeSettings _episodeSettings;
        [SerializeField]
        private string _resourcesFolderName;

        #endregion

        #region Properties

        public DistributionPlatform DistributionPlatform
        {
            get { return _distributionPlatform; }
            set { _distributionPlatform = value; }
        }

        public EpisodeSettings EpisodeSettings
        {
            get { return _episodeSettings; }
            set { _episodeSettings = value; }
        }

        public string ResourcesFolderName
        {
            get { return _resourcesFolderName; }
            set { _resourcesFolderName = value; }
        }

        #endregion

        #region Methods

        public override string[] GetScenePaths()
        {
            using (var lst = TempCollection.GetList<string>())
            {
                if (this.BootScene != null) lst.Add(AssetDatabase.GetAssetPath(this.BootScene));

                foreach (var scene in this.Scenes)
                {
                    lst.Add(AssetDatabase.GetAssetPath(scene));
                }

                if (this.EpisodeSettings != null && this.EpisodeSettings.TitleScreen.SceneAsset != null)
                {
                    var path = AssetDatabase.GetAssetPath(this.EpisodeSettings.TitleScreen.SceneAsset);
                    //if (lst.Contains(path)) lst.Remove(path);
                    //lst.Insert(1, path);
                    if (!lst.Contains(path))
                        lst.Add(path);
                }

                return lst.ToArray();
            }
        }

     

        #endregion

    }

    [CustomEditor(typeof(EpisodeBuildSettings))]
    public class EpisodeBuildSettingsEditor : BuildSettingsEditor
    {

        public const string PROP_DISTPLATFORM = "_distributionPlatform";
        public const string PROP_EPISODESETTINGS = "_episodeSettings";
        public const string PROP_RESOURCESFOLDERNAME = "_resourcesFolderName";

        public override void DrawBuildOptions()
        {
            base.DrawBuildOptions();

            this.DrawPropertyField(PROP_DISTPLATFORM);
            this.DrawPropertyField(PROP_EPISODESETTINGS);
            this.DrawPropertyField(PROP_RESOURCESFOLDERNAME);
        }



        public override void SyncToGlobalBuild()
        {
            var settings = this.target as EpisodeBuildSettings;
            var gameSettings = AssetDatabase.LoadAssetAtPath<Game>("Assets/Resources/GameSettings.asset");
            if (gameSettings != null)
            {
                Undo.RecordObject(gameSettings, "Sync Build Settings to GameSettings");
                gameSettings.SetEpisodeSettings(settings.EpisodeSettings);
            }

            base.SyncToGlobalBuild();
        }


        protected override IEnumerator DoBuild(BuildSettings.PostBuildOption postBuildOption)
        {
            var settings = this.target as EpisodeBuildSettings;
         
            if(!string.IsNullOrEmpty(settings.ResourcesFolderName))
            {
                var spath = @"Assets/Build/" + settings.ResourcesFolderName;
                AssetHelper.MoveFolder(spath, @"Assets/Build/Resources");
                AssetDatabase.Refresh();
            }
         
            var gameSettings = AssetDatabase.LoadAssetAtPath<Game>(@"Assets/Resources/GameSettings.asset");
            EpisodeSettings backupEpSettings = null;
            DistributionPlatform backupPlatform = DistributionPlatform.Generic;
            if (gameSettings != null)
            {
                backupEpSettings = gameSettings.GetEpisodeSettings();
                backupPlatform = gameSettings.Platform;

                gameSettings.SetEpisodeSettings(settings.EpisodeSettings);
                gameSettings.Platform = settings.DistributionPlatform;
            }

            try
            {
                settings.Build(postBuildOption);
            }
            finally
            {
                if (gameSettings != null)
                {
                    gameSettings.SetEpisodeSettings(backupEpSettings);
                    gameSettings.Platform = backupPlatform;
                }

                if (!string.IsNullOrEmpty(settings.ResourcesFolderName))
                {
                    AssetHelper.MoveFolder("Assets/Build/Resources", "Assets/Build/" + settings.ResourcesFolderName);
                    AssetDatabase.Refresh();
                }
            }
         
            yield break;
        }


    }

}

Bulk Build Settings
And lastly ‘Bulk Build Settings’. This allows me to select multiple BuildSettings to get built, as well as some batch files to run when complete building.
spacepuppy-unity-framework-3.0/SpacepuppyUnityFrameworkEditor/Settings/BulkBuildSettings.cs at master · lordofduct/spacepuppy-unity-framework-3.0 · GitHub
3575196--288636--Build_BulkSettings.png

The batch files that run at the end of mine are the deploy scripts that upload the steam release and my itch releases using ‘steamcmd’ and ‘butler’ programs respectively.

And now I have a asset folder with all my build settings in a simple place to find. And the bulk settings give me a one-click deploy.
3575196--288637--Build_BuildFolder.png

So yeah, this isn’t really a question thread.

More or less just a discussion thread.

Would love to hear how y’all deal with the problem? If you consider it a problem? Maybe get influenced by your methodologies as well.

Nice one! This is an area that I feel is still a bit lacking in Unity. I wrote a tool called Hdg Build that I sell on the asset store to manage build configurations for different platforms (defines, scenes). I wrote it for my own use but found it so useful that I thought I’d try selling it. I’ll probably add to it as I find things that I need.

Seeing how you’ve done yours has already given me some ideas! Currently I don’t specify the build output location and instead require the user to either select it if they hit build in the editor or pass it in to the build method if they are calling Hdg Build from their own pipeline. But I think it makes way more sense to just save it as part of the build config.

I haven’t put in any support for post-build stuff because I figure people can just use their own post-build methods to do that. For my own game, my CI server runs a python script to run the build and I do a lot of the post-build type stuff in there, because I have a way for it to show up as a separate build step on my CI server. Otherwise my “Unity build” build step would just be massive.

Sam

1 Like

I currently use platform specific preprocessor directives and store the settings in a script. It’s horribly brittle. Would not recommend.