Unity如何使用枚举值代替字符串Layer

背景

Unity中的Layer经常被开发者用来控制物理碰撞和射线检测,也可以用于渲染特定的Layer。
但在使用Layer的过程中,我们需要注意以下问题。首先LayerName是string类型的, 因此很容易打错字符。还有就是代码中存储的Layer,经常会出现和存储layer的项目资产TagManager.Asset不一致的情况。

本文将介绍如何使用枚举值代替字符串类型的Layer,并且解决代码Layer和Asset Layer同步的问题。

内容

目标

这里我们希望

  1. 能够将包含字符串的代码gameObject.layer = LayerMask.NameToLayer("SceneUnit"); 转化为枚举值的LayergameObject.layer = (int)UnityLayerEnum.SceneUnit;
  2. 以及让 代码里面的Layer数据和 项目资产的TagManager.Asset数据保持一致。

收集信息

首先,我们可以观察一下修改Layer的面板。

image

感觉像是什么EditorWindow,或者Inspector显示的内容。 不知道没有关系,我们可以搜一下源码(Unity4.7.1f1)。
通过在源码中搜索User Layer,很容易发现这里Inspector显示的是TagManager里面的属性,以及我们可以通过EditorApplication.tagManager去访问这个对象。

点开TagManager类,发现里面没有相关的接口可以用来修改Layer的信息,这里我们选择去看一下对应的Editor,TagManagerInspector

观察到DrawLayerListElement这个函数里面修改了 layer的值。 我们照猫画虎可以使用m_Layers.GetArrayElementAtIndex(index).stringValue = text;这段代码。

又因为m_layersprotected的,因此我们需要一个派生类去访问它。

TagManagerInpectorEx类

这里我们希望提供一个函数,能够设置对应index的Layer的值。

        [CustomEditor(typeof(TagManager))]
        internal class TagManagerInspectorEx : TagManagerInspector
        {

            //int 是对应的索引index,
            // string是对应索引位置的Layer的值
            public void SetLayers(List<(int, string)> layerIndexNameList)
            {

                foreach(var indexNamePair in layerIndexNameList)
                {
                    var index = indexNamePair.Item1;
                    var name = indexNamePair.Item2;
                    m_Layers.GetArrayElementAtIndex(index).stringValue = name;
                }

                base.serializedObject.ApplyModifiedProperties();
            }

        }

TagManagerExtend类

但由于TagManagerInspectorEx是internal,我们还需要再定义一个类去调用这个接口。


    [InitializeOnLoad]
    public static class TagManagerExtend
    {

        public static void SetLayers(List<(int,string)> layerIndexNameList)
        {
#if UNITY_EDITOR
            var tagManager = (TagManager)EditorApplication.tagManager;
            var tagManagerInspector = TagManagerInspectorEx.CreateEditor(tagManager, typeof(TagManagerInspectorEx)) as TagManagerInspectorEx;

            tagManagerInspector.SetLayers(layerIndexNameList);
#else 
            UnityEngine.Debug.LogError("Cannot SetLayers , UNITY_EDITOR not set");
#endif
        }
  }

到这里为止,我们已经做到了将设置Layer的接口暴露出来,接着我们就需要定义一个枚举值,然后将枚举值转化为对应的index和string值。

    //[UnityLayerEnum(-1)]
    /// <summary>
    /// Unity 默认的Layer的值
    /// </summary>
    public enum UnityDefaultLayerEnum
    {
        Default = 0,
        TransparentFX = 1,
        IgnoreRaycast = 2,
        // layer3 is empty,
        Water = 4,
        UI = 5,

    }


比如我们有一个UnityDefaultLayerEnum这样的枚举值。我们需要遍历Enum里面的所有值,将Enum的string value作为layer的string value,将Enum的int value作为layer的int value。

        /// <summary>
        /// 从类似<see cref="UnityDefaultLayerEnum"/>的枚举值中,获取Layer的信息并设置
        /// </summary>
        /// <param name="layerEnumType"></param>
        public static void SetLayerFromEnumType(Type layerEnumType)
        {
            Debug.Assert(layerEnumType.IsEnum);

            UnityEngine.Debug.Log($"准备从枚举类{layerEnumType.Name}获取项目Layer信息并注册。");


            List<(int, string)> layerIndexNameList = new List<(int, string)>();

            int[] enumValues = (int[])Enum.GetValues(layerEnumType);
            foreach(int index in enumValues)
            {
                var name = Enum.GetName(layerEnumType, index);
                layerIndexNameList.Add((index, name));    
            }

            SetLayers(layerIndexNameList);
        }

以上这些代码就可以让我们实现将代码中的Enum枚举值导入到项目资产中的Layer里面(TagManager.Asset)。

最后,我们希望能够将这部分的代码和逻辑代码分离,那么底层代码如何访问上层代码定义的枚举值呢?
我们可能需要手动调用,或者是通过反射来找到对应的枚举值。

LayerMask类对应的扩展

    public static class LayerMaskExtend
    {
        /// <summary>
        /// <see cref="LayerMask.GetMask(string[])"/>
        /// </summary>
        /// <param name="layer"></param>
        /// <returns></returns>
        public static int GetMask(params int[] layers)
        {
            int finalMask = 0;
            foreach (var layer in layers)
            {
                if (layer != -1)
                {
                    finalMask |= 1 << layer;
                }
            }
            return finalMask;
        }

        public static int GetMask<T>(params T[] layers)
            where T: Enum
        {
            int finalMask = 0;
            foreach (var layer in layers)
            {
                var layerVal = Convert.ToInt32(layer);
                if (layerVal != -1)
                {
                    finalMask |= 1 << layerVal;
                }
            }
            return finalMask;
        }
    }

参考

unity4.7.1f1源码

posted @ 2026-01-02 00:23  dewxin  阅读(6)  评论(0)    收藏  举报