你肯定对u3d中的LayerMask十分熟悉。在u3d中一个gameObject属于一个layer,通常用作物理和渲染的划分。LayerMask则表示这些layer的某种组合,我们先简单回顾下LayerMask的运作机制,然后再探讨一些有意思的玩法。
public struct LayerMask { public int value { get; set; } public static int GetMask(params string[] layerNames); public static string LayerToName(int layer); public static int NameToLayer(string layerName); public static implicit operator int(LayerMask mask); public static implicit operator LayerMask(int intVal); }
上图便是LayerMask的函数和属性定义了。可以看出LayerMask和int存在相互转换关系,在u3d中一共有32个layer,正好对应32位int数的每一位。比如,我们希望一个layerMask包含序号为0、4、5的layer,就需要按从右往左的顺序将第0、4、5位置为1,其他位置0。即整数49。
layerMask = 0000 0000 0000 0000 0000 0000 0011 0001 = 1 + 16 + 32 = 49
假设第0、4、5层代表角色可以站立的平台,现在我们有了layerMask = 49以及layer = 4,如何判断出角色当前正处在可站立的平台上呢?LayerMask中并没有提供这样的方法,需要用到位操作。
首先,定义整型数1,然后让它左移到layer所在的第4位
... 0000 0000 0000 0001
... 0000 0000 0001 0000(结果)
接着,将该数与layerMask按位与&,规则是:对应位都为1则结果为1,否则为0
... 0000 0000 0001 0000
... 0000 0000 0011 0001
... 0000 0000 0001 0000(结果)
于是发现结果非0,也就检测出了layerMask的第4位为1。通过函数表示如下:
public static bool BitCheck(int mask, int pos){ return ((1 << pos) & mask) != 0; }
我们可以通过扩展方法把这个功能“添加”给u3d的LayerMask。
//判断LayerMask是否包含指定的layer public static bool ContainsLayer(this LayerMask mask, int layer){ return ((1 << layer) & mask.value) != 0; }
有趣的新玩法
既然32位int数可以方便的表示层与层属之间的关系,我们可以更进一步。
public enum TeamFlag{ Player = 1, //玩家角色 Enemy = 2, //敌对角色 Neutral = 4, //中立 }
上面的代码中定义了一个TeamFlag枚举来代表角色所属的阵营,一个角色只能属于一个阵营,但是角色的技能却能以多个阵营为目标。比如,我们希望表示火球术(Fireball)可以对敌对或中立角色使用,但不能对玩家自己使用。应该怎么做呢?
细心的你一定能发现,这些枚举数的值正好是按2的幂排列的,它们可以对应int整型数的某些位!
int targetMask = (int)(TeamFlag.Enemy | TeamFlag.Neutral);
这行代码所做的,就是把敌对和中立阵营打包,放在一个int型数里。这种运算叫做按位或|,其规则是:对应位都为0时,结果为0,否则为1。
... 0000 0000 0000 0010
... 0000 0000 0000 0100
... 0000 0000 0000 0110(结果)
现在可以用BitCheck函数来测试目标阵营是否包含在内啦,不过,我们还想在u3d面板中像LayerMask那样方便的配置它。比如这样:
unity需要知道类中的targetMask字段表示一种枚举的组合,而不是一个int数字。这可以借助PropertyAttribute来实现(它是unity提供的一个特性类,可以给字段做标记)。
自定义一个类EnumFlagsAttribute继承它,并写一个简单的构造函数,让使用者把枚举的类型传进来并保存。
//EnumFlagsAttribute.cs using UnityEngine; public class EnumFlagsAttribute : PropertyAttribute{ public System.Type enumType { get; private set; } public EnumFlagsAttribute(System.Type enumType){ this.enumType = enumType; } }
随后,用中括号[ ]语法把EnumFlags特性挂在targetMask字段上,并传入TeamFlag枚举的类型。
//Fireball.cs public class Fireball : MonoBehaviour{ [EnumFlags(typeof(TeamFlag))] public int targetMask; }
现在,unity的序列化系统就能正确地识别到targetMask字段上的特性标记了。最后,自定义一个处理EnumFlags特性的属性画笔EnumFlagsDrawer,该文件要放到名为Editor的文件夹中。
//EnumFlagsDrawer.cs using System; using UnityEditor; using UnityEngine; [CustomPropertyDrawer(typeof(EnumFlagsAttribute))] public class EnumFlagsDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label){ //画出displayName position = EditorGUI.PrefixLabel(position, new GUIContent(property.displayName)); //EditorGUI.EnumFlagsField用来展示枚举类型的组合,就像LayerMask那样 var e = EditorGUI.EnumFlagsField(position, (Enum)Enum.ToObject((attribute as EnumFlagsAttribute).enumType, property.intValue)); //把枚举值赋回给property property.intValue = Convert.ToInt32(e); } }
通过这波操作,我们就可以在u3d面板随心所欲地配置自定义的Flags了~
暂无关于此日志的评论。