檢查 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);
}
}