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

    状态机,整理完成。。

  • 相关阅读:
    IBM QRadar SIEM 社区版安装
    haproxy+keepalived+rabbitmq3.8实现集群的高可用
    windows使用vmware虚拟机搭建centos7开发环境①打通笔记表和虚拟机之间的网络
    mysql8学习笔记②选择合适的存储引擎和字段类型
    mysql8学习笔记①业务分析和数据库三范式
    centos7.8环境安装rabbitmq3.8.5最新版并设置集群
    centos7环境下编译安装redis5.0.8
    使用ansible的palybook创建指定的www站点nginx和apache配置
    如何在element-UI 组件的change事件中传递自定义参数
    Go 相关库
  • 原文地址:https://www.cnblogs.com/shizuchengxuyuan/p/4513401.html
Copyright © 2020-2023  润新知