一、简介
利用有限状态机来控制对象的行为,其原理就是利用多态,常常我们自己写代码,需要很大篇幅,万一需要再加一个或者几个状态,那么我们自己维护时就会很麻烦,SMC这个工具可以帮助我们解决这个问题。
使用这个工具之前我们需要jdk环境,最新版的SMC支持jdk1.7,之前版本的SMC支持jdk1.6,要想中间没有问题,我们一定要将电脑上的jdk版本与SMC的jdk版本对应起来。
下载地址:http://sourceforge.net/projects/smc/files/
SMC可以通过一个配置文件,生成有限状态机所需的所有状态类以及状态机类,同时还包括了所有的状态间的转换逻辑。
SMC支持多种开发语言:C、JavaScript、Python、C++、Lua、Ruby、C#、Objective-C、Scala、Groovy、Perl、TCL、Java、PHP、VB.net,而我们所需要做的唯一的工作就是编写拥有这些状态的主体类。
二、编写smc文件
首先写一个sm的文件 Hero.sm:
1 // entity class
2 %class Hero
3
4 // entity class header
5 %header Hero.h
6
7 // inital state
8 %start HeroMap::STOP
9
10 // entity state map
11 %map HeroMap
12 %%
13 STOP
14 Entry {
15 stop();
16 }
17 Exit {
18 exit();
19 }
20 {
21 walk WALK {}
22 }
23
24 WALK
25 Entry {
26 walk();
27 }
28 Exit {
29 exit();
30 }
31 {
32 stop STOP {}
33 turn TURN {}
34 }
35
36 TURN
37 Entry {
38 turn();
39 }
40 Exit {
41 exit();
42 }
43 {
44 walk WALK {}
45 }
46 %%
其中:
1、%class Hero: %class标签用于定义状态的主体对象,也就是说,指定哪个类具备这些状态和行为。比如:Hero(Hero.h和Hero.cpp)
2、%header Hero.h:%header标签用于定义主体对象的头文件。
3、%start HeroMap::STOP:%start标签用于定义对象的初始状态,STOP是一个状态类,对应的类是:HeroMap_STOP。
4、%map HeroMap:重点来了,%map标签用于定义状态表对象的名称。什么是状态表呢?SMC在生成状态机代码时,会将对象的各种状态都创建为静态对象。而状态表存放所有的静态变量(包含全部状态)。对应的是:HeroMap。
5、%%...%%:这一对%%中间定义了各个状态类以及状态的各种行为。Entry{}代表在切换到该状态时要执行的事件,Ext{}表示离开改状态时要执行的事件,Entry和Exit是有限状态机常用的技巧,可以在开始和结束时执行一些动作。后面有一对花括号用于定义状态的行为,比如状态在执行到哪个函数后切换到另外一个状态,以及切换状态时要执行的动作。也就是状态存活期间的行为。格式如下:
1 STOP // 状态名
2
3 Entry {
4 // 执行这个函数进入该状态
5 stop();
6 }
7
8 Exit {
9 // 执行这个函数退出该状态
10 exit();
11 }
12
13 {
14 // 状态切换逻辑
15 walk WALK {}
16 }
当运行下面的命令,会自动生成文件:Hero_sm.h和Hero_sm.cpp。连同自带的statemap.h一起加入到项目中。
1 java -jar [$PATH]Smc.jar -c++ Hero.sm //[PATH] smc.jar的路径
三、添加实体类
业务逻辑仍然要我们自己实现,那就是写Hero.h和Hero.cpp。不过这次写Hero类需要按一定的规则,下面是源代码:
1 // Hero.h
2 //
3 #ifndef HERO_H_
4 #define HERO_H_
5
6 #include "cocos2d.h"
7 USING_NS_CC;
8
9 #include "Hero_sm.h"
10
11
12 #define MAX_STOP_TIME 3
13 #define MAX_WALK_TIME 10
14 #define MAX_WALK_DIST 200
15
16
17 class Hero : public Node
18 {
19 public:
20 CREATE_FUNC(Hero);
21
22 virtual bool init();
23
24 void stop();
25
26 void walk();
27
28 void turn();
29
30 void exit();
31
32 private:
33 HeroContext * _fsm;
34
35 int _step;
36 int _curPos;
37 time_t _curTime;
38
39 // Sprite * _sprite;
40
41 private:
42 void onStop(float dt)
43 {
44 int d = (int) (time(0) - _curTime);
45 if (d > MAX_STOP_TIME) {
46 _fsm->walk();
47 }
48 }
49
50
51 void onWalk(float dt)
52 {
53 if (_curPos > MAX_WALK_DIST || _curPos < -MAX_WALK_DIST) {
54 _fsm->turn();
55 }
56
57 int d = (int) (time(0) - _curTime);
58 if (d > MAX_WALK_TIME) {
59 _fsm->stop();
60 }
61
62 _curPos += _step;
63 }
64
65
66 void onTurn(float dt)
67 {
68 _fsm->walk();
69 }
70 };
71
72 #endif // HERO_H_
上面的on***是触发状态的回调函数,实体状态改变的业务逻辑在这里实现。
1 // Hero.cpp
2 //
3 #include "Hero.h"
4
5 #include <time.h>
6 #include <assert.h>
7
8
9 void Hero::exit()
10 {
11 this->unscheduleAllCallbacks();
12 CCLOG("exit()");
13 }
14
15
16 bool Hero::init()
17 {
18 _step = 1;
19 _curPos = 0;
20 _curTime = time(0);
21
22 _fsm = new HeroContext(*this);
23 assert(_fsm);
24
25 _fsm->setDebugFlag(true);
26 _fsm->enterStartState();
27
28 return true;
29 }
30
31
32 void Hero::stop()
33 {
34 _curTime = time(0);
35
36 CCLOG("stop(): pos=%d", _curPos);
37
38 this->schedule(schedule_selector(Hero::onStop), 0.1f);
39 }
40
41
42 void Hero::walk()
43 {
44 _curTime = time(0);
45
46 CCLOG("walk(): pos=%d", _curPos);
47
48 this->schedule(schedule_selector(Hero::onWalk), 0.1f);
49 }
50
51
52 void Hero::turn()
53 {
54 _step *= -1;
55 CCLOG("turn(): step=%d", _step);
56
57 this->schedule(schedule_selector(Hero::onTurn), 0.1f);
58 }
四、状态机类
框架代码Smc已经帮我们生成好了:Hero_sm.h和Hero_sm.cpp。
五、使用
1 Hero* hero= Hero::create();
2 addChild(hero);
六、总结
FSM是一种固定的范式,因此采用工具帮我们实现可以减少犯错误的机会。输入的文件就是:实体.sm。我们把重点放在业务逻辑上,所以与状态有关的代码smc都帮我们生成好了。