Cocos2dx 3.2 横版过关游戏Brave学习笔记(四)

索引篇:
http://www.cocoachina.com/bbs/read.php?tid=227226

http://www.cocoachina.com/bbs/read.php?tid=227229

http://www.cocoachina.com/bbs/read.php?tid=227305

前几天我把笔记写的差不多了,突然觉得可以发到论坛和大家分享一下,感觉学习cocos2d的新人很多,能相互学习一下还是挺好的。

在发第一个帖子的时候出了点问题,有些文字莫名其妙的成了斜体……我查了一下,原来是代码中有数组用了下标i,这个 i],被论坛识别为斜体标记了…晕

刚发现上次写的代码存在问题,即在角色正在行走中,如果再次点击,PlayAnimationForever会再次运行,出现了多个动画同时播放的情况。如果想要播放的和正在进行的动画相同,不应该停止然后在重新开始,因为这样会导致不连贯。所以我加了检查,通过getActionByTag检查是否正在进行所需的action,如果正在进行,则不做任何操作。好像原版也有这个问题。

不同的帧动画不应该同时播放,所以在播放一个帧动画之前,可以通过stopActionByTag把所有帧动画都停止掉。

另外,我移除了私有变量_seq,增加了一个枚举用于标记动作Tag。在walkTo中,用检查Tag方法来防止动作的重复进行。之前用Tag停止动作失败的原因,很有可能是不同的动作用了相同的Tag。

接下来我提交一下代码,名为"fix animation superposition" 。哈哈,好像提交代码上瘾了啊。

游戏中的状态机设计

Quick-Cocos2d-x内置了对状态机的支持,所以这里的状态机就要自己想办法了,初步的想法是设计一个状态机对象,然后让Player类持有一个状态机对象。当然也可以让Player继承状态机对象……不过我们先考虑用组合的方法把。

状态机的必备构件:

1.状态(State)
这里的状态有 idle,walking,attacking, dead 等。
先假设他们是互斥的。虽然一边walking一边attacking也是可能的。

2.事件(Event)
可以理解为指令,即要求满足一定条件的状态机改变状态到指定态。
例如
{name=“walk”, from=“idle”, to=“walking”}
如果令状态机执行这个事件,则当其处于idle状态时,会变化至walking态。
所以状态机对象需要保存所有状态,以及所有的事件,以供使用。

3.动作(Action)
例如在进入dead状态后,角色需要播放dead动画,并移除自身。
每个状态都要提供一个函数如onIdleEnter,在进入这个态时调用,当然也可为空。
按理说退出一个状态也应该调用一个函数,如onIdleExit,不过我们暂时可以不用这个。

状态和事件是否需要单独设计class?如果是class是否要继承Ref?纠结了半天,也写了下Event类和State类,感觉直接用字符串表示状态也是可行的。所以果断删了,直接用字符串。

set _states; 用这个保存所有的状态,这里不应该有两个状态名字相同。

map<string, map<string, string>> _events; 用于保存所有的事件,形式为<eventName, <from, to>>

map<string, function<void()>> _onEnters; 保存每个态的回调函数,如果不为空就在进入状态时调用这个函数。
这个函数做什么用呢?当然是状态转换后的行为控制了。例如_onEnters"idle"]可以负责停止所有帧动画的播放。
_onEnters"dead"]让角色播放死亡动画,然后处理后事等等。
然后还需要保存当前状态,前一个状态。

折腾了半天,看了网上的资料,发现状态机也可以挺复杂,也参考了别人的简易状态机,还有状态机的数学语言定义等等……又发现了C++里的map容器可以用unordered_map,他的性能测试,set容器用法,map插入内容的方法……总算弄出一个能用的。

头文件如下:

#ifndef __FSM__
#define __FSM__

#include "cocos2d.h"

class FSM :public cocos2d::Ref
{
public:

    bool init();
    //Create FSM with a initial state name and optional callback function
    static FSM* create(std::string state, std::function onEnter = nullptr);
    
    FSM(std::string state, std::function onEnter = nullptr);
    //add state into FSM
    FSM* addState(std::string state, std::function onEnter = nullptr);
    //add Event into FSM
    FSM* addEvent(std::string eventName, std::string from, std::string to);
    //check if state is already in FSM
    bool isContainState(std::string stateName);
    //print a list of states
    void printState();
    //do the event
    void doEvent(std::string eventName);
    //check if the event can change state
    bool canDoEvent(std::string eventName);
    //set the onEnter callback for a specified state
    void setOnEnter(std::string state, std::function onEnter);
private:
    //change state and run callback.
    void changeToState(std::string state);
private:
    std::set _states;
    std::unordered_map<std::string,std::unordered_map> _events;
    std::unordered_map<std::string,std::function> _onEnters;
    std::string _currentState;
    std::string _previousState;
};

#endif

```


现在不妨做个测试,可以先写到init里。
bool FSM::init()
{
    this->addState("walking",](){cocos2d::log("Enter walking");})
        ->addState("attacking",](){cocos2d::log("Enter attacking");})
        ->addState("dead",](){cocos2d::log("Enter dead");});

    this->addEvent("walk","idle","walking")
        ->addEvent("walk","attacking","walking")
        ->addEvent("attack","idle","attacking")
        ->addEvent("attack","walking", "attacking")
        ->addEvent("die","idle","dead")
        ->addEvent("die","walking","dead")
        ->addEvent("die","attacking","dead")
        ->addEvent("stop","walking","idle")
        ->addEvent("stop","attacking","idle")
        ->addEvent("walk","walking","walking");

    this->doEvent("walk");
    this->doEvent("attack");
    this->doEvent("eat");
    this->doEvent("stop");
    this->doEvent("die");
    this->doEvent("walk");
    return true;
}

```


在MainScene::init中加入:
    auto fsm = FSM::create("idle",](){cocos2d::log("Enter idle");});

```


运行输出如下:


```

FSM::doEvent: doing event walk
FSM::changeToState: idle -> walking
Enter walking
FSM::doEvent: doing event attack
FSM::changeToState: walking -> attacking
Enter attacking
FSM::doEvent: cannot do event eat
FSM::doEvent: doing event stop
FSM::changeToState: attacking -> idle
Enter idle
FSM::doEvent: doing event die
FSM::changeToState: idle -> dead
Enter dead
FSM::doEvent: cannot do event walk

```


第一个walk Event成功,idle -> walking
第二个attack Event成功,walking -> attacking
第三个eat Event失败,因为我们没有定义eat Event
第四个stop Event成功,attacking -> idle 
第五个die Event 成功,idle -> dead 
第六个walk Event失败,这也是我们期望的,因为死了之后不应该还能行走。

下面应该考虑在player中使用FSM, 可以新建一个私有成员持有一个实例。
在尝试过程中出了点故障,好久才搞定,原来是FSM create之后我没有retain,访问出问题了。
既然要retain,那就别忘了release。

我们先把以前的walkTo改变一下,让他用状态机来实现。
void Player::walkTo(Vec2 dest)
{
    std::function onWalk = CC_CALLBACK_0(Player::onWalk, this, dest);
    _fsm->setOnEnter("walking", onWalk);
    _fsm->doEvent("walk");
}

```


即现在是委托"walking"状态的回调函数来进行动作,回调函数是由另一个函数Player::onWalk bind得到的。
这个函数如下:
void Player::onWalk(Vec2 dest)
{
    log("onIdle: Enter walk");
    this->stopActionByTag(WALKTO_TAG);
    auto curPos = this->getPosition();

    if(curPos.x > dest.x)
        this->setFlippedX(true);
    else
        this->setFlippedX(false);

    auto diff = dest - curPos;
    auto time = diff.getLength()/_speed;
    auto move = MoveTo::create(time, dest);
    auto func = &]()
    {
        this->_fsm->doEvent("stop");
    };
    auto callback = CallFunc::create(func);
    auto seq = Sequence::create(move, callback, nullptr);
    seq->setTag(WALKTO_TAG);
    this->runAction(seq);
    this->playAnimationForever(0);
}

```


这个函数和原来的walkTo基本一样除了:
    auto func = &]()
    {
        this->_fsm->doEvent("stop");
    };

```


这里的回调函数会使用状态机,将角色回到idle状态,而idle的回调函数会停止播放动画。
另外在上面的代码中有一句: 
->addEvent("walk","walking","walking");
这个的作用是允许在从walking状态转换到walking状态,当点击屏幕时,walk的目的发生变化,即使在walking中也应该即刻改变目标。

现在的情况好像和之前一样,不一样的是现在用的是状态机。

然后 我做了一个名为Note 4 的commit.

下一篇:
http://www.cocoachina.com/bbs/read.php?tid=227522

占个沙发:2::2::2:

:2: 很不错 最近也在看状态机

举一个小例子,

当我们点击了屏幕上的一个敌人,

此时,有两种选择,

一种是,遥控自己的主角,走到某个位置,然后进攻。

另一种是,告知自己的主角,我现在需要你去攻击这个敌人。

哪一个更抽象,更方便一点呢?

一种主角如同提线的木偶,

而另一种主角自己知道要去做什么。

花点时间教会主角做一件事情,比每次都遥控他要省事很多,

而且,随着游戏复杂度的提升,我们会逐渐的发现,保姆越来越不好当了。

可以尝试给主角一个“大脑”,

当点击某个敌人后,仅仅只是告知主角,去攻击某个敌人,

void toAttackEnemy(Enemy enemyP);

之后发生了什么呢?

如果此时主角的状态正好是空闲,那么,他需要去做的,就是去接近敌人

if(ownState == STATE_WAIT)
{
toStateNearEnemy();
}

之后,状态变成了接近敌人

ownState = STATE_NEAR_ENEMY;

在这个主角的逻辑中,大概是这样的

void runLogic(float timeP)
{
switch(ownState)
case STATE_WAIT:
{
stateWaitLogic(timeP);
}
break;
case STATE_NEAR_ENEMY:
{
stateNearEnemyLogic(timeP);
}
break;

}

而在接近敌人的逻辑方法中,

主角获取敌人的坐标,

判断当前主角和敌人的距离,当距离足够近时,主角很可能会想要进行一次攻击,

那么,此时,就切换到攻击状态,

toStateAttack();

而也有可能,玩家在接近敌人的过程中,敌人已经被友军,陷阱,自己的宠物等消灭掉了,

而此时,玩家仅仅需要回复到最初的空闲状态。

toStateWait();

让主角自己去询问这个游戏世界的情况,教会他自己去观察敌人在做什么,敌人是什么类型的。

假设游戏中有一个炮塔,炮塔上有两个狙击兵,而炮塔上还有一个探照灯在寻找着主角的踪迹,

如果让我们来控制这个炮塔,这还仅仅是游戏中的一个敌军单位而已,都够让我们手忙脚乱的了。

但,如果让炮塔自己学会思考,让探照灯搜寻主角的踪迹,发现踪迹之后通知狙击手射击,

如果这些事情它们自己就会去做,那么,即使游戏元素增加了,我们也不会手忙脚乱了。

做到这一步 出现报错了
error LNK2019: 无法解析的外部符号 "public: class FSM * __thiscall FSM::addState(class std::basic_string<char,struct std::char_traits<char

如何解决啊?急求。

std::unordered_map<std::string,std::unordered_mapstd::string,std::string> 这个用法,你狗二的能讲下吗

抱歉,现在才看到。 我上面说的只是大概,具体在哪个文件夹下面建立什么文件等等细节, 可以下载代码参考……

FSM* FSM::addEvent(std::string eventName, std::string from, std::string to)
{
if("" == eventName)
{
cocos2d::log(“FSM::addEvent: eventName can’t be empty!”);
return nullptr;
}
if(!isContainState(from))
{
cocos2d::log(“FSM::addEvent: from state %s does not exit”, from.c_str());
return nullptr;
}
if(!isContainState(to))
{
cocos2d::log(“FSM::addEvent: to state %s does not exit”, to.c_str());
return nullptr;
}
std::unordered_map<std::string, std::string>& oneEvent = _events;
oneEvent = to;
return this;
}
最后3句是什么 意思啊

用cocos2dx3.2创建的项目中,objectGroupNamed方法相对于之前变成了什么,
当我定义了一个TMXTiledMap * m_GameMap 然后m_GameMap->objectGroupNamed(“Role”);时xcode提示
“objectGroupNamed is deprecated” 使用2.15教程中这个方法是可用的 我用的3.2就不行了 当你输入这个方法时,在xcode下拉栏中这个方法上划有一条横线 在官网文档中并未提及这个方法的变化
请问这个方法的名字变成了什么

getObjectGroup

话说你怎么在这里问。跟帖子好像无关。
:11:

抱歉,才看到。你说的太好了,谢谢!!
:14:

FSM* FSM::addEvent(std::string eventName, std::string from, std::string to)
{
if("" == eventName)
{
cocos2d::log(“FSM::addEvent: eventName can’t be empty!”);
return nullptr;
}
if(!isContainState(from))
{
cocos2d::log(“FSM::addEvent: from state %s does not exit”, from.c_str());
return nullptr;
}
if(!isContainState(to))
{
cocos2d::log(“FSM::addEvent: to state %s does not exit”, to.c_str());
return nullptr;
}
std::unordered_map<std::string, std::string>& oneEvent = _events;
oneEvent = to;
return this;
}
这个函数中 oneEvent = to;什么作用啊

正在学习,感谢分享

楼主,第四讲的代码在哪里下载?没看到啊

出错啦,,帮我看看吧,