Unity如何使用枚举值代替字符串Layer
背景
Unity中的Layer经常被开发者用来控制物理碰撞和射线检测,也可以用于渲染特定的Layer。
但在使用Layer的过程中,我们需要注意以下问题。首先LayerName是string类型的, 因此很容易打错字符。还有就是代码中存储的Layer,经常会出现和存储layer的项目资产TagManager.Asset不一致的情况。
本文将介绍如何使用枚举值代替字符串类型的Layer,并且解决代码Layer和Asset Layer同步的问题。
内容
目标
这里我们希望
- 能够将包含字符串的代码
gameObject.layer = LayerMask.NameToLayer("SceneUnit");转化为枚举值的LayergameObject.layer = (int)UnityLayerEnum.SceneUnit; - 以及让 代码里面的Layer数据和 项目资产的
TagManager.Asset数据保持一致。
收集信息
首先,我们可以观察一下修改Layer的面板。

感觉像是什么EditorWindow,或者Inspector显示的内容。 不知道没有关系,我们可以搜一下源码(Unity4.7.1f1)。
通过在源码中搜索User Layer,很容易发现这里Inspector显示的是TagManager里面的属性,以及我们可以通过EditorApplication.tagManager去访问这个对象。
点开TagManager类,发现里面没有相关的接口可以用来修改Layer的信息,这里我们选择去看一下对应的Editor,TagManagerInspector。
观察到DrawLayerListElement这个函数里面修改了 layer的值。 我们照猫画虎可以使用m_Layers.GetArrayElementAtIndex(index).stringValue = text;这段代码。
又因为m_layers是protected的,因此我们需要一个派生类去访问它。
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源码

浙公网安备 33010602011771号