Last active
May 14, 2018 17:38
-
-
Save 5argon/51411b2afb60f1f438ead33b99247d78 to your computer and use it in GitHub Desktop.
A dockable build switcher and defines manager. Edit `Defines` and `AllBuilds` in the code to fit your project.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// OdinBuildSwitcher | |
/// 5argon - Exceed7 Experiments | |
/// http://exceed7.com , [email protected] | |
using UnityEditor; | |
using UnityEngine; | |
using UnityEngine.SceneManagement; | |
using System.Collections.Generic; | |
using System.Linq; | |
using Sirenix.OdinInspector; | |
using Sirenix.Utilities; | |
using Sirenix.OdinInspector.Editor; | |
using Sirenix.Utilities.Editor; | |
public class OdinBuildSwitcher : PreSetupOdinEditorWindow<OdinBuildSwitcher> | |
{ | |
#region Your configurations | |
/// <summary> | |
/// All of your game's custom defines here. Please type the enum as if they are preprocessor strings. | |
/// </summary> | |
[System.Flags] | |
public enum Defines | |
{ | |
LOG_NETWORK = 1 << 1, | |
CHEAT_BUILD = 1 << 2, | |
STRIP_CODE = 1 << 3, | |
TESTING = 1 << 4 | |
}; | |
/// <summary> | |
/// Your desired platform presets here. They must be in pairs of BuildTargetGroup and BuildTarget used for Apply buttons, and optinally build path for that preset + any numbers of BuildOptions used for Build buttons. | |
/// </summary> | |
private static Dictionary<string, BuildConfiguration> AllBuilds = new Dictionary<string, BuildConfiguration> | |
{ | |
["Android"] = new BuildConfiguration(BuildTargetGroup.Android, BuildTarget.Android, "./AndroidBuild/AndroidBuild.apk", BuildOptions.AutoRunPlayer, BuildOptions.StrictMode, BuildOptions.Development), | |
["iOS"] = new BuildConfiguration(BuildTargetGroup.iOS, BuildTarget.iOS), | |
["Standalone"] = new BuildConfiguration(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows) | |
}; | |
#endregion | |
#region Odin | |
[EnumToggleButtons, LabelText("Select Defines")] | |
public Defines selectedDefines; | |
[ValueDropdown("AllBuildKeys"), LabelText("Select Build")] | |
public string selectedBuildKey; | |
[Button(ButtonSizes.Medium), GUIColor(1.0f, 0.4f, 0.4f)] | |
public void Apply() => Apply(selectedBuildKey, selectedDefines); | |
[Button(ButtonSizes.Medium), GUIColor(1.0f, 0.9f, 0.5f), PropertyTooltip("Will switch to selected preset and defines, build, and switch everything back. You still have to wait for a project recompilation afterwards but at least you don't have to babysit the build just to change everything back when it finishes.")] | |
public void BuildWithSelectedSettings() => ApplyBuildRevert(selectedBuildKey, selectedDefines); | |
[Title("")] | |
[ShowInInspector, ListDrawerSettings(Expanded = true), PropertyOrder(10)] | |
public static string[] CurrentDefines => PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup).Trim().Split(';'); | |
[ShowInInspector, HorizontalGroup("BuildTarget", Width = 0.66f), LabelText("Current Build Target"), PropertyOrder(11)] | |
public static BuildTargetGroup CurrentBuildTargetGroup => EditorUserBuildSettings.selectedBuildTargetGroup; | |
[ShowInInspector, HorizontalGroup("BuildTarget", Width = 0.33f), HideLabel, PropertyOrder(12)] | |
public static BuildTarget CurrentBuildTarget => EditorUserBuildSettings.activeBuildTarget; | |
#endregion | |
protected override string Title => "Builds"; | |
protected override EditorIcon Icon => EditorIcons.GridBlocks; | |
[MenuItem("Window/Your Game/Build Switcher")] | |
private static void Open() => OpenWindow(); | |
private struct BuildConfiguration | |
{ | |
public BuildTargetGroup buildTargetGroup { get; } | |
public BuildTarget buildTarget { get; } | |
public string buildPath { get; } | |
public BuildOptions[] buildOptions { get; } | |
/// <param name="buildPath">Default is your previous build location you manually build before.</param> | |
public BuildConfiguration(BuildTargetGroup buildTargetGroup, BuildTarget buildTarget, string buildPath = default(string), params BuildOptions[] buildOptions) | |
{ | |
this.buildTargetGroup = buildTargetGroup; | |
this.buildTarget = buildTarget; | |
this.buildPath = buildPath; | |
this.buildOptions = buildOptions; | |
} | |
} | |
private static IEnumerable<string> AllPossibleDefines => System.Enum.GetNames(typeof(Defines)); | |
private static IEnumerable<string> AllBuildKeys => AllBuilds.Keys; | |
private static BuildTargetGroup TargetGroup(string buildKey) => AllBuilds[buildKey].buildTargetGroup; | |
private static BuildTarget Target(string buildKey) => AllBuilds[buildKey].buildTarget; | |
private static string BuildPath(string buildKey) => AllBuilds[buildKey].buildPath; | |
private static IEnumerable<BuildOptions> GetBuildOptions(string buildKey) => AllBuilds[buildKey].buildOptions; | |
/// <param name="withDefines">Use bitwise OR to merge multiple defines</param> | |
public static void Apply(string buildKey, Defines withDefines) | |
{ | |
Debug.Log("Changing defines..."); | |
List<string> currentDefines = CurrentDefines.ToList(); | |
List<string> newDefines = new List<string>(); | |
foreach (string oneOfCurrentDefines in currentDefines) | |
{ | |
if (!AllPossibleDefines.Contains(oneOfCurrentDefines)) | |
{ | |
//Don't touch other defines we don't know about | |
newDefines.Add(oneOfCurrentDefines); | |
} | |
} | |
//Add new defines | |
foreach (Defines d in System.Enum.GetValues(typeof(Defines))) | |
{ | |
if ((d & withDefines) != 0) | |
{ | |
newDefines.Add(d.ToString()); | |
} | |
} | |
PlayerSettings.SetScriptingDefineSymbolsForGroup(TargetGroup(buildKey), string.Join(";", newDefines)); | |
Debug.Log("Switching build target..."); | |
bool switchingResult = EditorUserBuildSettings.SwitchActiveBuildTargetAsync(TargetGroup(buildKey), Target(buildKey)); | |
Debug.Log(switchingResult ? $"Switched to {Target(buildKey)}." : $"Switching to {Target(buildKey)} failed!"); | |
} | |
/// <param name="withDefines">Use bitwise OR to merge multiple defines</param> | |
public static void ApplyBuildRevert(string buildKey, Defines buildWithDefines) | |
{ | |
string[] rememberDefines = CurrentDefines; | |
BuildTarget rememberBuildTarget = CurrentBuildTarget; | |
BuildTargetGroup rememberBuildTargetGroup = CurrentBuildTargetGroup; | |
Apply(buildKey, buildWithDefines); | |
BuildWithCurrentDefines(buildKey); | |
EditorUserBuildSettings.SwitchActiveBuildTargetAsync(rememberBuildTargetGroup, rememberBuildTarget); | |
PlayerSettings.SetScriptingDefineSymbolsForGroup(rememberBuildTargetGroup, string.Join(";", rememberDefines)); | |
} | |
/// <summary> | |
/// You can call this from somewhere else as you like. | |
/// </summary> | |
public static void BuildWithCurrentDefines(string buildKey) | |
{ | |
Debug.Log("Building..."); | |
BuildPlayerOptions bpo = new BuildPlayerOptions(); | |
bpo.scenes = EditorBuildSettings.scenes.Select(scene => scene.path).ToArray(); | |
bpo.targetGroup = TargetGroup(buildKey); | |
bpo.target = Target(buildKey); | |
if(BuildPath(buildKey) == default(string)) | |
{ | |
bpo.locationPathName = EditorUserBuildSettings.GetBuildLocation(Target(buildKey)); | |
} | |
else | |
{ | |
bpo.locationPathName = BuildPath(buildKey); | |
} | |
bpo.options = BuildOptions.None; | |
foreach (BuildOptions bo in GetBuildOptions(buildKey)) | |
{ | |
bpo.options |= bo; | |
} | |
var report = BuildPipeline.BuildPlayer(bpo); | |
Debug.Log($"[{report.summary.buildStartedAt}~{report.summary.buildEndedAt}] Build result : {report.summary.result}, Output : {report.summary.outputPath}"); | |
} | |
} | |
/// <summary> | |
/// Gives a good template for adding title and icon that persists. | |
/// After subclassing, your class can just call base.OpenWindow() on the code with [MenuItem] | |
/// </summary> | |
public abstract class PreSetupOdinEditorWindow<T> : OdinEditorWindow where T : PreSetupOdinEditorWindow<T> | |
{ | |
protected abstract EditorIcon Icon { get; } | |
protected abstract string Title { get; } | |
private static T window; | |
protected static void OpenWindow() | |
{ | |
window = GetWindow<T>(); | |
window.position = GUIHelper.GetEditorWindowRect().AlignCenter(700, 700); | |
} | |
protected override void OnEnable() | |
{ | |
window = GetWindow<T>("", false); | |
RegisterRefreshTitle(); | |
base.OnEnable(); | |
} | |
private static void RefreshTitle() | |
{ | |
window.titleContent = new GUIContent(window.Title, window.Icon?.Active); | |
} | |
private static void RegisterRefreshTitle() | |
{ | |
RefreshTitle(); | |
UnityEditor.SceneManagement.EditorSceneManager.activeSceneChangedInEditMode -= RefreshTitle; | |
UnityEditor.SceneManagement.EditorSceneManager.activeSceneChangedInEditMode += RefreshTitle; | |
} | |
private static void RefreshTitle(Scene from, Scene to) => RefreshTitle(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment