目录
缘由:
分析:
准备:
完整代码:
在使用FairyGUI作为项目UI开发时,有时会使用FairyGUI提供的Scripting Define Symbols。当前FairyGUI中的Scripting Define Symbols有:
骨骼动画 Spine:FAIRYGUI_SPINE,龙骨:FAIRYGUI_DRAGONBONES
字体 TextMeshPro:FAIRYGUI_TMPRO
使用ToLua:FAIRYGUI_TOLUA
使用Puerts:FAIRYGUI_PUERTS
显示阿拉伯文本:RTL_TEXT_SUPPORT
UI自动化测试:FAIRYGUI_TEST
为了方便开发,以上Scripting Define Symbols我将其做成了Unity的菜单,直接看完整代码。
在开始制作菜单之前,需要做一些准备工作。比如上述的Scripting Define Symbols是否需要额外的Unity资源包,是否需要熟悉Unity编辑器中的一些方法才能进行?
而且需要注意的是新建的Unity工程中,TextMeshPro组件的必要资源库是需要手动导入或引用的,所以在使用FGUI提供的FAIRYGUI_TMPRO时,需要对TextMeshPro包进行检测。
对于编辑器中的Scripting Define Symbols设置可以通过方法GetScriptingDefineSymbolsForGrouph和SetScriptingDefineSymbolsForGroup进行操作。菜单状态则可以通过方法:GetChecked和SetChecked来进行操作。菜单状态的变化Scripting Define Symbols设置有关,所以还要自定义一个同步刷新菜单状态的方法RefreshMenuState。
分析结束,我们在代码中写一下上面的分析结果。新建脚本EditorMenuTool,空间名设置为FairyGUIEditor,添加对UnityEditor的引用。将新建的EditorMenuTool脚本,放到FairyGUI的Editor目录下(也可以根据项目目录结构放置),打开脚本。
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
namespace FairyGUIEditor
{
public class EditorMenuTool
{
}
}
#endif
Scripting Define Symbols的操作方法:
///
/// 获取Scripting Define Symbols的值
///
///
private static string GetScriptingDefineSymbolsForGroup()
{
return PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
}
///
/// 设置Scripting Define Symbols的值
///
/// 新的宏定义
private static void SetScriptingDefineSymbolsForGroup(string newSymbol)
{
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup,newSymbol);
}
///
/// 检测Scripting Define Symbols中是否存在目标值
///
/// 目标宏
///
private static bool CheckScriptingDefineSymbolsExist(string define)
{
string symbol = GetScriptingDefineSymbolsForGroup();
return symbol.Contains(define);
}
///
/// 根据菜单状态修改目标宏
///
/// 目标宏定义
/// 当前菜单状态
private static void SwitchToTargetState(string define, bool menuState = false)
{
//检测目标宏定义
if (define == null)
return;
//获取当前的宏定义
string symbol = GetScriptingDefineSymbolsForGroup();
if (menuState) //菜单已选中
{
//获取目标宏所在的位置
int index = symbol.IndexOf(define);
if (index < 0)
return;
//如果不在第一个 则将其前面的分号删掉
if (index > 0)
index -= 1;
int length = define.Length;
//如果当前宏长度大于要删除的当前长度,才会有分号
if (symbol.Length > length)
length += 1;
//删除目标宏定义
symbol = symbol.Remove(index, length);
SetScriptingDefineSymbolsForGroup(symbol);
}
else //菜单未选中
{
//如果当前的宏是空的,则直接将目标的宏加入
if (symbol.Equals(string.Empty))
SetScriptingDefineSymbolsForGroup(define);
else
{
//否则,以分号分割加入目标宏
string newSymbol = symbol + ";" + define;
SetScriptingDefineSymbolsForGroup(newSymbol);
}
}
}
包管理(PackageManager)的检测方法:
///
/// 检测目标包是否存在
///
/// 包名
///
private static void CheckTargetPackageExists(string packageName,Action callback = null)
{
// 创建一个ListRequest请求,用来查询PackageManager中已经安装的packages列表
ListRequest request = Client.List();
// 发送ListRequest请求,并在每一帧检查请求是否已经完成
EditorApplication.CallbackFunction checkUpdateAction = null;
checkUpdateAction = () =>
{
if (request.IsCompleted)
{
bool packageExists = false;
EditorUtility.ClearProgressBar();
if (request.Status == StatusCode.Success)
{
// 遍历packages列表,查找目标packageName是否存在
foreach (var package in request.Result)
{
if (package.name.Contains(packageName) || package.displayName.Contains(packageName))
{
packageExists = true;
break;
}
}
}
else if (request.Status >= StatusCode.Failure)
{
Debug.LogError(request.Error.message);
}
callback?.Invoke(packageExists);
// 取消update回调函数
EditorApplication.update -= checkUpdateAction;
}
};
EditorApplication.update += checkUpdateAction;
EditorUtility.DisplayProgressBar("Check", "Please wait...", 0f);
}
同步刷新菜单的方法:
///
/// 刷新菜单状态
/// 标签"UnityEditor.Callbacks.DidReloadScripts"可以在脚本编译完成后自动回调这个方法
/// 标签"RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)"在场景加载前执行这个方法
///
[UnityEditor.Callbacks.DidReloadScripts,RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void RefreshMenuState()
{
//添加需要更新菜单
}
添加UnityEditor.Callbacks.DidReloadScripts和RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)标签,会在脚本编译完或场景加载前进行状态同步,是个很方便的功能。举例:和小伙伴们一起开发时,只需要自己这边配置好,同步到其他小伙伴那边时,他们的编译器自动就会刷新新配置对应菜单状态。
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.UI;
using UnityEditor.PackageManager.Requests;
namespace FairyGUIEditor
{
public class EditorMenuTool
{
#region Define Symbols
///
/// 骨骼动画 Spine:FAIRYGUI_SPINE,龙骨:FAIRYGUI_DRAGONBONES
///
private const string DefineSpine = "FAIRYGUI_SPINE", DefineDragonBones = "FAIRYGUI_DRAGONBONES";
///
/// 字体 TextMeshPro :FAIRYGUI_TMPRO
///
private const string DefineTMP = "FAIRYGUI_TMPRO";
///
/// 使用ToLua :FAIRYGUI_TOLUA
///
private const string DefineToLua = "FAIRYGUI_TOLUA";
///
/// 使用Puerts :FAIRYGUI_PUERTS
///
private const string DefinePuerts = "FAIRYGUI_PUERTS";
///
/// 阿拉伯语言文字显示 :RTL_TEXT_SUPPORT
///
private const string DefineRTL = "RTL_TEXT_SUPPORT";
///
/// UI自动化测试 :FAIRYGUI_TEST
///
private const string DefineAirTest = "FAIRYGUI_TEST";
#endregion
private const string PackageName = "TextMeshPro";
private const string TextMeshProCheckPkg = "FairyGUI/TextMeshPro/Check Package";
private const string TextMeshProEssential = "FairyGUI/TextMeshPro/Import TMP Essential Resources";
private const string MenuNameTextMeshPro = "FairyGUI/TextMeshPro/Use TextMeshPro";
private const string MenuNameSpine = "FairyGUI/Use Spine";
private const string MenuNameDragonBones = "FairyGUI/Use DragonBones";
private const string MenuNameToLua = "FairyGUI/Use ToLua";
private const string MenuNamePuerts = "FairyGUI/Use Puerts";
private const string MenuNameRTLTextSupport = "FairyGUI/Use RTLTextSupport";
private const string MenuNameAirTest = "FairyGUI/Use AirTest";
///
/// 检测TextMeshPro包是否存在
///
[MenuItem(TextMeshProCheckPkg,false,1000)]
private static void CheckTextMeshProPackage()
{
CheckTargetPackageExists(PackageName, b =>
{
//不存在则打开包管理面板,并搜索
if (!b)
{
//UnityEditor.PackageManager.UI
Window.Open(PackageName);
}
else
{
//提示已经安装了
EditorUtility.DisplayDialog("Tips",
$"{PackageName} package is installed!\n{PackageName}包已存在!","Ok");
}
});
}
///
/// 导入TextMeshPro包必备资源
///
[MenuItem(TextMeshProEssential,false,1015)]
private static void ImportTMPEssentialResources()
{
//做一次安全检查
if (UnityEditor.PackageManager.PackageInfo.FindForAssetPath("Packages/com.unity.textmeshpro/Scripts/Runtime/TextMeshPro.cs") == null) {
Debug.LogError("TextMeshPro package is not installed.");
return;
}
//执行目标菜单项
EditorApplication.ExecuteMenuItem("Window/TextMeshPro/Import TMP Essential Resources");
}
///
/// 启用或关闭TextMeshPro支持
///
[MenuItem(MenuNameTextMeshPro,false,1030)]
private static void SelectTextMeshPro()
{
//获取当前状态
bool state = Menu.GetChecked(MenuNameTextMeshPro);
//切换宏定义状态
SwitchToTargetState(DefineTMP,state);
//刷新菜单状态
RefreshMenuState();
//更新
AssetDatabase.Refresh();
}
///
/// 启用或关闭Spine
///
[MenuItem(MenuNameSpine,false,1015)]
private static void SelectSpine()
{
//获取当前状态
bool state = Menu.GetChecked(MenuNameSpine);
//切换宏定义状态
SwitchToTargetState(DefineSpine,state);
//刷新菜单状态
RefreshMenuState();
//更新
AssetDatabase.Refresh();
}
///
/// 启用或关闭DragonBones
///
[MenuItem(MenuNameDragonBones,false,1030)]
private static void SelectDragonBones()
{
//获取当前状态
bool state = Menu.GetChecked(MenuNameDragonBones);
//切换宏定义状态
SwitchToTargetState(DefineDragonBones,state);
//刷新菜单状态
RefreshMenuState();
//更新
AssetDatabase.Refresh();
}
///
/// 启用或关闭ToLua
///
[MenuItem(MenuNameToLua,false,1045)]
private static void SelectToLua()
{
//获取当前状态
bool state = Menu.GetChecked(MenuNameToLua);
//切换宏定义状态
SwitchToTargetState(DefineToLua,state);
//刷新菜单状态
RefreshMenuState();
//更新
AssetDatabase.Refresh();
}
///
/// 启用或关闭Puerts
///
[MenuItem(MenuNamePuerts,false,1060)]
private static void SelectPuerts()
{
//获取当前状态
bool state = Menu.GetChecked(MenuNamePuerts);
//切换宏定义状态
SwitchToTargetState(DefinePuerts,state);
//刷新菜单状态
RefreshMenuState();
//更新
AssetDatabase.Refresh();
}
///
/// 启用或关闭阿拉伯语言文字
///
[MenuItem(MenuNameRTLTextSupport,false,1075)]
private static void SelectRTLTextSupport()
{
//获取当前状态
bool state = Menu.GetChecked(MenuNameRTLTextSupport);
//切换宏定义状态
SwitchToTargetState(DefineRTL,state);
//刷新菜单状态
RefreshMenuState();
//更新
AssetDatabase.Refresh();
}
///
/// 启用或关闭UI自动化测试
///
[MenuItem(MenuNameAirTest,false,1090)]
private static void SelectUIAirTest()
{
//获取当前状态
bool state = Menu.GetChecked(MenuNameAirTest);
//切换宏定义状态
SwitchToTargetState(DefineAirTest,state);
//刷新菜单状态
RefreshMenuState();
//更新
AssetDatabase.Refresh();
}
///
/// 刷新菜单状态
/// 标签"UnityEditor.Callbacks.DidReloadScripts"可以在脚本编译完成后自动回调这个方法
/// 标签"RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)"在场景加载前执行这个方法
///
[UnityEditor.Callbacks.DidReloadScripts,RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void RefreshMenuState()
{
//放置需要更新菜单
Menu.SetChecked(MenuNameTextMeshPro, CheckScriptingDefineSymbolsExist(DefineTMP));
Menu.SetChecked(MenuNameSpine, CheckScriptingDefineSymbolsExist(DefineSpine));
Menu.SetChecked(MenuNameDragonBones, CheckScriptingDefineSymbolsExist(DefineDragonBones));
Menu.SetChecked(MenuNameToLua, CheckScriptingDefineSymbolsExist(DefineToLua));
Menu.SetChecked(MenuNamePuerts, CheckScriptingDefineSymbolsExist(DefinePuerts));
Menu.SetChecked(MenuNameRTLTextSupport, CheckScriptingDefineSymbolsExist(DefineRTL));
Menu.SetChecked(MenuNameAirTest, CheckScriptingDefineSymbolsExist(DefineAirTest));
}
#region Package Check
///
/// 检测目标包是否存在
///
/// 包名
///
private static void CheckTargetPackageExists(string packageName,Action callback = null)
{
// 创建一个ListRequest请求,用来查询PackageManager中已经安装的packages列表
ListRequest request = Client.List();
// 发送ListRequest请求,并在每一帧检查请求是否已经完成
EditorApplication.CallbackFunction checkUpdateAction = null;
checkUpdateAction = () =>
{
if (request.IsCompleted)
{
bool packageExists = false;
EditorUtility.ClearProgressBar();
if (request.Status == StatusCode.Success)
{
// 遍历packages列表,查找目标packageName是否存在
foreach (var package in request.Result)
{
if (package.name.Contains(packageName) || package.displayName.Contains(packageName))
{
packageExists = true;
break;
}
}
}
else if (request.Status >= StatusCode.Failure)
{
Debug.LogError(request.Error.message);
}
callback?.Invoke(packageExists);
// 取消update回调函数
EditorApplication.update -= checkUpdateAction;
}
};
EditorApplication.update += checkUpdateAction;
EditorUtility.DisplayProgressBar("Check", "Please wait...", 0f);
}
#endregion
#region Scripting Define Symbols
///
/// 获取Scripting Define Symbols的值
///
///
private static string GetScriptingDefineSymbolsForGroup()
{
return PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
}
///
/// 设置Scripting Define Symbols的值
///
/// 新的宏定义
private static void SetScriptingDefineSymbolsForGroup(string newSymbol)
{
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup,newSymbol);
}
///
/// 检测Scripting Define Symbols中是否存在目标值
///
/// 目标宏
///
private static bool CheckScriptingDefineSymbolsExist(string define)
{
string symbol = GetScriptingDefineSymbolsForGroup();
return symbol.Contains(define);
}
///
/// 更新菜单状态修改目标宏
///
/// 目标宏定义
/// 当前菜单状态
private static void SwitchToTargetState(string define, bool menuState = false)
{
//检测目标宏定义
if (define == null)
return;
//获取当前的宏定义
string symbol = GetScriptingDefineSymbolsForGroup();
if (menuState) //菜单已选中
{
//获取目标宏所在的位置
int index = symbol.IndexOf(define);
if (index < 0)
return;
//如果不在第一个 则将其前面的分号删掉
if (index > 0)
index -= 1;
int length = define.Length;
//如果当前宏长度大于要删除的当前长度,才会有分号
if (symbol.Length > length)
length += 1;
//删除目标宏定义
symbol = symbol.Remove(index, length);
SetScriptingDefineSymbolsForGroup(symbol);
}
else //菜单未选中
{
//如果当前的宏是空的,则直接将目标的宏加入
if (symbol.Equals(string.Empty))
SetScriptingDefineSymbolsForGroup(define);
else
{
//否则,以分号分割加入目标宏
string newSymbol = symbol + ";" + define;
SetScriptingDefineSymbolsForGroup(newSymbol);
}
}
}
#endregion
}
}
#endif