Skip to content

Instantly share code, notes, and snippets.

@CSaratakij
Last active September 30, 2024 07:15
Show Gist options
  • Save CSaratakij/bf6e7eb0108e963c59c833ad5a9f7a0a to your computer and use it in GitHub Desktop.
Save CSaratakij/bf6e7eb0108e963c59c833ad5a9f7a0a to your computer and use it in GitHub Desktop.
Quick change unity player setting build type from Mono to IL2CPP menu item "Help/Quick change build type/..."
/*
Note : adhocs for unity 2022.3.31
TODO : Need to Support Unity 6 with the new callback : https://docs.unity3d.com/6000.0/Documentation/ScriptReference/EditorApplication.UpdateMainWindowTitle.html
and the new force update window title method : https://docs.unity3d.com/6000.0/Documentation/ScriptReference/EditorApplication.UpdateMainWindowTitle.html
*/
#if UNITY_2022_3_31
#define EDITOR_ALLOW_BUILDTYPE_STATUS
#endif
#if UNITY_EDITOR && UNITY_EDITOR_WIN && EDITOR_ALLOW_BUILDTYPE_STATUS
using System;
using System.Reflection;
using UnityEditor;
namespace Game.EditorUtility
{
internal static class BuildTypeEditorWindowTitleStatus
{
internal static event Action<string> OnInternalEditorUpdateMainWindowTitle;
internal static event Action<bool> OnEditorApplicationFocusChanged;
private static bool isPreviousFocus = false;
private static bool isCurrentFocus = false;
internal static string CurrentCustomEditorWindowTitle = "";
private static string CurrentInternalEditorWindowTitle = "";
private static object CurrentInternalApplicationTitleDescriptor = null;
private static Delegate updateMainWindowTitleEventHandler = null;
internal static bool IsInternalApplicationTitleDescriptorDirty => (CurrentInternalApplicationTitleDescriptor == null);
[InitializeOnLoadMethod]
private static void Initialize()
{
Reset();
try
{
EventInfo eventInfo = GetUpdateMainWindowTitleEvent(out var bindingFlags);
if (eventInfo != null)
{
MethodInfo callbackMethodInfo = typeof(BuildTypeEditorWindowTitleStatus).GetMethod(nameof(OnUpdateMainWindowTitleHandler), BindingFlags.Static | BindingFlags.NonPublic);
updateMainWindowTitleEventHandler = Delegate.CreateDelegate(eventInfo.EventHandlerType, callbackMethodInfo, throwOnBindFailure: true);
MethodInfo addEventHandlerMethodInfo = eventInfo.GetAddMethod(nonPublic: true);
addEventHandlerMethodInfo.Invoke(null, new object[] { updateMainWindowTitleEventHandler });
}
else
{
throw new Exception($"{nameof(BuildTypeEditorWindowTitleStatus)}: target event 'updateMainWindowTitle' not found in this unity version");
}
isPreviousFocus = isCurrentFocus;
isCurrentFocus = EditorApplication.isFocused;
EditorApplication.focusChanged += OnEditorApplicationFocusChangedHandler;
EditorApplication.playModeStateChanged += OnPlayModeStateChange;
QuickChangeBuildTypeMenu.OnBuildTypeChanged += OnBuildTypeChanged;
}
catch (Exception e)
{
UnityEngine.Debug.LogError(e);
}
void Reset()
{
isPreviousFocus = false;
isCurrentFocus = false;
CurrentInternalApplicationTitleDescriptor = null;
}
}
private static void OnEditorApplicationFocusChangedHandler(bool isFocused)
{
isPreviousFocus = isCurrentFocus;
isCurrentFocus = isFocused;
bool isFocusDirty = (isPreviousFocus != isCurrentFocus);
if (isFocusDirty)
{
isPreviousFocus = isCurrentFocus;
ForceBuildInternalMainWindowTitle();
}
OnEditorApplicationFocusChanged?.Invoke(isFocused);
}
private static void OnPlayModeStateChange(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredEditMode)
{
ForceBuildInternalMainWindowTitle();
}
}
private static void OnBuildTypeChanged()
{
ForceBuildInternalMainWindowTitle();
}
// Note : can only update window title in the exact reference of application title descriptor in this callback only
private static void OnUpdateMainWindowTitleHandler(object descriptor)
{
BuildType? buildType = GetCurrentBuildType();
if (buildType != null)
{
UpdateWindowTitle(descriptor, buildType.Value);
}
}
private static void UpdateWindowTitle(object descriptor, BuildType buildType)
{
try
{
if (descriptor == null)
{
return;
}
FieldInfo editorWindowTitleFieldInfo = descriptor.GetType()?.GetField("title");
if (editorWindowTitleFieldInfo == null)
{
return;
}
string internalWindowTitle = (editorWindowTitleFieldInfo.GetValue(descriptor) as string);
string customWindowTitle = $"{internalWindowTitle} <{buildType}>";
CurrentInternalApplicationTitleDescriptor = descriptor;
CurrentInternalEditorWindowTitle = internalWindowTitle;
CurrentCustomEditorWindowTitle = customWindowTitle;
editorWindowTitleFieldInfo.SetValue(descriptor, customWindowTitle);
OnInternalEditorUpdateMainWindowTitle?.Invoke(customWindowTitle);
}
catch (Exception e)
{
UnityEngine.Debug.LogError(e);
}
}
private static bool ForceBuildInternalMainWindowTitle()
{
try
{
MethodInfo methodInfo_BuildMainWindowTitle = typeof(EditorApplication).GetMethod("BuildMainWindowTitle", BindingFlags.Static | BindingFlags.NonPublic);
MethodInfo methodInfo_UpdateMainWindowTitle = typeof(EditorApplication).GetMethod("UpdateMainWindowTitle", BindingFlags.Static | BindingFlags.NonPublic);
bool isValid = (methodInfo_BuildMainWindowTitle != null) && (methodInfo_UpdateMainWindowTitle != null);
if (!isValid)
{
return false;
}
// Hacks : force build internal main window title to get the lastest application title descriptor
if (IsInternalApplicationTitleDescriptorDirty)
{
methodInfo_BuildMainWindowTitle?.Invoke(null, null);
}
// Hacks : make sure window title gui update from application title descriptor
methodInfo_UpdateMainWindowTitle?.Invoke(null, null);
return true;
}
catch (Exception e)
{
UnityEngine.Debug.LogError(e);
return false;
}
}
internal static void ForceUpdateMainWindowTitle(object reflection_ApplicationTitleDescriptor)
{
try
{
EventInfo eventInfo = GetUpdateMainWindowTitleEvent(out var bindingFlags);
if (eventInfo == null)
{
return;
}
FieldInfo fieldInfo = typeof(EditorApplication).GetField(eventInfo.Name, bindingFlags);
if (fieldInfo != null)
{
var eventDelegate = (fieldInfo.GetValue(null) as Delegate);
eventDelegate?.DynamicInvoke(new object[] { reflection_ApplicationTitleDescriptor });
}
}
catch (Exception e)
{
UnityEngine.Debug.LogError(e);
}
}
private static EventInfo GetUpdateMainWindowTitleEvent(out BindingFlags bindingFlags)
{
bindingFlags = BindingFlags.Static | BindingFlags.NonPublic;
try
{
EventInfo targetEvent = typeof(EditorApplication).GetEvent("updateMainWindowTitle", bindingFlags);
return targetEvent;
}
catch (Exception e)
{
UnityEngine.Debug.LogError(e);
return null;
}
}
private static BuildType? GetCurrentBuildType()
{
BuildTargetGroup currentBuildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
ScriptingImplementation currentScriptingImplementation = PlayerSettings.GetScriptingBackend(currentBuildTargetGroup);
return currentScriptingImplementation switch
{
ScriptingImplementation.Mono2x => BuildType.Mono,
ScriptingImplementation.IL2CPP => BuildType.IL2CPP,
_ => null
};
}
}
}
#endif
using System;
using UnityEditor;
namespace Game.EditorUtility
{
public enum BuildType
{
Mono,
IL2CPP
}
public static class QuickChangeBuildTypeMenu
{
public static event Action OnBuildTypeChanged;
private const int MENU_ITEM_PRIORITY = -1;
private const string PATH_MENU_MONO = "Help/Quick change build type/Mono";
private const string PATH_MENU_IL2CPP = "Help/Quick change build type/IL2CPP";
[MenuItem(PATH_MENU_MONO, true)]
private static bool ValidateChangeToMonoBuildMenu()
{
if (EditorApplication.isPlaying)
{
return false;
}
bool shouldCheckedMenu = IsUseCurrentBuildType(BuildType.Mono);
Menu.SetChecked(PATH_MENU_MONO, isChecked: shouldCheckedMenu);
return true;
}
[MenuItem(PATH_MENU_IL2CPP, true)]
private static bool ValidateChangeToIL2CPPBuildMenu()
{
if (EditorApplication.isPlaying)
{
return false;
}
bool shouldCheckedMenu = IsUseCurrentBuildType(BuildType.IL2CPP);
Menu.SetChecked(PATH_MENU_IL2CPP, isChecked: shouldCheckedMenu);
return true;
}
[MenuItem(PATH_MENU_MONO, false, MENU_ITEM_PRIORITY)]
private static void ChangeToMonoBuildMenu()
{
ChangeBuildType(BuildType.Mono);
}
[MenuItem(PATH_MENU_IL2CPP, false, MENU_ITEM_PRIORITY)]
private static void ChangeToIL2CPPBuildMenu()
{
ChangeBuildType(BuildType.IL2CPP);
}
private static void ChangeBuildType(BuildType buildType)
{
BuildTargetGroup currentBuildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
switch (buildType)
{
case BuildType.Mono:
{
PlayerSettings.SetScriptingBackend(currentBuildTargetGroup, ScriptingImplementation.Mono2x);
PlayerSettings.SetManagedStrippingLevel(currentBuildTargetGroup, ManagedStrippingLevel.Disabled);
AssetDatabase.SaveAssets();
}
break;
case BuildType.IL2CPP:
{
PlayerSettings.SetScriptingBackend(currentBuildTargetGroup, ScriptingImplementation.IL2CPP);
PlayerSettings.SetManagedStrippingLevel(currentBuildTargetGroup, ManagedStrippingLevel.Minimal);
AssetDatabase.SaveAssets();
}
break;
}
OnBuildTypeChanged?.Invoke();
}
private static bool IsUseCurrentBuildType(BuildType buildType)
{
BuildTargetGroup currentBuildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
ScriptingImplementation currentScriptingImplementation = PlayerSettings.GetScriptingBackend(currentBuildTargetGroup);
return buildType switch
{
BuildType.Mono => (currentScriptingImplementation == ScriptingImplementation.Mono2x),
BuildType.IL2CPP => (currentScriptingImplementation == ScriptingImplementation.IL2CPP),
_ => false
};
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment