一应用背景

在项目开发过程中,常常会使用一些宏命令来开启或关闭某些功能模块,在项目日渐复杂后这种情况越来越普遍。此时我们可以在Project Settings->Player中针对不同平台设定其需要开启的宏,如下图:

unity3d控件 unity控制面板_游戏开发

但这种做法存在一些问题,我们需要不厌其烦的对不同平台进行设置,很容易误删或者拼写错误,因此一个统一管理工程所需宏命令的工具显得尤为必要,本文将针对这种需求设计一款宏命令控制面板。

二.设计思想

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.支持过滤掉当前不支持的平台。

五.宏命令控制面板如下

unity3d控件 unity控制面板_游戏开发_02