…hm…
No, it’s not available at the time of writing.
Nope, but you can just programmatically build your project using the BuildPipeline:
Then through whatever means you want, you can trigger this (menu entry, button in a gui window, whatever). And you’re hook to before is just whatever lines you insert before calling BuildPlayer.
I do it as a ScriptableObject asset, and generalized a lot of the build process so I can create configurations and save them as assets:
I can even inherit from it to make project specific tweaks, like this one:
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;
}
}
}
It visually looks like this:
I even have a ‘build build’ process to group builds together:
I use this to build multiple versions of the game and upload them in one click. Here is me building win/linux/macos and uploading for steam and itch.io:
…
As to your hook, if you wanted to use something similar to this.
You could insert a static event that gets trigger.
Or you could even use reflection, search through all the static methods out in the assembly, check for a custom attribute of your liking. And call that method. Completely emulating the PostProcessBuildAttribute.

