cocos2d-x v3.9 与ActionInterval的孩子们之间的对话(中)
我:我看在ActionInterval的孩子中你们好像有些与众不同。 Sequence:我能将您交给我的动作一个接着一个的执行,对了就是您刚才说的词,One By One。给我多少个动作我都能胜任,但是请注意一定要在最后一个动作参数之后加上一个 sprite->runAction(Sequence::create(action1,action2,...,nullptr)); // ...代表可以写任意多个动作。
还可以接收一个存储着多个动作的容器, Vector<FiniteTimeAction *> vector;
vector.pushBack(static_cast<FiniteTimeAction *>(action1));
vector.pushBack(static_cast<FiniteTimeAction *>(action2));
...
sprite->runAction(Sequence::create(vector));
我:我看到你的第一种使用方式的函数声明最后有个CC_REQUIRES_NULL_TERMINATION,我研究了一下,请参见我的这篇博文。 // 第一个是我,后面是我影分身出的3个同胞。
Sequence -- Sequence -- Sequence -- Sequence -- action1
| | | |- action2
| | |- action3
| |- action4
|- action5
此外还要说明两点, Sequence(40) -- Sequence(34) -- Sequence(27) -- Sequence(19) -- action1(10)
| | | |- action2(9)
| | |- action3(8)
| |- action4(7)
|- action5(6)
2、如果您只传递给我一个动作,那么我的另一只手也不会空着,我会自己创建一个ExtraAction类型的动作在手里攥着,这个动作的“规定的时间”为0(因为这个动作也是ActionInterval的儿子,并且他没有上报规定的时间,而长辈FiniteTimeAction在构造函数中初始化规定的时间为0)。 Sequence(10) -- action1(10)
|- extraaction(0)
并且这个动作在update()的时候什么也不做,您可以理解为一个空动作。 void Sequence::startWithTarget(Node *target)
{
ActionInterval::startWithTarget(target);
/* _split代表第一个动作与第二个动作在时间上的百分比分界线。 * 比如我的_split = 34 / 40,我的第一个影分身的_split = 27 / 34,我的其他影分身的_split以此类推。 * _last代表上一次update()后执行的是第几个动作,这个值会在update()中赋值。 */
_split = _actions[0]->getDuration() / _duration; _last = -1; // 现在还没有执行过动作,所以初始化为-1。 }
接下来动作运行起来后,ActionManager会调用我的update(), void Sequence::update(float t)
{
int found = 0; // 本次需要执行第几个动作,初始化为需要执行第一个动作。
/* 因为Sequence上报了两个动作的规定时间和,所以t是这个时间和的时间进度百分比。 * new_t的作用是存储针对于每个动作(_actions[0]或_actions[1])的时间进度百分比。 */
float new_t = 0.0f;
if( t < _split ) { // 需要执行第一个动作。
found = 0;
/* 如果_actions[0]上报的规定的时间就为0时,_split就等于0。 * 否则_split就是个时间上的百分比分界线。 */
if( _split != 0 )
new_t = t / _split; // _actions[0]的时间进度。
else
new_t = 1; // 对于没有实质的动作,进度直接是100%就好了。
} else { // 需要执行第二个动作。
found = 1;
/* 如果_actions[1]为ExtraAction或者上报的规定的时间就为0时,_split就等于1。 * 否则_split就是个时间上的百分比分界线。 */
if ( _split == 1 )
new_t = 1; // 对于没有实质的动作,进度直接是100%就好了。
else // 如果_actions[1]非ExtraAction。
new_t = (t-_split) / (1 - _split ); // _actions[1]的时间进度。
}
if ( found==1 ) {
if( _last == -1 ) {
// _actions[0]被跳过了(有可能在别的代码部分运行时间过长),需要直接执行完它。
_actions[0]->startWithTarget(_target);
if (!(sendUpdateEventToScript(1.0f,_actions[0])))
_actions[0]->update(1.0f);
_actions[0]->stop();
}
else if( _last == 0 )
{
// _actions[0] --> _actions[1]。
if (!(sendUpdateEventToScript(1.0f,_actions[0])))
_actions[0]->update(1.0f);
_actions[0]->stop();
}
}
else if(found==0 && _last==1 ) // 现在需要执行_actions[0],但上一次执行的是_actions[1],这种情况应该不会出现。
{
// Reverse mode ?
// FIXME: Bug. this case doesn't contemplate when _last==-1,found=0 and in "reverse mode"
// since it will require a hack to know if an action is on reverse mode or not.
// "step" should be overridden,and the "reverseMode" value propagated to inner Sequences.
if (!(sendUpdateEventToScript(0,_actions[1])))
_actions[1]->update(0); // _actions[1]复位。
_actions[1]->stop(); // _actions[1]停止执行。
}
* _actions[0]执行完成不会走到这里,_actions[1]执行完成会进入这里。
if( found == _last && _actions[found]->isDone() ) // 如果一直在执行的动作已经执行完成了。
{
// _actions[0]执行完成不会走到这里,_actions[1]执行完成会进入这里。
return;
}
if( found != _last ) // 需要切换动作(开始执行_actions[0]或者_actions[0] --> _actions[1])。
{
/* 着重看这里和下面的update()。 * 如果这个动作是Sequence,那么同样会执行这个Sequence的这么个流程, * 联系上面多个动作如何上报规定的时间看,把他们联系在一起思考。 * 如果你想通了,会发现这种调用方式有点儿像递归, * 但却不是通过不断的调用自己相同的函数而实现的,而是通过调用同胞的。 */
_actions[found]->startWithTarget(_target);
}
// 如果绑定了脚本,会将时间进度和动作交给脚本,由脚本来执行动作。
if (!(sendUpdateEventToScript(new_t,_actions[found])))
_actions[found]->update(new_t); // 如果没有绑定脚本就会到这里来处理,执行动作。
_last = found; // 存储本次执行的是第几个动作。
}
我:这种实现方式很有意思,看起来也很聪明。 我:下一个到谁了? bool Repeat::initWithAction(FiniteTimeAction *action,unsigned int times)
{
// 上报的规定的时间为:动作每次用时 × 执行次数。
float d = action->getDuration() * times;
if (ActionInterval::initWithDuration(d))
{
_times = times;
_innerAction = action;
action->retain();
/* 这里的dynamic_cast有类型检查的作用, * 判断action是否与ActionInstant有继承关系,即action是否为瞬时动作类型。 */
_actionInstant = dynamic_cast<ActionInstant*>(action) ? true : false;
//an instant action needs to be executed one time less in the update method since it uses startWithTarget to execute the action
/* 上面的这段注释可能是历史遗留问题, * 因为现在所有的瞬时动作没有用startWithTarget()执行动作的, */
if (_actionInstant)
{
_times -=1; // 我认为Repeat::update()中的issue #1288就是这里导致的。
}
_total = 0;
return true;
}
return false;
}
接着runAction()时会调用我的startWithTarget(), void Repeat::startWithTarget(Node *target)
{
_total = 0;
_nextDt = _innerAction->getDuration()/_duration; // 存储每次动作执行完成的时间进度百分比(这里初始化为第一次的)。
ActionInterval::startWithTarget(target); // Repeat父类的startWithTarget()。
_innerAction->startWithTarget(target); // 被执行动作的startWithTarget()。
}
最后动作运行起来后,ActionManager会调用我的update(), void Repeat::update(float dt)
{
if (dt >= _nextDt) // 如果本次动作执行完成。
{
// 本次动作执行完成并且还未超出执行次数。
while (dt > _nextDt && _total < _times)
{
// 如果绑定了脚本,会将时间进度和动作交给脚本,由脚本来执行动作。
if (!(sendUpdateEventToScript(1.0f,_innerAction)))
_innerAction->update(1.0f); // 让本次动作update()完成。
_total++; // 执行次数+1。
_innerAction->stop(); // 本次动作结束。
_innerAction->startWithTarget(_target); // 重新开始。
_nextDt = _innerAction->getDuration()/_duration * (_total+1); // 下一次动作执行完成的时间进度百分比。
}
// fix for issue #1288,incorrect end value of repeat
// 非瞬时动作不会有这个问题,瞬时动作才会有,罪魁祸首看Repeat::initWithAction()中。
if(dt >= 1.0f && _total < _times)
{
_total++;
}
// don't set an instant action back or update it,it has no use because it has no duration
if (!_actionInstant) // 如果是非瞬时动作。
{
if (_total == _times) // 如果整个Repeat执行完成。
{
/* 这里这么做是因为上面的while()在整个Repeat执行完成时没有做判断, * 又将动作重新启动了,所以这里要停止。 */
if (!(sendUpdateEventToScript(1,_innerAction)))
_innerAction->update(1);
_innerAction->stop();
}
else // 如果整个Repeat还未执行完成。
{
// issue #390 prevent jerk,use right update
/* 这里和下面的fmodf(dt * _times,1.0f)是一个作用,只不过是另一种实现方式。 */
if (!(sendUpdateEventToScript(dt - (_nextDt - _innerAction->getDuration()/_duration),_innerAction)))
_innerAction->update(dt - (_nextDt - _innerAction->getDuration()/_duration));
}
}
}
else // 如果本次动作还没有执行完成。
{
// 如果绑定了脚本,会将时间进度和动作交给脚本,由脚本来执行动作。
if (!(sendUpdateEventToScript(fmodf(dt * _times,1.0f),_innerAction)))
/* dt = 当前流逝时间 / (动作的_duration × _times),看Repeat上报的时间就是这个, * 所以dt × _times = 当前流逝时间 / 动作的_duration, * 所以fmodf(dt * _times,1.0f)就是针对于每次动作的时间进度百分比。 */
_innerAction->update(fmodf(dt * _times,1.0f)); // 继续执行动作。
}
}
我:实现的好像有些臃肿,其实我觉得可以更简单的,比如, bool Repeat::initWithAction(FiniteTimeAction *action,unsigned int times)
{
float d = action->getDuration() * times;
if (ActionInterval::initWithDuration(d))
{
_times = times;
_innerAction = action;
action->retain();
_total = 0;
return true;
}
return false;
}
void Repeat::update(float dt)
{
if (dt >= _nextDt)
{
while (dt > _nextDt && _total < _times)
{
if (!(sendUpdateEventToScript(1.0f,_innerAction)))
_innerAction->update(1.0f);
_innerAction->stop();
if(++_total < _times)
{
_innerAction->startWithTarget(_target);
_nextDt = _innerAction->getDuration()/_duration * (_total+1);
}
}
}
else
{
if (!(sendUpdateEventToScript(fmodf(dt * _times,_innerAction)))
_innerAction->update(fmodf(dt * _times,1.0f));
}
}
Repeat:嗯,好的,希望设计者能看到。 void RepeatForever::step(float dt) // 注意这里传入的是从上一帧到这一帧流逝的时间。
{
/* 让具体动作的老爸管理他的孩子的时间。 * 本次动作未执行完成,ActionManager不停的调用RepeatForever::step(), * RepeatForever::step()不停的调用具体动作的step()。 */
_innerAction->step(dt);
if (_innerAction->isDone()) // 本次动作执行完成。
/* 以下对于diff的计算以及使用均是为了防止抖动(jerk)。 * 但具体抖动是什么样的我没有研究过。 */
// 动作刚刚执行完成时getElapsed()会比getDuration()大一点点,isDone()就是据此判断动作是否执行完成的。
float diff = _innerAction->getElapsed() - _innerAction->getDuration();
if (diff > _innerAction->getDuration()) // 如果跳过了多次动作(有可能是在别的地方执行时间过长)。
diff = fmodf(diff,_innerAction->getDuration()); // 忽略跳过的动作。
_innerAction->startWithTarget(_target); // 动作重新开始。
// to prevent jerk. issue #390,1247
// 由于是永远重复执行,所以两次动作的衔接要连贯,diff这点时间也要step()一下。
_innerAction->step(0.0f);
_innerAction->step(diff);
}
}
我:实现的层面不同,不过我觉得你俩既然功能类似,还是应该合并在一起,实现上往相同的模式转化更合理。 我:还有两位,不要害羞,也来说两句吧。 // 假设sprite的初始位置为(10,10)。
auto moveby = MoveBy::create(3,Vec2(100,100));
/* 本来moveby是让sprite在3秒内从(10,10)移动到(110,110), * 而经过我手之后,sprite将会在3秒内从(100,100)移动到(10,10)。 * 这里可以对比moveby->reverse(),效果是不一样的哦。 */
sprite->runAction(ReverseTime::create(moveby));
而我的实现上也很简单,创建一个您提供的动作的分身,然后传递给他总时间进度与当前时间进度百分比的差值, _other->update(1 - time);
不过为什么要创建个动作的分身我也没有搞明白,嘿嘿。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |