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,,,,
状态机,整理完成。。
跪求保留标示符 /** * @author: Troy.Chen(失足程序员, 15388152619) * @version: 2021-07-20 10:55 **/ C#版本代码 vs2010及以上工具可以 java 开发工具是netbeans 和 idea 版本,只有项目导入如果出现异常,请根据自己的工具调整 提供免费仓储。 最新的代码地址:↓↓↓ https://gitee.com/wuxindao 觉得我还可以,打赏一下吧,你的肯定是我努力的最大动力