一:cocos2dx当前使用版本以及test项目路径(本文是以windows下的项目作为demo)
二:今天的代码分析部分:ActionManager
三:详细分析如下:
1:ActionManagerTest和 2:ActionManagerTest是连接在一起的一并整理:
void CrashTest::onEnter()
{
ActionManagerTest::onEnter();
auto child = Sprite::create(s_pathGrossini);
child->setPosition( VisibleRect::center() );
addChild(child, 1, kTagGrossini);
//Sum of all action's duration is 1.5 second.
child->runAction(RotateBy::create(1.5f, 90));
child->runAction(Sequence::create(
DelayTime::create(1.4f),
FadeOut::create(1.1f),
nullptr)
);
//After 1.5 second, self will be removed.
child->runAction(Sequence::create(
DelayTime::create(1.4f),
CallFunc::create( CC_CALLBACK_0(CrashTest::removeThis,this)),
nullptr)
);
}
void CrashTest::removeThis()
{
auto child = getChildByTag(kTagGrossini);
child->removeChild(child, true);
getTestSuite()->enterNextTest();
}
1:ActionManagerTest主要实现的功能是:以一个图片为纹理创建一个精灵,并执行一个旋转90度的动作(顺时针),这里有两个动作,一个是RotateBy这个是一个旋转动作,而后续的Sequence是一个组合动作(由DelayTime(延时动作)+FadeOut(淡出动作)),用言语来组织这样一个现象就是,小人先顺时针旋转90度,然后再渐渐消失。
这里的动作是呈现线性关系的,在代码执行的过程中,代码是线性往下面执行的,而非一个动作执行完成再去执行另外一个动作,所以可以理解为下图:
所以哈,该精灵的动画时间其实就只是旋转的1.4f秒,随后立即就被释放,并且转换到我们的2ActionManagerTest(代码如下)
void LogicTest::onEnter()
{
ActionManagerTest::onEnter();
auto grossini = Sprite::create(s_pathGrossini);
addChild(grossini, 0, 2);
grossini->setPosition(VisibleRect::center());
grossini->runAction( Sequence::create(
MoveBy::create(1, Vec2(150,0)),
CallFuncN::create(CC_CALLBACK_1(LogicTest::bugMe,this)),
nullptr)
);
}
void LogicTest::bugMe(Node* node)
{
node->stopAllActions(); //After this stop next action not working, if remove this stop everything is working
node->runAction(ScaleTo::create(2, 2));
}
2ActionManagerTest实现的主要功能是:一个组合动作,由MoveBy(移动指定距离)+ScaleTo(缩放至),这里有两个小细节可以和大家分享,第一个是大家可以查询一下CallFuncN的作用,第二是CC_CALLBACK_1的作用,熟悉C++特性的小伙伴们,可以参考一下std::bind,这两个小彩蛋,放在明天跟大家详细扯一扯。stopAllAction停止节点正在执行的所有行为,注意这里是停止,不是暂停,函数执行后节点中的所有行为类实例都将从内存中清除。
3ActionManagerTest实现的主要功能是3秒后,精灵执行一个移动的动作,代码如下:
void PauseTest::onEnter()
{
//
// This test MUST be done in 'onEnter' and not on 'init'
// otherwise the paused action will be resumed at 'onEnter' time
//
ActionManagerTest::onEnter();
auto l = Label::createWithTTF("After 5 seconds grossini should move", "fonts/Thonburi.ttf", 16.0f);
addChild(l);
l->setPosition(VisibleRect::center().x, VisibleRect::top().y-75);
//
// Also, this test MUST be done, after [super onEnter]
//
auto grossini = Sprite::create(s_pathGrossini);
addChild(grossini, 0, kTagGrossini);
grossini->setPosition(VisibleRect::center() );
auto action = MoveBy::create(1, Vec2(150,0));
auto director = Director::getInstance();
director->getActionManager()->addAction(action, grossini, true);
schedule( CC_SCHEDULE_SELECTOR(PauseTest::unpause), 3);
}
void PauseTest::unpause(float dt)
{
unschedule( CC_SCHEDULE_SELECTOR(PauseTest::unpause) );
auto node = getChildByTag( kTagGrossini );
auto director = Director::getInstance();
director->getActionManager()->resumeTarget(node);
}
代码分析:先是创建了一个精灵,然后创建了一个动作,将这个动作加入到这个精灵里,schedule被称之为调度器,这里的函数的意义是在三秒后,执行前面selector中对应的函数,即代码中的PauseTest::unpause, 进入unpause函数后第一件事情是停止这个调度器,并且获取精灵节点,并且回复精灵的动作,resumeTarget在回复精灵动作的同时,它会是精灵执行一边动作,这也是为什么这里不需要runAction但我们已经能看到移动的效果的原因。
4ActionManagerTest实现的主要功能是:
移动一段距离后,停止当前动作,需要注明一下的是,这里提供了一种很有意思的方法,可以将我们的函数也作为一个动作拼接在Sequence这个组合动作中,并且为这个组合动作设置对应的Tag,便于后续的stopActionByTag的使用提供参数,代码如下:
void StopActionTest::onEnter()
{
ActionManagerTest::onEnter();
auto l = Label::createWithTTF("Should not crash", "fonts/Thonburi.ttf", 16.0f);
addChild(l);
l->setPosition(VisibleRect::center().x, VisibleRect::top().y - 75);
auto pMove = MoveBy::create(2, Vec2(200, 0));
auto pCallback = CallFunc::create(CC_CALLBACK_0(StopActionTest::stopAction,this));
auto pSequence = Sequence::create(pMove, pCallback, nullptr);
pSequence->setTag(kTagSequence);
auto pChild = Sprite::create(s_pathGrossini);
pChild->setPosition( VisibleRect::center() );
addChild(pChild, 1, kTagGrossini);
pChild->runAction(pSequence);
}
void StopActionTest::stopAction()
{
auto sprite = getChildByTag(kTagGrossini);
sprite->stopActionByTag(kTagSequence);
}
思考如下:这个Demo提示我们,如果在动画和逻辑处理代码交叉的时候,我们可以使用这样一种结构,当然这个方法不是最佳的方案,其实动画或者是动作完成后的回调callback更加直观,但是这里也是一种方法,大家可以借鉴一。如果再到后续的骨骼动画中的帧事件的话,这里所说的callback就会展示其简洁直观的处理方式,当然这个是后话了。
5ActionManagerTest实现的功能是:小人一边移动一边旋转,最后执行repeat动作保持一直旋转,代码如下:
void StopAllActionsTest::onEnter()
{
ActionManagerTest::onEnter();
auto l = Label::createWithTTF("Should stop scale & move after 4 seconds but keep rotate", "fonts/Thonburi.ttf", 16.0f);
addChild(l);
l->setPosition( Vec2(VisibleRect::center().x, VisibleRect::top().y - 75) );
auto pMove1 = MoveBy::create(2, Vec2(200, 0));
auto pMove2 = MoveBy::create(2, Vec2(-200, 0));
auto pSequenceMove = Sequence::createWithTwoActions(pMove1, pMove2);
auto pRepeatMove = RepeatForever::create(pSequenceMove);
pRepeatMove->setTag(kTagSequence);
auto pScale1 = ScaleBy::create(2, 1.5f);
auto pScale2 = ScaleBy::create(2, 1.0f/1.5f);
auto pSequenceScale = Sequence::createWithTwoActions(pScale1, pScale2);
auto pRepeatScale = RepeatForever::create(pSequenceScale);
pRepeatScale->setTag(kTagSequence);
auto pRotate = RotateBy::create(2, 360);
auto pRepeatRotate = RepeatForever::create(pRotate);
auto pChild = Sprite::create(s_pathGrossini);
pChild->setPosition( VisibleRect::center() );
addChild(pChild, 1, kTagGrossini);
pChild->runAction(pRepeatMove);
pChild->runAction(pRepeatScale);
pChild->runAction(pRepeatRotate);
this->scheduleOnce((SEL_SCHEDULE)&StopAllActionsTest::stopAction, 4);
}
void StopAllActionsTest::stopAction(float time)
{
auto sprite = getChildByTag(kTagGrossini);
sprite->stopAllActionsByTag(kTagSequence);
}
如果对于动作执行顺序不理解的小伙伴们,可以返还回去查看1ActionManagerTest和2ActionManagerTest中绘制的代码执行和动作执行顺序的时序图,如果顺序不难理解的话,这里的动作现象就不难解释,这里有一个不错的小细节大家可以注意一下:在放大小人的时候我们的传入的参数是1.5f(大于1即是放大),为了缩小回原图大小,这个时候我们传入的值为(1/1.5f),挺有意思的小细节的。还有一个细节,在停止动作的时候,是按照tag来停止的,而最后一个重复旋转的动作是没有对应的tag的,也就意味着最后一个动作其实是没有停止下来的。
6ActionManagerTest实现的功能和5ActionManagerTest效果一样,但是代码存在差异,代码如下:
void StopActionsByFlagsTest::onEnter()
{
ActionManagerTest::onEnter();
auto l = Label::createWithTTF("Should stop scale & move after 4 seconds but keep rotate", "fonts/Thonburi.ttf", 16.0f);
addChild(l);
l->setPosition( Vec2(VisibleRect::center().x, VisibleRect::top().y - 75) );
auto pMove1 = MoveBy::create(2, Vec2(200, 0));
auto pMove2 = MoveBy::create(2, Vec2(-200, 0));
auto pSequenceMove = Sequence::createWithTwoActions(pMove1, pMove2);
auto pRepeatMove = RepeatForever::create(pSequenceMove);
pRepeatMove->setFlags(kMoveFlag | kRepeatForeverFlag);
auto pScale1 = ScaleBy::create(2, 1.5f);
auto pScale2 = ScaleBy::create(2, 1.0f/1.5f);
auto pSequenceScale = Sequence::createWithTwoActions(pScale1, pScale2);
auto pRepeatScale = RepeatForever::create(pSequenceScale);
pRepeatScale->setFlags(kScaleFlag | kRepeatForeverFlag);
auto pRotate = RotateBy::create(2, 360);
auto pRepeatRotate = RepeatForever::create(pRotate);
pRepeatRotate->setFlags(kRotateFlag | kRepeatForeverFlag);
auto pChild = Sprite::create(s_pathGrossini);
pChild->setPosition( VisibleRect::center() );
addChild(pChild, 1, kTagGrossini);
pChild->runAction(pRepeatMove);
pChild->runAction(pRepeatScale);
pChild->runAction(pRepeatRotate);
this->scheduleOnce((SEL_SCHEDULE)&StopActionsByFlagsTest::stopAction, 4);
}
void StopActionsByFlagsTest::stopAction(float time)
{
auto sprite = getChildByTag(kTagGrossini);
sprite->stopActionsByFlags(kMoveFlag | kScaleFlag);
}
在设置Tag的时候与5ActionManagerTest不同了,使用了位或的方式,这种方式使得代码更加容易阅读,便于维护,和5ActionManagerTest中一样,stop的时候并没有停止最后一个旋转动作,所以现象和5一致,如果大家有疑惑的话,可以在stopActionByFlag中加入代表着最后一个动作的kRotateFlag作为参数。
7ActionManagerTest实现的效果是:3秒后小人旋转变大,代码如下:
void ResumeTest::onEnter()
{
ActionManagerTest::onEnter();
auto l = Label::createWithTTF("Grossini only rotate/scale in 3 seconds", "fonts/Thonburi.ttf", 16.0f);
addChild(l);
l->setPosition(VisibleRect::center().x, VisibleRect::top().y - 75);
auto pGrossini = Sprite::create(s_pathGrossini);
addChild(pGrossini, 0, kTagGrossini);
pGrossini->setPosition(VisibleRect::center());
pGrossini->runAction(ScaleBy::create(2, 2));
auto director = Director::getInstance();
director->getActionManager()->pauseTarget(pGrossini);
pGrossini->runAction(RotateBy::create(2, 360));
this->schedule(CC_SCHEDULE_SELECTOR(ResumeTest::resumeGrossini), 3.0f);
}
void ResumeTest::resumeGrossini(float time)
{
this->unschedule(CC_SCHEDULE_SELECTOR(ResumeTest::resumeGrossini));
auto pGrossini = getChildByTag(kTagGrossini);
auto director = Director::getInstance();
director->getActionManager()->resumeTarget(pGrossini);
}
可以参考在5ActionManagerTest中描述的段落,使用schedule调度器,在三秒后调用对应函数,resumeTarget是会自动执行精灵里的动作的。
8ActionManagerTest没有显示该精灵的原因是:
精灵在创建之后没有加入到对应的TestCase场景中,少了AddChild的处理,所以该精灵没有显示,即使它包含一个动作。关于场景、层、节点之间的关系,我明天会结合项目中的使用心得,举几个容易理解的栗子,那今天介绍的ActionManagerTest就到这里,如果有需要交流的,或者是哪里讲解的不对的,欢迎勘误。
写于2018-01-30 by Mr.whale