cocos2d-x v3.9 与ActionInterval的孩子们之间的对话(下)
我:今天咱们继续聊,还~有~谁~? auto sprite1 = Sprite::create("image1.png");
auto sprite2 = Sprite::create("image2.png");
sprite1->setPosition(100,100);
sprite2->setPosition(200,200);
this->addChild(sprite1);
this->addChild(sprite2);
auto jumpby1 = JumpBy::create(3,Vec2(0,0),100,3);
auto jumpby2 = jumpby1->clone();
sprite1->runAction(Sequence::create(jumpby1,TargetedAction::create(sprite2,jumpby2),nullptr)); // sprite1跳3下之后sprite2才会跳。
实现上也很简单,就是在runAction()调用我的startWithTarget()时,我去调用您指定动作的startWithTarget();在ActionManager调用我的update()时我去调用您指定动作的update()。 我:接下来ActionFloat,你的功能是什么? auto actionfloat = ActionFloat::create(3,2,5,[this](float value) {
_tamara->setScale(value);
}); // 让执行动作的sprite在3秒内从2倍大小放大到5倍。
我接收规定的时间、动作的起始值、动作的终止值以及一个回调函数。回调函数的参数我会传递根据当前时间进度百分比,动作应被设定的值。以上面的例子为例,比如规定的时间过去一半时,我会向回调函数中传递2 + (5 - 2) * 0.5 = 3.5,所以此时执行动作的sprite应被放大到原尺寸的3.5倍。实现上同样很简单,首先在startWithTarget()中计算出终止值与起始值之差_delta,然后在update()中计算出当前动作应被设定的值,以此值调用回调函数。 // 当前动作应被设定的值。实现上用的是5 - (5 - 2) * 0.5 = 3.5,一样。
float value = _to - _delta * (1 - delta);
if (_callback)
{
_callback(value);
}
我:你的功能有些奇怪,看起来就像是个快捷方式,省去了在执行动作之前设置动作初始值的步骤。 我:最后就剩你了,Animate。 auto animate = Animate::create(animation); // animation是创建好的动画。
我:关于动画你做个简单的说明。 bool Animate::initWithAnimation(Animation* animation)
{
CCASSERT( animation!=nullptr,"Animate: argument Animation must be non-nullptr");
// 动画播放一次的持续时间。
float singleDuration = animation->getDuration();
// 上报:动画播放一次的持续时间 × 动画播放的次数 = 整个动画播放完成的持续时间。
if ( ActionInterval::initWithDuration(singleDuration * animation->getLoops() ) )
{
_nextFrame = 0; // 下一次要播放哪一帧动画(暂时可以理解为哪一张图片),初始化为第一帧。
setAnimation(animation); // 存储动画。
_origFrame = nullptr; // 精灵在播放动画前原本的样子。可以设置动画播放完成后精灵回到原本的样子。
_executedLoops = 0; // 当前是在第几次播放动画。
// _splitTimes存储每一帧动画在动画播放一次的持续时间中的百分之多少时播放。
_splitTimes->reserve(animation->getFrames().size());
float accumUnitsOfTime = 0; // 此帧动画前用了多少个时间片,初始化为0。
/* 动画中每一个时间片占用多长时间 = 动画播放一次的持续时间 / 动画一共使用了多少个时间片。 * 其实直接使用Animation::getDelayPerUnit()不就好了。 */
float newUnitOfTimeValue = singleDuration / animation->getTotalDelayUnits();
auto& frames = animation->getFrames();
for (auto& frame : frames) // 依次获取每一帧动画。
{
/* (此帧动画前用了多少个时间片 × 每一个时间片占用多长时间) / 动画播放一次的持续时间 * = 每一帧动画在动画播放一次的持续时间中的百分之多少时播放。 */
float value = (accumUnitsOfTime * newUnitOfTimeValue) / singleDuration;
accumUnitsOfTime += frame->getDelayUnits(); // 此帧动画用了多少个时间片。
_splitTimes->push_back(value);
}
return true;
}
return false;
}
接着在runAction()是会调用我的startWithTarget(), void Animate::startWithTarget(Node *target)
{
ActionInterval::startWithTarget(target);
Sprite *sprite = static_cast<Sprite*>(target);
CC_SAFE_RELEASE(_origFrame);
if (_animation->getRestoreOriginalFrame()) // 整个动画播放完成之后是否需要恢复sprite本来的样子。
{
_origFrame = sprite->getSpriteFrame(); // 先把sprite原本的样子记住。
_origFrame->retain();
}
_nextFrame = 0; // 下一次要播放哪一帧动画,初始化为第一帧。
_executedLoops = 0; // 当前是在第几次播放动画,初始化为第0次。
}
最后在动作实际运行起来后,ActionManager会调用我的update(), void Animate::update(float t)
{
// if t==1,ignore. Animation should finish with t==1
if( t < 1.0f ) {
t *= _animation->getLoops();
// ((unsigned int)(t * 动画播放次数)) --> 当前正在第几次播放动画。
unsigned int loopNumber = (unsigned int)t;
if( loopNumber > _executedLoops ) { // 如果是进入了新的一次播放。
_nextFrame = 0; // 下一次要播放哪一帧动画,初始化为第一帧。
_executedLoops++; // 当前第几次播放动画+1。
}
t = fmodf(t,1.0f); // 转换为当次动画播放中的时间进度百分比。
}
auto& frames = _animation->getFrames();
auto numberOfFrames = frames.size(); // 动画一共有多少帧。
SpriteFrame *frameToDisplay = nullptr; // 将要播放的动画帧。
/* _nextFrame是下一次要播放的动画帧。 * 每次的update()走到这里是从下一次要播放的动画帧开始判断的,而不是从第一个动画帧。 * 这里用for()是因为怕程序的延时过长, * t传进来的时候已经是跳过了多个动画帧后的时间进度百分比。 * 用for()可以更新到当前时间应该正确被播放的动画帧。 */
for( int i=_nextFrame; i < numberOfFrames; i++ ) {
// 获取下一个要播放的动画帧在播放一次动画中的时间进度百分比。
float splitTime = _splitTimes->at(i);
if( splitTime <= t ) { // 如果下一个要播放的动画帧应该被播放。
_currFrameIndex = i; // 当前正在播放的动画帧索引。
AnimationFrame* frame = frames.at(_currFrameIndex); // 获取下一个要播放的动画帧。
frameToDisplay = frame->getSpriteFrame(); // 从动画帧中取出精灵要显示的图片帧。
static_cast<Sprite*>(_target)->setSpriteFrame(frameToDisplay); // 让精灵显示该图片帧。
const ValueMap& dict = frame->getUserInfo(); // 动画帧的用户数据。
if ( !dict.empty() ) // 如果用户数据存在的话。
{
if (_frameDisplayedEvent == nullptr)
_frameDisplayedEvent = new (std::nothrow) EventCustom(AnimationFrameDisplayedNotification); // 创建用户自定义事件。
/* _frameDisplayedEventInfo是AnimationFrame中定义的一种结构体, * 用户自定义事件以这种结构体的形式向用户发送用户数据。 */
_frameDisplayedEventInfo.target = _target;
_frameDisplayedEventInfo.userInfo = &dict;
_frameDisplayedEvent->setUserData(&_frameDisplayedEventInfo); // 填写用户自定义事件的信息。
Director::getInstance()->getEventDispatcher()->dispatchEvent(_frameDisplayedEvent); // 发送用户自定义事件。
}
_nextFrame = i+1; // 下一次要播放的动画帧+1。
}
// Issue 1438. Could be more than one frame per tick,due to low frame rate or frame delta < 1/FPS
else { // 如果下一个要播放的动画帧不应该被播放。
break;
}
}
}
我:你的实现还真是稍复杂了一些,看来要想比较好的理解你还需要找动画制作组聊聊。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |