快速檢查 enum 是否為基礎值 – IsPowerOfTwo

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

 

, ,

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *

*