一应用背景
在项目开发过程中,常常会使用一些宏命令来开启或关闭某些功能模块,在项目日渐复杂后这种情况越来越普遍。此时我们可以在Project Settings->Player中针对不同平台设定其需要开启的宏,如下图:
但这种做法存在一些问题,我们需要不厌其烦的对不同平台进行设置,很容易误删或者拼写错误,因此一个统一管理工程所需宏命令的工具显得尤为必要,本文将针对这种需求设计一款宏命令控制面板。
二.设计思想
1.获取指定平台当前已设定的宏命令:PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup targetGroup);
获取到的宏命令是以分号分割的一串字符串,为了方便每个宏在不同平台的开启或关闭,我们需要将其分割开来;
2.给指定平台设置某个宏命令:PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup targetGroup, string defines);其中defines是以分号分割的各宏命令组成的字符串;
3.为了获取当前支持的平台,这里需要使用反射机制来调用Unity私有方法及访问私有字段/属性,主要涉及到“UnityEditor.Build.BuildPlatforms”类及“UnityEditor.Build.BuildPlatform”类相关的方法和字段及属性的反射;
4.为了便于保存宏命令及其在相应平台的开关情况,这里继承ScriptableObject设计了一个类来达到该目的,同时提供对应的面板,以便于宏命令设定。
三.核心源码
1.宏命令相关参数存储结构
using System.Collections;
using System.Linq;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class Macro : ScriptableObject
{
//unity内置宏命令,不可以重复设定
private static List<string> BuiltMacro = new List<string>()
{"UNITY_EDITOR","UNITY_EDITOR_WIN","UNITY_EDITOR_OSX","UNITY_EDITOR_LINUX","UNITY_STANDALONE_OSX",
"UNITY_STANDALONE_WIN","UNITY_STANDALONE_LINUX","UNITY_STANDALONE","UNITY_WII","UNITY_IOS","UNITY_IPHONE",
"UNITY_ANDROID","UNITY_PS4","UNITY_XBOXONE","UNITY_LUMIN","UNITY_TIZEN","UNITY_TVOS","UNITY_WSA","UNITY_WSA_10_0",
"UNITY_WINRT","UNITY_WINRT_10_0","UNITY_WEBGL","UNITY_FACEBOOK","UNITY_ADS","UNITY_ANALYTICS","UNITY_ASSERTIONS","UNITY_64"};
public const string path = "Assets/macro.asset";
public List<MacroItem> MacroItems = new List<MacroItem>();
public void Init()
{
var validPlatforms = GetSupportGroup();
var list = new List<string>();
foreach (var platform in validPlatforms)
{
var group = platform.targetGroup;
var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group).Split(';');
list.AddRange(defines);
}
list = list.Distinct().ToList();
foreach (var item in MacroItems)
{
if (list.Contains(item.MacroName))
{
list.Remove(item.MacroName);
}
}
foreach (var macro in list)
{
AddMacro(macro);
}
}
/// <summary>
/// 反射获取当前支持平台
/// </summary>
/// <returns></returns>
public static List<BuildTargetGroupItem> GetSupportGroup()
{
var result = new List<BuildTargetGroupItem>();
var buildPlatforms = typeof(Editor).Assembly.GetType("UnityEditor.Build.BuildPlatforms");
var instance = buildPlatforms?.GetProperty("instance", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static)?.GetValue(null);
var temp = buildPlatforms?.InvokeMember("GetValidPlatforms", System.Reflection.BindingFlags.InvokeMethod, null, instance, null);
var items = temp as IEnumerable;
var buildPlatform = typeof(Editor).Assembly.GetType("UnityEditor.Build.BuildPlatform");
foreach (var item in items)
{
var name = buildPlatform?.GetField("name", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)?.GetValue(item) as string;
var targetGroup = (BuildTargetGroup)buildPlatform?.GetField("targetGroup", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)?.GetValue(item);
var title1 = buildPlatform.GetField("title", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
var title = buildPlatform.GetProperty("title", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)?.GetValue(item) as GUIContent;
var smallIcon = buildPlatform.GetProperty("smallIcon", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)?.GetValue(item) as Texture2D;
result.Add(new BuildTargetGroupItem { name = name, targetGroup = targetGroup, title = title, smallIcon = smallIcon });
}
return result;
}
/// <summary>
/// 添加宏命令
/// </summary>
/// <param name="macroname"></param>
/// <returns></returns>
public bool AddMacro(string macroname)
{
if (string.IsNullOrEmpty(macroname.Trim()))
{
Debug.Log("宏名不能为空!");
return false;
}
if (!MacroItems.Exists(mitem => mitem.MacroName == macroname))
{
var item = new MacroItem { MacroName = macroname };
item.Init();
MacroItems.Add(item);
return true;
}
else
{
Debug.LogWarning("该宏已存在!");
return false;
}
}
/// <summary>
/// 移除宏命令
/// </summary>
/// <param name="macroname"></param>
/// <returns></returns>
public bool RemoveMacro(string macroname)
{
for (var index = 0; index < MacroItems.Count; index++)
{
var item = MacroItems[index];
if (item.MacroName == macroname)
{
foreach (var group in item.SwitchItems)
{
if (group.isOn)
{
MacroItem.SwitchMicro(group.targetGroup, false, macroname);
}
}
MacroItems.RemoveAt(index);
return true;
}
}
Debug.LogWarning("该宏不存在!");
return false;
}
/// <summary>
/// 替换宏命令
/// </summary>
/// <param name="srcMacro"></param>
/// <param name="targetMacro"></param>
/// <returns></returns>
public bool ReplaceMacro(string srcMacro, string targetMacro)
{
if (!CheckName(targetMacro))
return false;
foreach (var item in MacroItems)
{
var srcname = item.MacroName;
if (srcname == srcMacro)
{
item.MacroName = targetMacro;
foreach (var sitem in item.SwitchItems)
{
if (sitem.isOn)
{
MacroItem.SwitchMicro(sitem.targetGroup, false, srcname);
MacroItem.SwitchMicro(sitem.targetGroup, true, targetMacro);
}
}
break;
}
}
return true;
}
/// <summary>
/// 检测宏命令是否合法
/// </summary>
/// <param name="macroName"></param>
/// <returns></returns>
public bool CheckName(string macroName)
{
if (string.IsNullOrEmpty(macroName))
{
Debug.LogError("宏名不能为空!");
return false;
}
if (BuiltMacro.Contains(macroName))
{
Debug.LogError("内置宏不可重复定义!");
return false;
}
foreach (var item in MacroItems)
{
if (item.MacroName == macroName)
{
return false;
}
}
return true;
}
/// <summary>
/// 随机获取宏命令
/// </summary>
/// <returns></returns>
public string GetRandomName()
{
var startIndex = MacroItems.Count;
while (true)
{
var name = $"MACRO{startIndex++}";
if (CheckName(name))
{
return name;
}
}
}
}
[System.Serializable]
public class MacroItem
{
public List<SwitchItem> SwitchItems = new List<SwitchItem>();
public string MacroName;
public string Description;
public void Init()
{
var groups = System.Enum.GetValues(typeof(BuildTargetGroup)) as BuildTargetGroup[];
var validPlatforms = Macro.GetSupportGroup();
foreach (var group in groups)
{
var isOn = false;
if (validPlatforms.Exists(item => item.targetGroup == group))
{
var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group).Split(';').ToList();
isOn = defines.Contains(MacroName);
}
var switchitem = new SwitchItem()
{
targetGroup = group,
isOn = isOn
};
SwitchItems.Add(switchitem);
}
}
/// <summary>
/// 针对指定平台开启或关闭指定宏命令
/// </summary>
/// <param name="targetGroup"></param>
/// <param name="isOn"></param>
/// <param name="macroName"></param>
/// <returns></returns>
public static bool SwitchMicro(BuildTargetGroup targetGroup, bool isOn, string macroName)
{
var macro = PlayerSettings.GetScriptingDefineSymbolsForGroup(targetGroup);
var defines = macro.Split(';').Distinct().ToList();
defines.Sort();
if (isOn)
defines.Add(macroName);
else
defines.Remove(macroName);
macro = "";
defines.ForEach(item => macro += $"{item};");
PlayerSettings.SetScriptingDefineSymbolsForGroup(targetGroup, macro);
return isOn;
}
}
[System.Serializable]
public class SwitchItem
{
public BuildTargetGroup targetGroup;
public bool isOn;
}
public class BuildTargetGroupItem
{
public string name;
public BuildTargetGroup targetGroup;
public GUIContent title;
public Texture2D smallIcon;
}
2.宏命令控制面板源码
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEditor;
public class MacroHelper : EditorWindow
{
[MenuItem("Tools/MacroHelper")]
static void Open()
{
EditorWindow.GetWindow<MacroHelper>("MacroHelper");
}
private Macro macro;
private List<BuildTargetGroupItem> supportItems;
private Vector2 scroll;
private void OnEnable()
{
macro = LoadOrCreate();
supportItems = Macro.GetSupportGroup();
}
private void OnGUI()
{
GUILayout.BeginHorizontal();
GUILayout.Label("宏名", EditorStyles.boldLabel, GUILayout.Width(100));
foreach (var item in supportItems)
{
GUILayout.Label(item.smallIcon, EditorStyles.boldLabel, GUILayout.Width(50));
foreach (var macroitem in macro.MacroItems)
{
if (!macroitem.SwitchItems.Exists(sitem => sitem.targetGroup == item.targetGroup))
{
macroitem.SwitchItems.Add(new SwitchItem()
{
isOn = false,
targetGroup = item.targetGroup
});
EditorUtility.SetDirty(macro);
}
}
}
GUILayout.Label("备注", EditorStyles.boldLabel, GUILayout.Width(200));
GUILayout.EndHorizontal();
scroll = GUILayout.BeginScrollView(scroll);
GUILayout.BeginVertical(EditorStyles.textArea);
for (var index = 0; index < macro.MacroItems.Count; index++)
{
var macroitem = macro.MacroItems[index];
GUILayout.BeginHorizontal();
var name = GUILayout.TextField(macroitem.MacroName, GUILayout.Width(100));
if (name != macroitem.MacroName && macro.CheckName(name))
{
if (macro.ReplaceMacro(macroitem.MacroName, name))
EditorUtility.SetDirty(macro);
}
foreach (var sitem in macroitem.SwitchItems)
{
foreach (var item in supportItems)
{
if (item.targetGroup == sitem.targetGroup)
{
var ison = GUILayout.Toggle(sitem.isOn, sitem.isOn ? "关闭" : "开启", GUILayout.Width(50));
if (ison != sitem.isOn)
{
sitem.isOn = MacroItem.SwitchMicro(sitem.targetGroup, ison, macroitem.MacroName);
EditorUtility.SetDirty(macro);
}
}
}
}
var description = GUILayout.TextField(macroitem.Description, GUILayout.Width(200));
if (description != macroitem.Description)
{
macroitem.Description = description;
EditorUtility.SetDirty(macro);
}
if (GUILayout.Button("-", GUILayout.Width(50)) && EditorUtility.DisplayDialog("Warning", "确认删除?", "确认", "取消"))
{
if (macro.RemoveMacro(macroitem.MacroName))
EditorUtility.SetDirty(macro);
}
GUILayout.EndHorizontal();
}
GUILayout.EndVertical();
GUILayout.EndScrollView();
GUILayout.Space(10);
GUILayout.BeginHorizontal();
if (GUILayout.Button("+"))
{
var name = macro.GetRandomName();
if (macro.AddMacro(name))
{
EditorUtility.SetDirty(macro);
}
}
if (GUILayout.Button("-") && EditorUtility.DisplayDialog("Warning", "确认删除?", "确认", "取消"))
{
var index = macro.MacroItems.Count - 1;
var name = macro.MacroItems[index].MacroName;
if (macro.RemoveMacro(name))
{
EditorUtility.SetDirty(macro);
}
}
GUILayout.EndHorizontal();
GUILayout.Space(10);
}
private Macro LoadOrCreate()
{
if (!File.Exists(Macro.path))
{
var asset = ScriptableObject.CreateInstance<Macro>();
AssetDatabase.CreateAsset(asset, Macro.path);
}
var macro = AssetDatabase.LoadAssetAtPath<Macro>(Macro.path);
macro.Init();
return macro;
}
}
四.功能说明
1.支持宏命令添加、删除、修改;
2.第一次使用时支持自动导入当前已设定的宏命令;
3.支持过滤掉当前不支持的平台。