1. 程式人生 > >Game中的狀態機

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,,,,

狀態機,整理完成。。