Game中的狀態機
我相信大多數博友都會玩遊戲。
玩遊戲,牽涉到狀態包含 登陸,正常,死亡,復活,下線,
在上面狀態的基礎上。同時包含 站立,走動,跑動,不可移動施法狀態,
戰鬥狀態,
通常這是三個不同的分組。也就說可以同時存在的狀態和不可同時存在的狀態。
通常情況下也許你會這麼定義,
//分組1 public bool 登陸 = false; public bool 死亡 = false; public bool 復活 = false; public bool 下線 = false; public bool 正常 = false; public bool 打坐 = false; //分組2 public bool 站立 = false; public bool 走動 = false; public bool 跑動 = false; public bool 施法 = false; //分組3 public bool 戰鬥 = false;
這種情況下,判斷就變得很複雜,因為存在,同時成立和非同時成立的狀態,
例如,戰鬥的同時可以走動,也可以跑動,或者技能施法
但是在打坐,下線,死亡等狀態的時候既不能走動,也不能戰鬥也不能施法。
這樣一來一去,判斷的if else 會多的,讓你頭痛欲裂的吧。
或許你會想到,是要列舉,因為列舉提供位運算,提供Enum.HasFlag()方法來判斷是否存在值
public enum Stauts { //分組1 空值, 登陸, 死亡, 復活, 下線, 正常, 打坐, //分組2 站立, 走動, 跑動, 施法, //分組3 戰鬥, }
檢視一下使用方式
Stauts sValue = Stauts.登陸; sValue = sValue | Stauts.站立; if (sValue.HasFlag(Stauts.站立)) { Console.WriteLine("存在站立狀態"); }
輸出:
但是,繼續測試一下
這樣看出來了,我並沒有設定復活狀態,但是卻存在復活狀態~!
探究得明原因,是因為 位或(|) 運算, 按位或(|)表示相對應的每位至少有一個為1,則結果為1,只有兩個都為0,結果才為0.
所以,我們應該設定列舉值的不同值;
為了更直觀的檢視運算方式,我們輸出列舉值的二進位制值,
public static void Show(Stauts value) { string log = ("結果:" + ((long)value).ToString().PadLeft(10, ' ') + " -> " + Convert.ToString(((long)value), 2).PadLeft(64, '0')); Console.WriteLine(log); }
檢視一下
我們看到
Stauts.登陸 | Stauts.死亡 位或計算方式,結果值。
這些明白了,那就是列舉的值,定義不合法,
public enum Stauts { //分組1 空值 = 0, 登陸 = 1, 死亡 = 2, 復活 = 4, 下線 = 8, 正常 = 10, 打坐 = 20, //分組2 站立 = 40, 走動 = 80, 跑動 = 100, 施法 = 200, //分組3 戰鬥 = 400, }
輸出結果
這下,沒有了復活狀態。這樣判斷,會減少一半,
可還是出現一個問題。那就是分組問題,也就說,死亡狀態和復活狀態不能同時存在,
死亡狀態和跑動狀態也不可能同時存在
那麼我們在想,列舉值的能不能分組呢?
可惜C#下面列舉值,只能使用整型的基礎型別。
沒辦法只能另闢蹊徑:
public class EnumStatus { public long Value { get; private set; } public long GroupValue { private set; get; } public EnumStatus(long value, long group) { this.Value = value; } }
接下來我們設定一組常量
// 0x000000一旦竟然這組狀態忽略一切狀態 public static EnumStatus Status0_空值 = new EnumStatus(0, 0x000000); public static EnumStatus Status0_登陸 = new EnumStatus(1 << 0, 0x000000); public static EnumStatus Status0_死亡 = new EnumStatus(1 << 1, 0x000000); public static EnumStatus Status0_復活 = new EnumStatus(1 << 2, 0x000000); public static EnumStatus Status0_下線 = new EnumStatus(1 << 3, 0x000000); public static EnumStatus Status0_正常 = new EnumStatus(1 << 4, 0x000000); public static EnumStatus Status1_打坐 = new EnumStatus(1 << 5, 0x000000); //移動組狀態 4個狀態值 public static EnumStatus Status1_站立 = new EnumStatus(1 << 6, 0x00000f);//4位一組 public static EnumStatus Status1_走動 = new EnumStatus(1 << 7, 0x00000f);// public static EnumStatus Status1_跑動 = new EnumStatus(1 << 8, 0x00000f);// public static EnumStatus Status1_施法 = new EnumStatus(1 << 9, 0x00000f);// 無法移動的施法 //戰鬥狀態 這組只有一個狀態值 public static EnumStatus Status2_戰鬥 = new EnumStatus(1 << 10, 0x000010);//
後面的分組值,的由來是
0x00000f 二進位制 0000,1111,
0x000010 二進位制 0001,0000,
這樣就成功分組了
上面分組程式碼程式碼有錯誤:
// 0x000000一旦竟然這組狀態忽略一切狀態 public static EnumStatus Status0_空值 = new EnumStatus(0, 0x000000); public static EnumStatus Status0_登陸 = new EnumStatus(1 << 0, 0x000000); public static EnumStatus Status0_死亡 = new EnumStatus(1 << 1, 0x000000); public static EnumStatus Status0_復活 = new EnumStatus(1 << 2, 0x000000); public static EnumStatus Status0_下線 = new EnumStatus(1 << 3, 0x000000); public static EnumStatus Status0_正常 = new EnumStatus(1 << 4, 0x000000); public static EnumStatus Status1_打坐 = new EnumStatus(1 << 5, 0x000000); //移動組狀態 4個狀態值 public static EnumStatus Status1_站立 = new EnumStatus(1 << 6, 0x0003c0);//4位一組 public static EnumStatus Status1_走動 = new EnumStatus(1 << 7, 0x0003c0);// public static EnumStatus Status1_跑動 = new EnumStatus(1 << 8, 0x0003c0);// public static EnumStatus Status1_施法 = new EnumStatus(1 << 9, 0x0003c0);// 無法移動的施法 //戰鬥狀態 這組只有一個狀態值 public static EnumStatus Status2_戰鬥 = new EnumStatus(1 << 10, 0x000400);//
由於前面的分組0佔用了6位,
所以分組應該改為,
也許看到這裡你需要一定的位運算知識了;
接下來我在介紹一下
位運算的操作符
//// 按位異或(^)比較特殊,它比較的是如果兩個不同則值為1(如:(1、0)(0、1)),相同則為0(如:(1、1)(0、0))。 //// 按位與(&)表示相對應的兩位必須都為1,結果才為1,否則為0 //// 按位或(|)表示相對應的每位至少有一個為1,則結果為1,只有兩個都為0,結果才為0. //// 按位取反~ 運算子對運算元執行按位求補運算,其效果相當於反轉每一位。
修改一下計算值
public class EnumStatus { public long Value { get; private set; } public long GroupValue { private set; get; } public EnumStatus(long value, long group) { this.Value = value; } public bool HasFlag(EnumStatus status) { return (this.Value & status.Value) != 0; } public void Show() { string log = ("結果:" + this.Value.ToString().PadLeft(10, ' ') + " -> " + Convert.ToString(this.Value, 2).PadLeft(64, '0')); Console.WriteLine(log); } public static EnumStatus operator |(EnumStatus statusLeft, EnumStatus statusRight) { statusLeft.Value = statusLeft.Value & statusRight.GroupValue | statusRight.Value; return statusLeft; } public static EnumStatus operator &(EnumStatus statusLeft, EnumStatus statusRight) { statusLeft.Value = statusLeft.Value & (~statusRight.Value); return statusLeft; } }
上面過載的 位域演算法也是有問題的。
public static EnumStatus operator |(EnumStatus statusLeft, EnumStatus statusRight) { if (statusRight.GroupValue==0)//當分組為0的時候清除所有狀態 { statusLeft.Value = statusLeft.Value & (statusRight.GroupValue) | statusRight.Value; } else {//當分組不為零 statusLeft.Value = statusLeft.Value & (~statusRight.GroupValue) | statusRight.Value; } return statusLeft; }
這下才是正確的結果
這下是不是很輕鬆的解決了這一系列的狀態問題呢?包括各種同時存在和不同時的存在的狀態~!
但是這也有一個弊端,那就是值只有64個,也就是64個狀態,,雖然存在分組。但是每一個分組的值都必須不同才行。
不知道,各位看官還有麼沒有更好的辦法????
==============================================================================================
java版本的
java的列舉是可以自定義的這方面比較方便
但是java沒有運算子過載,
enum EnumStatus { //0x000000一旦竟然這組狀態忽略一切狀態 Status0_空值(0, 0x000000), Status0_登陸(1 << 0, 0x000000), Status0_下線(1 << 1, 0x000000), Status0_正常(1 << 2, 0x000000), //移動組狀態 Status1_走動(1 << 3, 0x00000f), Status1_跑動(1 << 4, 0x00000f); Long value = 0L; Long group = 0L; private EnumStatus(long value, long group) { this.value = value; this.group = group; } public boolean hasFlag(EnumStatus status) { return (value & (~status.value)) != 0; } public void addStatus(EnumStatus status) { value = value & status.group | status.value; } public void removeStatus(EnumStatus status) { value = value & (~status.value); } public void show() { String log = "結果:" + padLeft(this.value + "", 10, " ") + " -> " + padLeft(Long.toBinaryString(this.value), 64, "0"); System.out.println(log); } String padLeft(String source, int length, String paddingChar) { String strB = Long.toBinaryString(this.value); int forCount = length - strB.length(); for (int i = 0; i < forCount; i++) { strB = paddingChar + strB; } return strB; } }
上面程式碼分組計算錯誤。修改為
public void addStatus(EnumStatus status) { if (status.group == 0) {//分組為零 value = value & status.group | status.value; } else {//分組不為零 value = value & (~status.group) | status.value; } }
測試程式碼
EnumStatus status = EnumStatus.Status0_空值; System.out.println("===============Status.Status0_空值================"); status.show(); System.out.println("===============Status.Status0_登陸================"); status.addStatus(EnumStatus.Status0_登陸); status.show(); if (status.hasFlag(EnumStatus.Status0_登陸)) { System.out.println("存在狀態 Status.Status0_登陸"); } else { System.out.println("不存在狀態 Status.Status0_登陸"); } System.out.println("===============Status.Status0_正常================"); status.addStatus(EnumStatus.Status0_正常); status.show(); if (status.hasFlag(EnumStatus.Status0_登陸)) { System.out.println("存在狀態 Status.Status0_登陸"); } else { System.out.println("不存在狀態 Status.Status0_登陸"); } System.out.println("===============Status.Status1_跑動================"); status.addStatus(EnumStatus.Status1_跑動); status.show(); if (status.hasFlag(EnumStatus.Status0_正常)) { System.out.println("存在狀態 Status.Status0_正常"); } else { System.out.println("不存在狀態 Status.Status0_正常"); } System.out.println("===============Status.Status0_下線================"); status.addStatus(EnumStatus.Status0_下線); status.show(); if (status.hasFlag(EnumStatus.Status1_跑動)) { System.out.println("存在狀態 Status.Status1_跑動"); } else { System.out.println("不存在狀態 Status.Status1_跑動"); }
輸出結果為
===============Status.Status0_空值================ 結果: 0 -> 0000000000000000000000000000000000000000000000000000000000000000 ===============Status.Status0_登陸================ 結果: 1 -> 0000000000000000000000000000000000000000000000000000000000000001 不存在狀態 Status.Status0_登陸 ===============Status.Status0_正常================ 結果: 100 -> 0000000000000000000000000000000000000000000000000000000000000100 存在狀態 Status.Status0_登陸 ===============Status.Status1_跑動================ 結果: 10100 -> 0000000000000000000000000000000000000000000000000000000000010100 存在狀態 Status.Status0_正常 ===============Status.Status0_下線================ 結果: 10 -> 0000000000000000000000000000000000000000000000000000000000000010 存在狀態 Status.Status1_跑動
到此為止。。ok,,,,
狀態機,整理完成。。