檢查 Enum bitmask 的時候經常需要檢查 Enum 是否單一或者包含多個值.
這時候 IsPowerOfTwo 就是需要的算式了, 找到一個很高效的運算法, 筆記一下.
public bool IsPowerOfTwo(long value) { return (value != 0) && ((value & (value - 1)) == 0); }
公式很簡單但內裡的想法很深入.
第一節 value != 0 很簡單的就是除去 0, 因為 bitmask 就是不帶0的
第二節 ((value & (value-1)) == 0 直接用上二進制的減法模式跟十進制的運算.
這邊說一下二進制的減法模式 :
這邊我們知道 1,2,4,8 是2的次方數,
二進制的次方數減去「一」的時候就剛好是把整組數字右方的零都填上 1 的情況.
稍為列一下次方數的模式.
e.g. 8 = 1000, 8-1 = 0111
e.g. 4 = 0100, 4-1 = 0011
e.g. 2 = 0010, 2-1 = 0001
e.g. 1 = 0001, 1-1 = 0000
很明顯的模式, 而這個程式的檢查法就是把減「一」前後的兩組二進制數字做 And 比對.
結果當然是永遠等於「零」
應用
遊戲時利用這個 IsPowerOfTwo, 我們可以很簡單的介定一下傷害層
假設遊戲中的對傷害的判定值是這個 :
[System.Flags] public enum eDamageLayerMask { None = 0, Player = 1 << 0, Neutral = 1 << 2, GroupA = Player | Alliance, GroupB = Enemies | Alliance, GroupC = Enemies | Neutral, Alliance = 1 << 1, Enemies = 1 << 3, ALL = Player | Alliance | Neutral | Enemies }
在平衡設定時我們會希望只能夠在 { Player, Neutral, Alliance, Enemies } 四個值之間選,
但同時某些時候又希望可以簡單的檢查是否為 eDamageLayerMask.GroupC
之類的情況.
這樣就可以簡單的分辦出基礎元素及多重元素的分別.
後記
一個簡單的 wrapper 把 Unity3D 的 EditorGUI.MaskField 做成 custom property drawer.
public class MaskFieldAttribute : UnityEngine.PropertyAttribute { public readonly System.Type type; public MaskFieldAttribute(System.Type enumType) { this.type = enumType; } }
[CustomPropertyDrawer(typeof(MaskFieldAttribute))] public class MaskFieldDrawer : PropertyDrawer { MaskFieldAttribute maskFieldAttribute { get { return (MaskFieldAttribute)attribute; } } string[] m_Options = null; public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, label, property); if (property.propertyType == SerializedPropertyType.Enum) { if (m_Options == null) { var labels = System.Enum.GetNames(maskFieldAttribute.type); var values = System.Enum.GetValues(maskFieldAttribute.type).GetEnumerator(); List<string> tmp = new List<string>(); int index = 0; while (values.MoveNext()) { // filter out multiple enum tag int enumValue = (int)values.Current; if (IsPowerOfTwo(enumValue)) tmp.Add(labels[index]); index++; } m_Options = tmp.ToArray(); } EditorGUI.BeginChangeCheck(); int rst = EditorGUI.MaskField(position, property.intValue, m_Options); if (EditorGUI.EndChangeCheck()) { property.intValue = rst; } } else { EditorGUI.LabelField(position, label, typeof(MaskFieldAttribute).Name + " only allow to use with { Enum }."); } EditorGUI.EndProperty(); } public bool IsPowerOfTwo(long value) { return (value != 0) && ((value & (value - 1)) == 0); } }